# HG changeset patch # User Bouke Haarsma # Date 2023-09-02 17:46:52 # Node ID 21baf162bd57344d20a9e553f847747d63b85620 # Parent 2937e4e5367770c52aef16daaa1bcc8eb826e75c Codechange: workaround CMake/Xcode duplicate file name issue (#11186) Having a library with files with the same name isn't supported in CMake's Xcode project file generation: https://gitlab.kitware.com/cmake/cmake/-/issues/20501. One of the files is renamed to work around this bug. diff --git a/src/console_cmds.cpp b/src/console_cmds.cpp --- a/src/console_cmds.cpp +++ b/src/console_cmds.cpp @@ -13,7 +13,7 @@ #include "engine_func.h" #include "landscape.h" #include "saveload/saveload.h" -#include "network/core/game_info.h" +#include "network/core/network_game_info.h" #include "network/network.h" #include "network/network_func.h" #include "network/network_base.h" diff --git a/src/network/core/CMakeLists.txt b/src/network/core/CMakeLists.txt --- a/src/network/core/CMakeLists.txt +++ b/src/network/core/CMakeLists.txt @@ -5,8 +5,8 @@ add_files( config.h core.cpp core.h - game_info.cpp - game_info.h + network_game_info.cpp + network_game_info.h host.cpp host.h http.h diff --git a/src/network/core/game_info.cpp b/src/network/core/game_info.cpp deleted file mode 100644 --- a/src/network/core/game_info.cpp +++ /dev/null @@ -1,393 +0,0 @@ -/* - * This file is part of OpenTTD. - * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. - * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . - */ - -/** - * @file game_info.cpp Functions to convert NetworkGameInfo to Packet and back. - */ - -#include "../../stdafx.h" -#include "game_info.h" -#include "../../core/bitmath_func.hpp" -#include "../../company_base.h" -#include "../../timer/timer_game_calendar.h" -#include "../../debug.h" -#include "../../map_func.h" -#include "../../game/game.hpp" -#include "../../game/game_info.hpp" -#include "../../settings_type.h" -#include "../../string_func.h" -#include "../../rev.h" -#include "../network_func.h" -#include "../network.h" -#include "packet.h" - -#include "../../safeguards.h" - - -/** - * How many hex digits of the git hash to include in network revision string. - * Determined as 10 hex digits + 2 characters for -g/-u/-m prefix. - */ -static const uint GITHASH_SUFFIX_LEN = 12; - -NetworkServerGameInfo _network_game_info; ///< Information about our game. - -/** - * Get the network version string used by this build. - * The returned string is guaranteed to be at most NETWORK_REVISON_LENGTH bytes including '\0' terminator. - */ -std::string_view GetNetworkRevisionString() -{ - static std::string network_revision; - - if (network_revision.empty()) { - network_revision = _openttd_revision; - if (_openttd_revision_tagged) { - /* Tagged; do not mangle further, though ensure it's not too long. */ - if (network_revision.size() >= NETWORK_REVISION_LENGTH) network_revision.resize(NETWORK_REVISION_LENGTH - 1); - } else { - /* Not tagged; add the githash suffix while ensuring the string does not become too long. */ - assert(_openttd_revision_modified < 3); - std::string githash_suffix = fmt::format("-{}{}", "gum"[_openttd_revision_modified], _openttd_revision_hash); - if (githash_suffix.size() > GITHASH_SUFFIX_LEN) githash_suffix.resize(GITHASH_SUFFIX_LEN); - - /* Where did the hash start in the original string? Overwrite from that position, unless that would create a too long string. */ - size_t hash_end = network_revision.find_last_of('-'); - if (hash_end == std::string::npos) hash_end = network_revision.size(); - if (hash_end + githash_suffix.size() >= NETWORK_REVISION_LENGTH) hash_end = NETWORK_REVISION_LENGTH - githash_suffix.size() - 1; - - /* Replace the git hash in revision string. */ - network_revision.replace(hash_end, std::string::npos, githash_suffix); - } - assert(network_revision.size() < NETWORK_REVISION_LENGTH); // size does not include terminator, constant does, hence strictly less than - Debug(net, 3, "Network revision name: {}", network_revision); - } - - return network_revision; -} - -/** - * Extract the git hash from the revision string. - * @param revision_string The revision string (formatted as DATE-BRANCH-GITHASH). - * @return The git has part of the revision. - */ -static std::string_view ExtractNetworkRevisionHash(std::string_view revision_string) -{ - size_t index = revision_string.find_last_of('-'); - if (index == std::string::npos) return {}; - return revision_string.substr(index); -} - -/** - * Checks whether the given version string is compatible with our version. - * First tries to match the full string, if that fails, attempts to compare just git hashes. - * @param other the version string to compare to - */ -bool IsNetworkCompatibleVersion(std::string_view other) -{ - if (GetNetworkRevisionString() == other) return true; - - /* If this version is tagged, then the revision string must be a complete match, - * since there is no git hash suffix in it. - * This is needed to avoid situations like "1.9.0-beta1" comparing equal to "2.0.0-beta1". */ - if (_openttd_revision_tagged) return false; - - std::string_view hash1 = ExtractNetworkRevisionHash(GetNetworkRevisionString()); - std::string_view hash2 = ExtractNetworkRevisionHash(other); - return hash1 == hash2; -} - -/** - * Check if an game entry is compatible with our client. - */ -void CheckGameCompatibility(NetworkGameInfo &ngi) -{ - /* Check if we are allowed on this server based on the revision-check. */ - ngi.version_compatible = IsNetworkCompatibleVersion(ngi.server_revision); - ngi.compatible = ngi.version_compatible; - - /* Check if we have all the GRFs on the client-system too. */ - for (const GRFConfig *c = ngi.grfconfig; c != nullptr; c = c->next) { - if (c->status == GCS_NOT_FOUND) ngi.compatible = false; - } -} - -/** - * Fill a NetworkServerGameInfo structure with the static content, or things - * that are so static they can be updated on request from a settings change. - */ -void FillStaticNetworkServerGameInfo() -{ - _network_game_info.use_password = !_settings_client.network.server_password.empty(); - _network_game_info.start_date = TimerGameCalendar::ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1); - _network_game_info.clients_max = _settings_client.network.max_clients; - _network_game_info.companies_max = _settings_client.network.max_companies; - _network_game_info.map_width = Map::SizeX(); - _network_game_info.map_height = Map::SizeY(); - _network_game_info.landscape = _settings_game.game_creation.landscape; - _network_game_info.dedicated = _network_dedicated; - _network_game_info.grfconfig = _grfconfig; - - _network_game_info.server_name = _settings_client.network.server_name; - _network_game_info.server_revision = GetNetworkRevisionString(); -} - -/** - * Get the NetworkServerGameInfo structure with the latest information of the server. - * @return The current NetworkServerGameInfo. - */ -const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo() -{ - /* These variables are updated inside _network_game_info as if they are global variables: - * - clients_on - * - invite_code - * These don't need to be updated manually here. - */ - _network_game_info.companies_on = (byte)Company::GetNumItems(); - _network_game_info.spectators_on = NetworkSpectatorCount(); - _network_game_info.game_date = TimerGameCalendar::date; - return &_network_game_info; -} - -/** - * Function that is called for every GRFConfig that is read when receiving - * a NetworkGameInfo. Only grfid and md5sum are set, the rest is zero. This - * function must set all appropriate fields. This GRF is later appended to - * the grfconfig list of the NetworkGameInfo. - * @param config The GRF to handle. - * @param name The name of the NewGRF, empty when unknown. - */ -static void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config, std::string name) -{ - /* Find the matching GRF file */ - const GRFConfig *f = FindGRFConfig(config->ident.grfid, FGCM_EXACT, &config->ident.md5sum); - if (f == nullptr) { - AddGRFTextToList(config->name, name.empty() ? GetString(STR_CONFIG_ERROR_INVALID_GRF_UNKNOWN) : name); - config->status = GCS_NOT_FOUND; - } else { - config->filename = f->filename; - config->name = f->name; - config->info = f->info; - config->url = f->url; - } - SetBit(config->flags, GCF_COPY); -} - -/** - * Serializes the NetworkGameInfo struct to the packet. - * @param p the packet to write the data to. - * @param info the NetworkGameInfo struct to serialize from. - */ -void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info, bool send_newgrf_names) -{ - p->Send_uint8 (NETWORK_GAME_INFO_VERSION); - - /* - * Please observe the order. - * The parts must be read in the same order as they are sent! - */ - - /* Update the documentation in game_info.h on changes - * to the NetworkGameInfo wire-protocol! */ - - /* NETWORK_GAME_INFO_VERSION = 6 */ - p->Send_uint8(send_newgrf_names ? NST_GRFID_MD5_NAME : NST_GRFID_MD5); - - /* NETWORK_GAME_INFO_VERSION = 5 */ - GameInfo *game_info = Game::GetInfo(); - p->Send_uint32(game_info == nullptr ? -1 : (uint32_t)game_info->GetVersion()); - p->Send_string(game_info == nullptr ? "" : game_info->GetName()); - - /* NETWORK_GAME_INFO_VERSION = 4 */ - { - /* Only send the GRF Identification (GRF_ID and MD5 checksum) of - * the GRFs that are needed, i.e. the ones that the server has - * selected in the NewGRF GUI and not the ones that are used due - * to the fact that they are in [newgrf-static] in openttd.cfg */ - const GRFConfig *c; - uint count = 0; - - /* Count number of GRFs to send information about */ - for (c = info->grfconfig; c != nullptr; c = c->next) { - if (!HasBit(c->flags, GCF_STATIC)) count++; - } - p->Send_uint8 (count); // Send number of GRFs - - /* Send actual GRF Identifications */ - for (c = info->grfconfig; c != nullptr; c = c->next) { - if (HasBit(c->flags, GCF_STATIC)) continue; - - SerializeGRFIdentifier(p, &c->ident); - if (send_newgrf_names) p->Send_string(c->GetName()); - } - } - - /* NETWORK_GAME_INFO_VERSION = 3 */ - p->Send_uint32(static_cast(info->game_date)); - p->Send_uint32(static_cast(info->start_date)); - - /* NETWORK_GAME_INFO_VERSION = 2 */ - p->Send_uint8 (info->companies_max); - p->Send_uint8 (info->companies_on); - p->Send_uint8 (info->clients_max); // Used to be max-spectators - - /* NETWORK_GAME_INFO_VERSION = 1 */ - p->Send_string(info->server_name); - p->Send_string(info->server_revision); - p->Send_bool (info->use_password); - p->Send_uint8 (info->clients_max); - p->Send_uint8 (info->clients_on); - p->Send_uint8 (info->spectators_on); - p->Send_uint16(info->map_width); - p->Send_uint16(info->map_height); - p->Send_uint8 (info->landscape); - p->Send_bool (info->dedicated); -} - -/** - * Deserializes the NetworkGameInfo struct from the packet. - * @param p the packet to read the data from. - * @param info the NetworkGameInfo to deserialize into. - */ -void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info, const GameInfoNewGRFLookupTable *newgrf_lookup_table) -{ - static const TimerGameCalendar::Date MAX_DATE = TimerGameCalendar::ConvertYMDToDate(MAX_YEAR, 11, 31); // December is month 11 - - byte game_info_version = p->Recv_uint8(); - NewGRFSerializationType newgrf_serialisation = NST_GRFID_MD5; - - /* - * Please observe the order. - * The parts must be read in the same order as they are sent! - */ - - /* Update the documentation in game_info.h on changes - * to the NetworkGameInfo wire-protocol! */ - - switch (game_info_version) { - case 6: - newgrf_serialisation = (NewGRFSerializationType)p->Recv_uint8(); - if (newgrf_serialisation >= NST_END) return; - FALLTHROUGH; - - case 5: { - info->gamescript_version = (int)p->Recv_uint32(); - info->gamescript_name = p->Recv_string(NETWORK_NAME_LENGTH); - FALLTHROUGH; - } - - case 4: { - /* Ensure that the maximum number of NewGRFs and the field in the network - * protocol are matched to eachother. If that is not the case anymore a - * check must be added to ensure the received data is still valid. */ - static_assert(std::numeric_limits::max() == NETWORK_MAX_GRF_COUNT); - uint num_grfs = p->Recv_uint8(); - - GRFConfig **dst = &info->grfconfig; - for (uint i = 0; i < num_grfs; i++) { - NamedGRFIdentifier grf; - switch (newgrf_serialisation) { - case NST_GRFID_MD5: - DeserializeGRFIdentifier(p, &grf.ident); - break; - - case NST_GRFID_MD5_NAME: - DeserializeGRFIdentifierWithName(p, &grf); - break; - - case NST_LOOKUP_ID: { - if (newgrf_lookup_table == nullptr) return; - auto it = newgrf_lookup_table->find(p->Recv_uint32()); - if (it == newgrf_lookup_table->end()) return; - grf = it->second; - break; - } - - default: - NOT_REACHED(); - } - - GRFConfig *c = new GRFConfig(); - c->ident = grf.ident; - HandleIncomingNetworkGameInfoGRFConfig(c, grf.name); - - /* Append GRFConfig to the list */ - *dst = c; - dst = &c->next; - } - FALLTHROUGH; - } - - case 3: - info->game_date = Clamp(p->Recv_uint32(), 0, static_cast(MAX_DATE)); - info->start_date = Clamp(p->Recv_uint32(), 0, static_cast(MAX_DATE)); - FALLTHROUGH; - - case 2: - info->companies_max = p->Recv_uint8 (); - info->companies_on = p->Recv_uint8 (); - p->Recv_uint8(); // Used to contain max-spectators. - FALLTHROUGH; - - case 1: - info->server_name = p->Recv_string(NETWORK_NAME_LENGTH); - info->server_revision = p->Recv_string(NETWORK_REVISION_LENGTH); - if (game_info_version < 6) p->Recv_uint8 (); // Used to contain server-lang. - info->use_password = p->Recv_bool (); - info->clients_max = p->Recv_uint8 (); - info->clients_on = p->Recv_uint8 (); - info->spectators_on = p->Recv_uint8 (); - if (game_info_version < 3) { // 16 bits dates got scrapped and are read earlier - info->game_date = p->Recv_uint16() + DAYS_TILL_ORIGINAL_BASE_YEAR; - info->start_date = p->Recv_uint16() + DAYS_TILL_ORIGINAL_BASE_YEAR; - } - if (game_info_version < 6) while (p->Recv_uint8() != 0) {} // Used to contain the map-name. - info->map_width = p->Recv_uint16(); - info->map_height = p->Recv_uint16(); - info->landscape = p->Recv_uint8 (); - info->dedicated = p->Recv_bool (); - - if (info->landscape >= NUM_LANDSCAPE) info->landscape = 0; - } -} - -/** - * Serializes the GRFIdentifier (GRF ID and MD5 checksum) to the packet - * @param p the packet to write the data to. - * @param grf the GRFIdentifier to serialize. - */ -void SerializeGRFIdentifier(Packet *p, const GRFIdentifier *grf) -{ - p->Send_uint32(grf->grfid); - for (size_t j = 0; j < grf->md5sum.size(); j++) { - p->Send_uint8(grf->md5sum[j]); - } -} - -/** - * Deserializes the GRFIdentifier (GRF ID and MD5 checksum) from the packet - * @param p the packet to read the data from. - * @param grf the GRFIdentifier to deserialize. - */ -void DeserializeGRFIdentifier(Packet *p, GRFIdentifier *grf) -{ - grf->grfid = p->Recv_uint32(); - for (size_t j = 0; j < grf->md5sum.size(); j++) { - grf->md5sum[j] = p->Recv_uint8(); - } -} - -/** - * Deserializes the NamedGRFIdentifier (GRF ID, MD5 checksum and name) from the packet - * @param p the packet to read the data from. - * @param grf the NamedGRFIdentifier to deserialize. - */ -void DeserializeGRFIdentifierWithName(Packet *p, NamedGRFIdentifier *grf) -{ - DeserializeGRFIdentifier(p, &grf->ident); - grf->name = p->Recv_string(NETWORK_GRF_NAME_LENGTH); -} diff --git a/src/network/core/game_info.h b/src/network/core/game_info.h deleted file mode 100644 --- a/src/network/core/game_info.h +++ /dev/null @@ -1,149 +0,0 @@ -/* - * This file is part of OpenTTD. - * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. - * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. - * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . - */ - -/** - * @file game_info.h Convert NetworkGameInfo to Packet and back. - */ - -#ifndef NETWORK_CORE_GAME_INFO_H -#define NETWORK_CORE_GAME_INFO_H - -#include "config.h" -#include "core.h" -#include "../../newgrf_config.h" -#include "../../timer/timer_game_calendar.h" - -#include - -/* - * NetworkGameInfo has several revisions which we still need to support on the - * wire. The table below shows the version and size for each field of the - * serialized NetworkGameInfo. - * - * Version: Bytes: Description: - * all 1 the version of this packet's structure - * - * 6+ 1 type of storage for the NewGRFs below: - * 0 = NewGRF ID and MD5 checksum. - * Used as default for version 5 and below, and for - * later game updates to the Game Coordinator. - * 1 = NewGRF ID, MD5 checksum and name. - * Used for direct requests and the first game - * update to Game Coordinator. - * 2 = Index in NewGRF lookup table. - * Used for sending server listing from the Game - * Coordinator to the clients. - * - * 5+ 4 version number of the Game Script (-1 is case none is selected). - * 5+ var string with the name of the Game Script. - * - * 4+ 1 number of GRFs attached (n). - * 4+ n * var identifiers for GRF files. Consists of: - * Note: the 'vN' refers to packet version and 'type' - * refers to the v6+ type of storage for the NewGRFs. - * - 4 byte variable with the GRF ID. - * For v4, v5, and v6+ in case of type 0 and/or type 1. - * - 16 bytes with the MD5 checksum of the GRF. - * For v4, v5, and v6+ in case of type 0 and/or type 1. - * - string with name of NewGRF. - * For v6+ in case of type 1. - * - 4 byte lookup table index. - * For v6+ in case of type 2. - * - * 3+ 4 current game date in days since 1-1-0 (DMY) - * 3+ 4 game introduction date in days since 1-1-0 (DMY) - * - * 2+ 1 maximum number of companies allowed on the server - * 2+ 1 number of companies on the server - * 2+ 1 maximum number of spectators allowed on the server - * - * 1+ var string with the name of the server - * 1+ var string with the revision of the server - * 1 - 5 1 the language run on the server - * (0 = any, 1 = English, 2 = German, 3 = French) - * 1+ 1 whether the server uses a password (0 = no, 1 = yes) - * 1+ 1 maximum number of clients allowed on the server - * 1+ 1 number of clients on the server - * 1+ 1 number of spectators on the server - * 1 & 2 2 current game date in days since 1-1-1920 (DMY) - * 1 & 2 2 game introduction date in days since 1-1-1920 (DMY) - * 1 - 5 var string with the name of the map - * 1+ 2 width of the map in tiles - * 1+ 2 height of the map in tiles - * 1+ 1 type of map: - * (0 = temperate, 1 = arctic, 2 = desert, 3 = toyland) - * 1+ 1 whether the server is dedicated (0 = no, 1 = yes) - */ - -/** The different types/ways a NewGRF can be serialized in the GameInfo since version 6. */ -enum NewGRFSerializationType { - NST_GRFID_MD5 = 0, ///< Unique GRF ID and MD5 checksum. - NST_GRFID_MD5_NAME = 1, ///< Unique GRF ID, MD5 checksum and name. - NST_LOOKUP_ID = 2, ///< Unique ID into a lookup table that is sent before. - NST_END ///< The end of the list (period). -}; - -/** - * The game information that is sent from the server to the client. - */ -struct NetworkServerGameInfo { - GRFConfig *grfconfig; ///< List of NewGRF files used - TimerGameCalendar::Date start_date; ///< When the game started - TimerGameCalendar::Date game_date; ///< Current date - uint16_t map_width; ///< Map width - uint16_t map_height; ///< Map height - std::string server_name; ///< Server name - std::string server_revision; ///< The version number the server is using (e.g.: 'r304' or 0.5.0) - bool dedicated; ///< Is this a dedicated server? - bool use_password; ///< Is this server passworded? - byte clients_on; ///< Current count of clients on server - byte clients_max; ///< Max clients allowed on server - byte companies_on; ///< How many started companies do we have - byte companies_max; ///< Max companies allowed on server - byte spectators_on; ///< How many spectators do we have? - byte landscape; ///< The used landscape - int gamescript_version; ///< Version of the gamescript. - std::string gamescript_name; ///< Name of the gamescript. -}; - -/** - * The game information that is sent from the server to the clients - * with extra information only required at the client side. - */ -struct NetworkGameInfo : NetworkServerGameInfo { - bool version_compatible; ///< Can we connect to this server or not? (based on server_revision) - bool compatible; ///< Can we connect to this server or not? (based on server_revision _and_ grf_match -}; - -/** - * Container to hold the GRF identifier (GRF ID + MD5 checksum) and the name - * associated with that NewGRF. - */ -struct NamedGRFIdentifier { - GRFIdentifier ident; ///< The unique identifier of the NewGRF. - std::string name; ///< The name of the NewGRF. -}; -/** Lookup table for the GameInfo in case of #NST_LOOKUP_ID. */ -typedef std::unordered_map GameInfoNewGRFLookupTable; - -extern NetworkServerGameInfo _network_game_info; - -std::string_view GetNetworkRevisionString(); -bool IsNetworkCompatibleVersion(std::string_view other); -void CheckGameCompatibility(NetworkGameInfo &ngi); - -void FillStaticNetworkServerGameInfo(); -const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo(); - -void DeserializeGRFIdentifier(Packet *p, GRFIdentifier *grf); -void DeserializeGRFIdentifierWithName(Packet *p, NamedGRFIdentifier *grf); -void SerializeGRFIdentifier(Packet *p, const GRFIdentifier *grf); - -void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info, const GameInfoNewGRFLookupTable *newgrf_lookup_table = nullptr); -void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info, bool send_newgrf_names = true); - -#endif /* NETWORK_CORE_GAME_INFO_H */ diff --git a/src/network/core/network_game_info.cpp b/src/network/core/network_game_info.cpp new file mode 100644 --- /dev/null +++ b/src/network/core/network_game_info.cpp @@ -0,0 +1,393 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file game_info.cpp Functions to convert NetworkGameInfo to Packet and back. + */ + +#include "../../stdafx.h" +#include "network_game_info.h" +#include "../../core/bitmath_func.hpp" +#include "../../company_base.h" +#include "../../timer/timer_game_calendar.h" +#include "../../debug.h" +#include "../../map_func.h" +#include "../../game/game.hpp" +#include "../../game/game_info.hpp" +#include "../../settings_type.h" +#include "../../string_func.h" +#include "../../rev.h" +#include "../network_func.h" +#include "../network.h" +#include "packet.h" + +#include "../../safeguards.h" + + +/** + * How many hex digits of the git hash to include in network revision string. + * Determined as 10 hex digits + 2 characters for -g/-u/-m prefix. + */ +static const uint GITHASH_SUFFIX_LEN = 12; + +NetworkServerGameInfo _network_game_info; ///< Information about our game. + +/** + * Get the network version string used by this build. + * The returned string is guaranteed to be at most NETWORK_REVISON_LENGTH bytes including '\0' terminator. + */ +std::string_view GetNetworkRevisionString() +{ + static std::string network_revision; + + if (network_revision.empty()) { + network_revision = _openttd_revision; + if (_openttd_revision_tagged) { + /* Tagged; do not mangle further, though ensure it's not too long. */ + if (network_revision.size() >= NETWORK_REVISION_LENGTH) network_revision.resize(NETWORK_REVISION_LENGTH - 1); + } else { + /* Not tagged; add the githash suffix while ensuring the string does not become too long. */ + assert(_openttd_revision_modified < 3); + std::string githash_suffix = fmt::format("-{}{}", "gum"[_openttd_revision_modified], _openttd_revision_hash); + if (githash_suffix.size() > GITHASH_SUFFIX_LEN) githash_suffix.resize(GITHASH_SUFFIX_LEN); + + /* Where did the hash start in the original string? Overwrite from that position, unless that would create a too long string. */ + size_t hash_end = network_revision.find_last_of('-'); + if (hash_end == std::string::npos) hash_end = network_revision.size(); + if (hash_end + githash_suffix.size() >= NETWORK_REVISION_LENGTH) hash_end = NETWORK_REVISION_LENGTH - githash_suffix.size() - 1; + + /* Replace the git hash in revision string. */ + network_revision.replace(hash_end, std::string::npos, githash_suffix); + } + assert(network_revision.size() < NETWORK_REVISION_LENGTH); // size does not include terminator, constant does, hence strictly less than + Debug(net, 3, "Network revision name: {}", network_revision); + } + + return network_revision; +} + +/** + * Extract the git hash from the revision string. + * @param revision_string The revision string (formatted as DATE-BRANCH-GITHASH). + * @return The git has part of the revision. + */ +static std::string_view ExtractNetworkRevisionHash(std::string_view revision_string) +{ + size_t index = revision_string.find_last_of('-'); + if (index == std::string::npos) return {}; + return revision_string.substr(index); +} + +/** + * Checks whether the given version string is compatible with our version. + * First tries to match the full string, if that fails, attempts to compare just git hashes. + * @param other the version string to compare to + */ +bool IsNetworkCompatibleVersion(std::string_view other) +{ + if (GetNetworkRevisionString() == other) return true; + + /* If this version is tagged, then the revision string must be a complete match, + * since there is no git hash suffix in it. + * This is needed to avoid situations like "1.9.0-beta1" comparing equal to "2.0.0-beta1". */ + if (_openttd_revision_tagged) return false; + + std::string_view hash1 = ExtractNetworkRevisionHash(GetNetworkRevisionString()); + std::string_view hash2 = ExtractNetworkRevisionHash(other); + return hash1 == hash2; +} + +/** + * Check if an game entry is compatible with our client. + */ +void CheckGameCompatibility(NetworkGameInfo &ngi) +{ + /* Check if we are allowed on this server based on the revision-check. */ + ngi.version_compatible = IsNetworkCompatibleVersion(ngi.server_revision); + ngi.compatible = ngi.version_compatible; + + /* Check if we have all the GRFs on the client-system too. */ + for (const GRFConfig *c = ngi.grfconfig; c != nullptr; c = c->next) { + if (c->status == GCS_NOT_FOUND) ngi.compatible = false; + } +} + +/** + * Fill a NetworkServerGameInfo structure with the static content, or things + * that are so static they can be updated on request from a settings change. + */ +void FillStaticNetworkServerGameInfo() +{ + _network_game_info.use_password = !_settings_client.network.server_password.empty(); + _network_game_info.start_date = TimerGameCalendar::ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1); + _network_game_info.clients_max = _settings_client.network.max_clients; + _network_game_info.companies_max = _settings_client.network.max_companies; + _network_game_info.map_width = Map::SizeX(); + _network_game_info.map_height = Map::SizeY(); + _network_game_info.landscape = _settings_game.game_creation.landscape; + _network_game_info.dedicated = _network_dedicated; + _network_game_info.grfconfig = _grfconfig; + + _network_game_info.server_name = _settings_client.network.server_name; + _network_game_info.server_revision = GetNetworkRevisionString(); +} + +/** + * Get the NetworkServerGameInfo structure with the latest information of the server. + * @return The current NetworkServerGameInfo. + */ +const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo() +{ + /* These variables are updated inside _network_game_info as if they are global variables: + * - clients_on + * - invite_code + * These don't need to be updated manually here. + */ + _network_game_info.companies_on = (byte)Company::GetNumItems(); + _network_game_info.spectators_on = NetworkSpectatorCount(); + _network_game_info.game_date = TimerGameCalendar::date; + return &_network_game_info; +} + +/** + * Function that is called for every GRFConfig that is read when receiving + * a NetworkGameInfo. Only grfid and md5sum are set, the rest is zero. This + * function must set all appropriate fields. This GRF is later appended to + * the grfconfig list of the NetworkGameInfo. + * @param config The GRF to handle. + * @param name The name of the NewGRF, empty when unknown. + */ +static void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config, std::string name) +{ + /* Find the matching GRF file */ + const GRFConfig *f = FindGRFConfig(config->ident.grfid, FGCM_EXACT, &config->ident.md5sum); + if (f == nullptr) { + AddGRFTextToList(config->name, name.empty() ? GetString(STR_CONFIG_ERROR_INVALID_GRF_UNKNOWN) : name); + config->status = GCS_NOT_FOUND; + } else { + config->filename = f->filename; + config->name = f->name; + config->info = f->info; + config->url = f->url; + } + SetBit(config->flags, GCF_COPY); +} + +/** + * Serializes the NetworkGameInfo struct to the packet. + * @param p the packet to write the data to. + * @param info the NetworkGameInfo struct to serialize from. + */ +void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info, bool send_newgrf_names) +{ + p->Send_uint8 (NETWORK_GAME_INFO_VERSION); + + /* + * Please observe the order. + * The parts must be read in the same order as they are sent! + */ + + /* Update the documentation in game_info.h on changes + * to the NetworkGameInfo wire-protocol! */ + + /* NETWORK_GAME_INFO_VERSION = 6 */ + p->Send_uint8(send_newgrf_names ? NST_GRFID_MD5_NAME : NST_GRFID_MD5); + + /* NETWORK_GAME_INFO_VERSION = 5 */ + GameInfo *game_info = Game::GetInfo(); + p->Send_uint32(game_info == nullptr ? -1 : (uint32_t)game_info->GetVersion()); + p->Send_string(game_info == nullptr ? "" : game_info->GetName()); + + /* NETWORK_GAME_INFO_VERSION = 4 */ + { + /* Only send the GRF Identification (GRF_ID and MD5 checksum) of + * the GRFs that are needed, i.e. the ones that the server has + * selected in the NewGRF GUI and not the ones that are used due + * to the fact that they are in [newgrf-static] in openttd.cfg */ + const GRFConfig *c; + uint count = 0; + + /* Count number of GRFs to send information about */ + for (c = info->grfconfig; c != nullptr; c = c->next) { + if (!HasBit(c->flags, GCF_STATIC)) count++; + } + p->Send_uint8 (count); // Send number of GRFs + + /* Send actual GRF Identifications */ + for (c = info->grfconfig; c != nullptr; c = c->next) { + if (HasBit(c->flags, GCF_STATIC)) continue; + + SerializeGRFIdentifier(p, &c->ident); + if (send_newgrf_names) p->Send_string(c->GetName()); + } + } + + /* NETWORK_GAME_INFO_VERSION = 3 */ + p->Send_uint32(static_cast(info->game_date)); + p->Send_uint32(static_cast(info->start_date)); + + /* NETWORK_GAME_INFO_VERSION = 2 */ + p->Send_uint8 (info->companies_max); + p->Send_uint8 (info->companies_on); + p->Send_uint8 (info->clients_max); // Used to be max-spectators + + /* NETWORK_GAME_INFO_VERSION = 1 */ + p->Send_string(info->server_name); + p->Send_string(info->server_revision); + p->Send_bool (info->use_password); + p->Send_uint8 (info->clients_max); + p->Send_uint8 (info->clients_on); + p->Send_uint8 (info->spectators_on); + p->Send_uint16(info->map_width); + p->Send_uint16(info->map_height); + p->Send_uint8 (info->landscape); + p->Send_bool (info->dedicated); +} + +/** + * Deserializes the NetworkGameInfo struct from the packet. + * @param p the packet to read the data from. + * @param info the NetworkGameInfo to deserialize into. + */ +void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info, const GameInfoNewGRFLookupTable *newgrf_lookup_table) +{ + static const TimerGameCalendar::Date MAX_DATE = TimerGameCalendar::ConvertYMDToDate(MAX_YEAR, 11, 31); // December is month 11 + + byte game_info_version = p->Recv_uint8(); + NewGRFSerializationType newgrf_serialisation = NST_GRFID_MD5; + + /* + * Please observe the order. + * The parts must be read in the same order as they are sent! + */ + + /* Update the documentation in game_info.h on changes + * to the NetworkGameInfo wire-protocol! */ + + switch (game_info_version) { + case 6: + newgrf_serialisation = (NewGRFSerializationType)p->Recv_uint8(); + if (newgrf_serialisation >= NST_END) return; + FALLTHROUGH; + + case 5: { + info->gamescript_version = (int)p->Recv_uint32(); + info->gamescript_name = p->Recv_string(NETWORK_NAME_LENGTH); + FALLTHROUGH; + } + + case 4: { + /* Ensure that the maximum number of NewGRFs and the field in the network + * protocol are matched to eachother. If that is not the case anymore a + * check must be added to ensure the received data is still valid. */ + static_assert(std::numeric_limits::max() == NETWORK_MAX_GRF_COUNT); + uint num_grfs = p->Recv_uint8(); + + GRFConfig **dst = &info->grfconfig; + for (uint i = 0; i < num_grfs; i++) { + NamedGRFIdentifier grf; + switch (newgrf_serialisation) { + case NST_GRFID_MD5: + DeserializeGRFIdentifier(p, &grf.ident); + break; + + case NST_GRFID_MD5_NAME: + DeserializeGRFIdentifierWithName(p, &grf); + break; + + case NST_LOOKUP_ID: { + if (newgrf_lookup_table == nullptr) return; + auto it = newgrf_lookup_table->find(p->Recv_uint32()); + if (it == newgrf_lookup_table->end()) return; + grf = it->second; + break; + } + + default: + NOT_REACHED(); + } + + GRFConfig *c = new GRFConfig(); + c->ident = grf.ident; + HandleIncomingNetworkGameInfoGRFConfig(c, grf.name); + + /* Append GRFConfig to the list */ + *dst = c; + dst = &c->next; + } + FALLTHROUGH; + } + + case 3: + info->game_date = Clamp(p->Recv_uint32(), 0, static_cast(MAX_DATE)); + info->start_date = Clamp(p->Recv_uint32(), 0, static_cast(MAX_DATE)); + FALLTHROUGH; + + case 2: + info->companies_max = p->Recv_uint8 (); + info->companies_on = p->Recv_uint8 (); + p->Recv_uint8(); // Used to contain max-spectators. + FALLTHROUGH; + + case 1: + info->server_name = p->Recv_string(NETWORK_NAME_LENGTH); + info->server_revision = p->Recv_string(NETWORK_REVISION_LENGTH); + if (game_info_version < 6) p->Recv_uint8 (); // Used to contain server-lang. + info->use_password = p->Recv_bool (); + info->clients_max = p->Recv_uint8 (); + info->clients_on = p->Recv_uint8 (); + info->spectators_on = p->Recv_uint8 (); + if (game_info_version < 3) { // 16 bits dates got scrapped and are read earlier + info->game_date = p->Recv_uint16() + DAYS_TILL_ORIGINAL_BASE_YEAR; + info->start_date = p->Recv_uint16() + DAYS_TILL_ORIGINAL_BASE_YEAR; + } + if (game_info_version < 6) while (p->Recv_uint8() != 0) {} // Used to contain the map-name. + info->map_width = p->Recv_uint16(); + info->map_height = p->Recv_uint16(); + info->landscape = p->Recv_uint8 (); + info->dedicated = p->Recv_bool (); + + if (info->landscape >= NUM_LANDSCAPE) info->landscape = 0; + } +} + +/** + * Serializes the GRFIdentifier (GRF ID and MD5 checksum) to the packet + * @param p the packet to write the data to. + * @param grf the GRFIdentifier to serialize. + */ +void SerializeGRFIdentifier(Packet *p, const GRFIdentifier *grf) +{ + p->Send_uint32(grf->grfid); + for (size_t j = 0; j < grf->md5sum.size(); j++) { + p->Send_uint8(grf->md5sum[j]); + } +} + +/** + * Deserializes the GRFIdentifier (GRF ID and MD5 checksum) from the packet + * @param p the packet to read the data from. + * @param grf the GRFIdentifier to deserialize. + */ +void DeserializeGRFIdentifier(Packet *p, GRFIdentifier *grf) +{ + grf->grfid = p->Recv_uint32(); + for (size_t j = 0; j < grf->md5sum.size(); j++) { + grf->md5sum[j] = p->Recv_uint8(); + } +} + +/** + * Deserializes the NamedGRFIdentifier (GRF ID, MD5 checksum and name) from the packet + * @param p the packet to read the data from. + * @param grf the NamedGRFIdentifier to deserialize. + */ +void DeserializeGRFIdentifierWithName(Packet *p, NamedGRFIdentifier *grf) +{ + DeserializeGRFIdentifier(p, &grf->ident); + grf->name = p->Recv_string(NETWORK_GRF_NAME_LENGTH); +} diff --git a/src/network/core/network_game_info.h b/src/network/core/network_game_info.h new file mode 100644 --- /dev/null +++ b/src/network/core/network_game_info.h @@ -0,0 +1,149 @@ +/* + * This file is part of OpenTTD. + * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2. + * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. + * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see . + */ + +/** + * @file game_info.h Convert NetworkGameInfo to Packet and back. + */ + +#ifndef NETWORK_CORE_GAME_INFO_H +#define NETWORK_CORE_GAME_INFO_H + +#include "config.h" +#include "core.h" +#include "../../newgrf_config.h" +#include "../../timer/timer_game_calendar.h" + +#include + +/* + * NetworkGameInfo has several revisions which we still need to support on the + * wire. The table below shows the version and size for each field of the + * serialized NetworkGameInfo. + * + * Version: Bytes: Description: + * all 1 the version of this packet's structure + * + * 6+ 1 type of storage for the NewGRFs below: + * 0 = NewGRF ID and MD5 checksum. + * Used as default for version 5 and below, and for + * later game updates to the Game Coordinator. + * 1 = NewGRF ID, MD5 checksum and name. + * Used for direct requests and the first game + * update to Game Coordinator. + * 2 = Index in NewGRF lookup table. + * Used for sending server listing from the Game + * Coordinator to the clients. + * + * 5+ 4 version number of the Game Script (-1 is case none is selected). + * 5+ var string with the name of the Game Script. + * + * 4+ 1 number of GRFs attached (n). + * 4+ n * var identifiers for GRF files. Consists of: + * Note: the 'vN' refers to packet version and 'type' + * refers to the v6+ type of storage for the NewGRFs. + * - 4 byte variable with the GRF ID. + * For v4, v5, and v6+ in case of type 0 and/or type 1. + * - 16 bytes with the MD5 checksum of the GRF. + * For v4, v5, and v6+ in case of type 0 and/or type 1. + * - string with name of NewGRF. + * For v6+ in case of type 1. + * - 4 byte lookup table index. + * For v6+ in case of type 2. + * + * 3+ 4 current game date in days since 1-1-0 (DMY) + * 3+ 4 game introduction date in days since 1-1-0 (DMY) + * + * 2+ 1 maximum number of companies allowed on the server + * 2+ 1 number of companies on the server + * 2+ 1 maximum number of spectators allowed on the server + * + * 1+ var string with the name of the server + * 1+ var string with the revision of the server + * 1 - 5 1 the language run on the server + * (0 = any, 1 = English, 2 = German, 3 = French) + * 1+ 1 whether the server uses a password (0 = no, 1 = yes) + * 1+ 1 maximum number of clients allowed on the server + * 1+ 1 number of clients on the server + * 1+ 1 number of spectators on the server + * 1 & 2 2 current game date in days since 1-1-1920 (DMY) + * 1 & 2 2 game introduction date in days since 1-1-1920 (DMY) + * 1 - 5 var string with the name of the map + * 1+ 2 width of the map in tiles + * 1+ 2 height of the map in tiles + * 1+ 1 type of map: + * (0 = temperate, 1 = arctic, 2 = desert, 3 = toyland) + * 1+ 1 whether the server is dedicated (0 = no, 1 = yes) + */ + +/** The different types/ways a NewGRF can be serialized in the GameInfo since version 6. */ +enum NewGRFSerializationType { + NST_GRFID_MD5 = 0, ///< Unique GRF ID and MD5 checksum. + NST_GRFID_MD5_NAME = 1, ///< Unique GRF ID, MD5 checksum and name. + NST_LOOKUP_ID = 2, ///< Unique ID into a lookup table that is sent before. + NST_END ///< The end of the list (period). +}; + +/** + * The game information that is sent from the server to the client. + */ +struct NetworkServerGameInfo { + GRFConfig *grfconfig; ///< List of NewGRF files used + TimerGameCalendar::Date start_date; ///< When the game started + TimerGameCalendar::Date game_date; ///< Current date + uint16_t map_width; ///< Map width + uint16_t map_height; ///< Map height + std::string server_name; ///< Server name + std::string server_revision; ///< The version number the server is using (e.g.: 'r304' or 0.5.0) + bool dedicated; ///< Is this a dedicated server? + bool use_password; ///< Is this server passworded? + byte clients_on; ///< Current count of clients on server + byte clients_max; ///< Max clients allowed on server + byte companies_on; ///< How many started companies do we have + byte companies_max; ///< Max companies allowed on server + byte spectators_on; ///< How many spectators do we have? + byte landscape; ///< The used landscape + int gamescript_version; ///< Version of the gamescript. + std::string gamescript_name; ///< Name of the gamescript. +}; + +/** + * The game information that is sent from the server to the clients + * with extra information only required at the client side. + */ +struct NetworkGameInfo : NetworkServerGameInfo { + bool version_compatible; ///< Can we connect to this server or not? (based on server_revision) + bool compatible; ///< Can we connect to this server or not? (based on server_revision _and_ grf_match +}; + +/** + * Container to hold the GRF identifier (GRF ID + MD5 checksum) and the name + * associated with that NewGRF. + */ +struct NamedGRFIdentifier { + GRFIdentifier ident; ///< The unique identifier of the NewGRF. + std::string name; ///< The name of the NewGRF. +}; +/** Lookup table for the GameInfo in case of #NST_LOOKUP_ID. */ +typedef std::unordered_map GameInfoNewGRFLookupTable; + +extern NetworkServerGameInfo _network_game_info; + +std::string_view GetNetworkRevisionString(); +bool IsNetworkCompatibleVersion(std::string_view other); +void CheckGameCompatibility(NetworkGameInfo &ngi); + +void FillStaticNetworkServerGameInfo(); +const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo(); + +void DeserializeGRFIdentifier(Packet *p, GRFIdentifier *grf); +void DeserializeGRFIdentifierWithName(Packet *p, NamedGRFIdentifier *grf); +void SerializeGRFIdentifier(Packet *p, const GRFIdentifier *grf); + +void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info, const GameInfoNewGRFLookupTable *newgrf_lookup_table = nullptr); +void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info, bool send_newgrf_names = true); + +#endif /* NETWORK_CORE_GAME_INFO_H */ diff --git a/src/network/core/tcp_coordinator.h b/src/network/core/tcp_coordinator.h --- a/src/network/core/tcp_coordinator.h +++ b/src/network/core/tcp_coordinator.h @@ -15,7 +15,7 @@ #include "os_abstraction.h" #include "tcp.h" #include "packet.h" -#include "game_info.h" +#include "network_game_info.h" /** * Enum with all types of TCP Game Coordinator packets. The order MUST not be changed. diff --git a/src/network/core/tcp_turn.h b/src/network/core/tcp_turn.h --- a/src/network/core/tcp_turn.h +++ b/src/network/core/tcp_turn.h @@ -15,7 +15,7 @@ #include "os_abstraction.h" #include "tcp.h" #include "packet.h" -#include "game_info.h" +#include "network_game_info.h" /** Enum with all types of TCP TURN packets. The order MUST not be changed. **/ enum PacketTurnType { diff --git a/src/network/core/udp.cpp b/src/network/core/udp.cpp --- a/src/network/core/udp.cpp +++ b/src/network/core/udp.cpp @@ -12,7 +12,7 @@ #include "../../stdafx.h" #include "../../timer/timer_game_calendar.h" #include "../../debug.h" -#include "game_info.h" +#include "network_game_info.h" #include "udp.h" #include "../../safeguards.h" diff --git a/src/network/network_admin.cpp b/src/network/network_admin.cpp --- a/src/network/network_admin.cpp +++ b/src/network/network_admin.cpp @@ -11,7 +11,7 @@ #include "../strings_func.h" #include "../timer/timer_game_calendar.h" #include "../timer/timer_game_calendar.h" -#include "core/game_info.h" +#include "core/network_game_info.h" #include "network_admin.h" #include "network_base.h" #include "network_server.h" diff --git a/src/network/network_gamelist.h b/src/network/network_gamelist.h --- a/src/network/network_gamelist.h +++ b/src/network/network_gamelist.h @@ -11,7 +11,7 @@ #define NETWORK_GAMELIST_H #include "core/address.h" -#include "core/game_info.h" +#include "core/network_game_info.h" #include "network_type.h" /** The status a server can be in. */ diff --git a/src/network/network_query.cpp b/src/network/network_query.cpp --- a/src/network/network_query.cpp +++ b/src/network/network_query.cpp @@ -8,7 +8,7 @@ /** @file network_query.cpp Query part of the network protocol. */ #include "../stdafx.h" -#include "core/game_info.h" +#include "core/network_game_info.h" #include "network_query.h" #include "network_gamelist.h" #include "../error.h" diff --git a/src/network/network_server.cpp b/src/network/network_server.cpp --- a/src/network/network_server.cpp +++ b/src/network/network_server.cpp @@ -9,7 +9,7 @@ #include "../stdafx.h" #include "../strings_func.h" -#include "core/game_info.h" +#include "core/network_game_info.h" #include "network_admin.h" #include "network_server.h" #include "network_udp.h" diff --git a/src/network/network_udp.cpp b/src/network/network_udp.cpp --- a/src/network/network_udp.cpp +++ b/src/network/network_udp.cpp @@ -16,7 +16,7 @@ #include "../timer/timer_game_calendar.h" #include "../map_func.h" #include "../debug.h" -#include "core/game_info.h" +#include "core/network_game_info.h" #include "network_gamelist.h" #include "network_internal.h" #include "network_udp.h"