Update to 12.0-beta1

This commit is contained in:
dP
2021-08-15 14:57:29 +03:00
parent ac7d3eba75
commit 9df4f2c4fc
666 changed files with 61302 additions and 20466 deletions

View File

@@ -19,6 +19,7 @@
#include "network_udp.h"
#include "network_gamelist.h"
#include "network_base.h"
#include "network_coordinator.h"
#include "core/udp.h"
#include "core/host.h"
#include "network_gui.h"
@@ -33,6 +34,9 @@
#include "../core/pool_func.hpp"
#include "../gfx_func.h"
#include "../error.h"
#include <charconv>
#include <sstream>
#include <iomanip>
#include "../safeguards.h"
@@ -57,7 +61,6 @@ bool _is_network_server; ///< Does this client wants to be a network-server?
NetworkCompanyState *_network_company_states = nullptr; ///< Statistics about some companies.
ClientID _network_own_client_id; ///< Our client identifier.
ClientID _redirect_console_to_client; ///< If not invalid, redirect the console output to a client.
bool _network_need_advertise; ///< Whether we need to advertise.
uint8 _network_reconnect; ///< Reconnect timeout
StringList _network_bind_list; ///< The addresses to bind on.
StringList _network_host_list; ///< The servers we know.
@@ -75,8 +78,6 @@ uint32 _sync_frame; ///< The frame to perform the sync check.
bool _network_first_time; ///< Whether we have finished joining or not.
CompanyMask _network_company_passworded; ///< Bitmask of the password status of all companies.
/* Check whether NETWORK_NUM_LANDSCAPES is still in sync with NUM_LANDSCAPE */
static_assert((int)NETWORK_NUM_LANDSCAPES == (int)NUM_LANDSCAPE);
static_assert((int)NETWORK_COMPANY_NAME_LENGTH == MAX_LENGTH_COMPANY_NAME_CHARS * MAX_CHAR_LENGTH);
/** The amount of clients connected */
@@ -151,9 +152,9 @@ byte NetworkSpectatorCount()
* @param password The unhashed password we like to set ('*' or '' resets the password)
* @return The password.
*/
const char *NetworkChangeCompanyPassword(CompanyID company_id, const char *password)
std::string NetworkChangeCompanyPassword(CompanyID company_id, std::string password)
{
if (strcmp(password, "*") == 0) password = "";
if (password.compare("*") == 0) password = "";
if (_network_server) {
NetworkServerSetCompanyPassword(company_id, password, false);
@@ -171,33 +172,35 @@ const char *NetworkChangeCompanyPassword(CompanyID company_id, const char *passw
* @param password_game_seed Game seed.
* @return The hashed password.
*/
const char *GenerateCompanyPasswordHash(const char *password, const char *password_server_id, uint32 password_game_seed)
std::string GenerateCompanyPasswordHash(const std::string &password, const std::string &password_server_id, uint32 password_game_seed)
{
if (StrEmpty(password)) return password;
if (password.empty()) return password;
char salted_password[NETWORK_SERVER_ID_LENGTH];
size_t password_length = strlen(password);
size_t password_server_id_length = strlen(password_server_id);
size_t password_length = password.size();
size_t password_server_id_length = password_server_id.size();
/* Add the game seed and the server's ID as the salt. */
std::ostringstream salted_password;
/* Add the password with the server's ID and game seed as the salt. */
for (uint i = 0; i < NETWORK_SERVER_ID_LENGTH - 1; i++) {
char password_char = (i < password_length ? password[i] : 0);
char server_id_char = (i < password_server_id_length ? password_server_id[i] : 0);
char seed_char = password_game_seed >> (i % 32);
salted_password[i] = password_char ^ server_id_char ^ seed_char;
salted_password << (char)(password_char ^ server_id_char ^ seed_char); // Cast needed, otherwise interpreted as integer to format
}
Md5 checksum;
uint8 digest[16];
static char hashed_password[NETWORK_SERVER_ID_LENGTH];
/* Generate the MD5 hash */
checksum.Append(salted_password, sizeof(salted_password) - 1);
std::string salted_password_string = salted_password.str();
checksum.Append(salted_password_string.data(), salted_password_string.size());
checksum.Finish(digest);
for (int di = 0; di < 16; di++) seprintf(hashed_password + di * 2, lastof(hashed_password), "%02x", digest[di]);
std::ostringstream hashed_password;
hashed_password << std::hex << std::setfill('0');
for (int di = 0; di < 16; di++) hashed_password << std::setw(2) << (int)digest[di]; // Cast needed, otherwise interpreted as character to add
return hashed_password;
return hashed_password.str();
}
/**
@@ -213,7 +216,7 @@ bool NetworkCompanyIsPassworded(CompanyID company_id)
/* This puts a text-message to the console, or in the future, the chat-box,
* (to keep it all a bit more general)
* If 'self_send' is true, this is the client who is sending the message */
void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send, const char *name, const char *str, int64 data)
void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send, const std::string &name, const std::string &str, int64 data)
{
StringID strid;
switch (action) {
@@ -259,17 +262,17 @@ void NetworkTextMessage(NetworkAction action, TextColour colour, bool self_send,
char *msg_ptr = message + Utf8Encode(message, _current_text_dir == TD_LTR ? CHAR_TD_LRM : CHAR_TD_RLM);
GetString(msg_ptr, strid, lastof(message));
DEBUG(desync, 1, "msg: %08x; %02x; %s", _date, _date_fract, message);
IConsolePrintF(colour, "%s", message);
NetworkAddChatMessage((TextColour)colour, _settings_client.gui.network_chat_timeout, "%s", message);
Debug(desync, 1, "msg: {:08x}; {:02x}; {}", _date, _date_fract, message);
IConsolePrint(colour, message);
NetworkAddChatMessage((TextColour)colour, _settings_client.gui.network_chat_timeout, message);
}
/* Calculate the frame-lag of a client */
uint NetworkCalculateLag(const NetworkClientSocket *cs)
{
int lag = cs->last_frame_server - cs->last_frame;
/* This client has missed his ACK packet after 1 DAY_TICKS..
* so we increase his lag for every frame that passes!
/* This client has missed their ACK packet after 1 DAY_TICKS..
* so we increase their lag for every frame that passes!
* The packet can be out by a max of _net_frame_freq */
if (cs->last_frame_server + DAY_TICKS + _settings_client.network.frame_freq < _frame_counter) {
lag += _frame_counter - (cs->last_frame_server + DAY_TICKS + _settings_client.network.frame_freq);
@@ -316,6 +319,7 @@ StringID GetNetworkErrorMsg(NetworkErrorCode err)
STR_NETWORK_ERROR_CLIENT_TIMEOUT_COMPUTER,
STR_NETWORK_ERROR_CLIENT_TIMEOUT_MAP,
STR_NETWORK_ERROR_CLIENT_TIMEOUT_JOIN,
STR_NETWORK_ERROR_CLIENT_INVALID_CLIENT_NAME,
};
static_assert(lengthof(network_error_strings) == NETWORK_ERROR_END);
@@ -365,9 +369,7 @@ void NetworkHandlePauseChange(PauseMode prev_mode, PauseMode changed_mode)
str = paused ? STR_NETWORK_SERVER_MESSAGE_GAME_PAUSED : STR_NETWORK_SERVER_MESSAGE_GAME_UNPAUSED;
}
char buffer[DRAW_STRING_BUFFER];
GetString(buffer, str, lastof(buffer));
NetworkTextMessage(NETWORK_ACTION_SERVER_MESSAGE, CC_DEFAULT, false, nullptr, buffer);
NetworkTextMessage(NETWORK_ACTION_SERVER_MESSAGE, CC_DEFAULT, false, "", GetString(str));
break;
}
@@ -448,40 +450,96 @@ static void CheckPauseOnJoin()
CheckPauseHelper(NetworkHasJoiningClient(), PM_PAUSED_JOIN);
}
/**
* Parse the company part ("#company" postfix) of a connecting string.
* @param connection_string The string with the connection data.
* @param company_id The company ID to set, if available.
* @return A std::string_view into the connection string without the company part.
*/
std::string_view ParseCompanyFromConnectionString(const std::string &connection_string, CompanyID *company_id)
{
std::string_view ip = connection_string;
if (company_id == nullptr) return ip;
size_t offset = ip.find_last_of('#');
if (offset != std::string::npos) {
std::string_view company_string = ip.substr(offset + 1);
ip = ip.substr(0, offset);
uint8 company_value;
auto [_, err] = std::from_chars(company_string.data(), company_string.data() + company_string.size(), company_value);
if (err == std::errc()) {
if (company_value != COMPANY_NEW_COMPANY && company_value != COMPANY_SPECTATOR) {
if (company_value > MAX_COMPANIES || company_value == 0) {
*company_id = COMPANY_SPECTATOR;
} else {
/* "#1" means the first company, which has index 0. */
*company_id = (CompanyID)(company_value - 1);
}
} else {
*company_id = (CompanyID)company_value;
}
}
}
return ip;
}
/**
* Converts a string to ip/port/company
* Format: IP:port#company
*
* connection_string will be re-terminated to separate out the hostname, and company and port will
* be set to the company and port strings given by the user, inside the memory area originally
* occupied by connection_string.
* Returns the IP part as a string view into the passed string. This view is
* valid as long the passed connection string is valid. If there is no port
* present in the connection string, the port reference will not be touched.
* When there is no company ID present in the connection string or company_id
* is nullptr, then company ID will not be touched.
*
* @param connection_string The string with the connection data.
* @param port The port reference to set.
* @param company_id The company ID to set, if available.
* @return A std::string_view into the connection string with the (IP) address part.
*/
void ParseConnectionString(const char **company, const char **port, char *connection_string)
std::string_view ParseFullConnectionString(const std::string &connection_string, uint16 &port, CompanyID *company_id)
{
bool ipv6 = (strchr(connection_string, ':') != strrchr(connection_string, ':'));
char *p;
for (p = connection_string; *p != '\0'; p++) {
switch (*p) {
case '[':
ipv6 = true;
break;
std::string_view ip = ParseCompanyFromConnectionString(connection_string, company_id);
case ']':
ipv6 = false;
break;
case '#':
*company = p + 1;
*p = '\0';
break;
case ':':
if (ipv6) break;
*port = p + 1;
*p = '\0';
break;
}
size_t port_offset = ip.find_last_of(':');
size_t ipv6_close = ip.find_last_of(']');
if (port_offset != std::string::npos && (ipv6_close == std::string::npos || ipv6_close < port_offset)) {
std::string_view port_string = ip.substr(port_offset + 1);
ip = ip.substr(0, port_offset);
std::from_chars(port_string.data(), port_string.data() + port_string.size(), port);
}
return ip;
}
/**
* Normalize a connection string. That is, ensure there is a port in the string.
* @param connection_string The connection string to normalize.
* @param default_port The port to use if none is given.
* @return The normalized connection string.
*/
std::string NormalizeConnectionString(const std::string &connection_string, uint16 default_port)
{
uint16 port = default_port;
std::string_view ip = ParseFullConnectionString(connection_string, port);
return std::string(ip) + ":" + std::to_string(port);
}
/**
* Convert a string containing either "hostname" or "hostname:ip" to a
* NetworkAddress.
*
* @param connection_string The string to parse.
* @param default_port The default port to set port to if not in connection_string.
* @return A valid NetworkAddress of the parsed information.
*/
NetworkAddress ParseConnectionString(const std::string &connection_string, uint16 default_port)
{
uint16 port = default_port;
std::string_view ip = ParseFullConnectionString(connection_string, port);
return NetworkAddress(ip, port);
}
/**
@@ -494,9 +552,10 @@ void ParseConnectionString(const char **company, const char **port, char *connec
/* Register the login */
_network_clients_connected++;
SetWindowDirty(WC_CLIENT_LIST, 0);
ServerNetworkGameSocketHandler *cs = new ServerNetworkGameSocketHandler(s);
cs->client_address = address; // Save the IP of the client
InvalidateWindowData(WC_CLIENT_LIST, 0);
}
/**
@@ -522,13 +581,19 @@ void NetworkClose(bool close_admins)
}
for (NetworkClientSocket *cs : NetworkClientSocket::Iterate()) {
cs->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST);
cs->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT);
}
ServerNetworkGameSocketHandler::CloseListeners();
ServerNetworkAdminSocketHandler::CloseListeners();
} else if (MyClient::my_client != nullptr) {
MyClient::SendQuit();
MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CONN_LOST);
_network_coordinator_client.CloseConnection();
} else {
if (MyClient::my_client != nullptr) {
MyClient::SendQuit();
MyClient::my_client->CloseConnection(NETWORK_RECV_STATUS_CLIENT_QUIT);
}
_network_coordinator_client.CloseAllConnections();
}
TCPConnecter::KillAll();
@@ -538,7 +603,7 @@ void NetworkClose(bool close_admins)
NetworkFreeLocalCommandQueue();
free(_network_company_states);
delete[] _network_company_states;
_network_company_states = nullptr;
InitializeNetworkPools(close_admins);
@@ -548,7 +613,6 @@ void NetworkClose(bool close_admins)
static void NetworkInitialize(bool close_admins = true)
{
InitializeNetworkPools(close_admins);
NetworkUDPInitialize();
_sync_frame = 0;
_network_first_time = true;
@@ -556,59 +620,71 @@ static void NetworkInitialize(bool close_admins = true)
_network_reconnect = 0;
}
/** Non blocking connection create to query servers */
class TCPQueryConnecter : TCPConnecter {
/** Non blocking connection to query servers for their game info. */
class TCPQueryConnecter : TCPServerConnecter {
private:
std::string connection_string;
public:
TCPQueryConnecter(const NetworkAddress &address) : TCPConnecter(address) {}
TCPQueryConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
void OnFailure() override
{
NetworkDisconnect();
NetworkGameList *item = NetworkGameListAddItem(connection_string);
item->online = false;
UpdateNetworkGameWindow();
}
void OnConnect(SOCKET s) override
{
_networking = true;
new ClientNetworkGameSocketHandler(s);
MyClient::SendCompanyInformationQuery();
new ClientNetworkGameSocketHandler(s, this->connection_string);
MyClient::SendInformationQuery();
}
};
/**
* Query a server to fetch his game-info.
* @param address the address to query.
* Query a server to fetch the game-info.
* @param connection_string the address to query.
*/
void NetworkTCPQueryServer(NetworkAddress address)
void NetworkQueryServer(const std::string &connection_string)
{
if (!_network_available) return;
NetworkDisconnect();
NetworkInitialize();
new TCPQueryConnecter(address);
new TCPQueryConnecter(connection_string);
}
/* Validates an address entered as a string and adds the server to
/**
* Validates an address entered as a string and adds the server to
* the list. If you use this function, the games will be marked
* as manually added. */
void NetworkAddServer(const char *b)
* as manually added.
* @param connection_string The IP:port of the server to add.
* @param manually Whether the enter should be marked as manual added.
* @param never_expire Whether the entry can expire (removed when no longer found in the public listing).
* @return The entry on the game list.
*/
NetworkGameList *NetworkAddServer(const std::string &connection_string, bool manually, bool never_expire)
{
if (*b != '\0') {
const char *port = nullptr;
const char *company = nullptr;
char host[NETWORK_HOSTNAME_LENGTH];
uint16 rport;
if (connection_string.empty()) return nullptr;
strecpy(host, b, lastof(host));
/* Ensure the item already exists in the list */
NetworkGameList *item = NetworkGameListAddItem(connection_string);
if (item->info.server_name.empty()) {
ClearGRFConfigList(&item->info.grfconfig);
item->info.server_name = connection_string;
strecpy(_settings_client.network.connect_to_ip, b, lastof(_settings_client.network.connect_to_ip));
rport = NETWORK_DEFAULT_PORT;
UpdateNetworkGameWindow();
ParseConnectionString(&company, &port, host);
if (port != nullptr) rport = atoi(port);
NetworkUDPQueryServer(NetworkAddress(host, rport), true);
NetworkQueryServer(connection_string);
}
if (manually) item->manually = true;
if (never_expire) item->version = INT32_MAX;
return item;
}
/**
@@ -636,14 +712,17 @@ void NetworkRebuildHostList()
_network_host_list.clear();
for (NetworkGameList *item = _network_game_list; item != nullptr; item = item->next) {
if (item->manually) _network_host_list.emplace_back(item->address.GetAddressAsString(false));
if (item->manually) _network_host_list.emplace_back(item->connection_string);
}
}
/** Non blocking connection create to actually connect to servers */
class TCPClientConnecter : TCPConnecter {
class TCPClientConnecter : TCPServerConnecter {
private:
std::string connection_string;
public:
TCPClientConnecter(const NetworkAddress &address) : TCPConnecter(address) {}
TCPClientConnecter(const std::string &connection_string) : TCPServerConnecter(connection_string, NETWORK_DEFAULT_PORT), connection_string(connection_string) {}
void OnFailure() override
{
@@ -653,25 +732,41 @@ public:
void OnConnect(SOCKET s) override
{
_networking = true;
new ClientNetworkGameSocketHandler(s);
new ClientNetworkGameSocketHandler(s, this->connection_string);
IConsoleCmdExec("exec scripts/on_client.scr 0");
NetworkClient_Connected();
}
};
/* Used by clients, to connect to a server */
void NetworkClientConnectGame(const char *hostname, uint16 port, CompanyID join_as, const char *join_server_password, const char *join_company_password)
/**
* Join a client to the server at with the given connection string.
* The default for the passwords is \c nullptr. When the server or company needs a
* password and none is given, the user is asked to enter the password in the GUI.
* This function will return false whenever some information required to join is not
* correct such as the company number or the client's name, or when there is not
* networking avalabile at all. If the function returns false the connection with
* the existing server is not disconnected.
* It will return true when it starts the actual join process, i.e. when it
* actually shows the join status window.
*
* @param connection_string The IP address, port and company number to join as.
* @param default_company The company number to join as when none is given.
* @param join_server_password The password for the server.
* @param join_company_password The password for the company.
* @return Whether the join has started.
*/
bool NetworkClientConnectGame(const std::string &connection_string, CompanyID default_company, const std::string &join_server_password, const std::string &join_company_password)
{
if (!_network_available) return;
CompanyID join_as = default_company;
std::string resolved_connection_string = ServerAddress::Parse(connection_string, NETWORK_DEFAULT_PORT, &join_as).connection_string;
if (port == 0) return;
if (!_network_available) return false;
if (!NetworkValidateOurClientName()) return false;
strecpy(_settings_client.network.last_host, hostname, lastof(_settings_client.network.last_host));
_settings_client.network.last_port = port;
_network_join_as = join_as;
_network_join_server_password = join_server_password;
_network_join_company_password = join_company_password;
_network_join.connection_string = resolved_connection_string;
_network_join.company = join_as;
_network_join.server_password = join_server_password;
_network_join.company_password = join_company_password;
if (_game_mode == GM_MENU) {
/* From the menu we can immediately continue with the actual join. */
@@ -684,6 +779,7 @@ void NetworkClientConnectGame(const char *hostname, uint16 port, CompanyID join_
*/
_switch_mode = SM_JOIN_GAME;
}
return true;
}
/**
@@ -696,18 +792,16 @@ void NetworkClientJoinGame()
NetworkDisconnect();
NetworkInitialize();
_settings_client.network.last_joined = _network_join.connection_string;
_network_join_status = NETWORK_JOIN_STATUS_CONNECTING;
ShowJoinStatusWindow();
new TCPClientConnecter(NetworkAddress(_settings_client.network.last_host, _settings_client.network.last_port));
new TCPClientConnecter(_network_join.connection_string);
}
static void NetworkInitGameInfo()
{
if (StrEmpty(_settings_client.network.server_name)) {
seprintf(_settings_client.network.server_name, lastof(_settings_client.network.server_name), "Unnamed Server");
}
FillStaticNetworkServerGameInfo();
/* The server is a client too */
_network_game_info.clients_on = _network_dedicated ? 0 : 1;
@@ -716,7 +810,49 @@ static void NetworkInitGameInfo()
NetworkClientInfo *ci = new NetworkClientInfo(CLIENT_ID_SERVER);
ci->client_playas = _network_dedicated ? COMPANY_SPECTATOR : COMPANY_FIRST;
strecpy(ci->client_name, _settings_client.network.client_name, lastof(ci->client_name));
ci->client_name = _settings_client.network.client_name;
}
/**
* Trim the given server name in place, i.e. remove leading and trailing spaces.
* After the trim check whether the server name is not empty.
* When the server name is empty a GUI error message is shown telling the
* user to set the servername and this function returns false.
*
* @param server_name The server name to validate. It will be trimmed of leading
* and trailing spaces.
* @return True iff the server name is valid.
*/
bool NetworkValidateServerName(std::string &server_name)
{
StrTrimInPlace(server_name);
if (!server_name.empty()) return true;
ShowErrorMessage(STR_NETWORK_ERROR_BAD_SERVER_NAME, INVALID_STRING_ID, WL_ERROR);
return false;
}
/**
* Check whether the client and server name are set, for a dedicated server and if not set them to some default
* value and tell the user to change this as soon as possible.
* If the saved name is the default value, then the user is told to override this value too.
* This is only meant dedicated servers, as for the other servers the GUI ensures a name has been entered.
*/
static void CheckClientAndServerName()
{
static const std::string fallback_client_name = "Unnamed Client";
StrTrimInPlace(_settings_client.network.client_name);
if (_settings_client.network.client_name.empty() || _settings_client.network.client_name.compare(fallback_client_name) == 0) {
Debug(net, 1, "No \"client_name\" has been set, using \"{}\" instead. Please set this now using the \"name <new name>\" command", fallback_client_name);
_settings_client.network.client_name = fallback_client_name;
}
static const std::string fallback_server_name = "Unnamed Server";
StrTrimInPlace(_settings_client.network.server_name);
if (_settings_client.network.server_name.empty() || _settings_client.network.server_name.compare(fallback_server_name) == 0) {
Debug(net, 1, "No \"server_name\" has been set, using \"{}\" instead. Please set this now using the \"server_name <new name>\" command", fallback_server_name);
_settings_client.network.server_name = fallback_server_name;
}
}
bool NetworkServerStart()
@@ -727,22 +863,26 @@ bool NetworkServerStart()
IConsoleCmdExec("exec scripts/pre_server.scr 0");
if (_network_dedicated) IConsoleCmdExec("exec scripts/pre_dedicated.scr 0");
/* Check for the client and server names to be set, but only after the scripts had a chance to set them.*/
if (_network_dedicated) CheckClientAndServerName();
NetworkDisconnect(false, false);
NetworkInitialize(false);
DEBUG(net, 1, "starting listeners for clients");
NetworkUDPInitialize();
Debug(net, 5, "Starting listeners for clients");
if (!ServerNetworkGameSocketHandler::Listen(_settings_client.network.server_port)) return false;
/* Only listen for admins when the password isn't empty. */
if (!StrEmpty(_settings_client.network.admin_password)) {
DEBUG(net, 1, "starting listeners for admins");
if (!_settings_client.network.admin_password.empty()) {
Debug(net, 5, "Starting listeners for admins");
if (!ServerNetworkAdminSocketHandler::Listen(_settings_client.network.server_admin_port)) return false;
}
/* Try to start UDP-server */
DEBUG(net, 1, "starting listeners for incoming server queries");
Debug(net, 5, "Starting listeners for incoming server queries");
NetworkUDPServerListen();
_network_company_states = CallocT<NetworkCompanyState>(MAX_COMPANIES);
_network_company_states = new NetworkCompanyState[MAX_COMPANIES];
_network_server = true;
_networking = true;
_frame_counter = 0;
@@ -756,15 +896,15 @@ bool NetworkServerStart()
NetworkInitGameInfo();
if (_settings_client.network.server_game_type != SERVER_GAME_TYPE_LOCAL) {
_network_coordinator_client.Register();
}
/* execute server initialization script */
IConsoleCmdExec("exec scripts/on_server.scr 0");
/* if the server is dedicated ... add some other script */
if (_network_dedicated) IConsoleCmdExec("exec scripts/on_dedicated.scr 0");
/* Try to register us to the master server */
_network_need_advertise = true;
NetworkUDPAdvertise();
/* welcome possibly still connected admins - this can only happen on a dedicated server. */
if (_network_dedicated) ServerNetworkAdminSocketHandler::WelcomeAll();
@@ -813,9 +953,7 @@ void NetworkDisconnect(bool blocking, bool close_admins)
}
}
if (_settings_client.network.server_advertise) NetworkUDPRemoveAdvertise(blocking);
DeleteWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
CloseWindowById(WC_NETWORK_STATUS_WINDOW, WN_NETWORK_STATUS_WINDOW_JOIN);
NetworkClose(close_admins);
@@ -823,6 +961,29 @@ void NetworkDisconnect(bool blocking, bool close_admins)
NetworkUDPInitialize();
}
/**
* The setting server_game_type was updated; possibly we need to take some
* action.
*/
void NetworkUpdateServerGameType()
{
if (!_networking) return;
switch (_settings_client.network.server_game_type) {
case SERVER_GAME_TYPE_LOCAL:
_network_coordinator_client.CloseConnection();
break;
case SERVER_GAME_TYPE_INVITE_ONLY:
case SERVER_GAME_TYPE_PUBLIC:
_network_coordinator_client.Register();
break;
default:
NOT_REACHED();
}
}
/**
* Receives something from the network.
* @return true if everything went fine, false when the connection got closed.
@@ -856,6 +1017,7 @@ static void NetworkSend()
void NetworkBackgroundLoop()
{
_network_content_client.SendReceive();
_network_coordinator_client.SendReceive();
TCPConnecter::CheckCallbacks();
NetworkHTTPSocketHandler::HTTPReceive();
@@ -876,7 +1038,7 @@ void NetworkGameLoop()
/* We don't want to log multiple times if paused. */
static Date last_log;
if (last_log != _date) {
DEBUG(desync, 1, "sync: %08x; %02x; %08x; %08x", _date, _date_fract, _random.state[0], _random.state[1]);
Debug(desync, 1, "sync: {:08x}; {:02x}; {:08x}; {:08x}", _date, _date_fract, _random.state[0], _random.state[1]);
last_log = _date;
}
}
@@ -890,7 +1052,7 @@ void NetworkGameLoop()
static bool check_sync_state = false;
static uint32 sync_state[2];
if (f == nullptr && next_date == 0) {
DEBUG(net, 0, "Cannot open commands.log");
Debug(desync, 0, "Cannot open commands.log");
next_date = 1;
}
@@ -898,15 +1060,15 @@ void NetworkGameLoop()
if (_date == next_date && _date_fract == next_date_fract) {
if (cp != nullptr) {
NetworkSendCommand(cp->tile, cp->p1, cp->p2, cp->cmd & ~CMD_FLAGS_MASK, nullptr, cp->text, cp->company);
DEBUG(net, 0, "injecting: %08x; %02x; %02x; %06x; %08x; %08x; %08x; \"%s\" (%s)", _date, _date_fract, (int)_current_company, cp->tile, cp->p1, cp->p2, cp->cmd, cp->text, GetCommandName(cp->cmd));
free(cp);
Debug(desync, 0, "Injecting: {:08x}; {:02x}; {:02x}; {:06x}; {:08x}; {:08x}; {:08x}; \"{}\" ({})", _date, _date_fract, (int)_current_company, cp->tile, cp->p1, cp->p2, cp->cmd, cp->text, GetCommandName(cp->cmd));
delete cp;
cp = nullptr;
}
if (check_sync_state) {
if (sync_state[0] == _random.state[0] && sync_state[1] == _random.state[1]) {
DEBUG(net, 0, "sync check: %08x; %02x; match", _date, _date_fract);
Debug(desync, 0, "Sync check: {:08x}; {:02x}; match", _date, _date_fract);
} else {
DEBUG(net, 0, "sync check: %08x; %02x; mismatch expected {%08x, %08x}, got {%08x, %08x}",
Debug(desync, 0, "Sync check: {:08x}; {:02x}; mismatch expected {{:08x}, {:08x}}, got {{:08x}, {:08x}}",
_date, _date_fract, sync_state[0], sync_state[1], _random.state[0], _random.state[1]);
NOT_REACHED();
}
@@ -934,10 +1096,11 @@ void NetworkGameLoop()
) {
p += 5;
if (*p == ' ') p++;
cp = CallocT<CommandPacket>(1);
cp = new CommandPacket();
int company;
static_assert(sizeof(cp->text) == 128);
int ret = sscanf(p, "%x; %x; %x; %x; %x; %x; %x; \"%127[^\"]\"", &next_date, &next_date_fract, &company, &cp->tile, &cp->p1, &cp->p2, &cp->cmd, cp->text);
char buffer[128];
int ret = sscanf(p, "%x; %x; %x; %x; %x; %x; %x; \"%127[^\"]\"", &next_date, &next_date_fract, &company, &cp->tile, &cp->p1, &cp->p2, &cp->cmd, buffer);
cp->text = buffer;
/* There are 8 pieces of data to read, however the last is a
* string that might or might not exist. Ignore it if that
* string misses because in 99% of the time it's not used. */
@@ -947,8 +1110,8 @@ void NetworkGameLoop()
/* Manually insert a pause when joining; this way the client can join at the exact right time. */
int ret = sscanf(p + 6, "%x; %x", &next_date, &next_date_fract);
assert(ret == 2);
DEBUG(net, 0, "injecting pause for join at %08x:%02x; please join when paused", next_date, next_date_fract);
cp = CallocT<CommandPacket>(1);
Debug(desync, 0, "Injecting pause for join at {:08x}:{:02x}; please join when paused", next_date, next_date_fract);
cp = new CommandPacket();
cp->company = COMPANY_SPECTATOR;
cp->cmd = CMD_PAUSE;
cp->p1 = PM_PAUSED_NORMAL;
@@ -963,16 +1126,16 @@ void NetworkGameLoop()
/* A message that is not very important to the log playback, but part of the log. */
#ifndef DEBUG_FAILED_DUMP_COMMANDS
} else if (strncmp(p, "cmdf: ", 6) == 0) {
DEBUG(net, 0, "Skipping replay of failed command: %s", p + 6);
Debug(desync, 0, "Skipping replay of failed command: {}", p + 6);
#endif
} else {
/* Can't parse a line; what's wrong here? */
DEBUG(net, 0, "trying to parse: %s", p);
Debug(desync, 0, "Trying to parse: {}", p);
NOT_REACHED();
}
}
if (f != nullptr && feof(f)) {
DEBUG(net, 0, "End of commands.log");
Debug(desync, 0, "End of commands.log");
fclose(f);
f = nullptr;
}
@@ -1047,44 +1210,52 @@ static void NetworkGenerateServerId()
}
/* _settings_client.network.network_id is our id */
seprintf(_settings_client.network.network_id, lastof(_settings_client.network.network_id), "%s", hex_output);
_settings_client.network.network_id = hex_output;
}
void NetworkStartDebugLog(const char *hostname, uint16 port)
{
extern SOCKET _debug_socket; // Comes from debug.c
class TCPNetworkDebugConnecter : TCPConnecter {
private:
std::string connection_string;
DEBUG(net, 0, "Redirecting DEBUG() to %s:%d", hostname, port);
public:
TCPNetworkDebugConnecter(const std::string &connection_string) : TCPConnecter(connection_string, NETWORK_DEFAULT_DEBUGLOG_PORT), connection_string(connection_string) {}
NetworkAddress address(hostname, port);
SOCKET s = address.Connect();
if (s == INVALID_SOCKET) {
DEBUG(net, 0, "Failed to open socket for redirection DEBUG()");
return;
void OnFailure() override
{
Debug(net, 0, "Failed to open connection to {} for redirecting Debug()", this->connection_string);
}
_debug_socket = s;
void OnConnect(SOCKET s) override
{
Debug(net, 3, "Redirecting Debug() to {}", this->connection_string);
DEBUG(net, 0, "DEBUG() is now redirected");
extern SOCKET _debug_socket;
_debug_socket = s;
}
};
void NetworkStartDebugLog(const std::string &connection_string)
{
new TCPNetworkDebugConnecter(connection_string);
}
/** This tries to launch the network for a given OS */
void NetworkStartUp()
{
DEBUG(net, 3, "[core] starting network...");
Debug(net, 3, "Starting network");
/* Network is available */
_network_available = NetworkCoreInitialize();
_network_dedicated = false;
_network_need_advertise = true;
/* Generate an server id when there is none yet */
if (StrEmpty(_settings_client.network.network_id)) NetworkGenerateServerId();
if (_settings_client.network.network_id.empty()) NetworkGenerateServerId();
memset(&_network_game_info, 0, sizeof(_network_game_info));
_network_game_info = {};
NetworkInitialize();
DEBUG(net, 3, "[core] network online, multiplayer available");
NetworkUDPInitialize();
Debug(net, 3, "Network online, multiplayer available");
NetworkFindBroadcastIPs(&_broadcast_list);
}
@@ -1094,7 +1265,7 @@ void NetworkShutDown()
NetworkDisconnect(true);
NetworkUDPClose();
DEBUG(net, 3, "[core] shutting down network");
Debug(net, 3, "Shutting down network");
_network_available = false;
@@ -1104,9 +1275,9 @@ void NetworkShutDown()
#ifdef __EMSCRIPTEN__
extern "C" {
void CDECL em_openttd_add_server(const char *host, int port)
void CDECL em_openttd_add_server(const char *connection_string)
{
NetworkUDPQueryServer(NetworkAddress(host, port), true);
NetworkAddServer(connection_string, false, true);
}
}