1 /*
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/>.
6  */
7 
8 /**
9  * @file game_info.cpp Functions to convert NetworkGameInfo to Packet and back.
10  */
11 
12 #include "../../stdafx.h"
13 #include "game_info.h"
14 #include "../../core/bitmath_func.hpp"
15 #include "../../company_base.h"
16 #include "../../date_func.h"
17 #include "../../debug.h"
18 #include "../../map_func.h"
19 #include "../../game/game.hpp"
20 #include "../../game/game_info.hpp"
21 #include "../../settings_type.h"
22 #include "../../string_func.h"
23 #include "../../rev.h"
24 #include "../network_func.h"
25 #include "../network.h"
26 #include "packet.h"
27 
28 #include "../../safeguards.h"
29 
30 
31 /**
32  * How many hex digits of the git hash to include in network revision string.
33  * Determined as 10 hex digits + 2 characters for -g/-u/-m prefix.
34  */
35 static const uint GITHASH_SUFFIX_LEN = 12;
36 
37 NetworkServerGameInfo _network_game_info; ///< Information about our game.
38 
39 /**
40  * Get the network version string used by this build.
41  * The returned string is guaranteed to be at most NETWORK_REVISON_LENGTH bytes including '\0' terminator.
42  */
GetNetworkRevisionString()43 std::string_view GetNetworkRevisionString()
44 {
45 	static std::string network_revision;
46 
47 	if (network_revision.empty()) {
48 		network_revision = _openttd_revision;
49 		if (_openttd_revision_tagged) {
50 			/* Tagged; do not mangle further, though ensure it's not too long. */
51 			if (network_revision.size() >= NETWORK_REVISION_LENGTH) network_revision.resize(NETWORK_REVISION_LENGTH - 1);
52 		} else {
53 			/* Not tagged; add the githash suffix while ensuring the string does not become too long. */
54 			assert(_openttd_revision_modified < 3);
55 			std::string githash_suffix = fmt::format("-{}{}", "gum"[_openttd_revision_modified], _openttd_revision_hash);
56 			if (githash_suffix.size() > GITHASH_SUFFIX_LEN) githash_suffix.resize(GITHASH_SUFFIX_LEN);
57 
58 			/* Where did the hash start in the original string? Overwrite from that position, unless that would create a too long string. */
59 			size_t hash_end = network_revision.find_last_of('-');
60 			if (hash_end == std::string::npos) hash_end = network_revision.size();
61 			if (hash_end + githash_suffix.size() >= NETWORK_REVISION_LENGTH) hash_end = NETWORK_REVISION_LENGTH - githash_suffix.size() - 1;
62 
63 			/* Replace the git hash in revision string. */
64 			network_revision.replace(hash_end, std::string::npos, githash_suffix);
65 		}
66 		assert(network_revision.size() < NETWORK_REVISION_LENGTH); // size does not include terminator, constant does, hence strictly less than
67 		Debug(net, 3, "Network revision name: {}", network_revision);
68 	}
69 
70 	return network_revision;
71 }
72 
73 /**
74  * Extract the git hash from the revision string.
75  * @param revision_string The revision string (formatted as DATE-BRANCH-GITHASH).
76  * @return The git has part of the revision.
77  */
ExtractNetworkRevisionHash(std::string_view revision_string)78 static std::string_view ExtractNetworkRevisionHash(std::string_view revision_string)
79 {
80 	size_t index = revision_string.find_last_of('-');
81 	if (index == std::string::npos) return {};
82 	return revision_string.substr(index);
83 }
84 
85 /**
86  * Checks whether the given version string is compatible with our version.
87  * First tries to match the full string, if that fails, attempts to compare just git hashes.
88  * @param other the version string to compare to
89  */
IsNetworkCompatibleVersion(std::string_view other)90 bool IsNetworkCompatibleVersion(std::string_view other)
91 {
92 	if (GetNetworkRevisionString() == other) return true;
93 
94 	/* If this version is tagged, then the revision string must be a complete match,
95 	 * since there is no git hash suffix in it.
96 	 * This is needed to avoid situations like "1.9.0-beta1" comparing equal to "2.0.0-beta1".  */
97 	if (_openttd_revision_tagged) return false;
98 
99 	std::string_view hash1 = ExtractNetworkRevisionHash(GetNetworkRevisionString());
100 	std::string_view hash2 = ExtractNetworkRevisionHash(other);
101 	return hash1 == hash2;
102 }
103 
104 /**
105  * Check if an game entry is compatible with our client.
106  */
CheckGameCompatibility(NetworkGameInfo & ngi)107 void CheckGameCompatibility(NetworkGameInfo &ngi)
108 {
109 	/* Check if we are allowed on this server based on the revision-check. */
110 	ngi.version_compatible = IsNetworkCompatibleVersion(ngi.server_revision);
111 	ngi.compatible = ngi.version_compatible;
112 
113 	/* Check if we have all the GRFs on the client-system too. */
114 	for (const GRFConfig *c = ngi.grfconfig; c != nullptr; c = c->next) {
115 		if (c->status == GCS_NOT_FOUND) ngi.compatible = false;
116 	}
117 }
118 
119 /**
120  * Fill a NetworkServerGameInfo structure with the static content, or things
121  * that are so static they can be updated on request from a settings change.
122  */
FillStaticNetworkServerGameInfo()123 void FillStaticNetworkServerGameInfo()
124 {
125 	_network_game_info.use_password   = !_settings_client.network.server_password.empty();
126 	_network_game_info.start_date     = ConvertYMDToDate(_settings_game.game_creation.starting_year, 0, 1);
127 	_network_game_info.clients_max    = _settings_client.network.max_clients;
128 	_network_game_info.companies_max  = _settings_client.network.max_companies;
129 	_network_game_info.map_width      = MapSizeX();
130 	_network_game_info.map_height     = MapSizeY();
131 	_network_game_info.landscape      = _settings_game.game_creation.landscape;
132 	_network_game_info.dedicated      = _network_dedicated;
133 	_network_game_info.grfconfig      = _grfconfig;
134 
135 	_network_game_info.server_name = _settings_client.network.server_name;
136 	_network_game_info.server_revision = GetNetworkRevisionString();
137 }
138 
139 /**
140  * Get the NetworkServerGameInfo structure with the latest information of the server.
141  * @return The current NetworkServerGameInfo.
142  */
GetCurrentNetworkServerGameInfo()143 const NetworkServerGameInfo *GetCurrentNetworkServerGameInfo()
144 {
145 	/* These variables are updated inside _network_game_info as if they are global variables:
146 	 *  - clients_on
147 	 *  - invite_code
148 	 * These don't need to be updated manually here.
149 	 */
150 	_network_game_info.companies_on  = (byte)Company::GetNumItems();
151 	_network_game_info.spectators_on = NetworkSpectatorCount();
152 	_network_game_info.game_date     = _date;
153 	return &_network_game_info;
154 }
155 
156 /**
157  * Function that is called for every GRFConfig that is read when receiving
158  * a NetworkGameInfo. Only grfid and md5sum are set, the rest is zero. This
159  * function must set all appropriate fields. This GRF is later appended to
160  * the grfconfig list of the NetworkGameInfo.
161  * @param config The GRF to handle.
162  * @param name The name of the NewGRF, empty when unknown.
163  */
HandleIncomingNetworkGameInfoGRFConfig(GRFConfig * config,std::string name)164 static void HandleIncomingNetworkGameInfoGRFConfig(GRFConfig *config, std::string name)
165 {
166 	/* Find the matching GRF file */
167 	const GRFConfig *f = FindGRFConfig(config->ident.grfid, FGCM_EXACT, config->ident.md5sum);
168 	if (f == nullptr) {
169 		AddGRFTextToList(config->name, name.empty() ? GetString(STR_CONFIG_ERROR_INVALID_GRF_UNKNOWN) : name);
170 		config->status = GCS_NOT_FOUND;
171 	} else {
172 		config->filename = f->filename;
173 		config->name = f->name;
174 		config->info = f->info;
175 		config->url = f->url;
176 	}
177 	SetBit(config->flags, GCF_COPY);
178 }
179 
180 /**
181  * Serializes the NetworkGameInfo struct to the packet.
182  * @param p    the packet to write the data to.
183  * @param info the NetworkGameInfo struct to serialize from.
184  */
SerializeNetworkGameInfo(Packet * p,const NetworkServerGameInfo * info,bool send_newgrf_names)185 void SerializeNetworkGameInfo(Packet *p, const NetworkServerGameInfo *info, bool send_newgrf_names)
186 {
187 	p->Send_uint8 (NETWORK_GAME_INFO_VERSION);
188 
189 	/*
190 	 *              Please observe the order.
191 	 * The parts must be read in the same order as they are sent!
192 	 */
193 
194 	/* Update the documentation in game_info.h on changes
195 	 * to the NetworkGameInfo wire-protocol! */
196 
197 	/* NETWORK_GAME_INFO_VERSION = 6 */
198 	p->Send_uint8(send_newgrf_names ? NST_GRFID_MD5_NAME : NST_GRFID_MD5);
199 
200 	/* NETWORK_GAME_INFO_VERSION = 5 */
201 	GameInfo *game_info = Game::GetInfo();
202 	p->Send_uint32(game_info == nullptr ? -1 : (uint32)game_info->GetVersion());
203 	p->Send_string(game_info == nullptr ? "" : game_info->GetName());
204 
205 	/* NETWORK_GAME_INFO_VERSION = 4 */
206 	{
207 		/* Only send the GRF Identification (GRF_ID and MD5 checksum) of
208 		 * the GRFs that are needed, i.e. the ones that the server has
209 		 * selected in the NewGRF GUI and not the ones that are used due
210 		 * to the fact that they are in [newgrf-static] in openttd.cfg */
211 		const GRFConfig *c;
212 		uint count = 0;
213 
214 		/* Count number of GRFs to send information about */
215 		for (c = info->grfconfig; c != nullptr; c = c->next) {
216 			if (!HasBit(c->flags, GCF_STATIC)) count++;
217 		}
218 		p->Send_uint8 (count); // Send number of GRFs
219 
220 		/* Send actual GRF Identifications */
221 		for (c = info->grfconfig; c != nullptr; c = c->next) {
222 			if (HasBit(c->flags, GCF_STATIC)) continue;
223 
224 			SerializeGRFIdentifier(p, &c->ident);
225 			if (send_newgrf_names) p->Send_string(c->GetName());
226 		}
227 	}
228 
229 	/* NETWORK_GAME_INFO_VERSION = 3 */
230 	p->Send_uint32(info->game_date);
231 	p->Send_uint32(info->start_date);
232 
233 	/* NETWORK_GAME_INFO_VERSION = 2 */
234 	p->Send_uint8 (info->companies_max);
235 	p->Send_uint8 (info->companies_on);
236 	p->Send_uint8 (info->clients_max); // Used to be max-spectators
237 
238 	/* NETWORK_GAME_INFO_VERSION = 1 */
239 	p->Send_string(info->server_name);
240 	p->Send_string(info->server_revision);
241 	p->Send_bool  (info->use_password);
242 	p->Send_uint8 (info->clients_max);
243 	p->Send_uint8 (info->clients_on);
244 	p->Send_uint8 (info->spectators_on);
245 	p->Send_uint16(info->map_width);
246 	p->Send_uint16(info->map_height);
247 	p->Send_uint8 (info->landscape);
248 	p->Send_bool  (info->dedicated);
249 }
250 
251 /**
252  * Deserializes the NetworkGameInfo struct from the packet.
253  * @param p    the packet to read the data from.
254  * @param info the NetworkGameInfo to deserialize into.
255  */
DeserializeNetworkGameInfo(Packet * p,NetworkGameInfo * info,const GameInfoNewGRFLookupTable * newgrf_lookup_table)256 void DeserializeNetworkGameInfo(Packet *p, NetworkGameInfo *info, const GameInfoNewGRFLookupTable *newgrf_lookup_table)
257 {
258 	static const Date MAX_DATE = ConvertYMDToDate(MAX_YEAR, 11, 31); // December is month 11
259 
260 	byte game_info_version = p->Recv_uint8();
261 	NewGRFSerializationType newgrf_serialisation = NST_GRFID_MD5;
262 
263 	/*
264 	 *              Please observe the order.
265 	 * The parts must be read in the same order as they are sent!
266 	 */
267 
268 	/* Update the documentation in game_info.h on changes
269 	 * to the NetworkGameInfo wire-protocol! */
270 
271 	switch (game_info_version) {
272 		case 6:
273 			newgrf_serialisation = (NewGRFSerializationType)p->Recv_uint8();
274 			if (newgrf_serialisation >= NST_END) return;
275 			FALLTHROUGH;
276 
277 		case 5: {
278 			info->gamescript_version = (int)p->Recv_uint32();
279 			info->gamescript_name = p->Recv_string(NETWORK_NAME_LENGTH);
280 			FALLTHROUGH;
281 		}
282 
283 		case 4: {
284 			GRFConfig **dst = &info->grfconfig;
285 			uint i;
286 			uint num_grfs = p->Recv_uint8();
287 
288 			/* Broken/bad data. It cannot have that many NewGRFs. */
289 			if (num_grfs > NETWORK_MAX_GRF_COUNT) return;
290 
291 			for (i = 0; i < num_grfs; i++) {
292 				NamedGRFIdentifier grf;
293 				switch (newgrf_serialisation) {
294 					case NST_GRFID_MD5:
295 						DeserializeGRFIdentifier(p, &grf.ident);
296 						break;
297 
298 					case NST_GRFID_MD5_NAME:
299 						DeserializeGRFIdentifierWithName(p, &grf);
300 						break;
301 
302 					case NST_LOOKUP_ID: {
303 						if (newgrf_lookup_table == nullptr) return;
304 						auto it = newgrf_lookup_table->find(p->Recv_uint32());
305 						if (it == newgrf_lookup_table->end()) return;
306 						grf = it->second;
307 						break;
308 					}
309 
310 					default:
311 						NOT_REACHED();
312 				}
313 
314 				GRFConfig *c = new GRFConfig();
315 				c->ident = grf.ident;
316 				HandleIncomingNetworkGameInfoGRFConfig(c, grf.name);
317 
318 				/* Append GRFConfig to the list */
319 				*dst = c;
320 				dst = &c->next;
321 			}
322 			FALLTHROUGH;
323 		}
324 
325 		case 3:
326 			info->game_date      = Clamp(p->Recv_uint32(), 0, MAX_DATE);
327 			info->start_date     = Clamp(p->Recv_uint32(), 0, MAX_DATE);
328 			FALLTHROUGH;
329 
330 		case 2:
331 			info->companies_max  = p->Recv_uint8 ();
332 			info->companies_on   = p->Recv_uint8 ();
333 			p->Recv_uint8(); // Used to contain max-spectators.
334 			FALLTHROUGH;
335 
336 		case 1:
337 			info->server_name = p->Recv_string(NETWORK_NAME_LENGTH);
338 			info->server_revision = p->Recv_string(NETWORK_REVISION_LENGTH);
339 			if (game_info_version < 6) p->Recv_uint8 (); // Used to contain server-lang.
340 			info->use_password   = p->Recv_bool  ();
341 			info->clients_max    = p->Recv_uint8 ();
342 			info->clients_on     = p->Recv_uint8 ();
343 			info->spectators_on  = p->Recv_uint8 ();
344 			if (game_info_version < 3) { // 16 bits dates got scrapped and are read earlier
345 				info->game_date    = p->Recv_uint16() + DAYS_TILL_ORIGINAL_BASE_YEAR;
346 				info->start_date   = p->Recv_uint16() + DAYS_TILL_ORIGINAL_BASE_YEAR;
347 			}
348 			if (game_info_version < 6) while (p->Recv_uint8() != 0) {} // Used to contain the map-name.
349 			info->map_width      = p->Recv_uint16();
350 			info->map_height     = p->Recv_uint16();
351 			info->landscape      = p->Recv_uint8 ();
352 			info->dedicated      = p->Recv_bool  ();
353 
354 			if (info->landscape >= NUM_LANDSCAPE) info->landscape = 0;
355 	}
356 }
357 
358 /**
359  * Serializes the GRFIdentifier (GRF ID and MD5 checksum) to the packet
360  * @param p    the packet to write the data to.
361  * @param grf  the GRFIdentifier to serialize.
362  */
SerializeGRFIdentifier(Packet * p,const GRFIdentifier * grf)363 void SerializeGRFIdentifier(Packet *p, const GRFIdentifier *grf)
364 {
365 	uint j;
366 	p->Send_uint32(grf->grfid);
367 	for (j = 0; j < sizeof(grf->md5sum); j++) {
368 		p->Send_uint8(grf->md5sum[j]);
369 	}
370 }
371 
372 /**
373  * Deserializes the GRFIdentifier (GRF ID and MD5 checksum) from the packet
374  * @param p    the packet to read the data from.
375  * @param grf  the GRFIdentifier to deserialize.
376  */
DeserializeGRFIdentifier(Packet * p,GRFIdentifier * grf)377 void DeserializeGRFIdentifier(Packet *p, GRFIdentifier *grf)
378 {
379 	uint j;
380 	grf->grfid = p->Recv_uint32();
381 	for (j = 0; j < sizeof(grf->md5sum); j++) {
382 		grf->md5sum[j] = p->Recv_uint8();
383 	}
384 }
385 
386 /**
387  * Deserializes the NamedGRFIdentifier (GRF ID, MD5 checksum and name) from the packet
388  * @param p    the packet to read the data from.
389  * @param grf  the NamedGRFIdentifier to deserialize.
390  */
DeserializeGRFIdentifierWithName(Packet * p,NamedGRFIdentifier * grf)391 void DeserializeGRFIdentifierWithName(Packet *p, NamedGRFIdentifier *grf)
392 {
393 	DeserializeGRFIdentifier(p, &grf->ident);
394 	grf->name = p->Recv_string(NETWORK_GRF_NAME_LENGTH);
395 }
396