2 * This file is part of OpenTTD.
3 * 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.
4 * 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.
5 * 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 <http://www.gnu.org/licenses/>.
9 * @file game_info.cpp Functions to convert NetworkGameInfo to Packet and back.
12 #include "../../stdafx.h"
13 #include "network_game_info.h"
14 #include "../../core/bitmath_func.hpp"
15 #include "../../company_base.h"
16 #include "../../timer/timer_game_calendar.h"
17 #include "../../timer/timer_game_tick.h"
18 #include "../../debug.h"
19 #include "../../map_func.h"
20 #include "../../game/game.hpp"
21 #include "../../game/game_info.hpp"
22 #include "../../settings_type.h"
23 #include "../../string_func.h"
24 #include "../../rev.h"
25 #include "../network_func.h"
26 #include "../network.h"
27 #include "../network_internal.h"
30 #include "../../safeguards.h"
34 * How many hex digits of the git hash to include in network revision string.
35 * Determined as 10 hex digits + 2 characters for -g/-u/-m prefix.
37 static const uint GITHASH_SUFFIX_LEN
= 12;
39 NetworkServerGameInfo _network_game_info
; ///< Information about our game.
42 * Get the network version string used by this build.
43 * The returned string is guaranteed to be at most NETWORK_REVISON_LENGTH bytes including '\0' terminator.
45 std::string_view
GetNetworkRevisionString()
47 static std::string network_revision
;
49 if (network_revision
.empty()) {
50 #if not defined(NETWORK_INTERNAL_H)
51 # error("network_internal.h must be included, otherwise the debug related preprocessor tokens won't be picked up correctly.")
52 #elif not defined(ENABLE_NETWORK_SYNC_EVERY_FRAME)
53 /* Just a standard build. */
54 network_revision
= _openttd_revision
;
55 #elif defined(NETWORK_SEND_DOUBLE_SEED)
56 /* Build for debugging that sends both parts of the seeds and by doing that practically syncs every frame. */
57 network_revision
= fmt::format("dbg_seed-{}", _openttd_revision
);
59 /* Build for debugging that sends the first part of the seed every frame, practically syncing every frame. */
60 network_revision
= fmt::format("dbg_sync-{}", _openttd_revision
);
62 if (_openttd_revision_tagged
) {
63 /* Tagged; do not mangle further, though ensure it's not too long. */
64 if (network_revision
.size() >= NETWORK_REVISION_LENGTH
) network_revision
.resize(NETWORK_REVISION_LENGTH
- 1);
66 /* Not tagged; add the githash suffix while ensuring the string does not become too long. */
67 assert(_openttd_revision_modified
< 3);
68 std::string githash_suffix
= fmt::format("-{}{}", "gum"[_openttd_revision_modified
], _openttd_revision_hash
);
69 if (githash_suffix
.size() > GITHASH_SUFFIX_LEN
) githash_suffix
.resize(GITHASH_SUFFIX_LEN
);
71 /* Where did the hash start in the original string? Overwrite from that position, unless that would create a too long string. */
72 size_t hash_end
= network_revision
.find_last_of('-');
73 if (hash_end
== std::string::npos
) hash_end
= network_revision
.size();
74 if (hash_end
+ githash_suffix
.size() >= NETWORK_REVISION_LENGTH
) hash_end
= NETWORK_REVISION_LENGTH
- githash_suffix
.size() - 1;
76 /* Replace the git hash in revision string. */
77 network_revision
.replace(hash_end
, std::string::npos
, githash_suffix
);
79 assert(network_revision
.size() < NETWORK_REVISION_LENGTH
); // size does not include terminator, constant does, hence strictly less than
80 Debug(net
, 3, "Network revision name: {}", network_revision
);
83 return network_revision
;
87 * Extract the git hash from the revision string.
88 * @param revision_string The revision string (formatted as DATE-BRANCH-GITHASH).
89 * @return The git has part of the revision.
91 static std::string_view
ExtractNetworkRevisionHash(std::string_view revision_string
)
93 size_t index
= revision_string
.find_last_of('-');
94 if (index
== std::string::npos
) return {};
95 return revision_string
.substr(index
);
99 * Checks whether the given version string is compatible with our version.
100 * First tries to match the full string, if that fails, attempts to compare just git hashes.
101 * @param other the version string to compare to
103 bool IsNetworkCompatibleVersion(std::string_view other
)
105 std::string_view our_revision
= GetNetworkRevisionString();
106 if (our_revision
== other
) return true;
108 /* If this version is tagged, then the revision string must be a complete match,
109 * since there is no git hash suffix in it.
110 * This is needed to avoid situations like "1.9.0-beta1" comparing equal to "2.0.0-beta1". */
111 if (_openttd_revision_tagged
) return false;
113 /* One of the versions is for some sort of debugging, but not both. */
114 if (other
.starts_with("dbg_seed") != our_revision
.starts_with("dbg_seed")) return false;
115 if (other
.starts_with("dbg_sync") != our_revision
.starts_with("dbg_sync")) return false;
117 std::string_view hash1
= ExtractNetworkRevisionHash(our_revision
);
118 std::string_view hash2
= ExtractNetworkRevisionHash(other
);
119 return hash1
== hash2
;
123 * Check if an game entry is compatible with our client.
125 void CheckGameCompatibility(NetworkGameInfo
&ngi
)
127 /* Check if we are allowed on this server based on the revision-check. */
128 ngi
.version_compatible
= IsNetworkCompatibleVersion(ngi
.server_revision
);
129 ngi
.compatible
= ngi
.version_compatible
;
131 /* Check if we have all the GRFs on the client-system too. */
132 for (const GRFConfig
*c
= ngi
.grfconfig
; c
!= nullptr; c
= c
->next
) {
133 if (c
->status
== GCS_NOT_FOUND
) ngi
.compatible
= false;
138 * Fill a NetworkServerGameInfo structure with the static content, or things
139 * that are so static they can be updated on request from a settings change.
141 void FillStaticNetworkServerGameInfo()
143 _network_game_info
.use_password
= !_settings_client
.network
.server_password
.empty();
144 _network_game_info
.calendar_start
= TimerGameCalendar::ConvertYMDToDate(_settings_game
.game_creation
.starting_year
, 0, 1);
145 _network_game_info
.clients_max
= _settings_client
.network
.max_clients
;
146 _network_game_info
.companies_max
= _settings_client
.network
.max_companies
;
147 _network_game_info
.map_width
= Map::SizeX();
148 _network_game_info
.map_height
= Map::SizeY();
149 _network_game_info
.landscape
= _settings_game
.game_creation
.landscape
;
150 _network_game_info
.dedicated
= _network_dedicated
;
151 _network_game_info
.grfconfig
= _grfconfig
;
153 _network_game_info
.server_name
= _settings_client
.network
.server_name
;
154 _network_game_info
.server_revision
= GetNetworkRevisionString();
158 * Get the NetworkServerGameInfo structure with the latest information of the server.
159 * @return The current NetworkServerGameInfo.
161 const NetworkServerGameInfo
&GetCurrentNetworkServerGameInfo()
163 /* These variables are updated inside _network_game_info as if they are global variables:
166 * These don't need to be updated manually here.
168 _network_game_info
.companies_on
= (uint8_t)Company::GetNumItems();
169 _network_game_info
.spectators_on
= NetworkSpectatorCount();
170 _network_game_info
.calendar_date
= TimerGameCalendar::date
;
171 _network_game_info
.ticks_playing
= TimerGameTick::counter
;
172 return _network_game_info
;
176 * Function that is called for every GRFConfig that is read when receiving
177 * a NetworkGameInfo. Only grfid and md5sum are set, the rest is zero. This
178 * function must set all appropriate fields. This GRF is later appended to
179 * the grfconfig list of the NetworkGameInfo.
180 * @param config The GRF to handle.
181 * @param name The name of the NewGRF, empty when unknown.
183 static void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig
*config
, std::string_view name
)
185 /* Find the matching GRF file */
186 const GRFConfig
*f
= FindGRFConfig(config
->ident
.grfid
, FGCM_EXACT
, &config
->ident
.md5sum
);
188 AddGRFTextToList(config
->name
, name
.empty() ? GetString(STR_CONFIG_ERROR_INVALID_GRF_UNKNOWN
) : name
);
189 config
->status
= GCS_NOT_FOUND
;
191 config
->filename
= f
->filename
;
192 config
->name
= f
->name
;
193 config
->info
= f
->info
;
194 config
->url
= f
->url
;
196 SetBit(config
->flags
, GCF_COPY
);
200 * Serializes the NetworkGameInfo struct to the packet.
201 * @param p the packet to write the data to.
202 * @param info the NetworkGameInfo struct to serialize from.
204 void SerializeNetworkGameInfo(Packet
&p
, const NetworkServerGameInfo
&info
, bool send_newgrf_names
)
206 p
.Send_uint8 (NETWORK_GAME_INFO_VERSION
);
209 * Please observe the order.
210 * The parts must be read in the same order as they are sent!
213 /* Update the documentation in game_info.h on changes
214 * to the NetworkGameInfo wire-protocol! */
216 /* NETWORK_GAME_INFO_VERSION = 7 */
217 p
.Send_uint64(info
.ticks_playing
);
219 /* NETWORK_GAME_INFO_VERSION = 6 */
220 p
.Send_uint8(send_newgrf_names
? NST_GRFID_MD5_NAME
: NST_GRFID_MD5
);
222 /* NETWORK_GAME_INFO_VERSION = 5 */
223 GameInfo
*game_info
= Game::GetInfo();
224 p
.Send_uint32(game_info
== nullptr ? -1 : (uint32_t)game_info
->GetVersion());
225 p
.Send_string(game_info
== nullptr ? "" : game_info
->GetName());
227 /* NETWORK_GAME_INFO_VERSION = 4 */
229 /* Only send the GRF Identification (GRF_ID and MD5 checksum) of
230 * the GRFs that are needed, i.e. the ones that the server has
231 * selected in the NewGRF GUI and not the ones that are used due
232 * to the fact that they are in [newgrf-static] in openttd.cfg */
236 /* Count number of GRFs to send information about */
237 for (c
= info
.grfconfig
; c
!= nullptr; c
= c
->next
) {
238 if (!HasBit(c
->flags
, GCF_STATIC
)) count
++;
240 p
.Send_uint8 (count
); // Send number of GRFs
242 /* Send actual GRF Identifications */
243 for (c
= info
.grfconfig
; c
!= nullptr; c
= c
->next
) {
244 if (HasBit(c
->flags
, GCF_STATIC
)) continue;
246 SerializeGRFIdentifier(p
, c
->ident
);
247 if (send_newgrf_names
) p
.Send_string(c
->GetName());
251 /* NETWORK_GAME_INFO_VERSION = 3 */
252 p
.Send_uint32(info
.calendar_date
.base());
253 p
.Send_uint32(info
.calendar_start
.base());
255 /* NETWORK_GAME_INFO_VERSION = 2 */
256 p
.Send_uint8 (info
.companies_max
);
257 p
.Send_uint8 (info
.companies_on
);
258 p
.Send_uint8 (info
.clients_max
); // Used to be max-spectators
260 /* NETWORK_GAME_INFO_VERSION = 1 */
261 p
.Send_string(info
.server_name
);
262 p
.Send_string(info
.server_revision
);
263 p
.Send_bool (info
.use_password
);
264 p
.Send_uint8 (info
.clients_max
);
265 p
.Send_uint8 (info
.clients_on
);
266 p
.Send_uint8 (info
.spectators_on
);
267 p
.Send_uint16(info
.map_width
);
268 p
.Send_uint16(info
.map_height
);
269 p
.Send_uint8 (info
.landscape
);
270 p
.Send_bool (info
.dedicated
);
274 * Deserializes the NetworkGameInfo struct from the packet.
275 * @param p the packet to read the data from.
276 * @param info the NetworkGameInfo to deserialize into.
278 void DeserializeNetworkGameInfo(Packet
&p
, NetworkGameInfo
&info
, const GameInfoNewGRFLookupTable
*newgrf_lookup_table
)
280 uint8_t game_info_version
= p
.Recv_uint8();
281 NewGRFSerializationType newgrf_serialisation
= NST_GRFID_MD5
;
284 * Please observe the order.
285 * The parts must be read in the same order as they are sent!
288 /* Update the documentation in game_info.h on changes
289 * to the NetworkGameInfo wire-protocol! */
291 switch (game_info_version
) {
293 info
.ticks_playing
= p
.Recv_uint64();
297 newgrf_serialisation
= (NewGRFSerializationType
)p
.Recv_uint8();
298 if (newgrf_serialisation
>= NST_END
) return;
302 info
.gamescript_version
= (int)p
.Recv_uint32();
303 info
.gamescript_name
= p
.Recv_string(NETWORK_NAME_LENGTH
);
308 /* Ensure that the maximum number of NewGRFs and the field in the network
309 * protocol are matched to eachother. If that is not the case anymore a
310 * check must be added to ensure the received data is still valid. */
311 static_assert(std::numeric_limits
<uint8_t>::max() == NETWORK_MAX_GRF_COUNT
);
312 uint num_grfs
= p
.Recv_uint8();
314 GRFConfig
**dst
= &info
.grfconfig
;
315 for (uint i
= 0; i
< num_grfs
; i
++) {
316 NamedGRFIdentifier grf
;
317 switch (newgrf_serialisation
) {
319 DeserializeGRFIdentifier(p
, grf
.ident
);
322 case NST_GRFID_MD5_NAME
:
323 DeserializeGRFIdentifierWithName(p
, grf
);
326 case NST_LOOKUP_ID
: {
327 if (newgrf_lookup_table
== nullptr) return;
328 auto it
= newgrf_lookup_table
->find(p
.Recv_uint32());
329 if (it
== newgrf_lookup_table
->end()) return;
338 GRFConfig
*c
= new GRFConfig();
339 c
->ident
= grf
.ident
;
340 HandleIncomingNetworkGameInfoGRFConfig(c
, grf
.name
);
342 /* Append GRFConfig to the list */
350 info
.calendar_date
= Clamp(p
.Recv_uint32(), 0, CalendarTime::MAX_DATE
.base());
351 info
.calendar_start
= Clamp(p
.Recv_uint32(), 0, CalendarTime::MAX_DATE
.base());
355 info
.companies_max
= p
.Recv_uint8 ();
356 info
.companies_on
= p
.Recv_uint8 ();
357 p
.Recv_uint8(); // Used to contain max-spectators.
361 info
.server_name
= p
.Recv_string(NETWORK_NAME_LENGTH
);
362 info
.server_revision
= p
.Recv_string(NETWORK_REVISION_LENGTH
);
363 if (game_info_version
< 6) p
.Recv_uint8 (); // Used to contain server-lang.
364 info
.use_password
= p
.Recv_bool ();
365 info
.clients_max
= p
.Recv_uint8 ();
366 info
.clients_on
= p
.Recv_uint8 ();
367 info
.spectators_on
= p
.Recv_uint8 ();
368 if (game_info_version
< 3) { // 16 bits dates got scrapped and are read earlier
369 info
.calendar_date
= p
.Recv_uint16() + CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR
;
370 info
.calendar_start
= p
.Recv_uint16() + CalendarTime::DAYS_TILL_ORIGINAL_BASE_YEAR
;
372 if (game_info_version
< 6) while (p
.Recv_uint8() != 0) {} // Used to contain the map-name.
373 info
.map_width
= p
.Recv_uint16();
374 info
.map_height
= p
.Recv_uint16();
375 info
.landscape
= p
.Recv_uint8 ();
376 info
.dedicated
= p
.Recv_bool ();
378 if (info
.landscape
>= NUM_LANDSCAPE
) info
.landscape
= 0;
381 /* For older servers, estimate the ticks running based on the calendar date. */
382 if (game_info_version
< 7) {
383 info
.ticks_playing
= static_cast<uint64_t>(std::max(0, info
.calendar_date
.base() - info
.calendar_start
.base())) * Ticks::DAY_TICKS
;
388 * Serializes the GRFIdentifier (GRF ID and MD5 checksum) to the packet
389 * @param p the packet to write the data to.
390 * @param grf the GRFIdentifier to serialize.
392 void SerializeGRFIdentifier(Packet
&p
, const GRFIdentifier
&grf
)
394 p
.Send_uint32(grf
.grfid
);
395 p
.Send_bytes(grf
.md5sum
);
399 * Deserializes the GRFIdentifier (GRF ID and MD5 checksum) from the packet
400 * @param p the packet to read the data from.
401 * @param grf the GRFIdentifier to deserialize.
403 void DeserializeGRFIdentifier(Packet
&p
, GRFIdentifier
&grf
)
405 grf
.grfid
= p
.Recv_uint32();
406 p
.Recv_bytes(grf
.md5sum
);
410 * Deserializes the NamedGRFIdentifier (GRF ID, MD5 checksum and name) from the packet
411 * @param p the packet to read the data from.
412 * @param grf the NamedGRFIdentifier to deserialize.
414 void DeserializeGRFIdentifierWithName(Packet
&p
, NamedGRFIdentifier
&grf
)
416 DeserializeGRFIdentifier(p
, grf
.ident
);
417 grf
.name
= p
.Recv_string(NETWORK_GRF_NAME_LENGTH
);