Update to 12.0-beta1
This commit is contained in:
@@ -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);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user