1 /*
2 	This file is part of Warzone 2100.
3 	Copyright (C) 1999-2004  Eidos Interactive
4 	Copyright (C) 2005-2020  Warzone 2100 Project
5 
6 	Warzone 2100 is free software; you can redistribute it and/or modify
7 	it under the terms of the GNU General Public License as published by
8 	the Free Software Foundation; either version 2 of the License, or
9 	(at your option) any later version.
10 
11 	Warzone 2100 is distributed in the hope that it will be useful,
12 	but WITHOUT ANY WARRANTY; without even the implied warranty of
13 	MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 	GNU General Public License for more details.
15 
16 	You should have received a copy of the GNU General Public License
17 	along with Warzone 2100; if not, write to the Free Software
18 	Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
19 */
20 #include "lib/framework/wzapp.h"
21 
22 /* Standard library headers */
23 #include <physfs.h>
24 #include <string.h>
25 
26 /* Warzone src and library headers */
27 #include "lib/framework/endian_hack.h"
28 #include "lib/framework/math_ext.h"
29 #include "lib/framework/wzconfig.h"
30 #include "lib/framework/file.h"
31 #include "lib/framework/physfs_ext.h"
32 #include "lib/framework/strres.h"
33 #include "lib/framework/frameresource.h"
34 
35 #include "lib/gamelib/gtime.h"
36 #include "lib/ivis_opengl/ivisdef.h"
37 #include "lib/ivis_opengl/pieblitfunc.h"
38 #include "lib/ivis_opengl/piestate.h"
39 #include "lib/ivis_opengl/piepalette.h"
40 #include "lib/ivis_opengl/textdraw.h"
41 #include "lib/netplay/netplay.h"
42 #include "lib/sound/audio.h"
43 #include "lib/sound/audio_id.h"
44 #include "modding.h"
45 #include "main.h"
46 #include "game.h"
47 #include "qtscript.h"
48 #include "fpath.h"
49 #include "difficulty.h"
50 #include "map.h"
51 #include "move.h"
52 #include "droid.h"
53 #include "order.h"
54 #include "action.h"
55 #include "research.h"
56 #include "power.h"
57 #include "projectile.h"
58 #include "loadsave.h"
59 #include "text.h"
60 #include "message.h"
61 #include "hci.h"
62 #include "display.h"
63 #include "display3d.h"
64 #include "map.h"
65 #include "effects.h"
66 #include "init.h"
67 #include "mission.h"
68 #include "scores.h"
69 #include "design.h"
70 #include "component.h"
71 #include "radar.h"
72 #include "cmddroid.h"
73 #include "warzoneconfig.h"
74 #include "multiplay.h"
75 #include "frontend.h"
76 #include "levels.h"
77 #include "mission.h"
78 #include "geometry.h"
79 #include "gateway.h"
80 #include "multistat.h"
81 #include "multiint.h"
82 #include "wrappers.h"
83 #include "challenge.h"
84 #include "combat.h"
85 #include "template.h"
86 #include "version.h"
87 #include "lib/ivis_opengl/screen.h"
88 #include "keymap.h"
89 #include <ctime>
90 #include "multimenu.h"
91 #include "console.h"
92 #include "wzscriptdebug.h"
93 
94 #if defined(__clang__)
95 #pragma clang diagnostic ignored "-Wcast-align"	// TODO: FIXME!
96 #elif defined(__GNUC__)
97 #pragma GCC diagnostic ignored "-Wcast-align"	// TODO: FIXME!
98 #endif
99 
100 
gameScreenSizeDidChange(unsigned int oldWidth,unsigned int oldHeight,unsigned int newWidth,unsigned int newHeight)101 void gameScreenSizeDidChange(unsigned int oldWidth, unsigned int oldHeight, unsigned int newWidth, unsigned int newHeight)
102 {
103 	intScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
104 	loadSaveScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
105 	challengesScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
106 	multiMenuScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
107 	display3dScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
108 	consoleScreenDidChangeSize(oldWidth, oldHeight, newWidth, newHeight);
109 	scriptDebuggerScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight);
110 	frontendScreenSizeDidChange(oldWidth, oldHeight, newWidth, newHeight); // must be last!
111 }
112 
gameDisplayScaleFactorDidChange(float newDisplayScaleFactor)113 void gameDisplayScaleFactorDidChange(float newDisplayScaleFactor)
114 {
115 	// The text subsystem requires the game -> renderer scale factor, which potentially differs from
116 	// the display scale factor.
117 	float horizGameToRendererScaleFactor = 0.f, vertGameToRendererScaleFactor = 0.f;
118 	wzGetGameToRendererScaleFactor(&horizGameToRendererScaleFactor, &vertGameToRendererScaleFactor);
119 	iV_TextUpdateScaleFactor(horizGameToRendererScaleFactor, vertGameToRendererScaleFactor);
120 }
121 
122 
123 #define MAX_SAVE_NAME_SIZE_V19	40
124 #define MAX_SAVE_NAME_SIZE	60
125 
126 static const UDWORD NULL_ID = UDWORD_MAX;
127 #define SAVEKEY_ONMISSION	0x100
128 
129 static UDWORD RemapPlayerNumber(UDWORD OldNumber);
130 static void plotScriptFeature(ScriptMapData const &data, char *backDropSprite);
131 static void plotFeature(char *backDropSprite);
132 bool writeGameInfo(const char *pFileName);
133 
134 /** struct used to store the data for retreating. */
135 struct RUN_DATA
136 {
137 	Vector2i sPos = Vector2i(0, 0); ///< position to where units should flee to.
138 	uint8_t forceLevel = 0;  ///< number of units below which others might flee.
139 	uint8_t healthLevel = 0; ///< health percentage value below which it might flee. This value is used for groups only.
140 	uint8_t leadership = 0;  ///< basic value that will be used on calculations of the flee probability.
141 };
142 
143 // return positions for vtols, at one time.
144 Vector2i asVTOLReturnPos[MAX_PLAYERS];
145 
146 struct GAME_SAVEHEADER
147 {
148 	char        aFileType[4];
149 	uint32_t    version;
150 };
151 
serializeSaveGameHeader(PHYSFS_file * fileHandle,const GAME_SAVEHEADER * serializeHeader)152 static bool serializeSaveGameHeader(PHYSFS_file *fileHandle, const GAME_SAVEHEADER *serializeHeader)
153 {
154 	if (WZ_PHYSFS_writeBytes(fileHandle, serializeHeader->aFileType, 4) != 4)
155 	{
156 		return false;
157 	}
158 
159 	// Write version numbers below version 35 as little-endian, and those above as big-endian
160 	if (serializeHeader->version < VERSION_35)
161 	{
162 		return PHYSFS_writeULE32(fileHandle, serializeHeader->version);
163 	}
164 	else
165 	{
166 		return PHYSFS_writeUBE32(fileHandle, serializeHeader->version);
167 	}
168 }
169 
deserializeSaveGameHeader(PHYSFS_file * fileHandle,GAME_SAVEHEADER * serializeHeader)170 static bool deserializeSaveGameHeader(PHYSFS_file *fileHandle, GAME_SAVEHEADER *serializeHeader)
171 {
172 	// Read in the header from the file
173 	if (WZ_PHYSFS_readBytes(fileHandle, serializeHeader->aFileType, 4) != 4
174 	    || WZ_PHYSFS_readBytes(fileHandle, &serializeHeader->version, sizeof(uint32_t)) != sizeof(uint32_t))
175 	{
176 		return false;
177 	}
178 
179 	// All save game file versions below version 35 (i.e. _not_ version 35 itself)
180 	// have their version numbers stored as little endian. Versions from 35 and
181 	// onward use big-endian. This basically means that, because of endian
182 	// swapping, numbers from 35 and onward will be ridiculously high if a
183 	// little-endian byte-order is assumed.
184 
185 	// Convert from little endian to native byte-order and check if we get a
186 	// ridiculously high number
187 	endian_udword(&serializeHeader->version);
188 
189 	if (serializeHeader->version <= VERSION_34)
190 	{
191 		// Apparently we don't get a ridiculously high number if we assume
192 		// little-endian, so lets assume our version number is 34 at max and return
193 		debug(LOG_SAVE, "Version = %u (little-endian)", serializeHeader->version);
194 
195 		return true;
196 	}
197 	else
198 	{
199 		// Apparently we get a larger number than expected if using little-endian.
200 		// So assume we have a version of 35 and onward
201 
202 		// Reverse the little-endian decoding
203 		endian_udword(&serializeHeader->version);
204 	}
205 
206 	// Considering that little-endian didn't work we now use big-endian instead
207 	serializeHeader->version = PHYSFS_swapUBE32(serializeHeader->version);
208 	debug(LOG_SAVE, "Version %u = (big-endian)", serializeHeader->version);
209 
210 	return true;
211 }
212 
213 struct STRUCT_SAVEHEADER : public GAME_SAVEHEADER
214 {
215 	UDWORD		quantity;
216 };
217 
218 struct FEATURE_SAVEHEADER : public GAME_SAVEHEADER
219 {
220 	UDWORD		quantity;
221 };
222 
223 /* Structure definitions for loading and saving map data */
224 struct TILETYPE_SAVEHEADER : public GAME_SAVEHEADER
225 {
226 	UDWORD quantity;
227 };
228 
229 /* Sanity check definitions for the save struct file sizes */
230 #define DROIDINIT_HEADER_SIZE		12
231 #define STRUCT_HEADER_SIZE			12
232 #define FEATURE_HEADER_SIZE			12
233 #define TILETYPE_HEADER_SIZE		12
234 
235 // general save definitions
236 #define MAX_LEVEL_SIZE 20
237 
238 #define OBJECT_SAVE_V19 \
239 	char				name[MAX_SAVE_NAME_SIZE_V19]; \
240 	UDWORD				id; \
241 	UDWORD				x,y,z; \
242 	UDWORD				direction; \
243 	UDWORD				player; \
244 	int32_t				inFire; \
245 	UDWORD				periodicalDamageStart; \
246 	UDWORD				periodicalDamage
247 
248 #define OBJECT_SAVE_V20 \
249 	char				name[MAX_SAVE_NAME_SIZE]; \
250 	UDWORD				id; \
251 	UDWORD				x,y,z; \
252 	UDWORD				direction; \
253 	UDWORD				player; \
254 	int32_t		inFire; \
255 	UDWORD				periodicalDamageStart; \
256 	UDWORD				periodicalDamage
257 
258 struct SAVE_POWER
259 {
260 	uint32_t    currentPower;
261 	uint32_t    extractedPower; // used for hacks
262 };
263 
serializeSavePowerData(PHYSFS_file * fileHandle,const SAVE_POWER * serializePower)264 static bool serializeSavePowerData(PHYSFS_file *fileHandle, const SAVE_POWER *serializePower)
265 {
266 	return (PHYSFS_writeUBE32(fileHandle, serializePower->currentPower)
267 	        && PHYSFS_writeUBE32(fileHandle, serializePower->extractedPower));
268 }
269 
deserializeSavePowerData(PHYSFS_file * fileHandle,SAVE_POWER * serializePower)270 static bool deserializeSavePowerData(PHYSFS_file *fileHandle, SAVE_POWER *serializePower)
271 {
272 	return (PHYSFS_readUBE32(fileHandle, &serializePower->currentPower)
273 	        && PHYSFS_readUBE32(fileHandle, &serializePower->extractedPower));
274 }
275 
serializeVector3i(PHYSFS_file * fileHandle,const Vector3i * serializeVector)276 static bool serializeVector3i(PHYSFS_file *fileHandle, const Vector3i *serializeVector)
277 {
278 	return (PHYSFS_writeSBE32(fileHandle, serializeVector->x)
279 	        && PHYSFS_writeSBE32(fileHandle, serializeVector->y)
280 	        && PHYSFS_writeSBE32(fileHandle, serializeVector->z));
281 }
282 
deserializeVector3i(PHYSFS_file * fileHandle,Vector3i * serializeVector)283 static bool deserializeVector3i(PHYSFS_file *fileHandle, Vector3i *serializeVector)
284 {
285 	int32_t x, y, z;
286 
287 	if (!PHYSFS_readSBE32(fileHandle, &x)
288 	    || !PHYSFS_readSBE32(fileHandle, &y)
289 	    || !PHYSFS_readSBE32(fileHandle, &z))
290 	{
291 		return false;
292 	}
293 
294 	serializeVector-> x = x;
295 	serializeVector-> y = y;
296 	serializeVector-> z = z;
297 
298 	return true;
299 }
300 
serializeVector2i(PHYSFS_file * fileHandle,const Vector2i * serializeVector)301 static bool serializeVector2i(PHYSFS_file *fileHandle, const Vector2i *serializeVector)
302 {
303 	return (PHYSFS_writeSBE32(fileHandle, serializeVector->x)
304 	        && PHYSFS_writeSBE32(fileHandle, serializeVector->y));
305 }
306 
deserializeVector2i(PHYSFS_file * fileHandle,Vector2i * serializeVector)307 static bool deserializeVector2i(PHYSFS_file *fileHandle, Vector2i *serializeVector)
308 {
309 	int32_t x, y;
310 
311 	if (!PHYSFS_readSBE32(fileHandle, &x)
312 	    || !PHYSFS_readSBE32(fileHandle, &y))
313 	{
314 		return false;
315 	}
316 
317 	serializeVector-> x = x;
318 	serializeVector-> y = y;
319 
320 	return true;
321 }
322 
serializeiViewData(PHYSFS_file * fileHandle,const iView * serializeView)323 static bool serializeiViewData(PHYSFS_file *fileHandle, const iView *serializeView)
324 {
325 	return (serializeVector3i(fileHandle, &serializeView->p)
326 	        && serializeVector3i(fileHandle, &serializeView->r));
327 }
328 
deserializeiViewData(PHYSFS_file * fileHandle,iView * serializeView)329 static bool deserializeiViewData(PHYSFS_file *fileHandle, iView *serializeView)
330 {
331 	return (deserializeVector3i(fileHandle, &serializeView->p)
332 	        && deserializeVector3i(fileHandle, &serializeView->r));
333 }
334 
serializeRunData(PHYSFS_file * fileHandle,const RUN_DATA * serializeRun)335 static bool serializeRunData(PHYSFS_file *fileHandle, const RUN_DATA *serializeRun)
336 {
337 	return (serializeVector2i(fileHandle, &serializeRun->sPos)
338 	        && PHYSFS_writeUBE8(fileHandle, serializeRun->forceLevel)
339 	        && PHYSFS_writeUBE8(fileHandle, serializeRun->healthLevel)
340 	        && PHYSFS_writeUBE8(fileHandle, serializeRun->leadership));
341 }
342 
deserializeRunData(PHYSFS_file * fileHandle,RUN_DATA * serializeRun)343 static bool deserializeRunData(PHYSFS_file *fileHandle, RUN_DATA *serializeRun)
344 {
345 	return (deserializeVector2i(fileHandle, &serializeRun->sPos)
346 	        && PHYSFS_readUBE8(fileHandle, &serializeRun->forceLevel)
347 	        && PHYSFS_readUBE8(fileHandle, &serializeRun->healthLevel)
348 	        && PHYSFS_readUBE8(fileHandle, &serializeRun->leadership));
349 }
350 
serializeLandingZoneData(PHYSFS_file * fileHandle,const LANDING_ZONE * serializeLandZone)351 static bool serializeLandingZoneData(PHYSFS_file *fileHandle, const LANDING_ZONE *serializeLandZone)
352 {
353 	return (PHYSFS_writeUBE8(fileHandle, serializeLandZone->x1)
354 	        && PHYSFS_writeUBE8(fileHandle, serializeLandZone->y1)
355 	        && PHYSFS_writeUBE8(fileHandle, serializeLandZone->x2)
356 	        && PHYSFS_writeUBE8(fileHandle, serializeLandZone->y2));
357 }
358 
deserializeLandingZoneData(PHYSFS_file * fileHandle,LANDING_ZONE * serializeLandZone)359 static bool deserializeLandingZoneData(PHYSFS_file *fileHandle, LANDING_ZONE *serializeLandZone)
360 {
361 	return (PHYSFS_readUBE8(fileHandle, &serializeLandZone->x1)
362 	        && PHYSFS_readUBE8(fileHandle, &serializeLandZone->y1)
363 	        && PHYSFS_readUBE8(fileHandle, &serializeLandZone->x2)
364 	        && PHYSFS_readUBE8(fileHandle, &serializeLandZone->y2));
365 }
366 
serializeMultiplayerGame(PHYSFS_file * fileHandle,const MULTIPLAYERGAME * serializeMulti)367 static bool serializeMultiplayerGame(PHYSFS_file *fileHandle, const MULTIPLAYERGAME *serializeMulti)
368 {
369 	const char *dummy8c = "DUMMYSTRING";
370 
371 	if (!PHYSFS_writeUBE8(fileHandle, static_cast<uint8_t>(serializeMulti->type))
372 	    || WZ_PHYSFS_writeBytes(fileHandle, serializeMulti->map, 128) != 128
373 	    || WZ_PHYSFS_writeBytes(fileHandle, dummy8c, 8) != 8
374 	    || !PHYSFS_writeUBE8(fileHandle, serializeMulti->maxPlayers)
375 	    || WZ_PHYSFS_writeBytes(fileHandle, serializeMulti->name, 128) != 128
376 	    || !PHYSFS_writeSBE32(fileHandle, 0)
377 	    || !PHYSFS_writeUBE32(fileHandle, serializeMulti->power)
378 	    || !PHYSFS_writeUBE8(fileHandle, serializeMulti->base)
379 	    || !PHYSFS_writeUBE8(fileHandle, serializeMulti->alliance)
380 	    || !PHYSFS_writeUBE8(fileHandle, serializeMulti->hash.Bytes)
381 	    || !WZ_PHYSFS_writeBytes(fileHandle, serializeMulti->hash.bytes, serializeMulti->hash.Bytes)
382 	    || !PHYSFS_writeUBE16(fileHandle, 0)	// dummy, was bytesPerSec
383 	    || !PHYSFS_writeUBE8(fileHandle, 0)	// dummy, was packetsPerSec
384 	    || !PHYSFS_writeUBE8(fileHandle, challengeActive))	// reuse available field, was encryptKey
385 	{
386 		return false;
387 	}
388 
389 	for (unsigned int i = 0; i < MAX_PLAYERS; ++i)
390 	{
391 		// dummy, was `skDiff` for each player
392 		if (!PHYSFS_writeUBE8(fileHandle, 0))
393 		{
394 			return false;
395 		}
396 	}
397 
398 	return true;
399 }
400 
deserializeMultiplayerGame(PHYSFS_file * fileHandle,MULTIPLAYERGAME * serializeMulti)401 static bool deserializeMultiplayerGame(PHYSFS_file *fileHandle, MULTIPLAYERGAME *serializeMulti)
402 {
403 	int32_t boolFog;
404 	uint8_t dummy8;
405 	uint16_t dummy16;
406 	char dummy8c[8];
407 	uint8_t hashSize;
408 
409 	serializeMulti->hash.setZero();
410 
411 	if (!PHYSFS_readUBE8(fileHandle, reinterpret_cast<uint8_t*>(&serializeMulti->type))
412 	    || WZ_PHYSFS_readBytes(fileHandle, serializeMulti->map, 128) != 128
413 	    || WZ_PHYSFS_readBytes(fileHandle, dummy8c, 8) != 8
414 	    || !PHYSFS_readUBE8(fileHandle, &serializeMulti->maxPlayers)
415 	    || WZ_PHYSFS_readBytes(fileHandle, serializeMulti->name, 128) != 128
416 	    || !PHYSFS_readSBE32(fileHandle, &boolFog)
417 	    || !PHYSFS_readUBE32(fileHandle, &serializeMulti->power)
418 	    || !PHYSFS_readUBE8(fileHandle, &serializeMulti->base)
419 	    || !PHYSFS_readUBE8(fileHandle, &serializeMulti->alliance)
420 	    || !PHYSFS_readUBE8(fileHandle, &hashSize)
421 	    || (hashSize == serializeMulti->hash.Bytes && !WZ_PHYSFS_readBytes(fileHandle, serializeMulti->hash.bytes, serializeMulti->hash.Bytes))
422 	    || !PHYSFS_readUBE16(fileHandle, &dummy16)	// dummy, was bytesPerSec
423 	    || !PHYSFS_readUBE8(fileHandle, &dummy8)	// dummy, was packetsPerSec
424 	    || !PHYSFS_readUBE8(fileHandle, &dummy8))	// reused for challenge, was encryptKey
425 	{
426 		return false;
427 	}
428 	challengeActive = dummy8;	// hack
429 
430 	for (unsigned int i = 0; i < MAX_PLAYERS; ++i)
431 	{
432 		// dummy, was `skDiff` for each player
433 		if (!PHYSFS_readUBE8(fileHandle, &dummy8))
434 		{
435 			return false;
436 		}
437 	}
438 
439 	return true;
440 }
441 
serializePlayer(PHYSFS_file * fileHandle,const PLAYER * serializePlayer,int player)442 static bool serializePlayer(PHYSFS_file *fileHandle, const PLAYER *serializePlayer, int player)
443 {
444 	return (PHYSFS_writeUBE32(fileHandle, serializePlayer->position)
445 	        && WZ_PHYSFS_writeBytes(fileHandle, serializePlayer->name, StringSize) == StringSize
446 	        && WZ_PHYSFS_writeBytes(fileHandle, getAIName(player), MAX_LEN_AI_NAME) == MAX_LEN_AI_NAME
447 	        && PHYSFS_writeSBE8(fileHandle, static_cast<int8_t>(serializePlayer->difficulty))
448 	        && PHYSFS_writeUBE8(fileHandle, (uint8_t)serializePlayer->allocated)
449 	        && PHYSFS_writeUBE32(fileHandle, serializePlayer->colour)
450 	        && PHYSFS_writeUBE32(fileHandle, serializePlayer->team));
451 }
452 
deserializePlayer(PHYSFS_file * fileHandle,PLAYER * serializePlayer,int player)453 static bool deserializePlayer(PHYSFS_file *fileHandle, PLAYER *serializePlayer, int player)
454 {
455 	char aiName[MAX_LEN_AI_NAME] = { "THEREISNOAI" };
456 	uint32_t position = 0, colour = 0, team = 0;
457 	bool retval = false;
458 	uint8_t allocated = 0;
459 
460 	retval = (PHYSFS_readUBE32(fileHandle, &position)
461 	          && WZ_PHYSFS_readBytes(fileHandle, serializePlayer->name, StringSize) == StringSize
462 	          && WZ_PHYSFS_readBytes(fileHandle, aiName, MAX_LEN_AI_NAME) == MAX_LEN_AI_NAME
463 	          && PHYSFS_readSBE8(fileHandle, reinterpret_cast<int8_t*>(&serializePlayer->difficulty))
464 	          && PHYSFS_readUBE8(fileHandle, &allocated)
465 	          && PHYSFS_readUBE32(fileHandle, &colour)
466 	          && PHYSFS_readUBE32(fileHandle, &team));
467 
468 	serializePlayer->allocated = allocated;
469 	if (player < game.maxPlayers)
470 	{
471 		serializePlayer->ai = matchAIbyName(aiName);
472 		ASSERT(serializePlayer->ai != AI_NOT_FOUND, "AI \"%s\" not found -- script loading will fail (player %d / %d)",
473 		       aiName, player, game.maxPlayers);
474 	}
475 	serializePlayer->position = position;
476 	serializePlayer->colour = colour;
477 	serializePlayer->team = team;
478 	return retval;
479 }
480 
serializeNetPlay(PHYSFS_file * fileHandle,const NETPLAY * serializeNetPlay)481 static bool serializeNetPlay(PHYSFS_file *fileHandle, const NETPLAY *serializeNetPlay)
482 {
483 	unsigned int i;
484 
485 	for (i = 0; i < MAX_PLAYERS; ++i)
486 	{
487 		if (!serializePlayer(fileHandle, &serializeNetPlay->players[i], i))
488 		{
489 			return false;
490 		}
491 	}
492 
493 	return (PHYSFS_writeUBE32(fileHandle, serializeNetPlay->bComms)
494 	        && PHYSFS_writeUBE32(fileHandle, serializeNetPlay->playercount)
495 	        && PHYSFS_writeUBE32(fileHandle, serializeNetPlay->hostPlayer)
496 	        && PHYSFS_writeUBE32(fileHandle, selectedPlayer)
497 	        && PHYSFS_writeUBE32(fileHandle, (uint32_t)game.scavengers)
498 	        && PHYSFS_writeUBE32(fileHandle, 0)
499 	        && PHYSFS_writeUBE32(fileHandle, 0));
500 }
501 
deserializeNetPlay(PHYSFS_file * fileHandle,NETPLAY * serializeNetPlay)502 static bool deserializeNetPlay(PHYSFS_file *fileHandle, NETPLAY *serializeNetPlay)
503 {
504 	unsigned int i;
505 	uint32_t dummy, scavs = game.scavengers;
506 	bool retv;
507 
508 	for (i = 0; i < MAX_PLAYERS; ++i)
509 	{
510 		if (!deserializePlayer(fileHandle, &serializeNetPlay->players[i], i))
511 		{
512 			return false;
513 		}
514 	}
515 
516 	serializeNetPlay->isHost = true;	// only host can load
517 	retv = (PHYSFS_readUBE32(fileHandle, &serializeNetPlay->bComms)
518 	        && PHYSFS_readUBE32(fileHandle, &serializeNetPlay->playercount)
519 	        && PHYSFS_readUBE32(fileHandle, &serializeNetPlay->hostPlayer)
520 	        && PHYSFS_readUBE32(fileHandle, &selectedPlayer)
521 	        && PHYSFS_readUBE32(fileHandle, &scavs)
522 	        && PHYSFS_readUBE32(fileHandle, &dummy)
523 	        && PHYSFS_readUBE32(fileHandle, &dummy));
524 	game.scavengers = scavs;
525 	return retv;
526 }
527 
528 struct SAVE_GAME_V7
529 {
530 	uint32_t    gameTime;
531 	uint32_t    GameType;                   /* Type of game , one of the GTYPE_... enums. */
532 	int32_t     ScrollMinX;                 /* Scroll Limits */
533 	int32_t     ScrollMinY;
534 	uint32_t    ScrollMaxX;
535 	uint32_t    ScrollMaxY;
536 	char        levelName[MAX_LEVEL_SIZE];  //name of the level to load up when mid game
537 };
538 
serializeSaveGameV7Data(PHYSFS_file * fileHandle,const SAVE_GAME_V7 * serializeGame)539 static bool serializeSaveGameV7Data(PHYSFS_file *fileHandle, const SAVE_GAME_V7 *serializeGame)
540 {
541 	return (PHYSFS_writeUBE32(fileHandle, serializeGame->gameTime)
542 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->GameType)
543 	        && PHYSFS_writeSBE32(fileHandle, serializeGame->ScrollMinX)
544 	        && PHYSFS_writeSBE32(fileHandle, serializeGame->ScrollMinY)
545 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->ScrollMaxX)
546 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->ScrollMaxY)
547 	        && WZ_PHYSFS_writeBytes(fileHandle, serializeGame->levelName, MAX_LEVEL_SIZE) == MAX_LEVEL_SIZE);
548 }
549 
deserializeSaveGameV7Data(PHYSFS_file * fileHandle,SAVE_GAME_V7 * serializeGame)550 static bool deserializeSaveGameV7Data(PHYSFS_file *fileHandle, SAVE_GAME_V7 *serializeGame)
551 {
552 	return (PHYSFS_readUBE32(fileHandle, &serializeGame->gameTime)
553 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->GameType)
554 	        && PHYSFS_readSBE32(fileHandle, &serializeGame->ScrollMinX)
555 	        && PHYSFS_readSBE32(fileHandle, &serializeGame->ScrollMinY)
556 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->ScrollMaxX)
557 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->ScrollMaxY)
558 	        && WZ_PHYSFS_readBytes(fileHandle, serializeGame->levelName, MAX_LEVEL_SIZE) == MAX_LEVEL_SIZE);
559 }
560 
561 struct SAVE_GAME_V10 : public SAVE_GAME_V7
562 {
563 	SAVE_POWER  power[MAX_PLAYERS];
564 };
565 
serializeSaveGameV10Data(PHYSFS_file * fileHandle,const SAVE_GAME_V10 * serializeGame)566 static bool serializeSaveGameV10Data(PHYSFS_file *fileHandle, const SAVE_GAME_V10 *serializeGame)
567 {
568 	unsigned int i;
569 
570 	if (!serializeSaveGameV7Data(fileHandle, (const SAVE_GAME_V7 *) serializeGame))
571 	{
572 		return false;
573 	}
574 
575 	for (i = 0; i < MAX_PLAYERS; ++i)
576 	{
577 		if (!serializeSavePowerData(fileHandle, &serializeGame->power[i]))
578 		{
579 			return false;
580 		}
581 	}
582 
583 	return true;
584 }
585 
deserializeSaveGameV10Data(PHYSFS_file * fileHandle,SAVE_GAME_V10 * serializeGame)586 static bool deserializeSaveGameV10Data(PHYSFS_file *fileHandle, SAVE_GAME_V10 *serializeGame)
587 {
588 	unsigned int i;
589 
590 	if (!deserializeSaveGameV7Data(fileHandle, (SAVE_GAME_V7 *) serializeGame))
591 	{
592 		return false;
593 	}
594 
595 	for (i = 0; i < MAX_PLAYERS; ++i)
596 	{
597 		if (!deserializeSavePowerData(fileHandle, &serializeGame->power[i]))
598 		{
599 			return false;
600 		}
601 	}
602 
603 	return true;
604 }
605 
606 struct SAVE_GAME_V11 : public SAVE_GAME_V10
607 {
608 	iView currentPlayerPos;
609 };
610 
serializeSaveGameV11Data(PHYSFS_file * fileHandle,const SAVE_GAME_V11 * serializeGame)611 static bool serializeSaveGameV11Data(PHYSFS_file *fileHandle, const SAVE_GAME_V11 *serializeGame)
612 {
613 	return (serializeSaveGameV10Data(fileHandle, (const SAVE_GAME_V10 *) serializeGame)
614 	        && serializeiViewData(fileHandle, &serializeGame->currentPlayerPos));
615 }
616 
deserializeSaveGameV11Data(PHYSFS_file * fileHandle,SAVE_GAME_V11 * serializeGame)617 static bool deserializeSaveGameV11Data(PHYSFS_file *fileHandle, SAVE_GAME_V11 *serializeGame)
618 {
619 	return (deserializeSaveGameV10Data(fileHandle, (SAVE_GAME_V10 *) serializeGame)
620 	        && deserializeiViewData(fileHandle, &serializeGame->currentPlayerPos));
621 }
622 
623 struct SAVE_GAME_V12 : public SAVE_GAME_V11
624 {
625 	uint32_t    missionTime;
626 	uint32_t    saveKey;
627 };
628 
serializeSaveGameV12Data(PHYSFS_file * fileHandle,const SAVE_GAME_V12 * serializeGame)629 static bool serializeSaveGameV12Data(PHYSFS_file *fileHandle, const SAVE_GAME_V12 *serializeGame)
630 {
631 	return (serializeSaveGameV11Data(fileHandle, (const SAVE_GAME_V11 *) serializeGame)
632 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->missionTime)
633 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->saveKey));
634 }
635 
deserializeSaveGameV12Data(PHYSFS_file * fileHandle,SAVE_GAME_V12 * serializeGame)636 static bool deserializeSaveGameV12Data(PHYSFS_file *fileHandle, SAVE_GAME_V12 *serializeGame)
637 {
638 	return (deserializeSaveGameV11Data(fileHandle, (SAVE_GAME_V11 *) serializeGame)
639 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->missionTime)
640 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->saveKey));
641 }
642 
643 struct SAVE_GAME_V14 : public SAVE_GAME_V12
644 {
645 	int32_t     missionOffTime;
646 	int32_t     missionETA;
647 	uint16_t    missionHomeLZ_X;
648 	uint16_t    missionHomeLZ_Y;
649 	int32_t     missionPlayerX;
650 	int32_t     missionPlayerY;
651 	uint16_t    iTranspEntryTileX[MAX_PLAYERS];
652 	uint16_t    iTranspEntryTileY[MAX_PLAYERS];
653 	uint16_t    iTranspExitTileX[MAX_PLAYERS];
654 	uint16_t    iTranspExitTileY[MAX_PLAYERS];
655 	uint32_t    aDefaultSensor[MAX_PLAYERS];
656 	uint32_t    aDefaultECM[MAX_PLAYERS];
657 	uint32_t    aDefaultRepair[MAX_PLAYERS];
658 };
659 
serializeSaveGameV14Data(PHYSFS_file * fileHandle,const SAVE_GAME_V14 * serializeGame)660 static bool serializeSaveGameV14Data(PHYSFS_file *fileHandle, const SAVE_GAME_V14 *serializeGame)
661 {
662 	unsigned int i;
663 
664 	if (!serializeSaveGameV12Data(fileHandle, (const SAVE_GAME_V12 *) serializeGame)
665 	    || !PHYSFS_writeSBE32(fileHandle, serializeGame->missionOffTime)
666 	    || !PHYSFS_writeSBE32(fileHandle, serializeGame->missionETA)
667 	    || !PHYSFS_writeUBE16(fileHandle, serializeGame->missionHomeLZ_X)
668 	    || !PHYSFS_writeUBE16(fileHandle, serializeGame->missionHomeLZ_Y)
669 	    || !PHYSFS_writeSBE32(fileHandle, serializeGame->missionPlayerX)
670 	    || !PHYSFS_writeSBE32(fileHandle, serializeGame->missionPlayerY))
671 	{
672 		return false;
673 	}
674 
675 	for (i = 0; i < MAX_PLAYERS; ++i)
676 	{
677 		if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspEntryTileX[i]))
678 		{
679 			return false;
680 		}
681 	}
682 
683 	for (i = 0; i < MAX_PLAYERS; ++i)
684 	{
685 		if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspEntryTileY[i]))
686 		{
687 			return false;
688 		}
689 	}
690 
691 	for (i = 0; i < MAX_PLAYERS; ++i)
692 	{
693 		if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspExitTileX[i]))
694 		{
695 			return false;
696 		}
697 	}
698 
699 	for (i = 0; i < MAX_PLAYERS; ++i)
700 	{
701 		if (!PHYSFS_writeUBE16(fileHandle, serializeGame->iTranspExitTileY[i]))
702 		{
703 			return false;
704 		}
705 	}
706 
707 	for (i = 0; i < MAX_PLAYERS; ++i)
708 	{
709 		if (!PHYSFS_writeUBE32(fileHandle, serializeGame->aDefaultSensor[i]))
710 		{
711 			return false;
712 		}
713 	}
714 
715 	for (i = 0; i < MAX_PLAYERS; ++i)
716 	{
717 		if (!PHYSFS_writeUBE32(fileHandle, serializeGame->aDefaultECM[i]))
718 		{
719 			return false;
720 		}
721 	}
722 
723 	for (i = 0; i < MAX_PLAYERS; ++i)
724 	{
725 		if (!PHYSFS_writeUBE32(fileHandle, serializeGame->aDefaultRepair[i]))
726 		{
727 			return false;
728 		}
729 	}
730 
731 	return true;
732 }
733 
deserializeSaveGameV14Data(PHYSFS_file * fileHandle,SAVE_GAME_V14 * serializeGame)734 static bool deserializeSaveGameV14Data(PHYSFS_file *fileHandle, SAVE_GAME_V14 *serializeGame)
735 {
736 	unsigned int i;
737 
738 	if (!deserializeSaveGameV12Data(fileHandle, (SAVE_GAME_V12 *) serializeGame)
739 	    || !PHYSFS_readSBE32(fileHandle, &serializeGame->missionOffTime)
740 	    || !PHYSFS_readSBE32(fileHandle, &serializeGame->missionETA)
741 	    || !PHYSFS_readUBE16(fileHandle, &serializeGame->missionHomeLZ_X)
742 	    || !PHYSFS_readUBE16(fileHandle, &serializeGame->missionHomeLZ_Y)
743 	    || !PHYSFS_readSBE32(fileHandle, &serializeGame->missionPlayerX)
744 	    || !PHYSFS_readSBE32(fileHandle, &serializeGame->missionPlayerY))
745 	{
746 		return false;
747 	}
748 
749 	for (i = 0; i < MAX_PLAYERS; ++i)
750 	{
751 		if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspEntryTileX[i]))
752 		{
753 			return false;
754 		}
755 	}
756 
757 	for (i = 0; i < MAX_PLAYERS; ++i)
758 	{
759 		if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspEntryTileY[i]))
760 		{
761 			return false;
762 		}
763 	}
764 
765 	for (i = 0; i < MAX_PLAYERS; ++i)
766 	{
767 		if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspExitTileX[i]))
768 		{
769 			return false;
770 		}
771 	}
772 
773 	for (i = 0; i < MAX_PLAYERS; ++i)
774 	{
775 		if (!PHYSFS_readUBE16(fileHandle, &serializeGame->iTranspExitTileY[i]))
776 		{
777 			return false;
778 		}
779 	}
780 
781 	for (i = 0; i < MAX_PLAYERS; ++i)
782 	{
783 		if (!PHYSFS_readUBE32(fileHandle, &serializeGame->aDefaultSensor[i]))
784 		{
785 			return false;
786 		}
787 	}
788 
789 	for (i = 0; i < MAX_PLAYERS; ++i)
790 	{
791 		if (!PHYSFS_readUBE32(fileHandle, &serializeGame->aDefaultECM[i]))
792 		{
793 			return false;
794 		}
795 	}
796 
797 	for (i = 0; i < MAX_PLAYERS; ++i)
798 	{
799 		if (!PHYSFS_readUBE32(fileHandle, &serializeGame->aDefaultRepair[i]))
800 		{
801 			return false;
802 		}
803 	}
804 
805 	return true;
806 }
807 
808 struct SAVE_GAME_V15 : public SAVE_GAME_V14
809 {
810 	int32_t    offWorldKeepLists;	// was BOOL (which was a int)
811 	uint8_t     aDroidExperience[MAX_PLAYERS][MAX_RECYCLED_DROIDS];
812 	uint32_t    RubbleTile;
813 	uint32_t    WaterTile;
814 	uint32_t    fogColour;
815 	uint32_t    fogState;
816 };
817 
serializeSaveGameV15Data(PHYSFS_file * fileHandle,const SAVE_GAME_V15 * serializeGame)818 static bool serializeSaveGameV15Data(PHYSFS_file *fileHandle, const SAVE_GAME_V15 *serializeGame)
819 {
820 	unsigned int i, j;
821 
822 	if (!serializeSaveGameV14Data(fileHandle, (const SAVE_GAME_V14 *) serializeGame)
823 	    || !PHYSFS_writeSBE32(fileHandle, serializeGame->offWorldKeepLists))
824 	{
825 		return false;
826 	}
827 
828 	for (i = 0; i < MAX_PLAYERS; ++i)
829 	{
830 		for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
831 		{
832 			if (!PHYSFS_writeUBE8(fileHandle, 0)) // no longer saved in binary form
833 			{
834 				return false;
835 			}
836 		}
837 	}
838 
839 	return (PHYSFS_writeUBE32(fileHandle, serializeGame->RubbleTile)
840 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->WaterTile)
841 	        && PHYSFS_writeUBE32(fileHandle, 0)
842 	        && PHYSFS_writeUBE32(fileHandle, 0));
843 }
844 
deserializeSaveGameV15Data(PHYSFS_file * fileHandle,SAVE_GAME_V15 * serializeGame)845 static bool deserializeSaveGameV15Data(PHYSFS_file *fileHandle, SAVE_GAME_V15 *serializeGame)
846 {
847 	unsigned int i, j;
848 	int32_t boolOffWorldKeepLists;
849 
850 	if (!deserializeSaveGameV14Data(fileHandle, (SAVE_GAME_V14 *) serializeGame)
851 	    || !PHYSFS_readSBE32(fileHandle, &boolOffWorldKeepLists))
852 	{
853 		return false;
854 	}
855 
856 	serializeGame->offWorldKeepLists = boolOffWorldKeepLists;
857 
858 	for (i = 0; i < MAX_PLAYERS; ++i)
859 	{
860 		for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
861 		{
862 			uint8_t tmp;
863 			if (!PHYSFS_readUBE8(fileHandle, &tmp))
864 			{
865 				return false;
866 			}
867 			if (tmp > 0)
868 			{
869 				add_to_experience_queue(i, tmp * 65536);
870 			}
871 		}
872 	}
873 
874 	return (PHYSFS_readUBE32(fileHandle, &serializeGame->RubbleTile)
875 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->WaterTile)
876 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->fogColour)
877 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->fogState));
878 }
879 
880 struct SAVE_GAME_V16 : public SAVE_GAME_V15
881 {
882 	LANDING_ZONE   sLandingZone[MAX_NOGO_AREAS];
883 };
884 
serializeSaveGameV16Data(PHYSFS_file * fileHandle,const SAVE_GAME_V16 * serializeGame)885 static bool serializeSaveGameV16Data(PHYSFS_file *fileHandle, const SAVE_GAME_V16 *serializeGame)
886 {
887 	unsigned int i;
888 
889 	if (!serializeSaveGameV15Data(fileHandle, (const SAVE_GAME_V15 *) serializeGame))
890 	{
891 		return false;
892 	}
893 
894 	for (i = 0; i < MAX_NOGO_AREAS; ++i)
895 	{
896 		if (!serializeLandingZoneData(fileHandle, &serializeGame->sLandingZone[i]))
897 		{
898 			return false;
899 		}
900 	}
901 
902 	return true;
903 }
904 
deserializeSaveGameV16Data(PHYSFS_file * fileHandle,SAVE_GAME_V16 * serializeGame)905 static bool deserializeSaveGameV16Data(PHYSFS_file *fileHandle, SAVE_GAME_V16 *serializeGame)
906 {
907 	unsigned int i;
908 
909 	if (!deserializeSaveGameV15Data(fileHandle, (SAVE_GAME_V15 *) serializeGame))
910 	{
911 		return false;
912 	}
913 
914 	for (i = 0; i < MAX_NOGO_AREAS; ++i)
915 	{
916 		if (!deserializeLandingZoneData(fileHandle, &serializeGame->sLandingZone[i]))
917 		{
918 			return false;
919 		}
920 	}
921 
922 	return true;
923 }
924 
925 struct SAVE_GAME_V17 : public SAVE_GAME_V16
926 {
927 	uint32_t    objId;
928 };
929 
serializeSaveGameV17Data(PHYSFS_file * fileHandle,const SAVE_GAME_V17 * serializeGame)930 static bool serializeSaveGameV17Data(PHYSFS_file *fileHandle, const SAVE_GAME_V17 *serializeGame)
931 {
932 	return (serializeSaveGameV16Data(fileHandle, (const SAVE_GAME_V16 *) serializeGame)
933 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->objId));
934 }
935 
deserializeSaveGameV17Data(PHYSFS_file * fileHandle,SAVE_GAME_V17 * serializeGame)936 static bool deserializeSaveGameV17Data(PHYSFS_file *fileHandle, SAVE_GAME_V17 *serializeGame)
937 {
938 	return (deserializeSaveGameV16Data(fileHandle, (SAVE_GAME_V16 *) serializeGame)
939 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->objId));
940 }
941 
942 struct SAVE_GAME_V18 : public SAVE_GAME_V17
943 {
944 	char        buildDate[MAX_STR_LENGTH];
945 	uint32_t    oldestVersion;
946 	uint32_t    validityKey;
947 };
948 
serializeSaveGameV18Data(PHYSFS_file * fileHandle,const SAVE_GAME_V18 * serializeGame)949 static bool serializeSaveGameV18Data(PHYSFS_file *fileHandle, const SAVE_GAME_V18 *serializeGame)
950 {
951 	return (serializeSaveGameV17Data(fileHandle, (const SAVE_GAME_V17 *) serializeGame)
952 	        && WZ_PHYSFS_writeBytes(fileHandle, serializeGame->buildDate, MAX_STR_LENGTH) == MAX_STR_LENGTH
953 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->oldestVersion)
954 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->validityKey));
955 }
956 
deserializeSaveGameV18Data(PHYSFS_file * fileHandle,SAVE_GAME_V18 * serializeGame)957 static bool deserializeSaveGameV18Data(PHYSFS_file *fileHandle, SAVE_GAME_V18 *serializeGame)
958 {
959 	return (deserializeSaveGameV17Data(fileHandle, (SAVE_GAME_V17 *) serializeGame)
960 	        && WZ_PHYSFS_readBytes(fileHandle, serializeGame->buildDate, MAX_STR_LENGTH) == MAX_STR_LENGTH
961 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->oldestVersion)
962 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->validityKey));
963 }
964 
965 struct SAVE_GAME_V19 : public SAVE_GAME_V18
966 {
967 	uint8_t     alliances[MAX_PLAYERS][MAX_PLAYERS];
968 	uint8_t     playerColour[MAX_PLAYERS];
969 	uint8_t     radarZoom;
970 };
971 
serializeSaveGameV19Data(PHYSFS_file * fileHandle,const SAVE_GAME_V19 * serializeGame)972 static bool serializeSaveGameV19Data(PHYSFS_file *fileHandle, const SAVE_GAME_V19 *serializeGame)
973 {
974 	unsigned int i, j;
975 
976 	if (!serializeSaveGameV18Data(fileHandle, (const SAVE_GAME_V18 *) serializeGame))
977 	{
978 		return false;
979 	}
980 
981 	for (i = 0; i < MAX_PLAYERS; ++i)
982 	{
983 		for (j = 0; j < MAX_PLAYERS; ++j)
984 		{
985 			if (!PHYSFS_writeUBE8(fileHandle, serializeGame->alliances[i][j]))
986 			{
987 				return false;
988 			}
989 		}
990 	}
991 
992 	for (i = 0; i < MAX_PLAYERS; ++i)
993 	{
994 		if (!PHYSFS_writeUBE8(fileHandle, serializeGame->playerColour[i]))
995 		{
996 			return false;
997 		}
998 	}
999 
1000 	return PHYSFS_writeUBE8(fileHandle, serializeGame->radarZoom);
1001 }
1002 
deserializeSaveGameV19Data(PHYSFS_file * fileHandle,SAVE_GAME_V19 * serializeGame)1003 static bool deserializeSaveGameV19Data(PHYSFS_file *fileHandle, SAVE_GAME_V19 *serializeGame)
1004 {
1005 	unsigned int i, j;
1006 
1007 	if (!deserializeSaveGameV18Data(fileHandle, (SAVE_GAME_V18 *) serializeGame))
1008 	{
1009 		return false;
1010 	}
1011 
1012 	for (i = 0; i < MAX_PLAYERS; ++i)
1013 	{
1014 		for (j = 0; j < MAX_PLAYERS; ++j)
1015 		{
1016 			if (!PHYSFS_readUBE8(fileHandle, &serializeGame->alliances[i][j]))
1017 			{
1018 				return false;
1019 			}
1020 		}
1021 	}
1022 
1023 	for (i = 0; i < MAX_PLAYERS; ++i)
1024 	{
1025 		if (!PHYSFS_readUBE8(fileHandle, &serializeGame->playerColour[i]))
1026 		{
1027 			return false;
1028 		}
1029 	}
1030 
1031 	return PHYSFS_readUBE8(fileHandle, &serializeGame->radarZoom);
1032 }
1033 
1034 struct SAVE_GAME_V20 : public SAVE_GAME_V19
1035 {
1036 	uint8_t     bDroidsToSafetyFlag;
1037 	Vector2i    asVTOLReturnPos[MAX_PLAYERS];
1038 };
1039 
serializeSaveGameV20Data(PHYSFS_file * fileHandle,const SAVE_GAME_V20 * serializeGame)1040 static bool serializeSaveGameV20Data(PHYSFS_file *fileHandle, const SAVE_GAME_V20 *serializeGame)
1041 {
1042 	unsigned int i;
1043 
1044 	if (!serializeSaveGameV19Data(fileHandle, (const SAVE_GAME_V19 *) serializeGame)
1045 	    || !PHYSFS_writeUBE8(fileHandle, serializeGame->bDroidsToSafetyFlag))
1046 	{
1047 		return false;
1048 	}
1049 
1050 	for (i = 0; i < MAX_PLAYERS; ++i)
1051 	{
1052 		if (!serializeVector2i(fileHandle, &serializeGame->asVTOLReturnPos[i]))
1053 		{
1054 			return false;
1055 		}
1056 	}
1057 
1058 	return true;
1059 }
1060 
deserializeSaveGameV20Data(PHYSFS_file * fileHandle,SAVE_GAME_V20 * serializeGame)1061 static bool deserializeSaveGameV20Data(PHYSFS_file *fileHandle, SAVE_GAME_V20 *serializeGame)
1062 {
1063 	unsigned int i;
1064 
1065 	if (!deserializeSaveGameV19Data(fileHandle, (SAVE_GAME_V19 *) serializeGame)
1066 	    || !PHYSFS_readUBE8(fileHandle, &serializeGame->bDroidsToSafetyFlag))
1067 	{
1068 		return false;
1069 	}
1070 
1071 	for (i = 0; i < MAX_PLAYERS; ++i)
1072 	{
1073 		if (!deserializeVector2i(fileHandle, &serializeGame->asVTOLReturnPos[i]))
1074 		{
1075 			return false;
1076 		}
1077 	}
1078 
1079 	return true;
1080 }
1081 
1082 struct SAVE_GAME_V22 : public SAVE_GAME_V20
1083 {
1084 	RUN_DATA asRunData[MAX_PLAYERS];
1085 };
1086 
serializeSaveGameV22Data(PHYSFS_file * fileHandle,const SAVE_GAME_V22 * serializeGame)1087 static bool serializeSaveGameV22Data(PHYSFS_file *fileHandle, const SAVE_GAME_V22 *serializeGame)
1088 {
1089 	unsigned int i;
1090 
1091 	if (!serializeSaveGameV20Data(fileHandle, (const SAVE_GAME_V20 *) serializeGame))
1092 	{
1093 		return false;
1094 	}
1095 
1096 	for (i = 0; i < MAX_PLAYERS; ++i)
1097 	{
1098 		if (!serializeRunData(fileHandle, &serializeGame->asRunData[i]))
1099 		{
1100 			return false;
1101 		}
1102 	}
1103 
1104 	return true;
1105 }
1106 
deserializeSaveGameV22Data(PHYSFS_file * fileHandle,SAVE_GAME_V22 * serializeGame)1107 static bool deserializeSaveGameV22Data(PHYSFS_file *fileHandle, SAVE_GAME_V22 *serializeGame)
1108 {
1109 	unsigned int i;
1110 
1111 	if (!deserializeSaveGameV20Data(fileHandle, (SAVE_GAME_V20 *) serializeGame))
1112 	{
1113 		return false;
1114 	}
1115 
1116 	for (i = 0; i < MAX_PLAYERS; ++i)
1117 	{
1118 		if (!deserializeRunData(fileHandle, &serializeGame->asRunData[i]))
1119 		{
1120 			return false;
1121 		}
1122 	}
1123 
1124 	return true;
1125 }
1126 
1127 struct SAVE_GAME_V24 : public SAVE_GAME_V22
1128 {
1129 	uint32_t    reinforceTime;
1130 	uint8_t     bPlayCountDown;
1131 	uint8_t     bPlayerHasWon;
1132 	uint8_t     bPlayerHasLost;
1133 	uint8_t     dummy3;
1134 };
1135 
serializeSaveGameV24Data(PHYSFS_file * fileHandle,const SAVE_GAME_V24 * serializeGame)1136 static bool serializeSaveGameV24Data(PHYSFS_file *fileHandle, const SAVE_GAME_V24 *serializeGame)
1137 {
1138 	return (serializeSaveGameV22Data(fileHandle, (const SAVE_GAME_V22 *) serializeGame)
1139 	        && PHYSFS_writeUBE32(fileHandle, serializeGame->reinforceTime)
1140 	        && PHYSFS_writeUBE8(fileHandle, serializeGame->bPlayCountDown)
1141 	        && PHYSFS_writeUBE8(fileHandle, serializeGame->bPlayerHasWon)
1142 	        && PHYSFS_writeUBE8(fileHandle, serializeGame->bPlayerHasLost)
1143 	        && PHYSFS_writeUBE8(fileHandle, serializeGame->dummy3));
1144 }
1145 
deserializeSaveGameV24Data(PHYSFS_file * fileHandle,SAVE_GAME_V24 * serializeGame)1146 static bool deserializeSaveGameV24Data(PHYSFS_file *fileHandle, SAVE_GAME_V24 *serializeGame)
1147 {
1148 	return (deserializeSaveGameV22Data(fileHandle, (SAVE_GAME_V22 *) serializeGame)
1149 	        && PHYSFS_readUBE32(fileHandle, &serializeGame->reinforceTime)
1150 	        && PHYSFS_readUBE8(fileHandle, &serializeGame->bPlayCountDown)
1151 	        && PHYSFS_readUBE8(fileHandle, &serializeGame->bPlayerHasWon)
1152 	        && PHYSFS_readUBE8(fileHandle, &serializeGame->bPlayerHasLost)
1153 	        && PHYSFS_readUBE8(fileHandle, &serializeGame->dummy3));
1154 }
1155 
1156 struct SAVE_GAME_V27 : public SAVE_GAME_V24
1157 {
1158 	uint16_t    awDroidExperience[MAX_PLAYERS][MAX_RECYCLED_DROIDS];
1159 };
1160 
serializeSaveGameV27Data(PHYSFS_file * fileHandle,const SAVE_GAME_V27 * serializeGame)1161 static bool serializeSaveGameV27Data(PHYSFS_file *fileHandle, const SAVE_GAME_V27 *serializeGame)
1162 {
1163 	unsigned int i, j;
1164 
1165 	if (!serializeSaveGameV24Data(fileHandle, (const SAVE_GAME_V24 *) serializeGame))
1166 	{
1167 		return false;
1168 	}
1169 
1170 	for (i = 0; i < MAX_PLAYERS; ++i)
1171 	{
1172 		for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
1173 		{
1174 			if (!PHYSFS_writeUBE16(fileHandle, 0))
1175 			{
1176 				return false;
1177 			}
1178 		}
1179 	}
1180 
1181 	return true;
1182 }
1183 
deserializeSaveGameV27Data(PHYSFS_file * fileHandle,SAVE_GAME_V27 * serializeGame)1184 static bool deserializeSaveGameV27Data(PHYSFS_file *fileHandle, SAVE_GAME_V27 *serializeGame)
1185 {
1186 	unsigned int i, j;
1187 
1188 	if (!deserializeSaveGameV24Data(fileHandle, (SAVE_GAME_V24 *) serializeGame))
1189 	{
1190 		return false;
1191 	}
1192 
1193 	for (i = 0; i < MAX_PLAYERS; ++i)
1194 	{
1195 		for (j = 0; j < MAX_RECYCLED_DROIDS; ++j)
1196 		{
1197 			uint16_t tmp;
1198 			if (!PHYSFS_readUBE16(fileHandle, &tmp))
1199 			{
1200 				return false;
1201 			}
1202 			if (tmp > 0)
1203 			{
1204 				add_to_experience_queue(i, tmp * 65536);
1205 			}
1206 		}
1207 	}
1208 
1209 	return true;
1210 }
1211 
1212 struct SAVE_GAME_V29 : public SAVE_GAME_V27
1213 {
1214 	uint16_t    missionScrollMinX;
1215 	uint16_t    missionScrollMinY;
1216 	uint16_t    missionScrollMaxX;
1217 	uint16_t    missionScrollMaxY;
1218 };
1219 
serializeSaveGameV29Data(PHYSFS_file * fileHandle,const SAVE_GAME_V29 * serializeGame)1220 static bool serializeSaveGameV29Data(PHYSFS_file *fileHandle, const SAVE_GAME_V29 *serializeGame)
1221 {
1222 	return (serializeSaveGameV27Data(fileHandle, (const SAVE_GAME_V27 *) serializeGame)
1223 	        && PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMinX)
1224 	        && PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMinY)
1225 	        && PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMaxX)
1226 	        && PHYSFS_writeUBE16(fileHandle, serializeGame->missionScrollMaxY));
1227 }
1228 
deserializeSaveGameV29Data(PHYSFS_file * fileHandle,SAVE_GAME_V29 * serializeGame)1229 static bool deserializeSaveGameV29Data(PHYSFS_file *fileHandle, SAVE_GAME_V29 *serializeGame)
1230 {
1231 	return (deserializeSaveGameV27Data(fileHandle, (SAVE_GAME_V27 *) serializeGame)
1232 	        && PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMinX)
1233 	        && PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMinY)
1234 	        && PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMaxX)
1235 	        && PHYSFS_readUBE16(fileHandle, &serializeGame->missionScrollMaxY));
1236 }
1237 
1238 struct SAVE_GAME_V30 : public SAVE_GAME_V29
1239 {
1240 	int32_t     scrGameLevel;
1241 	uint8_t     bExtraVictoryFlag;
1242 	uint8_t     bExtraFailFlag;
1243 	uint8_t     bTrackTransporter;
1244 };
1245 
serializeSaveGameV30Data(PHYSFS_file * fileHandle,const SAVE_GAME_V30 * serializeGame)1246 static bool serializeSaveGameV30Data(PHYSFS_file *fileHandle, const SAVE_GAME_V30 *serializeGame)
1247 {
1248 	return (serializeSaveGameV29Data(fileHandle, (const SAVE_GAME_V29 *) serializeGame)
1249 	        && PHYSFS_writeSBE32(fileHandle, serializeGame->scrGameLevel)
1250 	        && PHYSFS_writeUBE8(fileHandle, serializeGame->bExtraVictoryFlag)
1251 	        && PHYSFS_writeUBE8(fileHandle, serializeGame->bExtraFailFlag)
1252 	        && PHYSFS_writeUBE8(fileHandle, serializeGame->bTrackTransporter));
1253 }
1254 
deserializeSaveGameV30Data(PHYSFS_file * fileHandle,SAVE_GAME_V30 * serializeGame)1255 static bool deserializeSaveGameV30Data(PHYSFS_file *fileHandle, SAVE_GAME_V30 *serializeGame)
1256 {
1257 	return (deserializeSaveGameV29Data(fileHandle, (SAVE_GAME_V29 *) serializeGame)
1258 	        && PHYSFS_readSBE32(fileHandle, &serializeGame->scrGameLevel)
1259 	        && PHYSFS_readUBE8(fileHandle, &serializeGame->bExtraVictoryFlag)
1260 	        && PHYSFS_readUBE8(fileHandle, &serializeGame->bExtraFailFlag)
1261 	        && PHYSFS_readUBE8(fileHandle, &serializeGame->bTrackTransporter));
1262 }
1263 
1264 //extra code for the patch - saves out whether cheated with the mission timer
1265 struct SAVE_GAME_V31 : public SAVE_GAME_V30
1266 {
1267 	int32_t     missionCheatTime;
1268 };
1269 
1270 
serializeSaveGameV31Data(PHYSFS_file * fileHandle,const SAVE_GAME_V31 * serializeGame)1271 static bool serializeSaveGameV31Data(PHYSFS_file *fileHandle, const SAVE_GAME_V31 *serializeGame)
1272 {
1273 	return (serializeSaveGameV30Data(fileHandle, (const SAVE_GAME_V30 *) serializeGame)
1274 	        && PHYSFS_writeSBE32(fileHandle, serializeGame->missionCheatTime));
1275 }
1276 
deserializeSaveGameV31Data(PHYSFS_file * fileHandle,SAVE_GAME_V31 * serializeGame)1277 static bool deserializeSaveGameV31Data(PHYSFS_file *fileHandle, SAVE_GAME_V31 *serializeGame)
1278 {
1279 	return (deserializeSaveGameV30Data(fileHandle, (SAVE_GAME_V30 *) serializeGame)
1280 	        && PHYSFS_readSBE32(fileHandle, &serializeGame->missionCheatTime));
1281 }
1282 
1283 // alexl. skirmish saves
1284 struct SAVE_GAME_V33 : public SAVE_GAME_V31
1285 {
1286 	MULTIPLAYERGAME sGame;
1287 	NETPLAY         sNetPlay;
1288 	uint32_t        savePlayer;
1289 	char            sPName[32];
1290 	int32_t         multiPlayer;	// was BOOL (int) ** see warning about conversion
1291 	uint32_t        sPlayerIndex[MAX_PLAYERS];
1292 };
1293 
serializeSaveGameV33Data(PHYSFS_file * fileHandle,const SAVE_GAME_V33 * serializeGame)1294 static bool serializeSaveGameV33Data(PHYSFS_file *fileHandle, const SAVE_GAME_V33 *serializeGame)
1295 {
1296 	unsigned int i;
1297 
1298 	if (!serializeSaveGameV31Data(fileHandle, (const SAVE_GAME_V31 *) serializeGame)
1299 	    || !serializeMultiplayerGame(fileHandle, &serializeGame->sGame)
1300 	    || !serializeNetPlay(fileHandle, &serializeGame->sNetPlay)
1301 	    || !PHYSFS_writeUBE32(fileHandle, serializeGame->savePlayer)
1302 	    || WZ_PHYSFS_writeBytes(fileHandle, serializeGame->sPName, 32) != 32
1303 	    || !PHYSFS_writeSBE32(fileHandle, serializeGame->multiPlayer))
1304 	{
1305 		return false;
1306 	}
1307 
1308 	for (i = 0; i < MAX_PLAYERS; ++i)
1309 	{
1310 		if (!PHYSFS_writeUBE32(fileHandle, serializeGame->sPlayerIndex[i]))
1311 		{
1312 			return false;
1313 		}
1314 	}
1315 
1316 	return true;
1317 }
1318 
deserializeSaveGameV33Data(PHYSFS_file * fileHandle,SAVE_GAME_V33 * serializeGame)1319 static bool deserializeSaveGameV33Data(PHYSFS_file *fileHandle, SAVE_GAME_V33 *serializeGame)
1320 {
1321 	unsigned int i;
1322 	int32_t boolMultiPlayer;
1323 
1324 	if (!deserializeSaveGameV31Data(fileHandle, (SAVE_GAME_V31 *) serializeGame)
1325 	    || !deserializeMultiplayerGame(fileHandle, &serializeGame->sGame)
1326 	    || !deserializeNetPlay(fileHandle, &serializeGame->sNetPlay)
1327 	    || !PHYSFS_readUBE32(fileHandle, &serializeGame->savePlayer)
1328 	    || WZ_PHYSFS_readBytes(fileHandle, serializeGame->sPName, 32) != 32
1329 	    || !PHYSFS_readSBE32(fileHandle, &boolMultiPlayer))
1330 	{
1331 		return false;
1332 	}
1333 
1334 	serializeGame->multiPlayer = boolMultiPlayer;
1335 
1336 	for (i = 0; i < MAX_PLAYERS; ++i)
1337 	{
1338 		if (!PHYSFS_readUBE32(fileHandle, &serializeGame->sPlayerIndex[i]))
1339 		{
1340 			return false;
1341 		}
1342 	}
1343 
1344 	return true;
1345 }
1346 
1347 //Now holds AI names for multiplayer
1348 struct SAVE_GAME_V34 : public SAVE_GAME_V33
1349 {
1350 	char sPlayerName[MAX_PLAYERS][StringSize];
1351 };
1352 
1353 
serializeSaveGameV34Data(PHYSFS_file * fileHandle,const SAVE_GAME_V34 * serializeGame)1354 static bool serializeSaveGameV34Data(PHYSFS_file *fileHandle, const SAVE_GAME_V34 *serializeGame)
1355 {
1356 	unsigned int i;
1357 	if (!serializeSaveGameV33Data(fileHandle, (const SAVE_GAME_V33 *) serializeGame))
1358 	{
1359 		return false;
1360 	}
1361 
1362 	for (i = 0; i < MAX_PLAYERS; ++i)
1363 	{
1364 		if (WZ_PHYSFS_writeBytes(fileHandle, serializeGame->sPlayerName[i], StringSize) != StringSize)
1365 		{
1366 			return false;
1367 		}
1368 	}
1369 
1370 	return true;
1371 }
1372 
deserializeSaveGameV34Data(PHYSFS_file * fileHandle,SAVE_GAME_V34 * serializeGame)1373 static bool deserializeSaveGameV34Data(PHYSFS_file *fileHandle, SAVE_GAME_V34 *serializeGame)
1374 {
1375 	unsigned int i;
1376 	if (!deserializeSaveGameV33Data(fileHandle, (SAVE_GAME_V33 *) serializeGame))
1377 	{
1378 		return false;
1379 	}
1380 
1381 	for (i = 0; i < MAX_PLAYERS; ++i)
1382 	{
1383 		if (WZ_PHYSFS_readBytes(fileHandle, serializeGame->sPlayerName[i], StringSize) != StringSize)
1384 		{
1385 			return false;
1386 		}
1387 	}
1388 
1389 	return true;
1390 }
1391 
1392 // First version to utilize (de)serialization API and first to be big-endian (instead of little-endian)
1393 struct SAVE_GAME_V35 : public SAVE_GAME_V34
1394 {
1395 };
1396 
serializeSaveGameV35Data(PHYSFS_file * fileHandle,const SAVE_GAME_V35 * serializeGame)1397 static bool serializeSaveGameV35Data(PHYSFS_file *fileHandle, const SAVE_GAME_V35 *serializeGame)
1398 {
1399 	return serializeSaveGameV34Data(fileHandle, (const SAVE_GAME_V34 *) serializeGame);
1400 }
1401 
deserializeSaveGameV35Data(PHYSFS_file * fileHandle,SAVE_GAME_V35 * serializeGame)1402 static bool deserializeSaveGameV35Data(PHYSFS_file *fileHandle, SAVE_GAME_V35 *serializeGame)
1403 {
1404 	return deserializeSaveGameV34Data(fileHandle, (SAVE_GAME_V34 *) serializeGame);
1405 }
1406 
1407 // Store loaded mods in savegame
1408 struct SAVE_GAME_V38 : public SAVE_GAME_V35
1409 {
1410 	char modList[modlist_string_size];
1411 };
1412 
serializeSaveGameV38Data(PHYSFS_file * fileHandle,const SAVE_GAME_V38 * serializeGame)1413 static bool serializeSaveGameV38Data(PHYSFS_file *fileHandle, const SAVE_GAME_V38 *serializeGame)
1414 {
1415 	if (!serializeSaveGameV35Data(fileHandle, (const SAVE_GAME_V35 *) serializeGame))
1416 	{
1417 		return false;
1418 	}
1419 
1420 	if (WZ_PHYSFS_writeBytes(fileHandle, serializeGame->modList, modlist_string_size) != modlist_string_size)
1421 	{
1422 		return false;
1423 	}
1424 
1425 	return true;
1426 }
1427 
deserializeSaveGameV38Data(PHYSFS_file * fileHandle,SAVE_GAME_V38 * serializeGame)1428 static bool deserializeSaveGameV38Data(PHYSFS_file *fileHandle, SAVE_GAME_V38 *serializeGame)
1429 {
1430 	if (!deserializeSaveGameV35Data(fileHandle, (SAVE_GAME_V35 *) serializeGame))
1431 	{
1432 		return false;
1433 	}
1434 
1435 	if (WZ_PHYSFS_readBytes(fileHandle, serializeGame->modList, modlist_string_size) != modlist_string_size)
1436 	{
1437 		return false;
1438 	}
1439 
1440 	return true;
1441 }
1442 
1443 // Current save game version
1444 typedef SAVE_GAME_V38 SAVE_GAME;
1445 
serializeSaveGameData(PHYSFS_file * fileHandle,const SAVE_GAME * serializeGame)1446 static bool serializeSaveGameData(PHYSFS_file *fileHandle, const SAVE_GAME *serializeGame)
1447 {
1448 	return serializeSaveGameV38Data(fileHandle, (const SAVE_GAME_V38 *) serializeGame);
1449 }
1450 
deserializeSaveGameData(PHYSFS_file * fileHandle,SAVE_GAME * serializeGame)1451 static bool deserializeSaveGameData(PHYSFS_file *fileHandle, SAVE_GAME *serializeGame)
1452 {
1453 	return deserializeSaveGameV38Data(fileHandle, (SAVE_GAME_V38 *) serializeGame);
1454 }
1455 
1456 struct DROIDINIT_SAVEHEADER : public GAME_SAVEHEADER
1457 {
1458 	UDWORD		quantity;
1459 };
1460 
1461 struct SAVE_DROIDINIT
1462 {
1463 	OBJECT_SAVE_V19;
1464 };
1465 
1466 /*
1467  *	STRUCTURE Definitions
1468  */
1469 
1470 #define STRUCTURE_SAVE_V2 \
1471 	OBJECT_SAVE_V19; \
1472 	UBYTE				status; \
1473 	SDWORD				currentBuildPts; \
1474 	UDWORD				body; \
1475 	UDWORD				armour; \
1476 	UDWORD				resistance; \
1477 	UDWORD				dummy1; \
1478 	UDWORD				subjectInc;  /*research inc or factory prod id*/\
1479 	UDWORD				timeStarted; \
1480 	UDWORD				output; \
1481 	UDWORD				capacity; \
1482 	UDWORD				quantity
1483 
1484 struct SAVE_STRUCTURE_V2
1485 {
1486 	STRUCTURE_SAVE_V2;
1487 };
1488 
1489 #define STRUCTURE_SAVE_V12 \
1490 	STRUCTURE_SAVE_V2; \
1491 	UDWORD				factoryInc;			\
1492 	UBYTE				loopsPerformed;		\
1493 	UDWORD				powerAccrued;		\
1494 	UDWORD				dummy2;			\
1495 	UDWORD				droidTimeStarted;	\
1496 	UDWORD				timeToBuild;		\
1497 	UDWORD				timeStartHold
1498 
1499 struct SAVE_STRUCTURE_V12
1500 {
1501 	STRUCTURE_SAVE_V12;
1502 };
1503 
1504 #define STRUCTURE_SAVE_V14 \
1505 	STRUCTURE_SAVE_V12; \
1506 	UBYTE	visible[MAX_PLAYERS]
1507 
1508 struct SAVE_STRUCTURE_V14
1509 {
1510 	STRUCTURE_SAVE_V14;
1511 };
1512 
1513 #define STRUCTURE_SAVE_V15 \
1514 	STRUCTURE_SAVE_V14; \
1515 	char	researchName[MAX_SAVE_NAME_SIZE_V19]
1516 
1517 struct SAVE_STRUCTURE_V15
1518 {
1519 	STRUCTURE_SAVE_V15;
1520 };
1521 
1522 #define STRUCTURE_SAVE_V17 \
1523 	STRUCTURE_SAVE_V15;\
1524 	SWORD				currentPowerAccrued
1525 
1526 struct SAVE_STRUCTURE_V17
1527 {
1528 	STRUCTURE_SAVE_V17;
1529 };
1530 
1531 #define STRUCTURE_SAVE_V20 \
1532 	OBJECT_SAVE_V20; \
1533 	UBYTE				status; \
1534 	SDWORD				currentBuildPts; \
1535 	UDWORD				body; \
1536 	UDWORD				armour; \
1537 	UDWORD				resistance; \
1538 	UDWORD				dummy1; \
1539 	UDWORD				subjectInc;  /*research inc or factory prod id*/\
1540 	UDWORD				timeStarted; \
1541 	UDWORD				output; \
1542 	UDWORD				capacity; \
1543 	UDWORD				quantity; \
1544 	UDWORD				factoryInc;			\
1545 	UBYTE				loopsPerformed;		\
1546 	UDWORD				powerAccrued;		\
1547 	UDWORD				dummy2;			\
1548 	UDWORD				droidTimeStarted;	\
1549 	UDWORD				timeToBuild;		\
1550 	UDWORD				timeStartHold; \
1551 	UBYTE				visible[MAX_PLAYERS]; \
1552 	char				researchName[MAX_SAVE_NAME_SIZE]; \
1553 	SWORD				currentPowerAccrued
1554 
1555 struct SAVE_STRUCTURE_V20
1556 {
1557 	STRUCTURE_SAVE_V20;
1558 };
1559 
1560 #define STRUCTURE_SAVE_V21 \
1561 	STRUCTURE_SAVE_V20; \
1562 	UDWORD				commandId
1563 
1564 struct SAVE_STRUCTURE_V21
1565 {
1566 	STRUCTURE_SAVE_V21;
1567 };
1568 
1569 struct SAVE_STRUCTURE
1570 {
1571 	STRUCTURE_SAVE_V21;
1572 };
1573 
1574 #define FEATURE_SAVE_V2 \
1575 	OBJECT_SAVE_V19
1576 
1577 struct SAVE_FEATURE_V2
1578 {
1579 	FEATURE_SAVE_V2;
1580 };
1581 
1582 #define FEATURE_SAVE_V14 \
1583 	FEATURE_SAVE_V2; \
1584 	UBYTE	visible[MAX_PLAYERS]
1585 
1586 struct SAVE_FEATURE_V14
1587 {
1588 	FEATURE_SAVE_V14;
1589 };
1590 
1591 /***************************************************************************/
1592 /*
1593  *	Local Variables
1594  */
1595 /***************************************************************************/
1596 extern uint32_t unsynchObjID;  // unique ID creation thing..
1597 extern uint32_t synchObjID;    // unique ID creation thing..
1598 
1599 static UDWORD			saveGameVersion = 0;
1600 static bool				saveGameOnMission = false;
1601 static SAVE_GAME		saveGameData;
1602 
1603 static UDWORD		savedGameTime;
1604 static UDWORD		savedObjId;
1605 static SDWORD		startX, startY;
1606 static UDWORD		width, height;
1607 static GAME_TYPE	gameType;
1608 static bool IsScenario;
1609 
1610 /***************************************************************************/
1611 /*
1612  *	Local ProtoTypes
1613  */
1614 /***************************************************************************/
1615 static bool gameLoadV7(PHYSFS_file *fileHandle);
1616 static bool gameLoadV(PHYSFS_file *fileHandle, unsigned int version);
1617 static bool loadMainFile(const std::string &fileName);
1618 static bool loadMainFileFinal(const std::string &fileName);
1619 static bool writeMainFile(const std::string &fileName, SDWORD saveType);
1620 static bool writeGameFile(const char *fileName, SDWORD saveType);
1621 static bool writeMapFile(const char *fileName);
1622 
1623 static bool loadSaveDroidInit(char *pFileData, UDWORD filesize);
1624 
1625 static bool loadSaveDroid(const char *pFileName, DROID **ppsCurrentDroidLists);
1626 static bool loadScriptDroid(ScriptMapData const &data);
1627 static bool loadSaveDroidPointers(const WzString &pFileName, DROID **ppsCurrentDroidLists);
1628 static bool writeDroidFile(const char *pFileName, DROID **ppsCurrentDroidLists);
1629 
1630 static bool loadSaveStructure(char *pFileData, UDWORD filesize);
1631 static bool loadSaveStructure2(const char *pFileName, STRUCTURE **ppList);
1632 static bool loadScriptStructure(ScriptMapData const &data);
1633 static bool loadSaveStructurePointers(const WzString& filename, STRUCTURE **ppList);
1634 static bool writeStructFile(const char *pFileName);
1635 
1636 static bool loadSaveTemplate(const char *pFileName);
1637 static bool writeTemplateFile(const char *pFileName);
1638 
1639 static bool loadSaveFeature(char *pFileData, UDWORD filesize);
1640 static bool writeFeatureFile(const char *pFileName);
1641 static bool loadSaveFeature2(const char *pFileName);
1642 static bool loadScriptFeature(ScriptMapData const &data);
1643 
1644 static bool writeTerrainTypeMapFile(char *pFileName);
1645 
1646 static bool loadSaveCompList(const char *pFileName);
1647 static bool writeCompListFile(const char *pFileName);
1648 
1649 static bool loadSaveStructTypeList(const char *pFileName);
1650 static bool writeStructTypeListFile(const char *pFileName);
1651 
1652 static bool loadSaveResearch(const char *pFileName);
1653 static bool writeResearchFile(char *pFileName);
1654 
1655 static bool loadSaveMessage(const char* pFileName, LEVEL_TYPE levelType);
1656 static bool writeMessageFile(const char *pFileName);
1657 
1658 static bool loadSaveStructLimits(const char *pFileName);
1659 static bool writeStructLimitsFile(const char *pFileName);
1660 
1661 static bool readFiresupportDesignators(const char *pFileName);
1662 static bool writeFiresupportDesignators(const char *pFileName);
1663 
1664 static bool writeScriptState(const char *pFileName);
1665 
1666 static bool gameLoad(const char *fileName);
1667 
1668 /* set the global scroll values to use for the save game */
1669 static void setMapScroll();
1670 
getSaveStructNameV19(SAVE_STRUCTURE_V17 * psSaveStructure)1671 static char *getSaveStructNameV19(SAVE_STRUCTURE_V17 *psSaveStructure)
1672 {
1673 	return (psSaveStructure->name);
1674 }
1675 
1676 /*This just loads up the .gam file to determine which level data to set up - split up
1677 so can be called in levLoadData when starting a game from a load save game*/
1678 
1679 // -----------------------------------------------------------------------------------------
loadGameInit(const char * fileName)1680 bool loadGameInit(const char *fileName)
1681 {
1682 	if (!gameLoad(fileName))
1683 	{
1684 		debug(LOG_ERROR, "Corrupted / unsupported savegame file %s, Unable to load!", fileName);
1685 		// NOTE: why do we start the game clock on a *failed* load?
1686 		// Start the game clock
1687 		gameTimeStart();
1688 
1689 		return false;
1690 	}
1691 
1692 	return true;
1693 }
1694 
1695 
1696 // -----------------------------------------------------------------------------------------
1697 // Load a file from a save game into the psx.
1698 // This is divided up into 2 parts ...
1699 //
1700 // if it is a level loaded up from CD then UserSaveGame will by false
1701 // UserSaveGame ... Extra stuff to load after scripts
loadMissionExtras(const char * pGameToLoad,LEVEL_TYPE levelType)1702 bool loadMissionExtras(const char* pGameToLoad, LEVEL_TYPE levelType)
1703 {
1704 	char			aFileName[256];
1705 	size_t			fileExten;
1706 
1707 	sstrcpy(aFileName, pGameToLoad);
1708 	fileExten = strlen(pGameToLoad) - 3;
1709 	aFileName[fileExten - 1] = '\0';
1710 	strcat(aFileName, "/");
1711 
1712 	if (saveGameVersion >= VERSION_11)
1713 	{
1714 		//if user save game then load up the messages AFTER any droids or structures are loaded
1715 		if (gameType == GTYPE_SAVE_START || gameType == GTYPE_SAVE_MIDMISSION)
1716 		{
1717 			//load in the message list file
1718 			aFileName[fileExten] = '\0';
1719 			strcat(aFileName, "messtate.json");
1720 			if (!loadSaveMessage(aFileName, levelType))
1721 			{
1722 				debug(LOG_ERROR, "Failed to load mission extras from %s", aFileName);
1723 				return false;
1724 			}
1725 		}
1726 	}
1727 
1728 	return true;
1729 }
1730 
sanityUpdate()1731 static void sanityUpdate()
1732 {
1733 	for (int player = 0; player < game.maxPlayers; player++)
1734 	{
1735 		for (DROID *psDroid = apsDroidLists[player]; psDroid; psDroid = psDroid->psNext)
1736 		{
1737 			orderCheckList(psDroid);
1738 			actionSanity(psDroid);
1739 		}
1740 	}
1741 }
1742 
getIniBaseObject(WzConfig & ini,WzString const & key,BASE_OBJECT * & object)1743 static void getIniBaseObject(WzConfig &ini, WzString const &key, BASE_OBJECT *&object)
1744 {
1745 	object = nullptr;
1746 	if (ini.contains(key + "/id"))
1747 	{
1748 		int tid = ini.value(key + "/id", -1).toInt();
1749 		int tplayer = ini.value(key + "/player", -1).toInt();
1750 		OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value(key + "/type", 0).toInt();
1751 		ASSERT_OR_RETURN(, tid >= 0 && tplayer >= 0, "Bad ID");
1752 		object = getBaseObjFromData(tid, tplayer, ttype);
1753 		ASSERT(object != nullptr, "Failed to find target");
1754 	}
1755 }
1756 
getIniStructureStats(WzConfig & ini,WzString const & key,STRUCTURE_STATS * & stats)1757 static void getIniStructureStats(WzConfig &ini, WzString const &key, STRUCTURE_STATS *&stats)
1758 {
1759 	stats = nullptr;
1760 	if (ini.contains(key))
1761 	{
1762 		WzString statName = ini.value(key).toWzString();
1763 		int tid = getStructStatFromName(statName);
1764 		ASSERT_OR_RETURN(, tid >= 0, "Target stats not found %s", statName.toUtf8().c_str());
1765 		stats = &asStructureStats[tid];
1766 	}
1767 }
1768 
getIniDroidOrder(WzConfig & ini,WzString const & key,DroidOrder & order)1769 static void getIniDroidOrder(WzConfig &ini, WzString const &key, DroidOrder &order)
1770 {
1771 	order.type = (DroidOrderType)ini.value(key + "/type", DORDER_NONE).toInt();
1772 	order.pos = ini.vector2i(key + "/pos");
1773 	order.pos2 = ini.vector2i(key + "/pos2");
1774 	order.direction = ini.value(key + "/direction").toInt();
1775 	getIniBaseObject(ini, key + "/obj", order.psObj);
1776 	getIniStructureStats(ini, key + "/stats", order.psStats);
1777 }
1778 
setIniBaseObject(nlohmann::json & json,WzString const & key,BASE_OBJECT const * object)1779 static void setIniBaseObject(nlohmann::json &json, WzString const &key, BASE_OBJECT const *object)
1780 {
1781 	if (object != nullptr && object->died <= 1)
1782 	{
1783 		const auto& keyStr = key.toStdString();
1784 		json[keyStr + "/id"] = object->id;
1785 		json[keyStr + "/player"] = object->player;
1786 		json[keyStr + "/type"] = object->type;
1787 #ifdef DEBUG
1788 		//ini.setValue(key + "/debugfunc", WzString::fromUtf8(psCurr->targetFunc));
1789 		//ini.setValue(key + "/debugline", psCurr->targetLine);
1790 #endif
1791 	}
1792 }
1793 
setIniStructureStats(nlohmann::json & jsonObj,WzString const & key,STRUCTURE_STATS const * stats)1794 static inline void setIniStructureStats(nlohmann::json &jsonObj, WzString const &key, STRUCTURE_STATS const *stats)
1795 {
1796 	if (stats != nullptr)
1797 	{
1798 		jsonObj[key.toStdString()] = stats->id;
1799 	}
1800 }
1801 
setIniDroidOrder(nlohmann::json & jsonObj,WzString const & key,DroidOrder const & order)1802 static inline void setIniDroidOrder(nlohmann::json &jsonObj, WzString const &key, DroidOrder const &order)
1803 {
1804 	const auto& keyStr = key.toStdString();
1805 	jsonObj[keyStr + "/type"] = order.type;
1806 	jsonObj[keyStr + "/pos"] = order.pos;
1807 	jsonObj[keyStr + "/pos2"] = order.pos2;
1808 	jsonObj[keyStr + "/direction"] = order.direction;
1809 	setIniBaseObject(jsonObj, key + "/obj", order.psObj);
1810 	setIniStructureStats(jsonObj, key + "/stats", order.psStats);
1811 }
1812 
allocatePlayers()1813 static void allocatePlayers()
1814 {
1815 	for (int i = 0; i < MAX_PLAYERS; i++)
1816 	{
1817 		NetPlay.players[i].ai = saveGameData.sNetPlay.players[i].ai;
1818 		NetPlay.players[i].difficulty = saveGameData.sNetPlay.players[i].difficulty;
1819 //		NetPlay.players[i].faction; // read and initialized by loadMainFile
1820 		sstrcpy(NetPlay.players[i].name, saveGameData.sNetPlay.players[i].name);
1821 		NetPlay.players[i].position = saveGameData.sNetPlay.players[i].position;
1822 		if (NetPlay.players[i].difficulty == AIDifficulty::HUMAN || (game.type == LEVEL_TYPE::CAMPAIGN && i == 0))
1823 		{
1824 			NetPlay.players[i].allocated = true;
1825 			//processDebugMappings ensures game does not start in DEBUG mode
1826 			processDebugMappings(i, false);
1827 		}
1828 		else
1829 		{
1830 			NetPlay.players[i].allocated = false;
1831 		}
1832 	}
1833 }
1834 
getPlayerNames()1835 static void getPlayerNames()
1836 {
1837 	/* Get human and AI players names */
1838 	if (saveGameVersion >= VERSION_34)
1839 	{
1840 		for (unsigned i = 0; i < MAX_PLAYERS; ++i)
1841 		{
1842 			(void)setPlayerName(i, saveGameData.sPlayerName[i]);
1843 		}
1844 	}
1845 }
1846 
1847 // -----------------------------------------------------------------------------------------
1848 // UserSaveGame ... this is true when you are loading a players save game
loadGame(const char * pGameToLoad,bool keepObjects,bool freeMem,bool UserSaveGame)1849 bool loadGame(const char *pGameToLoad, bool keepObjects, bool freeMem, bool UserSaveGame)
1850 {
1851 	ScriptMapData data;  // Only valid if generating from 'game.js'.
1852 	std::map<WzString, DROID **> droidMap;
1853 	std::map<WzString, STRUCTURE **> structMap;
1854 	char			aFileName[256];
1855 	size_t			fileExten;
1856 	UDWORD			fileSize;
1857 	char			*pFileData = nullptr;
1858 	UDWORD			player, inc, i, j;
1859 	DROID           *psCurr;
1860 	UWORD           missionScrollMinX = 0, missionScrollMinY = 0,
1861 	                missionScrollMaxX = 0, missionScrollMaxY = 0;
1862 
1863 	/* Stop the game clock */
1864 	gameTimeStop();
1865 
1866 	if ((gameType == GTYPE_SAVE_START) ||
1867 	    (gameType == GTYPE_SAVE_MIDMISSION))
1868 	{
1869 		gameTimeReset(savedGameTime);//added 14 may 98 JPS to solve kev's problem with no firing droids
1870 	}
1871 
1872 	/* Clear all the objects off the map and free up the map memory */
1873 	proj_FreeAllProjectiles();	//always clear this
1874 	if (freeMem)
1875 	{
1876 		//clear out the audio
1877 		audio_StopAll();
1878 
1879 		freeAllDroids();
1880 		freeAllStructs();
1881 		freeAllFeatures();
1882 
1883 		//clear all the messages?
1884 		releaseAllProxDisp();
1885 	}
1886 
1887 	if (!keepObjects)
1888 	{
1889 		//initialise the lists
1890 		for (player = 0; player < MAX_PLAYERS; player++)
1891 		{
1892 			apsDroidLists[player] = nullptr;
1893 			apsStructLists[player] = nullptr;
1894 			apsFeatureLists[player] = nullptr;
1895 			apsFlagPosLists[player] = nullptr;
1896 			//clear all the messages?
1897 			apsProxDisp[player] = nullptr;
1898 			apsSensorList[0] = nullptr;
1899 			apsExtractorLists[player] = nullptr;
1900 		}
1901 		apsOilList[0] = nullptr;
1902 		initFactoryNumFlag();
1903 	}
1904 
1905 	if (UserSaveGame)//always !keepObjects
1906 	{
1907 		//initialise the lists
1908 		for (player = 0; player < MAX_PLAYERS; player++)
1909 		{
1910 			apsLimboDroids[player] = nullptr;
1911 			mission.apsDroidLists[player] = nullptr;
1912 			mission.apsStructLists[player] = nullptr;
1913 			mission.apsFeatureLists[player] = nullptr;
1914 			mission.apsFlagPosLists[player] = nullptr;
1915 			mission.apsExtractorLists[player] = nullptr;
1916 		}
1917 		mission.apsOilList[0] = nullptr;
1918 		mission.apsSensorList[0] = nullptr;
1919 
1920 		// Stuff added after level load to avoid being reset or initialised during load
1921 		// always !keepObjects
1922 
1923 		if (saveGameVersion >= VERSION_33)
1924 		{
1925 			bMultiPlayer = saveGameData.multiPlayer;
1926 		}
1927 
1928 		if (saveGameVersion >= VERSION_12)
1929 		{
1930 			mission.startTime = saveGameData.missionTime;
1931 		}
1932 
1933 		//set the scroll varaibles
1934 		startX = saveGameData.ScrollMinX;
1935 		startY = saveGameData.ScrollMinY;
1936 		width = saveGameData.ScrollMaxX - saveGameData.ScrollMinX;
1937 		height = saveGameData.ScrollMaxY - saveGameData.ScrollMinY;
1938 		gameType = static_cast<GAME_TYPE>(saveGameData.GameType);
1939 
1940 		if (saveGameVersion >= VERSION_14)
1941 		{
1942 			//mission data
1943 			mission.time		=	saveGameData.missionOffTime;
1944 			mission.ETA			=	saveGameData.missionETA;
1945 			mission.homeLZ_X	=	saveGameData.missionHomeLZ_X;
1946 			mission.homeLZ_Y	=	saveGameData.missionHomeLZ_Y;
1947 			mission.playerX		=	saveGameData.missionPlayerX;
1948 			mission.playerY		=	saveGameData.missionPlayerY;
1949 			//mission data
1950 			for (player = 0; player < MAX_PLAYERS; player++)
1951 			{
1952 				aDefaultSensor[player]				= saveGameData.aDefaultSensor[player];
1953 				aDefaultECM[player]					= saveGameData.aDefaultECM[player];
1954 				aDefaultRepair[player]				= saveGameData.aDefaultRepair[player];
1955 				//check for self repair having been set
1956 				if (aDefaultRepair[player] != 0
1957 				    && asRepairStats[aDefaultRepair[player]].location == LOC_DEFAULT)
1958 				{
1959 					enableSelfRepair((UBYTE)player);
1960 				}
1961 				mission.iTranspEntryTileX[player]	= saveGameData.iTranspEntryTileX[player];
1962 				mission.iTranspEntryTileY[player]	= saveGameData.iTranspEntryTileY[player];
1963 				mission.iTranspExitTileX[player]	= saveGameData.iTranspExitTileX[player];
1964 				mission.iTranspExitTileY[player]	= saveGameData.iTranspExitTileY[player];
1965 			}
1966 		}
1967 
1968 		if (saveGameVersion >= VERSION_15)//V21
1969 		{
1970 			offWorldKeepLists	= saveGameData.offWorldKeepLists;
1971 			setRubbleTile(saveGameData.RubbleTile);
1972 			setUnderwaterTile(saveGameData.WaterTile);
1973 		}
1974 		if (saveGameVersion >= VERSION_19)//V21
1975 		{
1976 			for (i = 0; i < MAX_PLAYERS; i++)
1977 			{
1978 				for (j = 0; j < MAX_PLAYERS; j++)
1979 				{
1980 					alliances[i][j] = saveGameData.alliances[i][j];
1981 					if (i == j)
1982 					{
1983 						alliances[i][j] = ALLIANCE_FORMED;    // hack to fix old savegames
1984 					}
1985 					if (bMultiPlayer && alliancesSharedVision(game.alliance) && alliances[i][j] == ALLIANCE_FORMED)
1986 					{
1987 						alliancebits[i] |= 1 << j;
1988 					}
1989 				}
1990 			}
1991 			for (i = 0; i < MAX_PLAYERS; i++)
1992 			{
1993 				setPlayerColour(i, saveGameData.playerColour[i]);
1994 			}
1995 			SetRadarZoom(saveGameData.radarZoom);
1996 		}
1997 
1998 		if (saveGameVersion >= VERSION_20)//V21
1999 		{
2000 			setDroidsToSafetyFlag(saveGameData.bDroidsToSafetyFlag);
2001 		}
2002 
2003 		if (saveGameVersion >= VERSION_24)//V24
2004 		{
2005 			missionSetReinforcementTime(saveGameData.reinforceTime);
2006 			// horrible hack to catch savegames that were saving garbage into these fields
2007 			if (saveGameData.bPlayCountDown <= 1)
2008 			{
2009 				setPlayCountDown(saveGameData.bPlayCountDown);
2010 			}
2011 			if (saveGameData.bPlayerHasWon <= 1)
2012 			{
2013 				setPlayerHasWon(saveGameData.bPlayerHasWon);
2014 			}
2015 			if (saveGameData.bPlayerHasLost <= 1)
2016 			{
2017 				setPlayerHasLost(saveGameData.bPlayerHasLost);
2018 			}
2019 		}
2020 
2021 		//extra code added for the first patch (v1.1) to save out if mission time is not being counted
2022 		if (saveGameVersion >= VERSION_31)
2023 		{
2024 			//mission data
2025 			mission.cheatTime = saveGameData.missionCheatTime;
2026 		}
2027 
2028 		// skirmish saves.
2029 		if (saveGameVersion >= VERSION_33)
2030 		{
2031 			PLAYERSTATS		playerStats;
2032 			bool scav = game.scavengers;
2033 
2034 			game			= saveGameData.sGame;
2035 			game.scavengers = scav;	// ok, so this is butt ugly. but i'm just getting inspiration from the rest of the code around here. ok? - per
2036 			productionPlayer = selectedPlayer;
2037 			bMultiMessages	= bMultiPlayer;
2038 
2039 			NetPlay.bComms = (saveGameData.sNetPlay).bComms;
2040 
2041 			allocatePlayers();
2042 
2043 			if (bMultiPlayer)
2044 			{
2045 				loadMultiStats(saveGameData.sPName, &playerStats);				// stats stuff
2046 				setMultiStats(selectedPlayer, playerStats, false);
2047 				setMultiStats(selectedPlayer, playerStats, true);
2048 			}
2049 		}
2050 	}
2051 
2052 	getPlayerNames();
2053 
2054 	//clear the player Power structs
2055 	if ((gameType != GTYPE_SAVE_START) && (gameType != GTYPE_SAVE_MIDMISSION) &&
2056 	    (!keepObjects))
2057 	{
2058 		clearPlayerPower();
2059 	}
2060 
2061 	//before loading the data - turn power off so don't get any power low warnings
2062 	powerCalculated = false;
2063 
2064 	/* Load in the chosen file data */
2065 	sstrcpy(aFileName, pGameToLoad);
2066 	fileExten = strlen(aFileName) - 3;			// hack - !
2067 	aFileName[fileExten - 1] = '\0';
2068 	strcat(aFileName, "/");
2069 
2070 	//the terrain type WILL only change with Campaign changes (well at the moment!)
2071 	if (gameType != GTYPE_SCENARIO_EXPAND || UserSaveGame)
2072 	{
2073 		//load in the terrain type map
2074 		aFileName[fileExten] = '\0';
2075 		strcat(aFileName, "ttypes.ttp");
2076 		/* Load in the chosen file data */
2077 		pFileData = fileLoadBuffer;
2078 		if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2079 		{
2080 			debug(LOG_ERROR, "Failed with: %s", aFileName);
2081 			goto error;
2082 		}
2083 
2084 		//load the terrain type data
2085 		if (pFileData)
2086 		{
2087 			if (!loadTerrainTypeMap(pFileData, fileSize))
2088 			{
2089 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2090 				goto error;
2091 			}
2092 		}
2093 	}
2094 
2095 	//load up the Droid Templates BEFORE any structures are loaded
2096 	if (IsScenario == false)
2097 	{
2098 		if (bMultiPlayer)
2099 		{
2100 			droidTemplateShutDown();
2101 		}
2102 		else
2103 		{
2104 			clearTemplates(0);
2105 			localTemplates.clear();
2106 		}
2107 
2108 		aFileName[fileExten] = '\0';
2109 		strcat(aFileName, "templates.json");
2110 		if (!loadSaveTemplate(aFileName))
2111 		{
2112 			debug(LOG_ERROR, "Failed with: %s", aFileName);
2113 			goto error;
2114 		}
2115 	}
2116 
2117 	if (saveGameOnMission && UserSaveGame)
2118 	{
2119 		//the scroll limits for the mission map have already been written
2120 		if (saveGameVersion >= VERSION_29)
2121 		{
2122 			missionScrollMinX = (UWORD)mission.scrollMinX;
2123 			missionScrollMinY = (UWORD)mission.scrollMinY;
2124 			missionScrollMaxX = (UWORD)mission.scrollMaxX;
2125 			missionScrollMaxY = (UWORD)mission.scrollMaxY;
2126 		}
2127 
2128 		//load the map and the droids then swap pointers
2129 
2130 		//load in the map file
2131 		aFileName[fileExten] = '\0';
2132 		strcat(aFileName, "mission.map");
2133 		if (!mapLoad(aFileName, false))
2134 		{
2135 			debug(LOG_ERROR, "Failed with: %s", aFileName);
2136 			return false;
2137 		}
2138 
2139 		//load in the visibility file
2140 		aFileName[fileExten] = '\0';
2141 		strcat(aFileName, "misvis.bjo");
2142 
2143 		// Load in the visibility data from the chosen file
2144 		if (!readVisibilityData(aFileName))
2145 		{
2146 			debug(LOG_ERROR, "Failed with: %s", aFileName);
2147 			goto error;
2148 		}
2149 
2150 		// reload the objects that were in the mission list
2151 		//load in the features -do before the structures
2152 		aFileName[fileExten] = '\0';
2153 		strcat(aFileName, "mfeature.json");
2154 
2155 		//load the data into apsFeatureLists
2156 		if (!loadSaveFeature2(aFileName))
2157 		{
2158 			aFileName[fileExten] = '\0';
2159 			strcat(aFileName, "mfeat.bjo");
2160 			/* Load in the chosen file data */
2161 			pFileData = fileLoadBuffer;
2162 			if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2163 			{
2164 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2165 				goto error;
2166 			}
2167 			if (!loadSaveFeature(pFileData, fileSize))
2168 			{
2169 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2170 				goto error;
2171 			}
2172 		}
2173 
2174 		initStructLimits();
2175 		aFileName[fileExten] = '\0';
2176 		strcat(aFileName, "mstruct.json");
2177 
2178 		//load in the mission structures
2179 		if (!loadSaveStructure2(aFileName, apsStructLists))
2180 		{
2181 			aFileName[fileExten] = '\0';
2182 			strcat(aFileName, "mstruct.bjo");
2183 			/* Load in the chosen file data */
2184 			pFileData = fileLoadBuffer;
2185 			if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2186 			{
2187 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2188 				goto error;
2189 			}
2190 			//load the data into apsStructLists
2191 			if (!loadSaveStructure(pFileData, fileSize))
2192 			{
2193 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2194 				goto error;
2195 			}
2196 		}
2197 		else
2198 		{
2199 			structMap[aFileName] = mission.apsStructLists;	// we swap pointers below
2200 		}
2201 
2202 		// load in the mission droids, if any
2203 		aFileName[fileExten] = '\0';
2204 		strcat(aFileName, "mdroid.json");
2205 		if (loadSaveDroid(aFileName, apsDroidLists))
2206 		{
2207 			droidMap[aFileName] = mission.apsDroidLists; // need to swap here to read correct list later
2208 		}
2209 
2210 		/* after we've loaded in the units we need to redo the orientation because
2211 		 * the direction may have been saved - we need to do it outside of the loop
2212 		 * whilst the current map is valid for the units
2213 		 */
2214 		for (player = 0; player < MAX_PLAYERS; ++player)
2215 		{
2216 			for (psCurr = apsDroidLists[player]; psCurr != nullptr; psCurr = psCurr->psNext)
2217 			{
2218 				if (psCurr->droidType != DROID_PERSON
2219 				    // && psCurr->droidType != DROID_CYBORG
2220 				    && !cyborgDroid(psCurr)
2221 				    && (!isTransporter(psCurr))
2222 				    && psCurr->pos.x != INVALID_XY)
2223 				{
2224 					updateDroidOrientation(psCurr);
2225 				}
2226 			}
2227 		}
2228 
2229 		swapMissionPointers();
2230 
2231 		//once the mission map has been loaded reset the mission scroll limits
2232 		if (saveGameVersion >= VERSION_29)
2233 		{
2234 			mission.scrollMinX = missionScrollMinX;
2235 			mission.scrollMinY = missionScrollMinY;
2236 			mission.scrollMaxX = missionScrollMaxX;
2237 			mission.scrollMaxY = missionScrollMaxY;
2238 		}
2239 	}
2240 
2241 	//if Campaign Expand then don't load in another map
2242 	if (gameType != GTYPE_SCENARIO_EXPAND)
2243 	{
2244 		psMapTiles = nullptr;
2245 		//load in the map file
2246 		aFileName[fileExten] = '\0';
2247 		strcat(aFileName, "game.js");
2248 		bool haveScript = PHYSFS_exists(aFileName);
2249 		if (haveScript) {
2250 			data = runMapScript(aFileName, gameRandU32(), false);
2251 			syncDebug("mapSize = [%d, %d]", data.mapWidth, data.mapHeight);
2252 			syncDebug("crc(texture) = 0x%08x", crcSumU16(0, data.texture.data(), data.texture.size()));
2253 			syncDebug("crc(height) = 0x%08x", crcSumI16(0, data.height.data(), data.height.size()));
2254 			syncDebug("crc(structures) = 0x%08x", data.crcSumStructures(0));
2255 			syncDebug("crc(droids) = 0x%08x", data.crcSumDroids(0));
2256 			syncDebug("crc(features) = 0x%08x", data.crcSumFeatures(0));
2257 		}
2258 		else
2259 		{
2260 			aFileName[fileExten] = '\0';
2261 			strcat(aFileName, "game.map");
2262 		}
2263 		if (haveScript? !mapLoadFromScriptData(data, false) : !mapLoad(aFileName, false))
2264 		{
2265 			debug(LOG_ERROR, "Failed with: %s", aFileName);
2266 			return false;
2267 		}
2268 	}
2269 
2270 	// FIXME THIS FILE IS A HUGE MESS, this code should probably appear at another position...
2271 	if (saveGameVersion > VERSION_12)
2272 	{
2273 		//if user save game then load up the FX
2274 		if ((gameType == GTYPE_SAVE_START) ||
2275 		    (gameType == GTYPE_SAVE_MIDMISSION))
2276 		{
2277 			//load in the message list file
2278 			aFileName[fileExten] = '\0';
2279 			strcat(aFileName, "fxstate.json");
2280 
2281 			// load the fx data from the file
2282 			if (!readFXData(aFileName))
2283 			{
2284 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2285 				goto error;
2286 			}
2287 		}
2288 	}
2289 
2290 	//save game stuff added after map load
2291 
2292 	if (saveGameVersion >= VERSION_16)
2293 	{
2294 		for (inc = 0; inc < MAX_NOGO_AREAS; inc++)
2295 		{
2296 			setNoGoArea(saveGameData.sLandingZone[inc].x1, saveGameData.sLandingZone[inc].y1,
2297 			            saveGameData.sLandingZone[inc].x2, saveGameData.sLandingZone[inc].y2, (UBYTE)inc);
2298 		}
2299 	}
2300 
2301 	//adjust the scroll range for the new map or the expanded map
2302 	setMapScroll();
2303 
2304 	//if user save game then load up the research BEFORE any droids or structures are loaded
2305 	if (gameType == GTYPE_SAVE_START || gameType == GTYPE_SAVE_MIDMISSION)
2306 	{
2307 		//load in the research list file
2308 		aFileName[fileExten] = '\0';
2309 		strcat(aFileName, "resstate.json");
2310 		if (!loadSaveResearch(aFileName))
2311 		{
2312 			debug(LOG_ERROR, "Failed to load research data from %s", aFileName);
2313 			goto error;
2314 		}
2315 	}
2316 
2317 	if (IsScenario)
2318 	{
2319 		//load in the droids
2320 		aFileName[fileExten] = '\0';
2321 		strcat(aFileName, "droid.json");
2322 
2323 		//load the data into apsDroidLists
2324 		if (data.valid && loadScriptDroid(data))
2325 		{
2326 			// Nothing to do here.
2327 		}
2328 		else if (loadSaveDroid(aFileName, apsDroidLists))
2329 		{
2330 			debug(LOG_SAVE, "Loaded new style droids");
2331 			droidMap[aFileName] = apsDroidLists;	// load pointers later
2332 		}
2333 		else
2334 		{
2335 			// load in the old style droid initialisation file
2336 			aFileName[fileExten] = '\0';
2337 			strcat(aFileName, "dinit.bjo");
2338 			/* Load in the chosen file data */
2339 			pFileData = fileLoadBuffer;
2340 			if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2341 			{
2342 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2343 				goto error;
2344 			}
2345 			if (!loadSaveDroidInit(pFileData, fileSize))
2346 			{
2347 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2348 				goto error;
2349 			}
2350 			debug(LOG_SAVE, "Loaded old style droids");
2351 		}
2352 	}
2353 	else
2354 	{
2355 		//load in the droids
2356 		aFileName[fileExten] = '\0';
2357 		strcat(aFileName, "droid.json");
2358 
2359 		//load the data into apsDroidLists
2360 		if (!loadSaveDroid(aFileName, apsDroidLists))
2361 		{
2362 			debug(LOG_ERROR, "failed to load %s", aFileName);
2363 			goto error;
2364 		}
2365 		droidMap[aFileName] = apsDroidLists;	// load pointers later
2366 
2367 		/* after we've loaded in the units we need to redo the orientation because
2368 		 * the direction may have been saved - we need to do it outside of the loop
2369 		 * whilst the current map is valid for the units
2370 		 */
2371 		for (player = 0; player < MAX_PLAYERS; ++player)
2372 		{
2373 			for (psCurr = apsDroidLists[player]; psCurr != nullptr; psCurr = psCurr->psNext)
2374 			{
2375 				if (psCurr->droidType != DROID_PERSON
2376 				    && !cyborgDroid(psCurr)
2377 				    && (!isTransporter(psCurr))
2378 				    && psCurr->pos.x != INVALID_XY)
2379 				{
2380 					updateDroidOrientation(psCurr);
2381 				}
2382 			}
2383 		}
2384 		if (!saveGameOnMission)
2385 		{
2386 			//load in the mission droids
2387 			aFileName[fileExten] = '\0';
2388 			strcat(aFileName, "mdroid.json");
2389 
2390 			// load the data into mission.apsDroidLists, if any
2391 			if (loadSaveDroid(aFileName, mission.apsDroidLists))
2392 			{
2393 				droidMap[aFileName] = mission.apsDroidLists;
2394 			}
2395 		}
2396 	}
2397 
2398 	if (saveGameVersion >= VERSION_23)
2399 	{
2400 		// load in the limbo droids, if any
2401 		aFileName[fileExten] = '\0';
2402 		strcat(aFileName, "limbo.json");
2403 		if (loadSaveDroid(aFileName, apsLimboDroids))
2404 		{
2405 			droidMap[aFileName] = apsLimboDroids;
2406 		}
2407 	}
2408 
2409 	//load in the features -do before the structures
2410 	aFileName[fileExten] = '\0';
2411 	strcat(aFileName, "feature.json");
2412 	if (data.valid && loadScriptFeature(data))
2413 	{
2414 		// Nothing to do here.
2415 	}
2416 	else if (!loadSaveFeature2(aFileName))
2417 	{
2418 		aFileName[fileExten] = '\0';
2419 		strcat(aFileName, "feat.bjo");
2420 		/* Load in the chosen file data */
2421 		pFileData = fileLoadBuffer;
2422 		if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2423 		{
2424 			debug(LOG_ERROR, "Failed with: %s", aFileName);
2425 			goto error;
2426 		}
2427 
2428 		//load the data into apsFeatureLists
2429 		if (!loadSaveFeature(pFileData, fileSize))
2430 		{
2431 			debug(LOG_ERROR, "Failed with: %s", aFileName);
2432 			goto error;
2433 		}
2434 	}
2435 
2436 	//load in the structures
2437 	initStructLimits();
2438 	aFileName[fileExten] = '\0';
2439 	strcat(aFileName, "struct.json");
2440 	if (data.valid && loadScriptStructure(data))
2441 	{
2442 		// Nothing to do here.
2443 	}
2444 	else if (!loadSaveStructure2(aFileName, apsStructLists))
2445 	{
2446 		aFileName[fileExten] = '\0';
2447 		strcat(aFileName, "struct.bjo");
2448 		/* Load in the chosen file data */
2449 		pFileData = fileLoadBuffer;
2450 		if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
2451 		{
2452 			debug(LOG_ERROR, "Failed with: %s", aFileName);
2453 			goto error;
2454 		}
2455 		//load the data into apsStructLists
2456 		if (!loadSaveStructure(pFileData, fileSize))
2457 		{
2458 			debug(LOG_ERROR, "Failed with: %s", aFileName);
2459 			goto error;
2460 		}
2461 	}
2462 	else
2463 	{
2464 		structMap[aFileName] = apsStructLists;
2465 	}
2466 
2467 	//if user save game then load up the current level for structs and components
2468 	if (gameType == GTYPE_SAVE_START || gameType == GTYPE_SAVE_MIDMISSION)
2469 	{
2470 		//load in the component list file
2471 		aFileName[fileExten] = '\0';
2472 		strcat(aFileName, "complist.json");
2473 		if (!loadSaveCompList(aFileName))
2474 		{
2475 			debug(LOG_ERROR, "failed to load %s", aFileName);
2476 			goto error;
2477 		}
2478 		//load in the structure type list file
2479 		aFileName[fileExten] = '\0';
2480 		strcat(aFileName, "strtype.json");
2481 		if (!loadSaveStructTypeList(aFileName))
2482 		{
2483 			debug(LOG_ERROR, "failed to load %s", aFileName);
2484 			goto error;
2485 		}
2486 	}
2487 
2488 	if (saveGameVersion >= VERSION_11)
2489 	{
2490 		//if user save game then load up the Visibility
2491 		if ((gameType == GTYPE_SAVE_START) ||
2492 		    (gameType == GTYPE_SAVE_MIDMISSION))
2493 		{
2494 			//load in the visibility file
2495 			aFileName[fileExten] = '\0';
2496 			strcat(aFileName, "visstate.bjo");
2497 
2498 			// Load in the visibility data from the chosen file
2499 			if (!readVisibilityData(aFileName))
2500 			{
2501 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2502 				goto error;
2503 			}
2504 		}
2505 	}
2506 
2507 	if (saveGameVersion >= VERSION_16)
2508 	{
2509 		//if user save game then load up the FX
2510 		if ((gameType == GTYPE_SAVE_START) ||
2511 		    (gameType == GTYPE_SAVE_MIDMISSION))
2512 		{
2513 			aFileName[fileExten] = '\0';
2514 			strcat(aFileName, "score.json");
2515 
2516 			// Load the fx data from the chosen file
2517 			if (!readScoreData(aFileName))
2518 			{
2519 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2520 				goto error;
2521 			}
2522 		}
2523 	}
2524 
2525 	if (saveGameVersion >= VERSION_21)
2526 	{
2527 		//rebuild the apsCommandDesignation AFTER all droids and structures are loaded
2528 		if ((gameType == GTYPE_SAVE_START) ||
2529 		    (gameType == GTYPE_SAVE_MIDMISSION))
2530 		{
2531 			//load in the command list file
2532 			aFileName[fileExten] = '\0';
2533 			strcat(aFileName, "firesupport.json");
2534 
2535 			if (!readFiresupportDesignators(aFileName))
2536 			{
2537 				debug(LOG_ERROR, "Failed with: %s", aFileName);
2538 				goto error;
2539 			}
2540 		}
2541 	}
2542 
2543 	if ((saveGameVersion >= VERSION_15) && UserSaveGame)
2544 	{
2545 		//load in the mission structures
2546 		aFileName[fileExten] = '\0';
2547 		strcat(aFileName, "limits.json");
2548 
2549 		//load the data into apsStructLists
2550 		if (!loadSaveStructLimits(aFileName))
2551 		{
2552 			debug(LOG_ERROR, "failed to load %s", aFileName);
2553 			goto error;
2554 		}
2555 
2556 		//set up the structure Limits
2557 		setCurrentStructQuantity(false);
2558 	}
2559 	else
2560 	{
2561 		//set up the structure Limits
2562 		setCurrentStructQuantity(true);
2563 	}
2564 
2565 	//check that delivery points haven't been put down in invalid location
2566 	checkDeliveryPoints(saveGameVersion);
2567 
2568 	//turn power on for rest of game
2569 	powerCalculated = true;
2570 
2571 	if (!keepObjects)//only reset the pointers if they were set
2572 	{
2573 		// Reset the object pointers in the droid target lists
2574 		for (auto it = droidMap.begin(); it != droidMap.end(); ++it)
2575 		{
2576 			const WzString& key = it->first;
2577 			DROID **pList = it->second;
2578 			loadSaveDroidPointers(key, pList);
2579 		}
2580 		for (auto it = structMap.begin(); it != structMap.end(); ++it)
2581 		{
2582 			const WzString& key = it->first;
2583 			STRUCTURE **pList = it->second;
2584 			loadSaveStructurePointers(key, pList);
2585 		}
2586 	}
2587 
2588 	// Load labels
2589 	aFileName[fileExten] = '\0';
2590 	strcat(aFileName, "labels.json");
2591 	loadLabels(aFileName);
2592 
2593 	//if user save game then reset the time - BEWARE IF YOU USE IT
2594 	if ((gameType == GTYPE_SAVE_START) ||
2595 	    (gameType == GTYPE_SAVE_MIDMISSION))
2596 	{
2597 		ASSERT(gameTime == savedGameTime, "loadGame; game time modified during load");
2598 		gameTimeReset(savedGameTime);//added 14 may 98 JPS to solve kev's problem with no firing droids
2599 
2600 		//reset the objId for new objects
2601 		if (saveGameVersion >= VERSION_17)
2602 		{
2603 			unsynchObjID = (savedObjId + 1) / 2; // Make new object ID start at savedObjId*8.
2604 			synchObjID   = savedObjId * 4;      // Make new object ID start at savedObjId*8.
2605 		}
2606 
2607 		if (getDroidsToSafetyFlag())
2608 		{
2609 			//The droids lists are "reversed" as they are loaded in loadSaveDroid().
2610 			//Which later causes issues in saveCampaignData() which tries to extract
2611 			//the first transporter group sent off at Beta-end by reversing this very list.
2612 			reverseObjectList(&mission.apsDroidLists[selectedPlayer]);
2613 		}
2614 	}
2615 
2616 	//check the research button isn't flashing unnecessarily
2617 	//cancel first
2618 	stopReticuleButtonFlash(IDRET_RESEARCH);
2619 	//then see if needs to be set
2620 	intNotifyResearchButton(0);
2621 
2622 	//set up the mission countdown flag
2623 	setMissionCountDown();
2624 
2625 	/* Start the game clock */
2626 	gameTimeStart();
2627 
2628 	//check if limbo_expand mission has changed to an expand mission for user save game (mid-mission)
2629 	if (gameType == GTYPE_SAVE_MIDMISSION && missionLimboExpand())
2630 	{
2631 		/* when all the units have moved from the mission.apsDroidList then the
2632 		 * campaign has been reset to an EXPAND type - OK so there should have
2633 		 * been another flag to indicate this state has changed but its late in
2634 		 * the day excuses...excuses...excuses
2635 		 */
2636 		if (mission.apsDroidLists[selectedPlayer] == nullptr)
2637 		{
2638 			//set the mission type
2639 			startMissionSave(LEVEL_TYPE::LDS_EXPAND);
2640 		}
2641 	}
2642 
2643 	//set this if come into a save game mid mission
2644 	if (gameType == GTYPE_SAVE_MIDMISSION)
2645 	{
2646 		setScriptWinLoseVideo(PLAY_NONE);
2647 	}
2648 
2649 	//need to clear before setting up
2650 	clearMissionWidgets();
2651 	//put any widgets back on for the missions
2652 	resetMissionWidgets();
2653 
2654 	debug(LOG_NEVER, "Done loading");
2655 
2656 	return true;
2657 
2658 error:
2659 	debug(LOG_ERROR, "Game load failed for %s, FS:%s, params=%s,%s,%s", pGameToLoad, WZ_PHYSFS_getRealDir_String(pGameToLoad).c_str(),
2660 	      keepObjects ? "true" : "false", freeMem ? "true" : "false", UserSaveGame ? "true" : "false");
2661 
2662 	/* Clear all the objects off the map and free up the map memory */
2663 	freeAllDroids();
2664 	freeAllStructs();
2665 	freeAllFeatures();
2666 	droidTemplateShutDown();
2667 	psMapTiles = nullptr;
2668 
2669 	/* Start the game clock */
2670 	gameTimeStart();
2671 
2672 	return false;
2673 }
2674 // -----------------------------------------------------------------------------------------
2675 
saveGame(const char * aFileName,GAME_TYPE saveType)2676 bool saveGame(const char *aFileName, GAME_TYPE saveType)
2677 {
2678 	size_t			fileExtension;
2679 	DROID			*psDroid, *psNext;
2680 	char			CurrentFileName[PATH_MAX] = {'\0'};
2681 
2682 	triggerEvent(TRIGGER_GAME_SAVING);
2683 
2684 	ASSERT_OR_RETURN(false, aFileName && strlen(aFileName) > 4, "Bad savegame filename");
2685 	sstrcpy(CurrentFileName, aFileName);
2686 	debug(LOG_WZ, "saveGame: %s", CurrentFileName);
2687 
2688 	fileExtension = strlen(CurrentFileName) - 3;
2689 	gameTimeStop();
2690 	sanityUpdate();
2691 
2692 	/* Write the data to the file */
2693 	if (!writeGameFile(CurrentFileName, saveType))
2694 	{
2695 		debug(LOG_ERROR, "writeGameFile(\"%s\") failed", CurrentFileName);
2696 		goto error;
2697 	}
2698 
2699 	//remove the file extension
2700 	CurrentFileName[strlen(CurrentFileName) - 4] = '\0';
2701 
2702 	//create dir will fail if directory already exists but don't care!
2703 	(void) PHYSFS_mkdir(CurrentFileName);
2704 
2705 	writeMainFile(std::string(CurrentFileName) + "/main.json", saveType);
2706 
2707 	//save the map file
2708 	strcat(CurrentFileName, "/game.map");
2709 	/* Write the data to the file */
2710 	if (!writeMapFile(CurrentFileName))
2711 	{
2712 		debug(LOG_ERROR, "saveGame: writeMapFile(\"%s\") failed", CurrentFileName);
2713 		goto error;
2714 	}
2715 
2716 	// Save some game info
2717 	CurrentFileName[fileExtension] = '\0';
2718 	strcat(CurrentFileName, "gameinfo.json");
2719 	writeGameInfo(CurrentFileName);
2720 
2721 	// Save labels
2722 	CurrentFileName[fileExtension] = '\0';
2723 	strcat(CurrentFileName, "labels.json");
2724 	writeLabels(CurrentFileName);
2725 
2726 	//create the droids filename
2727 	CurrentFileName[fileExtension] = '\0';
2728 	strcat(CurrentFileName, "droid.json");
2729 	/*Write the current droid lists to the file*/
2730 	if (!writeDroidFile(CurrentFileName, apsDroidLists))
2731 	{
2732 		debug(LOG_ERROR, "writeDroidFile(\"%s\") failed", CurrentFileName);
2733 		goto error;
2734 	}
2735 
2736 	//create the structures filename
2737 	CurrentFileName[fileExtension] = '\0';
2738 	strcat(CurrentFileName, "struct.json");
2739 	/*Write the data to the file*/
2740 	if (!writeStructFile(CurrentFileName))
2741 	{
2742 		debug(LOG_ERROR, "saveGame: writeStructFile(\"%s\") failed", CurrentFileName);
2743 		goto error;
2744 	}
2745 
2746 	//create the templates filename
2747 	CurrentFileName[fileExtension] = '\0';
2748 	strcat(CurrentFileName, "templates.json");
2749 	/*Write the data to the file*/
2750 	if (!writeTemplateFile(CurrentFileName))
2751 	{
2752 		debug(LOG_ERROR, "saveGame: writeTemplateFile(\"%s\") failed", CurrentFileName);
2753 		goto error;
2754 	}
2755 
2756 	//create the features filename
2757 	CurrentFileName[fileExtension] = '\0';
2758 	strcat(CurrentFileName, "feature.json");
2759 	/*Write the data to the file*/
2760 	if (!writeFeatureFile(CurrentFileName))
2761 	{
2762 		debug(LOG_ERROR, "saveGame: writeFeatureFile(\"%s\") failed", CurrentFileName);
2763 		goto error;
2764 	}
2765 
2766 	//create the terrain types filename
2767 	CurrentFileName[fileExtension] = '\0';
2768 	strcat(CurrentFileName, "ttypes.ttp");
2769 	/*Write the data to the file*/
2770 	if (!writeTerrainTypeMapFile(CurrentFileName))
2771 	{
2772 		debug(LOG_ERROR, "saveGame: writeTerrainTypeMapFile(\"%s\") failed", CurrentFileName);
2773 		goto error;
2774 	}
2775 
2776 	//create the strucutLimits filename
2777 	CurrentFileName[fileExtension] = '\0';
2778 	strcat(CurrentFileName, "limits.json");
2779 	/*Write the data to the file*/
2780 	if (!writeStructLimitsFile(CurrentFileName))
2781 	{
2782 		debug(LOG_ERROR, "saveGame: writeStructLimitsFile(\"%s\") failed", CurrentFileName);
2783 		goto error;
2784 	}
2785 
2786 	//create the component lists filename
2787 	CurrentFileName[fileExtension] = '\0';
2788 	strcat(CurrentFileName, "complist.json");
2789 	/*Write the data to the file*/
2790 	if (!writeCompListFile(CurrentFileName))
2791 	{
2792 		debug(LOG_ERROR, "saveGame: writeCompListFile(\"%s\") failed", CurrentFileName);
2793 		goto error;
2794 	}
2795 	//create the structure type lists filename
2796 	CurrentFileName[fileExtension] = '\0';
2797 	strcat(CurrentFileName, "strtype.json");
2798 	/*Write the data to the file*/
2799 	if (!writeStructTypeListFile(CurrentFileName))
2800 	{
2801 		debug(LOG_ERROR, "saveGame: writeStructTypeListFile(\"%s\") failed", CurrentFileName);
2802 		goto error;
2803 	}
2804 
2805 	//create the research filename
2806 	CurrentFileName[fileExtension] = '\0';
2807 	strcat(CurrentFileName, "resstate.json");
2808 	/*Write the data to the file*/
2809 	if (!writeResearchFile(CurrentFileName))
2810 	{
2811 		debug(LOG_ERROR, "saveGame: writeResearchFile(\"%s\") failed", CurrentFileName);
2812 		goto error;
2813 	}
2814 
2815 	//create the message filename
2816 	CurrentFileName[fileExtension] = '\0';
2817 	strcat(CurrentFileName, "messtate.json");
2818 	/*Write the data to the file*/
2819 	if (!writeMessageFile(CurrentFileName))
2820 	{
2821 		debug(LOG_ERROR, "saveGame: writeMessageFile(\"%s\") failed", CurrentFileName);
2822 		goto error;
2823 	}
2824 
2825 	CurrentFileName[fileExtension] = '\0';
2826 	strcat(CurrentFileName, "visstate.bjo");
2827 	/*Write the data to the file*/
2828 	if (!writeVisibilityData(CurrentFileName))
2829 	{
2830 		debug(LOG_ERROR, "saveGame: writeVisibilityData(\"%s\") failed", CurrentFileName);
2831 		goto error;
2832 	}
2833 
2834 	CurrentFileName[fileExtension] = '\0';
2835 	strcat(CurrentFileName, "fxstate.json");
2836 	/*Write the data to the file*/
2837 	if (!writeFXData(CurrentFileName))
2838 	{
2839 		debug(LOG_ERROR, "saveGame: writeFXData(\"%s\") failed", CurrentFileName);
2840 		goto error;
2841 	}
2842 
2843 	//added at V15 save
2844 	CurrentFileName[fileExtension] = '\0';
2845 	strcat(CurrentFileName, "score.json");
2846 	/*Write the data to the file*/
2847 	if (!writeScoreData(CurrentFileName))
2848 	{
2849 		debug(LOG_ERROR, "saveGame: writeScoreData(\"%s\") failed", CurrentFileName);
2850 		goto error;
2851 	}
2852 
2853 	CurrentFileName[fileExtension] = '\0';
2854 	strcat(CurrentFileName, "firesupport.json");
2855 	/*Write the data to the file*/
2856 	if (!writeFiresupportDesignators(CurrentFileName))
2857 	{
2858 		debug(LOG_ERROR, "saveGame: writeFiresupportDesignators(\"%s\") failed", CurrentFileName);
2859 		goto error;
2860 	}
2861 
2862 	// save the script state if necessary
2863 	if (saveType == GTYPE_SAVE_MIDMISSION)
2864 	{
2865 		CurrentFileName[fileExtension] = '\0';
2866 		strcat(CurrentFileName, "scriptstate.es");
2867 		/*Write the data to the file*/
2868 		if (!writeScriptState(CurrentFileName))
2869 		{
2870 			debug(LOG_ERROR, "saveGame: writeScriptState(\"%s\") failed", CurrentFileName);
2871 			goto error;
2872 		}
2873 	}
2874 
2875 	//create the droids filename
2876 	CurrentFileName[fileExtension] = '\0';
2877 	strcat(CurrentFileName, "mdroid.json");
2878 	/*Write the swapped droid lists to the file*/
2879 	if (!writeDroidFile(CurrentFileName, mission.apsDroidLists))
2880 	{
2881 		debug(LOG_ERROR, "writeDroidFile(\"%s\") failed", CurrentFileName);
2882 		goto error;
2883 	}
2884 
2885 	//create the limbo filename
2886 	//clear the list
2887 	if (saveGameVersion < VERSION_25)
2888 	{
2889 		for (psDroid = apsLimboDroids[selectedPlayer]; psDroid != nullptr; psDroid = psNext)
2890 		{
2891 			psNext = psDroid->psNext;
2892 			//limbo list invalidate XY
2893 			psDroid->pos.x = INVALID_XY;
2894 			psDroid->pos.y = INVALID_XY;
2895 			//this is mainly for VTOLs
2896 			setSaveDroidBase(psDroid, nullptr);
2897 			orderDroid(psDroid, DORDER_STOP, ModeImmediate);
2898 		}
2899 	}
2900 
2901 	CurrentFileName[fileExtension] = '\0';
2902 	strcat(CurrentFileName, "limbo.json");
2903 	/*Write the swapped droid lists to the file*/
2904 	if (!writeDroidFile(CurrentFileName, apsLimboDroids))
2905 	{
2906 		debug(LOG_ERROR, "saveGame: writeDroidFile(\"%s\") failed", CurrentFileName);
2907 		goto error;
2908 	}
2909 
2910 	if (saveGameOnMission)
2911 	{
2912 		//mission save swap the mission pointers and save the changes
2913 		swapMissionPointers();
2914 		//now save the map and droids
2915 
2916 		//save the map file
2917 		CurrentFileName[fileExtension] = '\0';
2918 		strcat(CurrentFileName, "mission.map");
2919 		/* Write the data to the file */
2920 		if (!writeMapFile(CurrentFileName))
2921 		{
2922 			debug(LOG_ERROR, "saveGame: writeMapFile(\"%s\") failed", CurrentFileName);
2923 			goto error;
2924 		}
2925 
2926 		//save the map file
2927 		CurrentFileName[fileExtension] = '\0';
2928 		strcat(CurrentFileName, "misvis.bjo");
2929 		/* Write the data to the file */
2930 		if (!writeVisibilityData(CurrentFileName))
2931 		{
2932 			debug(LOG_ERROR, "saveGame: writeVisibilityData(\"%s\") failed", CurrentFileName);
2933 			goto error;
2934 		}
2935 
2936 		//create the structures filename
2937 		CurrentFileName[fileExtension] = '\0';
2938 		strcat(CurrentFileName, "mstruct.json");
2939 		/*Write the data to the file*/
2940 		if (!writeStructFile(CurrentFileName))
2941 		{
2942 			debug(LOG_ERROR, "saveGame: writeStructFile(\"%s\") failed", CurrentFileName);
2943 			goto error;
2944 		}
2945 
2946 		//create the features filename
2947 		CurrentFileName[fileExtension] = '\0';
2948 		strcat(CurrentFileName, "mfeature.json");
2949 		/*Write the data to the file*/
2950 		if (!writeFeatureFile(CurrentFileName))
2951 		{
2952 			debug(LOG_ERROR, "saveGame: writeFeatureFile(\"%s\") failed", CurrentFileName);
2953 			goto error;
2954 		}
2955 
2956 		//mission save swap back so we can restart the game
2957 		swapMissionPointers();
2958 	}
2959 
2960 	// strip the last filename
2961 	CurrentFileName[fileExtension - 1] = '\0';
2962 
2963 	/* Start the game clock */
2964 	triggerEvent(TRIGGER_GAME_SAVED);
2965 	gameTimeStart();
2966 	return true;
2967 
2968 error:
2969 	/* Start the game clock */
2970 	gameTimeStart();
2971 
2972 	return false;
2973 }
2974 
2975 // -----------------------------------------------------------------------------------------
writeMapFile(const char * fileName)2976 static bool writeMapFile(const char *fileName)
2977 {
2978 	char *pFileData = nullptr;
2979 	UDWORD fileSize;
2980 
2981 	/* Get the save data */
2982 	bool status = mapSave(&pFileData, &fileSize);
2983 
2984 	if (status)
2985 	{
2986 		/* Write the data to the file */
2987 		status = saveFile(fileName, pFileData, fileSize);
2988 	}
2989 
2990 	if (pFileData != nullptr)
2991 	{
2992 		free(pFileData);
2993 	}
2994 
2995 	return status;
2996 }
2997 
2998 // -----------------------------------------------------------------------------------------
gameLoad(const char * fileName)2999 static bool gameLoad(const char *fileName)
3000 {
3001 	char CurrentFileName[PATH_MAX];
3002 	strcpy(CurrentFileName, fileName);
3003 	GAME_SAVEHEADER fileHeader;
3004 
3005 	PHYSFS_file *fileHandle = openLoadFile(fileName, true);
3006 	if (!fileHandle)
3007 	{
3008 		// Failure to open the file is a failure to load the specified savegame
3009 		return false;
3010 	}
3011 	debug(LOG_WZ, "gameLoad");
3012 
3013 	// Read the header from the file
3014 	if (!deserializeSaveGameHeader(fileHandle, &fileHeader))
3015 	{
3016 		debug(LOG_ERROR, "gameLoad: error while reading header from file (%s): %s", fileName, WZ_PHYSFS_getLastError());
3017 		PHYSFS_close(fileHandle);
3018 		return false;
3019 	}
3020 
3021 	// Check the header to see if we've been given a file of the right type
3022 	if (fileHeader.aFileType[0] != 'g'
3023 	    || fileHeader.aFileType[1] != 'a'
3024 	    || fileHeader.aFileType[2] != 'm'
3025 	    || fileHeader.aFileType[3] != 'e')
3026 	{
3027 		debug(LOG_ERROR, "gameLoad: Weird file type found? Has header letters - '%c' '%c' '%c' '%c' (should be 'g' 'a' 'm' 'e')",
3028 		      fileHeader.aFileType[0],
3029 		      fileHeader.aFileType[1],
3030 		      fileHeader.aFileType[2],
3031 		      fileHeader.aFileType[3]);
3032 
3033 		PHYSFS_close(fileHandle);
3034 
3035 		return false;
3036 	}
3037 
3038 	debug(LOG_NEVER, "gl .gam file is version %u\n", fileHeader.version);
3039 
3040 	// Prior to getting here, the directory structure has been set to wherever the
3041 	// map or savegame is loaded from, so we will get the right ruleset file.
3042 	if (!PHYSFS_exists("ruleset.json"))
3043 	{
3044 		debug(LOG_ERROR, "ruleset.json not found! User generated data will not work.");
3045 		memset(rulesettag, 0, sizeof(rulesettag));
3046 	}
3047 	else
3048 	{
3049 		WzConfig ruleset(WzString::fromUtf8("ruleset.json"), WzConfig::ReadOnly);
3050 		if (!ruleset.contains("tag"))
3051 		{
3052 			debug(LOG_ERROR, "ruleset tag not found in ruleset.json!"); // fall-through
3053 		}
3054 		WzString tag = ruleset.value("tag", "[]").toWzString();
3055 		sstrcpy(rulesettag, tag.toUtf8().c_str());
3056 		if (strspn(rulesettag, "abcdefghijklmnopqrstuvwxyz") != strlen(rulesettag)) // for safety
3057 		{
3058 			debug(LOG_ERROR, "ruleset.json userdata tag contains invalid characters!");
3059 			debug(LOG_ERROR, "User generated data will not work.");
3060 			memset(rulesettag, 0, sizeof(rulesettag));
3061 		}
3062 	}
3063 
3064 	//set main version Id from game file
3065 	saveGameVersion = fileHeader.version;
3066 	debug(LOG_SAVE, "file version is %u, (%s)", fileHeader.version, fileName);
3067 	/* Check the file version */
3068 	if (fileHeader.version < VERSION_7)
3069 	{
3070 		debug(LOG_ERROR, "gameLoad: unsupported save format version %d", fileHeader.version);
3071 		PHYSFS_close(fileHandle);
3072 
3073 		return false;
3074 	}
3075 	else if (fileHeader.version < VERSION_9)
3076 	{
3077 		bool retVal = gameLoadV7(fileHandle);
3078 		PHYSFS_close(fileHandle);
3079 		return retVal;
3080 	}
3081 	else if (fileHeader.version <= CURRENT_VERSION_NUM)
3082 	{
3083 		//The in-game load menu was clearing level data AFTER loading in some savegame data.
3084 		//Hacking this in here so things make a little more sense and so that data loaded
3085 		//in from main.json is somewhat safe from being reset by mistake.
3086 		if (!levReleaseAll())
3087 		{
3088 			debug(LOG_ERROR, "Failed to unload old data. Attempting to load anyway");
3089 		}
3090 
3091 		//remove the file extension
3092 		CurrentFileName[strlen(CurrentFileName) - 4] = '\0';
3093 		loadMainFile(std::string(CurrentFileName) + "/main.json");
3094 
3095 		bool retVal = gameLoadV(fileHandle, fileHeader.version);
3096 		PHYSFS_close(fileHandle);
3097 
3098 		loadMainFileFinal(std::string(CurrentFileName) + "/main.json");
3099 
3100 		challengeFileName = "";
3101 		return retVal;
3102 	}
3103 	else
3104 	{
3105 		debug(LOG_ERROR, "Unsupported main save format version %u", fileHeader.version);
3106 		PHYSFS_close(fileHandle);
3107 
3108 		return false;
3109 	}
3110 }
3111 
3112 // Fix endianness of a savegame
endian_SaveGameV(SAVE_GAME * psSaveGame,UDWORD version)3113 static void endian_SaveGameV(SAVE_GAME *psSaveGame, UDWORD version)
3114 {
3115 	unsigned int i;
3116 	/* SAVE_GAME is GAME_SAVE_V33 */
3117 	/* GAME_SAVE_V33 includes GAME_SAVE_V31 */
3118 	if (version >= VERSION_33)
3119 	{
3120 		endian_udword(&psSaveGame->sGame.power);
3121 		endian_udword(&psSaveGame->sNetPlay.playercount);
3122 		endian_udword(&psSaveGame->savePlayer);
3123 		for (i = 0; i < MAX_PLAYERS; i++)
3124 		{
3125 			endian_udword(&psSaveGame->sPlayerIndex[i]);
3126 		}
3127 	}
3128 	/* GAME_SAVE_V31 includes GAME_SAVE_V30 */
3129 	if (version >= VERSION_31)
3130 	{
3131 		endian_sdword(&psSaveGame->missionCheatTime);
3132 	}
3133 	/* GAME_SAVE_V30 includes GAME_SAVE_V29 */
3134 	if (version >= VERSION_30)
3135 	{
3136 		endian_sdword(&psSaveGame->scrGameLevel);
3137 	}
3138 	/* GAME_SAVE_V29 includes GAME_SAVE_V27 */
3139 	if (version >= VERSION_29)
3140 	{
3141 		endian_uword(&psSaveGame->missionScrollMinX);
3142 		endian_uword(&psSaveGame->missionScrollMinY);
3143 		endian_uword(&psSaveGame->missionScrollMaxX);
3144 		endian_uword(&psSaveGame->missionScrollMaxY);
3145 	}
3146 	/* GAME_SAVE_V24 includes GAME_SAVE_V22 */
3147 	if (version >= VERSION_24)
3148 	{
3149 		endian_udword(&psSaveGame->reinforceTime);
3150 	}
3151 	/* GAME_SAVE_V19 includes GAME_SAVE_V18 */
3152 	if (version >= VERSION_19)
3153 	{
3154 	}
3155 	/* GAME_SAVE_V18 includes GAME_SAVE_V17 */
3156 	if (version >= VERSION_18)
3157 	{
3158 		endian_udword(&psSaveGame->oldestVersion);
3159 		endian_udword(&psSaveGame->validityKey);
3160 	}
3161 	/* GAME_SAVE_V17 includes GAME_SAVE_V16 */
3162 	if (version >= VERSION_17)
3163 	{
3164 		endian_udword(&psSaveGame->objId);
3165 	}
3166 	/* GAME_SAVE_V16 includes GAME_SAVE_V15 */
3167 	if (version >= VERSION_16)
3168 	{
3169 	}
3170 	/* GAME_SAVE_V15 includes GAME_SAVE_V14 */
3171 	if (version >= VERSION_15)
3172 	{
3173 		endian_udword(&psSaveGame->RubbleTile);
3174 		endian_udword(&psSaveGame->WaterTile);
3175 	}
3176 	/* GAME_SAVE_V14 includes GAME_SAVE_V12 */
3177 	if (version >= VERSION_14)
3178 	{
3179 		endian_sdword(&psSaveGame->missionOffTime);
3180 		endian_sdword(&psSaveGame->missionETA);
3181 		endian_uword(&psSaveGame->missionHomeLZ_X);
3182 		endian_uword(&psSaveGame->missionHomeLZ_Y);
3183 		endian_sdword(&psSaveGame->missionPlayerX);
3184 		endian_sdword(&psSaveGame->missionPlayerY);
3185 		for (i = 0; i < MAX_PLAYERS; i++)
3186 		{
3187 			endian_uword(&psSaveGame->iTranspEntryTileX[i]);
3188 			endian_uword(&psSaveGame->iTranspEntryTileY[i]);
3189 			endian_uword(&psSaveGame->iTranspExitTileX[i]);
3190 			endian_uword(&psSaveGame->iTranspExitTileY[i]);
3191 			endian_udword(&psSaveGame->aDefaultSensor[i]);
3192 			endian_udword(&psSaveGame->aDefaultECM[i]);
3193 			endian_udword(&psSaveGame->aDefaultRepair[i]);
3194 		}
3195 	}
3196 	/* GAME_SAVE_V12 includes GAME_SAVE_V11 */
3197 	if (version >= VERSION_12)
3198 	{
3199 		endian_udword(&psSaveGame->missionTime);
3200 		endian_udword(&psSaveGame->saveKey);
3201 	}
3202 	/* GAME_SAVE_V11 includes GAME_SAVE_V10 */
3203 	if (version >= VERSION_11)
3204 	{
3205 		endian_sdword(&psSaveGame->currentPlayerPos.p.x);
3206 		endian_sdword(&psSaveGame->currentPlayerPos.p.y);
3207 		endian_sdword(&psSaveGame->currentPlayerPos.p.z);
3208 		endian_sdword(&psSaveGame->currentPlayerPos.r.x);
3209 		endian_sdword(&psSaveGame->currentPlayerPos.r.y);
3210 		endian_sdword(&psSaveGame->currentPlayerPos.r.z);
3211 	}
3212 	/* GAME_SAVE_V10 includes GAME_SAVE_V7 */
3213 	if (version >= VERSION_10)
3214 	{
3215 		for (i = 0; i < MAX_PLAYERS; i++)
3216 		{
3217 			endian_udword(&psSaveGame->power[i].currentPower);
3218 			endian_udword(&psSaveGame->power[i].extractedPower);
3219 		}
3220 	}
3221 	/* GAME_SAVE_V7 */
3222 	if (version >= VERSION_7)
3223 	{
3224 		endian_udword(&psSaveGame->gameTime);
3225 		endian_udword(&psSaveGame->GameType);
3226 		endian_sdword(&psSaveGame->ScrollMinX);
3227 		endian_sdword(&psSaveGame->ScrollMinY);
3228 		endian_udword(&psSaveGame->ScrollMaxX);
3229 		endian_udword(&psSaveGame->ScrollMaxY);
3230 	}
3231 }
3232 
3233 // -----------------------------------------------------------------------------------------
3234 // Get campaign number stuff is not needed in this form on the PSX (thank you very much)
getCampaignV(PHYSFS_file * fileHandle,unsigned int version)3235 static UDWORD getCampaignV(PHYSFS_file *fileHandle, unsigned int version)
3236 {
3237 	SAVE_GAME_V14 saveGame;
3238 
3239 	debug(LOG_SAVE, "getCampaignV: version = %u", version);
3240 
3241 	if (version < VERSION_14)
3242 	{
3243 		return 0;
3244 	}
3245 	// We only need VERSION 12 data (saveGame.saveKey)
3246 	else if (version <= VERSION_34)
3247 	{
3248 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGame, sizeof(SAVE_GAME_V14)) != sizeof(SAVE_GAME_V14))
3249 		{
3250 			debug(LOG_ERROR, "getCampaignV: error while reading file: %s", WZ_PHYSFS_getLastError());
3251 
3252 			return 0;
3253 		}
3254 
3255 		// Convert from little-endian to native byte-order
3256 		endian_SaveGameV((SAVE_GAME *)&saveGame, VERSION_14);
3257 	}
3258 	else if (version <= CURRENT_VERSION_NUM)
3259 	{
3260 		if (!deserializeSaveGameV14Data(fileHandle, &saveGame))
3261 		{
3262 			debug(LOG_ERROR, "getCampaignV: error while reading file: %s", WZ_PHYSFS_getLastError());
3263 
3264 			return 0;
3265 		}
3266 	}
3267 	else
3268 	{
3269 		debug(LOG_ERROR, "Bad savegame version %u", version);
3270 		return 0;
3271 	}
3272 
3273 	return saveGame.saveKey & (SAVEKEY_ONMISSION - 1);
3274 }
3275 
3276 // -----------------------------------------------------------------------------------------
3277 // Returns the campaign number  --- apparently this is does alot less than it look like
3278 /// it now does even less than it looks like on the psx ... cause its pc only
getCampaign(const char * fileName)3279 UDWORD getCampaign(const char *fileName)
3280 {
3281 	GAME_SAVEHEADER fileHeader;
3282 
3283 	PHYSFS_file *fileHandle = openLoadFile(fileName, true);
3284 	if (!fileHandle)
3285 	{
3286 		// Failure to open the file is a failure to load the specified savegame
3287 		return false;
3288 	}
3289 
3290 	debug(LOG_WZ, "getCampaign: %s", fileName);
3291 
3292 	// Read the header from the file
3293 	if (!deserializeSaveGameHeader(fileHandle, &fileHeader))
3294 	{
3295 		debug(LOG_ERROR, "getCampaign: error while reading header from file (%s): %s", fileName, WZ_PHYSFS_getLastError());
3296 		PHYSFS_close(fileHandle);
3297 		return false;
3298 	}
3299 
3300 	// Check the header to see if we've been given a file of the right type
3301 	if (fileHeader.aFileType[0] != 'g'
3302 	    || fileHeader.aFileType[1] != 'a'
3303 	    || fileHeader.aFileType[2] != 'm'
3304 	    || fileHeader.aFileType[3] != 'e')
3305 	{
3306 		debug(LOG_ERROR, "getCampaign: Weird file type found? Has header letters - '%c' '%c' '%c' '%c' (should be 'g' 'a' 'm' 'e')",
3307 		      fileHeader.aFileType[0],
3308 		      fileHeader.aFileType[1],
3309 		      fileHeader.aFileType[2],
3310 		      fileHeader.aFileType[3]);
3311 
3312 		PHYSFS_close(fileHandle);
3313 
3314 		return false;
3315 	}
3316 
3317 	debug(LOG_NEVER, "gl .gam file is version %d\n", fileHeader.version);
3318 
3319 	//set main version Id from game file
3320 	saveGameVersion = fileHeader.version;
3321 
3322 	debug(LOG_SAVE, "fileversion is %u, (%s) ", fileHeader.version, fileName);
3323 	/* Check the file version */
3324 	if (fileHeader.version < VERSION_14)
3325 	{
3326 		PHYSFS_close(fileHandle);
3327 		return 0;
3328 	}
3329 
3330 	// what the arse bollocks is this
3331 	// the campaign number is fine prior to saving
3332 	// you save it out in a skirmish save and
3333 	// then don't bother putting it back in again
3334 	// when loading so it screws loads of stuff?!?
3335 	// don't check skirmish saves.
3336 	if (fileHeader.version <= CURRENT_VERSION_NUM)
3337 	{
3338 		UDWORD retVal = getCampaignV(fileHandle, fileHeader.version);
3339 		PHYSFS_close(fileHandle);
3340 		return retVal;
3341 	}
3342 	else
3343 	{
3344 		debug(LOG_ERROR, "getCampaign: undefined save format version %d", fileHeader.version);
3345 		PHYSFS_close(fileHandle);
3346 
3347 		return 0;
3348 	}
3349 }
3350 
3351 // -----------------------------------------------------------------------------------------
3352 /* code specific to version 7 of a save game */
gameLoadV7(PHYSFS_file * fileHandle)3353 bool gameLoadV7(PHYSFS_file *fileHandle)
3354 {
3355 	SAVE_GAME_V7 saveGame;
3356 
3357 	if (WZ_PHYSFS_readBytes(fileHandle, &saveGame, sizeof(saveGame)) != sizeof(saveGame))
3358 	{
3359 		debug(LOG_ERROR, "gameLoadV7: error while reading file: %s", WZ_PHYSFS_getLastError());
3360 
3361 		return false;
3362 	}
3363 
3364 	/* GAME_SAVE_V7 */
3365 	endian_udword(&saveGame.gameTime);
3366 	endian_udword(&saveGame.GameType);
3367 	endian_sdword(&saveGame.ScrollMinX);
3368 	endian_sdword(&saveGame.ScrollMinY);
3369 	endian_udword(&saveGame.ScrollMaxX);
3370 	endian_udword(&saveGame.ScrollMaxY);
3371 
3372 	savedGameTime = saveGame.gameTime;
3373 
3374 	//set the scroll varaibles
3375 	startX = saveGame.ScrollMinX;
3376 	startY = saveGame.ScrollMinY;
3377 	width = saveGame.ScrollMaxX - saveGame.ScrollMinX;
3378 	height = saveGame.ScrollMaxY - saveGame.ScrollMinY;
3379 	gameType = static_cast<GAME_TYPE>(saveGame.GameType);
3380 	//set IsScenario to true if not a user saved game
3381 	if (gameType == GTYPE_SAVE_START)
3382 	{
3383 		LEVEL_DATASET *psNewLevel;
3384 
3385 		IsScenario = false;
3386 		//copy the level name across
3387 		sstrcpy(aLevelName, saveGame.levelName);
3388 		//load up the level dataset
3389 		if (!levLoadData(aLevelName, nullptr, saveGameName, (GAME_TYPE)gameType))
3390 		{
3391 			return false;
3392 		}
3393 		// find the level dataset
3394 		psNewLevel = levFindDataSet(aLevelName);
3395 		if (psNewLevel == nullptr)
3396 		{
3397 			debug(LOG_ERROR, "gameLoadV7: couldn't find level data");
3398 
3399 			return false;
3400 		}
3401 		//check to see whether mission automatically starts
3402 		//shouldn't be able to be any other value at the moment!
3403 		if (psNewLevel->type == LEVEL_TYPE::LDS_CAMSTART
3404 		    || psNewLevel->type == LEVEL_TYPE::LDS_BETWEEN
3405 		    || psNewLevel->type == LEVEL_TYPE::LDS_EXPAND
3406 		    || psNewLevel->type == LEVEL_TYPE::LDS_EXPAND_LIMBO)
3407 		{
3408 			launchMission();
3409 		}
3410 
3411 	}
3412 	else
3413 	{
3414 		IsScenario = true;
3415 	}
3416 
3417 	return true;
3418 }
3419 
3420 // -----------------------------------------------------------------------------------------
3421 /* non specific version of a save game */
gameLoadV(PHYSFS_file * fileHandle,unsigned int version)3422 bool gameLoadV(PHYSFS_file *fileHandle, unsigned int version)
3423 {
3424 	unsigned int i, j;
3425 	static	SAVE_POWER	powerSaved[MAX_PLAYERS];
3426 	UDWORD			player;
3427 
3428 	debug(LOG_WZ, "gameLoadV: version %u", version);
3429 
3430 	// Version 7 and earlier are loaded separately in gameLoadV7
3431 
3432 	//size is now variable so only check old save games
3433 	if (version <= VERSION_10)
3434 	{
3435 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V10)) != sizeof(SAVE_GAME_V10))
3436 		{
3437 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3438 
3439 			return false;
3440 		}
3441 	}
3442 	else if (version == VERSION_11)
3443 	{
3444 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V11)) != sizeof(SAVE_GAME_V11))
3445 		{
3446 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3447 
3448 			return false;
3449 		}
3450 	}
3451 	else if (version <= VERSION_12)
3452 	{
3453 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V12)) != sizeof(SAVE_GAME_V12))
3454 		{
3455 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3456 
3457 			return false;
3458 		}
3459 	}
3460 	else if (version <= VERSION_14)
3461 	{
3462 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V14)) != sizeof(SAVE_GAME_V14))
3463 		{
3464 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3465 
3466 			return false;
3467 		}
3468 	}
3469 	else if (version <= VERSION_15)
3470 	{
3471 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V15)) != sizeof(SAVE_GAME_V15))
3472 		{
3473 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3474 
3475 			return false;
3476 		}
3477 	}
3478 	else if (version <= VERSION_16)
3479 	{
3480 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V16)) != sizeof(SAVE_GAME_V16))
3481 		{
3482 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3483 
3484 			return false;
3485 		}
3486 	}
3487 	else if (version <= VERSION_17)
3488 	{
3489 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V17)) != sizeof(SAVE_GAME_V17))
3490 		{
3491 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3492 
3493 			return false;
3494 		}
3495 	}
3496 	else if (version <= VERSION_18)
3497 	{
3498 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V18)) != sizeof(SAVE_GAME_V18))
3499 		{
3500 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3501 
3502 			return false;
3503 		}
3504 	}
3505 	else if (version <= VERSION_19)
3506 	{
3507 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V19)) != sizeof(SAVE_GAME_V19))
3508 		{
3509 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3510 
3511 			return false;
3512 		}
3513 	}
3514 	else if (version <= VERSION_21)
3515 	{
3516 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V20)) != sizeof(SAVE_GAME_V20))
3517 		{
3518 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3519 
3520 			return false;
3521 		}
3522 	}
3523 	else if (version <= VERSION_23)
3524 	{
3525 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V22)) != sizeof(SAVE_GAME_V22))
3526 		{
3527 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3528 
3529 			return false;
3530 		}
3531 	}
3532 	else if (version <= VERSION_26)
3533 	{
3534 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V24)) != sizeof(SAVE_GAME_V24))
3535 		{
3536 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3537 
3538 			return false;
3539 		}
3540 	}
3541 	else if (version <= VERSION_28)
3542 	{
3543 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V27)) != sizeof(SAVE_GAME_V27))
3544 		{
3545 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3546 
3547 			return false;
3548 		}
3549 	}
3550 	else if (version <= VERSION_29)
3551 	{
3552 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V29)) != sizeof(SAVE_GAME_V29))
3553 		{
3554 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3555 
3556 			return false;
3557 		}
3558 	}
3559 	else if (version <= VERSION_30)
3560 	{
3561 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V30)) != sizeof(SAVE_GAME_V30))
3562 		{
3563 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3564 
3565 			return false;
3566 		}
3567 	}
3568 	else if (version <= VERSION_32)
3569 	{
3570 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V31)) != sizeof(SAVE_GAME_V31))
3571 		{
3572 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3573 
3574 			return false;
3575 		}
3576 	}
3577 	else if (version <= VERSION_33)
3578 	{
3579 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V33)) != sizeof(SAVE_GAME_V33))
3580 		{
3581 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3582 
3583 			return false;
3584 		}
3585 	}
3586 	else if (version <= VERSION_34)
3587 	{
3588 		if (WZ_PHYSFS_readBytes(fileHandle, &saveGameData, sizeof(SAVE_GAME_V34)) != sizeof(SAVE_GAME_V34))
3589 		{
3590 			debug(LOG_ERROR, "gameLoadV: error while reading file (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3591 
3592 			return false;
3593 		}
3594 	}
3595 	else if (version < VERSION_39)
3596 	{
3597 		debug(LOG_ERROR, "Unsupported savegame version");
3598 		return false;
3599 	}
3600 	else if (version <= CURRENT_VERSION_NUM)
3601 	{
3602 		if (!deserializeSaveGameData(fileHandle, &saveGameData))
3603 		{
3604 			debug(LOG_ERROR, "gameLoadV: error while reading data from file for deserialization (with version number %u): %s", version, WZ_PHYSFS_getLastError());
3605 
3606 			return false;
3607 		}
3608 	}
3609 	else
3610 	{
3611 		debug(LOG_ERROR, "Unsupported version number (%u) for savegame", version);
3612 
3613 		return false;
3614 	}
3615 
3616 	debug(LOG_SAVE, "Savegame is of type: %u", static_cast<uint8_t>(saveGameData.sGame.type));
3617 	game.type = saveGameData.sGame.type;
3618 	/* Test mod list */
3619 	if (version >= VERSION_38)
3620 	{
3621 		setOverrideMods(saveGameData.modList);
3622 	}
3623 
3624 	// All savegames from version 34 or before are little endian so swap them. All
3625 	// from version 35, and onward, are already swapped to the native byte-order
3626 	// by the (de)serialization API
3627 	if (version <= VERSION_34)
3628 	{
3629 		endian_SaveGameV(&saveGameData, version);
3630 	}
3631 
3632 	savedGameTime = saveGameData.gameTime;
3633 
3634 	if (version >= VERSION_12)
3635 	{
3636 		mission.startTime = saveGameData.missionTime;
3637 		if (saveGameData.saveKey & SAVEKEY_ONMISSION)
3638 		{
3639 			saveGameOnMission = true;
3640 		}
3641 		else
3642 		{
3643 			saveGameOnMission = false;
3644 		}
3645 
3646 	}
3647 	else
3648 	{
3649 		saveGameOnMission = false;
3650 	}
3651 	//set the scroll varaibles
3652 	startX = saveGameData.ScrollMinX;
3653 	startY = saveGameData.ScrollMinY;
3654 	width = saveGameData.ScrollMaxX - saveGameData.ScrollMinX;
3655 	height = saveGameData.ScrollMaxY - saveGameData.ScrollMinY;
3656 	gameType = static_cast<GAME_TYPE>(saveGameData.GameType);
3657 
3658 	if (version >= VERSION_11)
3659 	{
3660 		//camera position
3661 		disp3d_setView(&saveGameData.currentPlayerPos);
3662 	}
3663 	else
3664 	{
3665 		disp3d_oldView();
3666 	}
3667 
3668 	//load mission data from save game these values reloaded after load game
3669 
3670 	if (version >= VERSION_14)
3671 	{
3672 		//mission data
3673 		mission.time     = saveGameData.missionOffTime;
3674 		mission.ETA      = saveGameData.missionETA;
3675 		mission.homeLZ_X = saveGameData.missionHomeLZ_X;
3676 		mission.homeLZ_Y = saveGameData.missionHomeLZ_Y;
3677 		mission.playerX  = saveGameData.missionPlayerX;
3678 		mission.playerY  = saveGameData.missionPlayerY;
3679 
3680 		for (player = 0; player < MAX_PLAYERS; player++)
3681 		{
3682 			mission.iTranspEntryTileX[player]	= saveGameData.iTranspEntryTileX[player];
3683 			mission.iTranspEntryTileY[player]	= saveGameData.iTranspEntryTileY[player];
3684 			mission.iTranspExitTileX[player]	= saveGameData.iTranspExitTileX[player];
3685 			mission.iTranspExitTileY[player]	= saveGameData.iTranspExitTileY[player];
3686 			aDefaultSensor[player]				= saveGameData.aDefaultSensor[player];
3687 			aDefaultECM[player]					= saveGameData.aDefaultECM[player];
3688 			aDefaultRepair[player]				= saveGameData.aDefaultRepair[player];
3689 		}
3690 	}
3691 
3692 	if (version >= VERSION_15)
3693 	{
3694 		offWorldKeepLists	= saveGameData.offWorldKeepLists;
3695 		setRubbleTile(saveGameData.RubbleTile);
3696 		setUnderwaterTile(saveGameData.WaterTile);
3697 	}
3698 
3699 	if (version >= VERSION_17)
3700 	{
3701 		unsynchObjID = (saveGameData.objId + 1) / 2; // Make new object ID start at savedObjId*8.
3702 		synchObjID   = saveGameData.objId * 4;      // Make new object ID start at savedObjId*8.
3703 		savedObjId = saveGameData.objId;
3704 	}
3705 
3706 	if (version >= VERSION_19)//version 19
3707 	{
3708 		for (i = 0; i < MAX_PLAYERS; i++)
3709 		{
3710 			for (j = 0; j < MAX_PLAYERS; j++)
3711 			{
3712 				alliances[i][j] = saveGameData.alliances[i][j];
3713 			}
3714 		}
3715 		for (i = 0; i < MAX_PLAYERS; i++)
3716 		{
3717 			setPlayerColour(i, saveGameData.playerColour[i]);
3718 		}
3719 	}
3720 
3721 	if (version >= VERSION_20)//version 20
3722 	{
3723 		setDroidsToSafetyFlag(saveGameData.bDroidsToSafetyFlag);
3724 	}
3725 
3726 	if (saveGameVersion >= VERSION_24)//V24
3727 	{
3728 		missionSetReinforcementTime(saveGameData.reinforceTime);
3729 
3730 		// horrible hack to catch savegames that were saving garbage into these fields
3731 		if (saveGameData.bPlayCountDown <= 1)
3732 		{
3733 			setPlayCountDown(saveGameData.bPlayCountDown);
3734 		}
3735 		if (saveGameData.bPlayerHasWon <= 1)
3736 		{
3737 			setPlayerHasWon(saveGameData.bPlayerHasWon);
3738 		}
3739 		if (saveGameData.bPlayerHasLost <= 1)
3740 		{
3741 			setPlayerHasLost(saveGameData.bPlayerHasLost);
3742 		}
3743 	}
3744 
3745 	if (saveGameVersion >= VERSION_29)
3746 	{
3747 		mission.scrollMinX = saveGameData.missionScrollMinX;
3748 		mission.scrollMinY = saveGameData.missionScrollMinY;
3749 		mission.scrollMaxX = saveGameData.missionScrollMaxX;
3750 		mission.scrollMaxY = saveGameData.missionScrollMaxY;
3751 	}
3752 
3753 	if (saveGameVersion >= VERSION_31)
3754 	{
3755 		mission.cheatTime = saveGameData.missionCheatTime;
3756 	}
3757 
3758 	droidInit();
3759 
3760 	//set IsScenario to true if not a user saved game
3761 	if ((gameType == GTYPE_SAVE_START) ||
3762 	    (gameType == GTYPE_SAVE_MIDMISSION))
3763 	{
3764 		for (i = 0; i < MAX_PLAYERS; ++i)
3765 		{
3766 			powerSaved[i].currentPower = saveGameData.power[i].currentPower;
3767 			powerSaved[i].extractedPower = saveGameData.power[i].extractedPower;
3768 		}
3769 
3770 		allocatePlayers();
3771 
3772 		IsScenario = false;
3773 		//copy the level name across
3774 		sstrcpy(aLevelName, saveGameData.levelName);
3775 		//load up the level dataset
3776 		// Not sure what aLevelName is, in relation to game.map. But need to use aLevelName here, to be able to start the right map for campaign, and need game.hash, to start the right non-campaign map, if there are multiple identically named maps.
3777 		if (!levLoadData(aLevelName, &saveGameData.sGame.hash, saveGameName, (GAME_TYPE)gameType))
3778 		{
3779 			return false;
3780 		}
3781 
3782 		if (saveGameVersion >= VERSION_33)
3783 		{
3784 			PLAYERSTATS		playerStats;
3785 			bool scav = game.scavengers; // loaded earlier, keep it over struct copy below
3786 
3787 			bMultiPlayer	= saveGameData.multiPlayer;
3788 			bMultiMessages	= bMultiPlayer;
3789 			productionPlayer = selectedPlayer;
3790 			game			= saveGameData.sGame;  // why do this again????
3791 			game.scavengers = scav;
3792 			NetPlay.bComms = (saveGameData.sNetPlay).bComms;
3793 			if (bMultiPlayer)
3794 			{
3795 				loadMultiStats(saveGameData.sPName, &playerStats);				// stats stuff
3796 				setMultiStats(selectedPlayer, playerStats, false);
3797 				setMultiStats(selectedPlayer, playerStats, true);
3798 			}
3799 		}
3800 	}
3801 	else
3802 	{
3803 		IsScenario = true;
3804 	}
3805 
3806 	getPlayerNames();
3807 
3808 	clearPlayerPower();
3809 	//don't adjust any power if a camStart (gameType is set to GTYPE_SCENARIO_START when a camChange saveGame is loaded)
3810 	if (gameType != GTYPE_SCENARIO_START)
3811 	{
3812 		//set the players power
3813 		for (i = 0; i < MAX_PLAYERS; ++i)
3814 		{
3815 			//only overwrite selectedPlayer's power on a startMission save game
3816 			if (gameType == GTYPE_SAVE_MIDMISSION || i == selectedPlayer)
3817 			{
3818 				setPower(i, powerSaved[i].currentPower);
3819 			}
3820 		}
3821 	}
3822 	radarPermitted = (bool)powerSaved[0].extractedPower; // nice hack, eh? don't want to break savegames now...
3823 	allowDesign = (bool)powerSaved[1].extractedPower; // nice hack, eh? don't want to break savegames now...
3824 
3825 	return true;
3826 }
3827 
3828 // -----------------------------------------------------------------------------------------
3829 // Load main game data from JSON. Only implement stuff here that we actually use instead of
3830 // the binary blobbery.
loadMainFile(const std::string & fileName)3831 static bool loadMainFile(const std::string &fileName)
3832 {
3833 	WzConfig save(WzString::fromUtf8(fileName), WzConfig::ReadOnly);
3834 
3835 	if (save.contains("playerBuiltHQ"))
3836 	{
3837 		playerBuiltHQ = save.value("playerBuiltHQ").toBool();
3838 	}
3839 	if (save.contains("challengeFileName"))
3840 	{
3841 		challengeFileName = save.string("challengeFileName");
3842 	}
3843 	if (save.contains("builtInMap"))
3844 	{
3845 		builtInMap = save.value("builtInMap").toBool();
3846 	}
3847 
3848 	save.beginArray("players");
3849 	while (save.remainingArrayItems() > 0)
3850 	{
3851 		int index = save.value("index").toInt();
3852 		if (!(index >= 0 && index < MAX_PLAYERS))
3853 		{
3854 			debug(LOG_ERROR, "Invalid player index: %d", index);
3855 			save.nextArrayItem();
3856 			continue;
3857 		}
3858 		unsigned int FactionValue = save.value("faction", static_cast<uint8_t>(FACTION_NORMAL)).toUInt();
3859 		NetPlay.players[index].faction = static_cast<FactionID>(FactionValue);
3860 		save.nextArrayItem();
3861 	}
3862 	save.endArray();
3863 
3864 	return true;
3865 }
3866 
loadMainFileFinal(const std::string & fileName)3867 static bool loadMainFileFinal(const std::string &fileName)
3868 {
3869 	WzConfig save(WzString::fromUtf8(fileName), WzConfig::ReadOnly);
3870 
3871 	if (save.contains("techLevel"))
3872 	{
3873 		game.techLevel = save.value("techLevel").toInt();
3874 	}
3875 
3876 	save.beginArray("players");
3877 	while (save.remainingArrayItems() > 0)
3878 	{
3879 		int index = save.value("index").toInt();
3880 		if (!(index >= 0 && index < MAX_PLAYERS))
3881 		{
3882 			debug(LOG_ERROR, "Invalid player index: %d", index);
3883 			save.nextArrayItem();
3884 			continue;
3885 		}
3886 		auto value = save.value("recycled_droids").jsonValue();
3887 		for (const auto &v : value)
3888 		{
3889 			add_to_experience_queue(index, json_variant(v).toInt());
3890 		}
3891 		save.nextArrayItem();
3892 	}
3893 	save.endArray();
3894 
3895 	return true;
3896 }
3897 
3898 // -----------------------------------------------------------------------------------------
3899 // Save main game data to JSON. We save more here than we need to, and duplicate some of the
3900 // binary blobbery, for future usage.
writeMainFile(const std::string & fileName,SDWORD saveType)3901 static bool writeMainFile(const std::string &fileName, SDWORD saveType)
3902 {
3903 	ASSERT(saveType == GTYPE_SAVE_START || saveType == GTYPE_SAVE_MIDMISSION, "invalid save type");
3904 
3905 	WzConfig save(WzString::fromUtf8(fileName), WzConfig::ReadAndWrite);
3906 
3907 	uint32_t saveKey = getCampaignNumber();
3908 	if (missionIsOffworld())
3909 	{
3910 		saveKey |= SAVEKEY_ONMISSION;
3911 		saveGameOnMission = true;
3912 	}
3913 	else
3914 	{
3915 		saveGameOnMission = false;
3916 	}
3917 
3918 	/* Put the save game data into the buffer */
3919 	save.setValue("version", 1); // version of this file
3920 	save.setValue("saveKey", saveKey);
3921 	save.setValue("gameTime", gameTime);
3922 	save.setValue("missionTime", mission.startTime);
3923 	save.setVector2i("scrollMin", Vector2i(scrollMinX, scrollMinY));
3924 	save.setVector2i("scrollMax", Vector2i(scrollMaxX, scrollMaxY));
3925 	save.setValue("saveType", saveType);
3926 	save.setValue("levelName", aLevelName);
3927 	save.setValue("radarPermitted", radarPermitted);
3928 	save.setValue("allowDesign", allowDesign);
3929 	save.setValue("missionOffTime", mission.time);
3930 	save.setValue("missionETA", mission.ETA);
3931 	save.setValue("missionCheatTime", mission.cheatTime);
3932 	save.setVector2i("missionHomeLZ", Vector2i(mission.homeLZ_X, mission.homeLZ_Y));
3933 	save.setVector2i("missionPlayerPos", Vector2i(mission.playerX, mission.playerY));
3934 	save.setVector2i("missionScrollMin", Vector2i(mission.scrollMinX, mission.scrollMinY));
3935 	save.setVector2i("missionScrollMax", Vector2i(mission.scrollMaxX, mission.scrollMaxY));
3936 	save.setValue("offWorldKeepLists", offWorldKeepLists);
3937 	save.setValue("rubbleTile", getRubbleTileNum());
3938 	save.setValue("waterTile", getWaterTileNum());
3939 	save.setValue("objId", MAX(unsynchObjID * 2, (synchObjID + 3) / 4));
3940 	save.setValue("radarZoom", GetRadarZoom());
3941 	save.setValue("droidsToSafetyFlag", getDroidsToSafetyFlag());
3942 	save.setValue("reinforceTime", missionGetReinforcementTime());
3943 	save.setValue("playCountDown", getPlayCountDown());
3944 
3945 	save.beginArray("players");
3946 	for (int i = 0; i < MAX_PLAYERS; ++i)
3947 	{
3948 		save.setValue("index", i);
3949 		save.setValue("power", getPower(i));
3950 		save.setVector2i("iTranspEntryTile", Vector2i(mission.iTranspEntryTileX[i], mission.iTranspEntryTileY[i]));
3951 		save.setVector2i("iTranspExitTile", Vector2i(mission.iTranspExitTileX[i], mission.iTranspExitTileY[i]));
3952 		save.setValue("aDefaultSensor", aDefaultSensor[i]);
3953 		save.setValue("aDefaultECM", aDefaultECM[i]);
3954 		save.setValue("aDefaultRepair", aDefaultRepair[i]);
3955 		save.setValue("colour", getPlayerColour(i));
3956 
3957 		std::priority_queue<int> experience = copy_experience_queue(i);
3958 		nlohmann::json recycled_droids = nlohmann::json::array();
3959 		while (!experience.empty())
3960 		{
3961 			recycled_droids.push_back(experience.top());
3962 			experience.pop();
3963 		}
3964 		save.set("recycled_droids", recycled_droids);
3965 
3966 		nlohmann::json allies = nlohmann::json::array();
3967 		for (int j = 0; j < MAX_PLAYERS; j++)
3968 		{
3969 			allies.push_back(alliances[i][j]);
3970 		}
3971 		save.set("alliances", allies);
3972 		save.setValue("difficulty", NetPlay.players[i].difficulty);
3973 
3974 		save.setValue("position", NetPlay.players[i].position);
3975 		save.setValue("colour", NetPlay.players[i].colour);
3976 		save.setValue("allocated", NetPlay.players[i].allocated);
3977 		save.setValue("faction", NetPlay.players[i].faction);
3978 		save.setValue("team", NetPlay.players[i].team);
3979 		save.setValue("ai", NetPlay.players[i].ai);
3980 		save.setValue("autoGame", NetPlay.players[i].autoGame);
3981 		save.setValue("ip", NetPlay.players[i].IPtextAddress);
3982 		save.setValue("name", getPlayerName(selectedPlayer));
3983 
3984 		save.nextArrayItem();
3985 	}
3986 	save.endArray();
3987 
3988 	iView currPlayerPos;
3989 	disp3d_getView(&currPlayerPos);
3990 	save.setVector3i("camera_position", currPlayerPos.p);
3991 	save.setVector3i("camera_rotation", currPlayerPos.r);
3992 
3993 	save.beginArray("landing_zones");
3994 	for (int i = 0; i < MAX_NOGO_AREAS; ++i)
3995 	{
3996 		LANDING_ZONE *psLandingZone = getLandingZone(i);
3997 		save.setVector2i("start", Vector2i(psLandingZone->x1, psLandingZone->x2));
3998 		save.setVector2i("end", Vector2i(psLandingZone->y1, psLandingZone->y2));
3999 		save.nextArrayItem();
4000 	}
4001 	save.endArray();
4002 
4003 	save.setValue("playerHasWon", testPlayerHasWon());
4004 	save.setValue("playerHasLost", testPlayerHasLost());
4005 	save.setValue("gameLevel", 0);
4006 	save.setValue("failFlag", 0);
4007 	save.setValue("trackTransporter", 0);
4008 	save.setValue("gameType", game.type);
4009 	save.setValue("scavengers", game.scavengers);
4010 	save.setValue("mapName", game.map);
4011 	save.setValue("maxPlayers", game.maxPlayers);
4012 	save.setValue("gameName", game.name);
4013 	save.setValue("powerSetting", game.power);
4014 	save.setValue("baseSetting", game.base);
4015 	save.setValue("allianceSetting", game.alliance);
4016 	save.setValue("mapHasScavengers", game.mapHasScavengers);
4017 	save.setValue("mapMod", game.isMapMod);
4018 	save.setValue("selectedPlayer", selectedPlayer);
4019 	save.setValue("multiplayer", bMultiPlayer);
4020 	save.setValue("playerCount", NetPlay.playercount);
4021 	save.setValue("hostPlayer", NetPlay.hostPlayer);
4022 	save.setValue("bComms", NetPlay.bComms);
4023 	save.setValue("modList", getModList().c_str());
4024 	save.setValue("playerBuiltHQ", playerBuiltHQ);
4025 	save.setValue("techLevel", game.techLevel);
4026 	save.setValue("challengeFileName", challengeFileName.toUtf8().c_str());
4027 	save.setValue("builtInMap", builtInMap);
4028 
4029 	return true;
4030 }
4031 
writeGameFile(const char * fileName,SDWORD saveType)4032 static bool writeGameFile(const char *fileName, SDWORD saveType)
4033 {
4034 	GAME_SAVEHEADER fileHeader;
4035 	SAVE_GAME       saveGame;
4036 	bool            status;
4037 	unsigned int    i, j;
4038 
4039 	PHYSFS_file *fileHandle = openSaveFile(fileName);
4040 	if (!fileHandle)
4041 	{
4042 		debug(LOG_ERROR, "openSaveFile(\"%s\") failed", fileName);
4043 		return false;
4044 	}
4045 
4046 	WZ_PHYSFS_SETBUFFER(fileHandle, 4096)//;
4047 
4048 	fileHeader.aFileType[0] = 'g';
4049 	fileHeader.aFileType[1] = 'a';
4050 	fileHeader.aFileType[2] = 'm';
4051 	fileHeader.aFileType[3] = 'e';
4052 
4053 	fileHeader.version = CURRENT_VERSION_NUM;
4054 
4055 	debug(LOG_SAVE, "fileversion is %u, (%s) ", fileHeader.version, fileName);
4056 
4057 	if (!serializeSaveGameHeader(fileHandle, &fileHeader))
4058 	{
4059 		debug(LOG_ERROR, "could not write header to %s; PHYSFS error: %s", fileName, WZ_PHYSFS_getLastError());
4060 		PHYSFS_close(fileHandle);
4061 		return false;
4062 	}
4063 
4064 	ASSERT(saveType == GTYPE_SAVE_START || saveType == GTYPE_SAVE_MIDMISSION, "invalid save type");
4065 	saveGame.saveKey = getCampaignNumber();
4066 	if (missionIsOffworld())
4067 	{
4068 		saveGame.saveKey |= SAVEKEY_ONMISSION;
4069 		saveGameOnMission = true;
4070 	}
4071 	else
4072 	{
4073 		saveGameOnMission = false;
4074 	}
4075 
4076 
4077 	/* Put the save game data into the buffer */
4078 	saveGame.gameTime = gameTime;
4079 	saveGame.missionTime = mission.startTime;
4080 
4081 	//put in the scroll data
4082 	saveGame.ScrollMinX = scrollMinX;
4083 	saveGame.ScrollMinY = scrollMinY;
4084 	saveGame.ScrollMaxX = scrollMaxX;
4085 	saveGame.ScrollMaxY = scrollMaxY;
4086 
4087 	saveGame.GameType = saveType;
4088 
4089 	//save the current level so we can load up the STARTING point of the mission
4090 	ASSERT_OR_RETURN(false, strlen(aLevelName) < MAX_LEVEL_SIZE, "Unable to save level name - too long (max %d) - %s",
4091 	                 (int)MAX_LEVEL_SIZE, aLevelName);
4092 	sstrcpy(saveGame.levelName, aLevelName);
4093 
4094 	//save out the players power
4095 	for (i = 0; i < MAX_PLAYERS; ++i)
4096 	{
4097 		saveGame.power[i].currentPower = getPower(i);
4098 	}
4099 	saveGame.power[0].extractedPower = radarPermitted; // hideous hack, don't want to break savegames now...
4100 	saveGame.power[1].extractedPower = allowDesign; // hideous hack, don't want to break savegames now...
4101 
4102 	//camera position
4103 	disp3d_getView(&(saveGame.currentPlayerPos));
4104 
4105 	//mission data
4106 	saveGame.missionOffTime =		mission.time;
4107 	saveGame.missionETA =			mission.ETA;
4108 	saveGame.missionCheatTime =		mission.cheatTime;
4109 	saveGame.missionHomeLZ_X =		mission.homeLZ_X;
4110 	saveGame.missionHomeLZ_Y =		mission.homeLZ_Y;
4111 	saveGame.missionPlayerX =		mission.playerX;
4112 	saveGame.missionPlayerY =		mission.playerY;
4113 	saveGame.missionScrollMinX = (UWORD)mission.scrollMinX;
4114 	saveGame.missionScrollMinY = (UWORD)mission.scrollMinY;
4115 	saveGame.missionScrollMaxX = (UWORD)mission.scrollMaxX;
4116 	saveGame.missionScrollMaxY = (UWORD)mission.scrollMaxY;
4117 
4118 	saveGame.offWorldKeepLists = offWorldKeepLists;
4119 	saveGame.RubbleTile	= getRubbleTileNum();
4120 	saveGame.WaterTile	= getWaterTileNum();
4121 
4122 	for (i = 0; i < MAX_PLAYERS; ++i)
4123 	{
4124 		saveGame.iTranspEntryTileX[i] = mission.iTranspEntryTileX[i];
4125 		saveGame.iTranspEntryTileY[i] = mission.iTranspEntryTileY[i];
4126 		saveGame.iTranspExitTileX[i]  = mission.iTranspExitTileX[i];
4127 		saveGame.iTranspExitTileY[i]  = mission.iTranspExitTileY[i];
4128 		saveGame.aDefaultSensor[i]    = aDefaultSensor[i];
4129 		saveGame.aDefaultECM[i]       = aDefaultECM[i];
4130 		saveGame.aDefaultRepair[i]    = aDefaultRepair[i];
4131 	}
4132 
4133 	for (i = 0; i < MAX_NOGO_AREAS; ++i)
4134 	{
4135 		LANDING_ZONE *psLandingZone = getLandingZone(i);
4136 		saveGame.sLandingZone[i].x1	= psLandingZone->x1; // in case struct changes
4137 		saveGame.sLandingZone[i].x2	= psLandingZone->x2;
4138 		saveGame.sLandingZone[i].y1	= psLandingZone->y1;
4139 		saveGame.sLandingZone[i].y2	= psLandingZone->y2;
4140 	}
4141 
4142 	//version 17
4143 	saveGame.objId = MAX(unsynchObjID * 2, (synchObjID + 3) / 4);
4144 
4145 	//version 18
4146 	memset(saveGame.buildDate, 0, sizeof(saveGame.buildDate));
4147 	saveGame.oldestVersion = 0;
4148 	saveGame.validityKey = 0;
4149 
4150 	//version 19
4151 	for (i = 0; i < MAX_PLAYERS; i++)
4152 	{
4153 		for (j = 0; j < MAX_PLAYERS; j++)
4154 		{
4155 			saveGame.alliances[i][j] = alliances[i][j];
4156 		}
4157 	}
4158 	for (i = 0; i < MAX_PLAYERS; i++)
4159 	{
4160 		saveGame.playerColour[i] = getPlayerColour(i);
4161 	}
4162 	saveGame.radarZoom = (UBYTE)GetRadarZoom();
4163 
4164 	//version 20
4165 	saveGame.bDroidsToSafetyFlag = (UBYTE)getDroidsToSafetyFlag();
4166 
4167 	//version 24
4168 	saveGame.reinforceTime = missionGetReinforcementTime();
4169 	saveGame.bPlayCountDown = (UBYTE)getPlayCountDown();
4170 	saveGame.bPlayerHasWon = (UBYTE)testPlayerHasWon();
4171 	saveGame.bPlayerHasLost = (UBYTE)testPlayerHasLost();
4172 
4173 	//version 30
4174 	saveGame.scrGameLevel = 0;
4175 	saveGame.bExtraFailFlag = 0;
4176 	saveGame.bExtraVictoryFlag = 0;
4177 	saveGame.bTrackTransporter = 0;
4178 
4179 	// version 33
4180 	saveGame.sGame		= game;
4181 	saveGame.savePlayer	= selectedPlayer;
4182 	saveGame.multiPlayer = bMultiPlayer;
4183 	saveGame.sNetPlay	= NetPlay;
4184 	sstrcpy(saveGame.sPName, getPlayerName(selectedPlayer));
4185 	for (i = 0; i < MAX_PLAYERS; ++i)
4186 	{
4187 		saveGame.sPlayerIndex[i] = i;
4188 	}
4189 
4190 	//version 34
4191 	for (i = 0; i < MAX_PLAYERS; ++i)
4192 	{
4193 		sstrcpy(saveGame.sPlayerName[i], getPlayerName(i));
4194 	}
4195 
4196 	//version 38
4197 	sstrcpy(saveGame.modList, getModList().c_str());
4198 	// Attempt to see if we have a corrupted game structure in campaigns.
4199 	if (saveGame.sGame.type == LEVEL_TYPE::CAMPAIGN)
4200 	{
4201 		// player 0 is always a human in campaign games
4202 		for (i = 1; i < MAX_PLAYERS; i++)
4203 		{
4204 			if (saveGame.sNetPlay.players[i].difficulty == AIDifficulty::HUMAN)
4205 			{
4206 				ASSERT(!"savegame corruption!", "savegame corruption!");
4207 				debug(LOG_ERROR, "Savegame corruption detected, trying to salvage.  Please Report this issue @ wz2100.net");
4208 				debug(LOG_ERROR, "players[i].difficulty was %d, level %s / %s, ", (int) static_cast<int8_t>(saveGame.sNetPlay.players[i].difficulty), saveGame.levelName, saveGame.sGame.map);
4209 				saveGame.sNetPlay.players[i].difficulty = AIDifficulty::DISABLED;
4210 			}
4211 		}
4212 	}
4213 
4214 	status = serializeSaveGameData(fileHandle, &saveGame);
4215 
4216 	// Close the file
4217 	PHYSFS_close(fileHandle);
4218 
4219 	// Return our success status with writing out the file!
4220 	return status;
4221 }
4222 
4223 // -----------------------------------------------------------------------------------------
4224 // Process the droid initialisation file (dinit.bjo). Creates droids for
4225 // the scenario being loaded. This is *NEVER* called for a user save game
4226 //
loadSaveDroidInit(char * pFileData,UDWORD filesize)4227 bool loadSaveDroidInit(char *pFileData, UDWORD filesize)
4228 {
4229 	DROIDINIT_SAVEHEADER		*psHeader;
4230 	SAVE_DROIDINIT *pDroidInit;
4231 	const DROID_TEMPLATE *psTemplate;
4232 	DROID *psDroid;
4233 	UDWORD i;
4234 	UDWORD NumberOfSkippedDroids = 0;
4235 
4236 	/* Check the file type */
4237 	psHeader = (DROIDINIT_SAVEHEADER *)pFileData;
4238 	if (psHeader->aFileType[0] != 'd' || psHeader->aFileType[1] != 'i' || psHeader->aFileType[2] != 'n' || psHeader->aFileType[3] != 't')
4239 	{
4240 		debug(LOG_ERROR, "Incorrect file type");
4241 		return false;
4242 	}
4243 
4244 	/* DROIDINIT_SAVEHEADER */
4245 	endian_udword(&psHeader->version);
4246 	endian_udword(&psHeader->quantity);
4247 
4248 	//increment to the start of the data
4249 	pFileData += DROIDINIT_HEADER_SIZE;
4250 
4251 	debug(LOG_SAVE, "fileversion is %u ", psHeader->version);
4252 
4253 	pDroidInit = (SAVE_DROIDINIT *)pFileData;
4254 
4255 	for (i = 0; i < psHeader->quantity; i++)
4256 	{
4257 		/* SAVE_DROIDINIT is OBJECT_SAVE_V19 */
4258 		/* OBJECT_SAVE_V19 */
4259 		endian_udword(&pDroidInit->id);
4260 		endian_udword(&pDroidInit->x);
4261 		endian_udword(&pDroidInit->y);
4262 		endian_udword(&pDroidInit->z);
4263 		endian_udword(&pDroidInit->direction);
4264 		endian_udword(&pDroidInit->player);
4265 		endian_udword(&pDroidInit->periodicalDamageStart);
4266 		endian_udword(&pDroidInit->periodicalDamage);
4267 
4268 		pDroidInit->player = RemapPlayerNumber(pDroidInit->player);
4269 		if (pDroidInit->player >= MAX_PLAYERS)
4270 		{
4271 			pDroidInit->player = MAX_PLAYERS - 1;	// now don't lose any droids ... force them to be the last player
4272 			NumberOfSkippedDroids++;
4273 		}
4274 
4275 		psTemplate = getTemplateFromTranslatedNameNoPlayer(pDroidInit->name);
4276 		if (psTemplate == nullptr)
4277 		{
4278 			debug(LOG_ERROR, "Unable to find template for %s for player %d", pDroidInit->name, pDroidInit->player);
4279 		}
4280 		else
4281 		{
4282 			psDroid = reallyBuildDroid(psTemplate, Position((pDroidInit->x & ~TILE_MASK) + TILE_UNITS / 2, (pDroidInit->y  & ~TILE_MASK) + TILE_UNITS / 2, 0), pDroidInit->player, false);
4283 			if (psDroid)
4284 			{
4285 				Vector2i startpos = getPlayerStartPosition(psDroid->player);
4286 
4287 				psDroid->id = pDroidInit->id > 0 ? pDroidInit->id : 0xFEDBCA98;	// hack to remove droid id zero
4288 				psDroid->rot.direction = DEG(pDroidInit->direction);
4289 				addDroid(psDroid, apsDroidLists);
4290 				if (psDroid->droidType == DROID_CONSTRUCT && startpos.x == 0 && startpos.y == 0)
4291 				{
4292 					scriptSetStartPos(psDroid->player, psDroid->pos.x, psDroid->pos.y);
4293 				}
4294 			}
4295 			else
4296 			{
4297 				debug(LOG_ERROR, "This droid cannot be built - %s", pDroidInit->name);
4298 				return false;
4299 			}
4300 		}
4301 		pDroidInit++;
4302 	}
4303 	if (NumberOfSkippedDroids)
4304 	{
4305 		debug(LOG_ERROR, "Bad Player number in %d unit(s)... assigned to the last player!", NumberOfSkippedDroids);
4306 		return false;
4307 	}
4308 	return true;
4309 }
4310 
4311 // -----------------------------------------------------------------------------------------
4312 // Remaps old player number based on position on map to new owner
RemapPlayerNumber(UDWORD OldNumber)4313 static UDWORD RemapPlayerNumber(UDWORD OldNumber)
4314 {
4315 	int i;
4316 
4317 	if (game.type == LEVEL_TYPE::CAMPAIGN)		// don't remap for SP games
4318 	{
4319 		return OldNumber;
4320 	}
4321 
4322 	for (i = 0; i < MAX_PLAYERS; i++)
4323 	{
4324 		if (OldNumber == NetPlay.players[i].position)
4325 		{
4326 			game.mapHasScavengers = game.mapHasScavengers || i == scavengerSlot();
4327 			return i;
4328 		}
4329 	}
4330 	ASSERT(false, "Found no player position for player %d", (int)OldNumber);
4331 	return 0;
4332 }
4333 
remapIntPlayerNumber(int oldNumber)4334 static int remapIntPlayerNumber(int oldNumber)
4335 {
4336 	if (oldNumber < 0)
4337 	{
4338 		game.mapHasScavengers = true;
4339 		return scavengerSlot();
4340 	}
4341 
4342 	for (int i = 0; i < MAX_PLAYERS; ++i)
4343 	{
4344 		if (oldNumber == NetPlay.players[i].position)
4345 		{
4346 			return i;
4347 		}
4348 	}
4349 	ASSERT(false, "Found no player position for player %d", oldNumber);
4350 	return 0;
4351 }
4352 
getPlayer(WzConfig & ini)4353 static int getPlayer(WzConfig &ini)
4354 {
4355 	if (ini.contains("player"))
4356 	{
4357 		json_variant result = ini.value("player");
4358 		if (result.toWzString().startsWith("scavenger"))
4359 		{
4360 			game.mapHasScavengers = true;
4361 			return scavengerSlot();
4362 		}
4363 		return result.toInt();
4364 	}
4365 	else if (ini.contains("startpos"))
4366 	{
4367 		int position = ini.value("startpos").toInt();
4368 		for (int i = 0; i < game.maxPlayers; i++)
4369 		{
4370 			if (NetPlay.players[i].position == position)
4371 			{
4372 				return i;
4373 			}
4374 		}
4375 	}
4376 	ASSERT(false, "No player info found!");
4377 	return 0;
4378 }
4379 
setPlayer(WzConfig & ini,int player)4380 static void setPlayer(WzConfig &ini, int player)
4381 {
4382 	if (scavengerSlot() == player)
4383 	{
4384 		ini.setValue("player", "scavenger");
4385 	}
4386 	else
4387 	{
4388 		ini.setValue("player", player);
4389 	}
4390 }
4391 
setPlayerJSON(nlohmann::json & jsonObj,int player)4392 static inline void setPlayerJSON(nlohmann::json &jsonObj, int player)
4393 {
4394 	if (scavengerSlot() == player)
4395 	{
4396 		jsonObj["player"] = "scavenger";
4397 	}
4398 	else
4399 	{
4400 		jsonObj["player"] = player;
4401 	}
4402 }
4403 
skipForDifficulty(WzConfig & ini,int player)4404 static bool skipForDifficulty(WzConfig &ini, int player)
4405 {
4406 	if (ini.contains("difficulty")) // optionally skip this object
4407 	{
4408 		int difficulty = ini.value("difficulty").toInt();
4409 		if ((game.type == LEVEL_TYPE::CAMPAIGN && difficulty > (int)getDifficultyLevel())
4410 		    || (game.type == LEVEL_TYPE::SKIRMISH && difficulty > static_cast<int8_t>(NetPlay.players[player].difficulty)))
4411 		{
4412 			return true;
4413 		}
4414 	}
4415 	return false;
4416 }
4417 
loadSaveDroidPointers(const WzString & pFileName,DROID ** ppsCurrentDroidLists)4418 static bool loadSaveDroidPointers(const WzString &pFileName, DROID **ppsCurrentDroidLists)
4419 {
4420 	WzConfig ini(pFileName, WzConfig::ReadOnly);
4421 	std::vector<WzString> list = ini.childGroups();
4422 
4423 	for (size_t i = 0; i < list.size(); ++i)
4424 	{
4425 		ini.beginGroup(list[i]);
4426 		DROID *psDroid;
4427 		int id = ini.value("id", -1).toInt();
4428 		int player = getPlayer(ini);
4429 
4430 		if (id <= 0)
4431 		{
4432 			ini.endGroup();
4433 			continue; // special hack for campaign missions, cannot have targets
4434 		}
4435 		if (skipForDifficulty(ini, player))
4436 		{
4437 			ini.endGroup();
4438 			continue; // another hack for campaign missions, cannot have targets
4439 		}
4440 
4441 		for (psDroid = ppsCurrentDroidLists[player]; psDroid && psDroid->id != id; psDroid = psDroid->psNext)
4442 		{
4443 			if (isTransporter(psDroid) && psDroid->psGroup != nullptr)  // Check for droids in the transporter.
4444 			{
4445 				for (DROID *psTrDroid = psDroid->psGroup->psList; psTrDroid != nullptr; psTrDroid = psTrDroid->psGrpNext)
4446 				{
4447 					if (psTrDroid->id == id)
4448 					{
4449 						psDroid = psTrDroid;
4450 						goto foundDroid;
4451 					}
4452 				}
4453 			}
4454 		}
4455 foundDroid:
4456 		if (!psDroid)
4457 		{
4458 			for (psDroid = mission.apsDroidLists[player]; psDroid && psDroid->id != id; psDroid = psDroid->psNext) {}
4459 			// FIXME
4460 			if (psDroid)
4461 			{
4462 				debug(LOG_ERROR, "Droid %s (%d) was in wrong file/list (was in %s)...", objInfo(psDroid), id, pFileName.toUtf8().c_str());
4463 			}
4464 		}
4465 		ASSERT_OR_RETURN(false, psDroid, "Droid %d not found", id);
4466 
4467 		getIniDroidOrder(ini, "order", psDroid->order);
4468 		psDroid->listSize = clip(ini.value("orderList/size", 0).toInt(), 0, 10000);
4469 		psDroid->asOrderList.resize(psDroid->listSize);  // Must resize before setting any orders, and must set in-place, since pointers are updated later.
4470 		for (int droidIdx = 0; droidIdx < psDroid->listSize; ++droidIdx)
4471 		{
4472 			getIniDroidOrder(ini, "orderList/" + WzString::number(droidIdx), psDroid->asOrderList[droidIdx]);
4473 		}
4474 		psDroid->listPendingBegin = 0;
4475 		for (int j = 0; j < MAX_WEAPONS; j++)
4476 		{
4477 			objTrace(psDroid->id, "weapon %d, nStat %d", j, psDroid->asWeaps[j].nStat);
4478 			getIniBaseObject(ini, "actionTarget/" + WzString::number(j), psDroid->psActionTarget[j]);
4479 		}
4480 		if (ini.contains("baseStruct/id"))
4481 		{
4482 			int tid = ini.value("baseStruct/id", -1).toInt();
4483 			int tplayer = ini.value("baseStruct/player", -1).toInt();
4484 			OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value("baseStruct/type", 0).toInt();
4485 			ASSERT(tid >= 0 && tplayer >= 0, "Bad ID");
4486 			BASE_OBJECT *psObj = getBaseObjFromData(tid, tplayer, ttype);
4487 			ASSERT(psObj, "Failed to find droid base structure");
4488 			ASSERT(psObj->type == OBJ_STRUCTURE, "Droid base structure not a structure");
4489 			setSaveDroidBase(psDroid, (STRUCTURE *)psObj);
4490 		}
4491 		if (ini.contains("commander"))
4492 		{
4493 			int tid = ini.value("commander", -1).toInt();
4494 			DROID *psCommander = (DROID *)getBaseObjFromData(tid, psDroid->player, OBJ_DROID);
4495 			ASSERT(psCommander, "Failed to find droid commander");
4496 			cmdDroidAddDroid(psCommander, psDroid);
4497 		}
4498 		ini.endGroup();
4499 	}
4500 	return true;
4501 }
4502 
healthValue(WzConfig & ini,int defaultValue)4503 static int healthValue(WzConfig &ini, int defaultValue)
4504 {
4505 	WzString health = ini.value("health").toWzString();
4506 	if (health.isEmpty() || defaultValue == 0)
4507 	{
4508 		return defaultValue;
4509 	}
4510 	else if (health.contains(WzUniCodepoint::fromASCII('%')))
4511 	{
4512 		int perc = health.replace("%", "").toInt();
4513 		return MAX(defaultValue * perc / 100, 1); //hp not supposed to be 0
4514 	}
4515 	else
4516 	{
4517 		return MIN(health.toInt(), defaultValue);
4518 	}
4519 }
4520 
loadSaveObject(WzConfig & ini,BASE_OBJECT * psObj)4521 static void loadSaveObject(WzConfig &ini, BASE_OBJECT *psObj)
4522 {
4523 	psObj->died = ini.value("died", 0).toInt();
4524 	memset(psObj->visible, 0, sizeof(psObj->visible));
4525 	for (int j = 0; j < game.maxPlayers; j++)
4526 	{
4527 		psObj->visible[j] = ini.value("visible/" + WzString::number(j), 0).toInt();
4528 	}
4529 	psObj->periodicalDamage = ini.value("periodicalDamage", 0).toInt();
4530 	psObj->periodicalDamageStart = ini.value("periodicalDamageStart", 0).toInt();
4531 	psObj->timeAnimationStarted = ini.value("timeAnimationStarted", 0).toInt();
4532 	psObj->animationEvent = ini.value("animationEvent", 0).toInt();
4533 	psObj->timeLastHit = ini.value("timeLastHit", UDWORD_MAX).toInt();
4534 	psObj->lastEmission = ini.value("lastEmission", 0).toInt();
4535 	psObj->selected = ini.value("selected", false).toBool();
4536 	psObj->born = ini.value("born", 2).toInt();
4537 }
4538 
writeSaveObject(WzConfig & ini,BASE_OBJECT * psObj)4539 static void writeSaveObject(WzConfig &ini, BASE_OBJECT *psObj)
4540 {
4541 	ini.setValue("id", psObj->id);
4542 	setPlayer(ini, psObj->player);
4543 	ini.setValue("health", psObj->body);
4544 	ini.setVector3i("position", psObj->pos);
4545 	ini.setVector3i("rotation", toVector(psObj->rot));
4546 	if (psObj->timeAnimationStarted)
4547 	{
4548 		ini.setValue("timeAnimationStarted", psObj->timeAnimationStarted);
4549 	}
4550 	if (psObj->animationEvent)
4551 	{
4552 		ini.setValue("animationEvent", psObj->animationEvent);
4553 	}
4554 	ini.setValue("selected", psObj->selected);	// third kind of group
4555 	if (psObj->lastEmission)
4556 	{
4557 		ini.setValue("lastEmission", psObj->lastEmission);
4558 	}
4559 	if (psObj->periodicalDamageStart > 0)
4560 	{
4561 		ini.setValue("periodicalDamageStart", psObj->periodicalDamageStart);
4562 	}
4563 	if (psObj->periodicalDamage > 0)
4564 	{
4565 		ini.setValue("periodicalDamage", psObj->periodicalDamage);
4566 	}
4567 	ini.setValue("born", psObj->born);
4568 	if (psObj->died > 0)
4569 	{
4570 		ini.setValue("died", psObj->died);
4571 	}
4572 	if (psObj->timeLastHit != UDWORD_MAX)
4573 	{
4574 		ini.setValue("timeLastHit", psObj->timeLastHit);
4575 	}
4576 	if (psObj->selected)
4577 	{
4578 		ini.setValue("selected", psObj->selected);
4579 	}
4580 	for (int i = 0; i < game.maxPlayers; i++)
4581 	{
4582 		if (psObj->visible[i])
4583 		{
4584 			ini.setValue("visible/" + WzString::number(i), psObj->visible[i]);
4585 		}
4586 	}
4587 }
4588 
writeSaveObjectJSON(nlohmann::json & jsonObj,BASE_OBJECT * psObj)4589 static void writeSaveObjectJSON(nlohmann::json &jsonObj, BASE_OBJECT *psObj)
4590 {
4591 	jsonObj["id"] = psObj->id;
4592 	setPlayerJSON(jsonObj, psObj->player);
4593 	jsonObj["health"] = psObj->body;
4594 	jsonObj["position"] = psObj->pos;
4595 	jsonObj["rotation"] = toVector(psObj->rot);
4596 	if (psObj->timeAnimationStarted)
4597 	{
4598 		jsonObj["timeAnimationStarted"] = psObj->timeAnimationStarted;
4599 	}
4600 	if (psObj->animationEvent)
4601 	{
4602 		jsonObj["animationEvent"] = psObj->animationEvent;
4603 	}
4604 	jsonObj["selected"] = psObj->selected;	// third kind of group
4605 	if (psObj->lastEmission)
4606 	{
4607 		jsonObj["lastEmission"] = psObj->lastEmission;
4608 	}
4609 	if (psObj->periodicalDamageStart > 0)
4610 	{
4611 		jsonObj["periodicalDamageStart"] = psObj->periodicalDamageStart;
4612 	}
4613 	if (psObj->periodicalDamage > 0)
4614 	{
4615 		jsonObj["periodicalDamage"] = psObj->periodicalDamage;
4616 	}
4617 	jsonObj["born"] = psObj->born;
4618 	if (psObj->died > 0)
4619 	{
4620 		jsonObj["died"] = psObj->died;
4621 	}
4622 	if (psObj->timeLastHit != UDWORD_MAX)
4623 	{
4624 		jsonObj["timeLastHit"] = psObj->timeLastHit;
4625 	}
4626 	if (psObj->selected)
4627 	{
4628 		jsonObj["selected"] = psObj->selected;
4629 	}
4630 	for (int i = 0; i < game.maxPlayers; i++)
4631 	{
4632 		if (psObj->visible[i])
4633 		{
4634 			jsonObj["visible/" + WzString::number(i).toStdString()] = psObj->visible[i];
4635 		}
4636 	}
4637 }
4638 
loadScriptDroid(ScriptMapData const & data)4639 static bool loadScriptDroid(ScriptMapData const &data)
4640 {
4641 	ASSERT_OR_RETURN(false, data.valid, "No data.");
4642 
4643 	for (auto &droid : data.droids)
4644 	{
4645 		unsigned player = remapIntPlayerNumber(droid.player);
4646 		auto psTemplate = getTemplateFromTranslatedNameNoPlayer(droid.name.toUtf8().c_str());
4647 		if (psTemplate == nullptr)
4648 		{
4649 			debug(LOG_ERROR, "Unable to find template for %s for player %d -- unit skipped", droid.name.toUtf8().c_str(), player);
4650 			continue;
4651 		}
4652 		turnOffMultiMsg(true);
4653 		auto psDroid = reallyBuildDroid(psTemplate, {droid.position, 0}, player, false, {droid.direction, 0, 0});
4654 		turnOffMultiMsg(false);
4655 		if (psDroid == nullptr)
4656 		{
4657 			debug(LOG_ERROR, "Failed to build unit %s", droid.name.toUtf8().c_str());
4658 			continue;
4659 		}
4660 		addDroid(psDroid, apsDroidLists);
4661 	}
4662 
4663 	return true;
4664 }
4665 
loadSaveDroid(const char * pFileName,DROID ** ppsCurrentDroidLists)4666 static bool loadSaveDroid(const char *pFileName, DROID **ppsCurrentDroidLists)
4667 {
4668 	if (!PHYSFS_exists(pFileName))
4669 	{
4670 		debug(LOG_SAVE, "No %s found -- use fallback method", pFileName);
4671 		return false;	// try to use fallback method
4672 	}
4673 	WzString fName = WzString::fromUtf8(pFileName);
4674 	WzConfig ini(fName, WzConfig::ReadOnly);
4675 	std::vector<WzString> list = ini.childGroups();
4676 	// Sort list so transports are loaded first, since they must be loaded before the droids they contain.
4677 	std::vector<std::pair<int, WzString>> sortedList;
4678 	bool missionList = fName.compare("mdroid");
4679 	for (size_t i = 0; i < list.size(); ++i)
4680 	{
4681 		ini.beginGroup(list[i]);
4682 		DROID_TYPE droidType = (DROID_TYPE)ini.value("droidType").toInt();
4683 		int priority = 0;
4684 		switch (droidType)
4685 		{
4686 		case DROID_TRANSPORTER:
4687 			++priority; // fallthrough
4688 		case DROID_SUPERTRANSPORTER:
4689 			++priority; // fallthrough
4690 		case DROID_COMMAND:
4691 			//Don't care about sorting commanders in the mission list for safety missions. They
4692 			//don't have a group to command and it messes up the order of the list sorting them
4693 			//which causes problems getting the first transporter group for Gamma-1.
4694 			if (!missionList || (missionList && !getDroidsToSafetyFlag()))
4695 			{
4696 				++priority;
4697 			}
4698 		default:
4699 			break;
4700 		}
4701 		sortedList.push_back(std::make_pair(-priority, list[i]));
4702 		ini.endGroup();
4703 	}
4704 	std::sort(sortedList.begin(), sortedList.end());
4705 
4706 	for (unsigned i = 0; i < sortedList.size(); ++i)
4707 	{
4708 		ini.beginGroup(sortedList[i].second);
4709 		DROID *psDroid;
4710 		int player = getPlayer(ini);
4711 		int id = ini.value("id", -1).toInt();
4712 		Position pos = ini.vector3i("position");
4713 		Rotation rot = ini.vector3i("rotation");
4714 		bool onMission = ini.value("onMission", false).toBool();
4715 		DROID_TEMPLATE templ;
4716 		const DROID_TEMPLATE *psTemplate = nullptr;
4717 
4718 		if (skipForDifficulty(ini, player))
4719 		{
4720 			ini.endGroup();
4721 			continue;
4722 		}
4723 
4724 		if (ini.contains("template"))
4725 		{
4726 			// Use real template (for maps)
4727 			WzString templName(ini.value("template").toWzString());
4728 			psTemplate = getTemplateFromTranslatedNameNoPlayer(templName.toUtf8().c_str());
4729 			if (psTemplate == nullptr)
4730 			{
4731 				debug(LOG_ERROR, "Unable to find template for %s for player %d -- unit skipped", templName.toUtf8().c_str(), player);
4732 				ini.endGroup();
4733 				continue;
4734 			}
4735 		}
4736 		else
4737 		{
4738 			// Create fake template
4739 			templ.name = ini.string("name", "UNKNOWN");
4740 			templ.droidType = (DROID_TYPE)ini.value("droidType").toInt();
4741 			templ.numWeaps = ini.value("weapons", 0).toInt();
4742 			ini.beginGroup("parts");	// the following is copy-pasted from loadSaveTemplate() -- fixme somehow
4743 			templ.asParts[COMP_BODY] = getCompFromName(COMP_BODY, ini.value("body", "ZNULLBODY").toWzString());
4744 			templ.asParts[COMP_BRAIN] = getCompFromName(COMP_BRAIN, ini.value("brain", "ZNULLBRAIN").toWzString());
4745 			templ.asParts[COMP_PROPULSION] = getCompFromName(COMP_PROPULSION, ini.value("propulsion", "ZNULLPROP").toWzString());
4746 			templ.asParts[COMP_REPAIRUNIT] = getCompFromName(COMP_REPAIRUNIT, ini.value("repair", "ZNULLREPAIR").toWzString());
4747 			templ.asParts[COMP_ECM] = getCompFromName(COMP_ECM, ini.value("ecm", "ZNULLECM").toWzString());
4748 			templ.asParts[COMP_SENSOR] = getCompFromName(COMP_SENSOR, ini.value("sensor", "ZNULLSENSOR").toWzString());
4749 			templ.asParts[COMP_CONSTRUCT] = getCompFromName(COMP_CONSTRUCT, ini.value("construct", "ZNULLCONSTRUCT").toWzString());
4750 			templ.asWeaps[0] = getCompFromName(COMP_WEAPON, ini.value("weapon/1", "ZNULLWEAPON").toWzString());
4751 			templ.asWeaps[1] = getCompFromName(COMP_WEAPON, ini.value("weapon/2", "ZNULLWEAPON").toWzString());
4752 			templ.asWeaps[2] = getCompFromName(COMP_WEAPON, ini.value("weapon/3", "ZNULLWEAPON").toWzString());
4753 			ini.endGroup();
4754 			psTemplate = &templ;
4755 		}
4756 
4757 		// If droid is on a mission, calling with the saved position might cause an assertion. Or something like that.
4758 		if (!onMission)
4759 		{
4760 			pos.x = clip(pos.x, world_coord(1), world_coord(mapWidth - 1));
4761 			pos.y = clip(pos.y, world_coord(1), world_coord(mapHeight - 1));
4762 		}
4763 
4764 		/* Create the Droid */
4765 		turnOffMultiMsg(true);
4766 		psDroid = reallyBuildDroid(psTemplate, pos, player, onMission, rot);
4767 		ASSERT_OR_RETURN(false, psDroid != nullptr, "Failed to build unit %s", sortedList[i].second.toUtf8().c_str());
4768 		turnOffMultiMsg(false);
4769 
4770 		// Copy the values across
4771 		if (id > 0)
4772 		{
4773 			psDroid->id = id; // force correct ID, unless ID is set to eg -1, in which case we should keep new ID (useful for starting units in campaign)
4774 		}
4775 		ASSERT(id != 0, "Droid ID should never be zero here");
4776 		psDroid->body = healthValue(ini, psDroid->originalBody);
4777 		ASSERT(psDroid->body != 0, "%s : %d has zero hp!", pFileName, i);
4778 		psDroid->experience = ini.value("experience", 0).toInt();
4779 		psDroid->kills = ini.value("kills", 0).toInt();
4780 		psDroid->secondaryOrder = ini.value("secondaryOrder", psDroid->secondaryOrder).toInt();
4781 		psDroid->secondaryOrderPending = psDroid->secondaryOrder;
4782 		psDroid->action = (DROID_ACTION)ini.value("action", DACTION_NONE).toInt();
4783 		psDroid->actionPos = ini.vector2i("action/pos");
4784 		psDroid->actionStarted = ini.value("actionStarted", 0).toInt();
4785 		psDroid->actionPoints = ini.value("actionPoints", 0).toInt();
4786 		psDroid->resistance = ini.value("resistance", 0).toInt(); // zero resistance == no electronic damage
4787 		psDroid->lastFrustratedTime = ini.value("lastFrustratedTime", 0).toInt();
4788 
4789 		// common BASE_OBJECT info
4790 		loadSaveObject(ini, psDroid);
4791 
4792 		// copy the droid's weapon stats
4793 		for (int j = 0; j < psDroid->numWeaps; j++)
4794 		{
4795 			if (psDroid->asWeaps[j].nStat > 0)
4796 			{
4797 				psDroid->asWeaps[j].ammo = ini.value("ammo/" + WzString::number(j)).toInt();
4798 				psDroid->asWeaps[j].lastFired = ini.value("lastFired/" + WzString::number(j)).toInt();
4799 				psDroid->asWeaps[j].shotsFired = ini.value("shotsFired/" + WzString::number(j)).toInt();
4800 				psDroid->asWeaps[j].rot = ini.vector3i("rotation/" + WzString::number(j));
4801 			}
4802 		}
4803 
4804 		psDroid->group = ini.value("group", UBYTE_MAX).toInt();
4805 		int aigroup = ini.value("aigroup", -1).toInt();
4806 		if (aigroup >= 0)
4807 		{
4808 			DROID_GROUP *psGroup = grpFind(aigroup);
4809 			psGroup->add(psDroid);
4810 			if (psGroup->type == GT_TRANSPORTER)
4811 			{
4812 				psDroid->selected = false;  // Droid should be visible in the transporter interface.
4813 				visRemoveVisibility(psDroid); // should not have visibility data when in a transporter
4814 			}
4815 		}
4816 		else
4817 		{
4818 			if (isTransporter(psDroid) || psDroid->droidType == DROID_COMMAND)
4819 			{
4820 				DROID_GROUP *psGroup = grpCreate();
4821 				psGroup->add(psDroid);
4822 			}
4823 			else
4824 			{
4825 				psDroid->psGroup = nullptr;
4826 			}
4827 		}
4828 
4829 		psDroid->sMove.Status = (MOVE_STATUS)ini.value("moveStatus", 0).toInt();
4830 		psDroid->sMove.pathIndex = ini.value("pathIndex", 0).toInt();
4831 		const int numPoints = ini.value("pathLength", 0).toInt();
4832 		psDroid->sMove.asPath.resize(numPoints);
4833 		for (int j = 0; j < numPoints; j++)
4834 		{
4835 			psDroid->sMove.asPath[j] = ini.vector2i("pathNode/" + WzString::number(j));
4836 		}
4837 		psDroid->sMove.destination = ini.vector2i("moveDestination");
4838 		psDroid->sMove.src = ini.vector2i("moveSource");
4839 		psDroid->sMove.target = ini.vector2i("moveTarget");
4840 		psDroid->sMove.speed = ini.value("moveSpeed").toInt();
4841 		psDroid->sMove.moveDir = ini.value("moveDirection").toInt();
4842 		psDroid->sMove.bumpDir = ini.value("bumpDir").toInt();
4843 		psDroid->sMove.iVertSpeed = ini.value("vertSpeed").toInt();
4844 		psDroid->sMove.bumpTime = ini.value("bumpTime").toInt();
4845 		psDroid->sMove.shuffleStart = ini.value("shuffleStart").toInt();
4846 		for (int j = 0; j < MAX_WEAPONS; ++j)
4847 		{
4848 			psDroid->asWeaps[j].usedAmmo = ini.value("attackRun/" + WzString::number(j)).toInt();
4849 		}
4850 		psDroid->sMove.lastBump = ini.value("lastBump").toInt();
4851 		psDroid->sMove.pauseTime = ini.value("pauseTime").toInt();
4852 		Vector2i tmp = ini.vector2i("bumpPosition");
4853 		psDroid->sMove.bumpPos = Vector3i(tmp.x, tmp.y, 0);
4854 
4855 		// Recreate path-finding jobs
4856 		if (psDroid->sMove.Status == MOVEWAITROUTE)
4857 		{
4858 			psDroid->sMove.Status = MOVEINACTIVE;
4859 			fpathDroidRoute(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y, FMT_MOVE);
4860 			psDroid->sMove.Status = MOVEWAITROUTE;
4861 
4862 			// Droid might be on a mission, so finish pathfinding now, in case pointers swap and map size changes.
4863 			FPATH_RETVAL dr = fpathDroidRoute(psDroid, psDroid->sMove.destination.x, psDroid->sMove.destination.y, FMT_MOVE);
4864 			if (dr == FPR_OK)
4865 			{
4866 				psDroid->sMove.Status = MOVENAVIGATE;
4867 				psDroid->sMove.pathIndex = 0;
4868 			}
4869 			else // if (retVal == FPR_FAILED)
4870 			{
4871 				psDroid->sMove.Status = MOVEINACTIVE;
4872 				actionDroid(psDroid, DACTION_SULK);
4873 			}
4874 			ASSERT(dr != FPR_WAIT, " ");
4875 		}
4876 
4877 		// HACK!!
4878 		Vector2i startpos = getPlayerStartPosition(player);
4879 		if (psDroid->droidType == DROID_CONSTRUCT && startpos.x == 0 && startpos.y == 0)
4880 		{
4881 			scriptSetStartPos(psDroid->player, psDroid->pos.x, psDroid->pos.y);	// set map start position, FIXME - save properly elsewhere!
4882 		}
4883 
4884 		if (psDroid->psGroup == nullptr || psDroid->psGroup->type != GT_TRANSPORTER || isTransporter(psDroid))  // do not add to list if on a transport, then the group list is used instead
4885 		{
4886 			addDroid(psDroid, ppsCurrentDroidLists);
4887 		}
4888 
4889 		ini.endGroup();
4890 	}
4891 	return true;
4892 }
4893 
4894 // -----------------------------------------------------------------------------------------
4895 /*
4896 Writes the linked list of droids for each player to a file
4897 */
writeDroid(DROID * psCurr,bool onMission,int & counter)4898 static nlohmann::json writeDroid(DROID *psCurr, bool onMission, int &counter)
4899 {
4900 	nlohmann::json droidObj = nlohmann::json::object();
4901 	droidObj["name"] = psCurr->aName;
4902 
4903 	// write common BASE_OBJECT info
4904 	writeSaveObjectJSON(droidObj, psCurr);
4905 
4906 	for (int i = 0; i < psCurr->numWeaps; i++)
4907 	{
4908 		if (psCurr->asWeaps[i].nStat > 0)
4909 		{
4910 			const std::string& numStr = WzString::number(i).toStdString();
4911 			droidObj["ammo/" + numStr] = psCurr->asWeaps[i].ammo;
4912 			droidObj["lastFired/" + numStr] = psCurr->asWeaps[i].lastFired;
4913 			droidObj["shotsFired/" + numStr] = psCurr->asWeaps[i].shotsFired;
4914 			droidObj["rotation/" + numStr] = toVector(psCurr->asWeaps[i].rot);
4915 		}
4916 	}
4917 	for (int i = 0; i < MAX_WEAPONS; i++)
4918 	{
4919 		setIniBaseObject(droidObj, "actionTarget/" + WzString::number(i), psCurr->psActionTarget[i]);
4920 	}
4921 	if (psCurr->lastFrustratedTime > 0)
4922 	{
4923 		droidObj["lastFrustratedTime"] = psCurr->lastFrustratedTime;
4924 	}
4925 	if (psCurr->experience > 0)
4926 	{
4927 		droidObj["experience"] = psCurr->experience;
4928 	}
4929 	if (psCurr->kills > 0)
4930 	{
4931 		droidObj["kills"] = psCurr->kills;
4932 	}
4933 
4934 	setIniDroidOrder(droidObj, "order", psCurr->order);
4935 	droidObj["orderList/size"] = psCurr->listSize;
4936 	for (int i = 0; i < psCurr->listSize; ++i)
4937 	{
4938 		setIniDroidOrder(droidObj, "orderList/" + WzString::number(i), psCurr->asOrderList[i]);
4939 	}
4940 	if (psCurr->timeLastHit != UDWORD_MAX)
4941 	{
4942 		droidObj["timeLastHit"] = psCurr->timeLastHit;
4943 	}
4944 	droidObj["secondaryOrder"] = psCurr->secondaryOrder;
4945 	droidObj["action"] = psCurr->action;
4946 	droidObj["actionString"] = getDroidActionName(psCurr->action); // future-proofing
4947 	droidObj["action/pos"] = psCurr->actionPos;
4948 	droidObj["actionStarted"] = psCurr->actionStarted;
4949 	droidObj["actionPoints"] = psCurr->actionPoints;
4950 	if (psCurr->psBaseStruct != nullptr)
4951 	{
4952 		droidObj["baseStruct/id"] = psCurr->psBaseStruct->id;
4953 		droidObj["baseStruct/player"] = psCurr->psBaseStruct->player;	// always ours, but for completeness
4954 		droidObj["baseStruct/type"] = psCurr->psBaseStruct->type;		// always a building, but for completeness
4955 	}
4956 	if (psCurr->psGroup)
4957 	{
4958 		droidObj["aigroup"] = psCurr->psGroup->id;	// AI and commander/transport group
4959 		droidObj["aigroup/type"] = psCurr->psGroup->type;
4960 	}
4961 	droidObj["group"] = psCurr->group;	// different kind of group. of course.
4962 	if (hasCommander(psCurr) && psCurr->psGroup->psCommander->died <= 1)
4963 	{
4964 		droidObj["commander"] = psCurr->psGroup->psCommander->id;
4965 	}
4966 	if (psCurr->resistance > 0)
4967 	{
4968 		droidObj["resistance"] = psCurr->resistance;
4969 	}
4970 	droidObj["droidType"] = psCurr->droidType;
4971 	droidObj["weapons"] = psCurr->numWeaps;
4972 	nlohmann::json partsObj = nlohmann::json::object();
4973 	partsObj["body"] = (asBodyStats + psCurr->asBits[COMP_BODY])->id;
4974 	partsObj["propulsion"] = (asPropulsionStats + psCurr->asBits[COMP_PROPULSION])->id;
4975 	partsObj["brain"] = (asBrainStats + psCurr->asBits[COMP_BRAIN])->id;
4976 	partsObj["repair"] = (asRepairStats + psCurr->asBits[COMP_REPAIRUNIT])->id;
4977 	partsObj["ecm"] = (asECMStats + psCurr->asBits[COMP_ECM])->id;
4978 	partsObj["sensor"] = (asSensorStats + psCurr->asBits[COMP_SENSOR])->id;
4979 	partsObj["construct"] = (asConstructStats + psCurr->asBits[COMP_CONSTRUCT])->id;
4980 	for (int j = 0; j < psCurr->numWeaps; j++)
4981 	{
4982 		partsObj["weapon/" + WzString::number(j + 1).toStdString()] = (asWeaponStats + psCurr->asWeaps[j].nStat)->id;
4983 	}
4984 	droidObj["parts"] = partsObj;
4985 	droidObj["moveStatus"] = psCurr->sMove.Status;
4986 	droidObj["pathIndex"] = psCurr->sMove.pathIndex;
4987 	droidObj["pathLength"] = psCurr->sMove.asPath.size();
4988 	for (unsigned i = 0; i < psCurr->sMove.asPath.size(); i++)
4989 	{
4990 		droidObj["pathNode/" + WzString::number(i).toStdString()] = psCurr->sMove.asPath[i];
4991 	}
4992 	droidObj["moveDestination"] = psCurr->sMove.destination;
4993 	droidObj["moveSource"] = psCurr->sMove.src;
4994 	droidObj["moveTarget"] = psCurr->sMove.target;
4995 	droidObj["moveSpeed"] = psCurr->sMove.speed;
4996 	droidObj["moveDirection"] = psCurr->sMove.moveDir;
4997 	droidObj["bumpDir"] = psCurr->sMove.bumpDir;
4998 	droidObj["vertSpeed"] = psCurr->sMove.iVertSpeed;
4999 	droidObj["bumpTime"] = psCurr->sMove.bumpTime;
5000 	droidObj["shuffleStart"] = psCurr->sMove.shuffleStart;
5001 	for (int i = 0; i < MAX_WEAPONS; ++i)
5002 	{
5003 		droidObj["attackRun/" + WzString::number(i).toStdString()] = psCurr->asWeaps[i].usedAmmo;
5004 	}
5005 	droidObj["lastBump"] = psCurr->sMove.lastBump;
5006 	droidObj["pauseTime"] = psCurr->sMove.pauseTime;
5007 	droidObj["bumpPosition"] = psCurr->sMove.bumpPos.xy();
5008 	droidObj["onMission"] = onMission;
5009 	return droidObj;
5010 }
5011 
writeDroidFile(const char * pFileName,DROID ** ppsCurrentDroidLists)5012 static bool writeDroidFile(const char *pFileName, DROID **ppsCurrentDroidLists)
5013 {
5014 	nlohmann::json mRoot = nlohmann::json::object();
5015 	int counter = 0;
5016 	bool onMission = (ppsCurrentDroidLists[0] == mission.apsDroidLists[0]);
5017 
5018 	for (int player = 0; player < MAX_PLAYERS; player++)
5019 	{
5020 		for (DROID *psCurr = ppsCurrentDroidLists[player]; psCurr != nullptr; psCurr = psCurr->psNext)
5021 		{
5022 			auto droidKey = "droid_" + (WzString::number(counter++).leftPadToMinimumLength(WzUniCodepoint::fromASCII('0'), 10));  // Zero padded so that alphabetical sort works.
5023 			mRoot[droidKey.toStdString()] = writeDroid(psCurr, onMission, counter);
5024 			if (isTransporter(psCurr))	// if transporter save any droids in the grp
5025 			{
5026 				for (DROID *psTrans = psCurr->psGroup->psList; psTrans != nullptr; psTrans = psTrans->psGrpNext)
5027 				{
5028 					if (psTrans != psCurr)
5029 					{
5030 						droidKey = "droid_" + (WzString::number(counter++).leftPadToMinimumLength(WzUniCodepoint::fromASCII('0'), 10));  // Zero padded so that alphabetical sort works.
5031 						mRoot[droidKey.toStdString()] = writeDroid(psTrans, onMission, counter);
5032 					}
5033 				}
5034 				//always save transporter droids that are in the mission list with an invalid value
5035 				if (ppsCurrentDroidLists[player] == mission.apsDroidLists[player])
5036 				{
5037 					mRoot[droidKey.toStdString()]["position"] = Vector3i(-1, -1, -1); // was INVALID_XY
5038 				}
5039 			}
5040 		}
5041 	}
5042 
5043 	std::ostringstream stream;
5044 	stream << mRoot.dump(4) << std::endl;
5045 	std::string jsonString = stream.str();
5046 	saveFile(pFileName, jsonString.c_str(), jsonString.size());
5047 	debug(LOG_SAVE, "%s %s", "Saving", pFileName);
5048 
5049 	return true;
5050 }
5051 
5052 
5053 // -----------------------------------------------------------------------------------------
loadSaveStructure(char * pFileData,UDWORD filesize)5054 bool loadSaveStructure(char *pFileData, UDWORD filesize)
5055 {
5056 	STRUCT_SAVEHEADER		*psHeader;
5057 	SAVE_STRUCTURE_V2		*psSaveStructure, sSaveStructure;
5058 	STRUCTURE			*psStructure;
5059 	STRUCTURE_STATS			*psStats = nullptr;
5060 	UDWORD				count, statInc;
5061 	int32_t				found;
5062 	UDWORD				NumberOfSkippedStructures = 0;
5063 	UDWORD				periodicalDamageTime;
5064 
5065 	/* Check the file type */
5066 	psHeader = (STRUCT_SAVEHEADER *)pFileData;
5067 	if (psHeader->aFileType[0] != 's' || psHeader->aFileType[1] != 't' ||
5068 	    psHeader->aFileType[2] != 'r' || psHeader->aFileType[3] != 'u')
5069 	{
5070 		debug(LOG_ERROR, "loadSaveStructure: Incorrect file type");
5071 
5072 		return false;
5073 	}
5074 
5075 	/* STRUCT_SAVEHEADER */
5076 	endian_udword(&psHeader->version);
5077 	endian_udword(&psHeader->quantity);
5078 
5079 	//increment to the start of the data
5080 	pFileData += STRUCT_HEADER_SIZE;
5081 
5082 	debug(LOG_SAVE, "file version is %u ", psHeader->version);
5083 
5084 	/* Check the file version */
5085 	if (psHeader->version < VERSION_7 || psHeader->version > VERSION_8)
5086 	{
5087 		debug(LOG_ERROR, "StructLoad: unsupported save format version %d", psHeader->version);
5088 
5089 		return false;
5090 	}
5091 
5092 	psSaveStructure = &sSaveStructure;
5093 
5094 	if ((sizeof(SAVE_STRUCTURE_V2) * psHeader->quantity + STRUCT_HEADER_SIZE) > filesize)
5095 	{
5096 		debug(LOG_ERROR, "structureLoad: unexpected end of file");
5097 		return false;
5098 	}
5099 
5100 	/* Load in the structure data */
5101 	for (count = 0; count < psHeader->quantity; count ++, pFileData += sizeof(SAVE_STRUCTURE_V2))
5102 	{
5103 		memcpy(psSaveStructure, pFileData, sizeof(SAVE_STRUCTURE_V2));
5104 
5105 		/* STRUCTURE_SAVE_V2 includes OBJECT_SAVE_V19 */
5106 		endian_sdword(&psSaveStructure->currentBuildPts);
5107 		endian_udword(&psSaveStructure->body);
5108 		endian_udword(&psSaveStructure->armour);
5109 		endian_udword(&psSaveStructure->resistance);
5110 		endian_udword(&psSaveStructure->dummy1);
5111 		endian_udword(&psSaveStructure->subjectInc);
5112 		endian_udword(&psSaveStructure->timeStarted);
5113 		endian_udword(&psSaveStructure->output);
5114 		endian_udword(&psSaveStructure->capacity);
5115 		endian_udword(&psSaveStructure->quantity);
5116 		/* OBJECT_SAVE_V19 */
5117 		endian_udword(&psSaveStructure->id);
5118 		endian_udword(&psSaveStructure->x);
5119 		endian_udword(&psSaveStructure->y);
5120 		endian_udword(&psSaveStructure->z);
5121 		endian_udword(&psSaveStructure->direction);
5122 		endian_udword(&psSaveStructure->player);
5123 		endian_udword(&psSaveStructure->periodicalDamageStart);
5124 		endian_udword(&psSaveStructure->periodicalDamage);
5125 
5126 		psSaveStructure->player = RemapPlayerNumber(psSaveStructure->player);
5127 
5128 		if (psSaveStructure->player >= MAX_PLAYERS)
5129 		{
5130 			psSaveStructure->player = MAX_PLAYERS - 1;
5131 			NumberOfSkippedStructures++;
5132 		}
5133 		//get the stats for this structure
5134 		found = false;
5135 
5136 		for (statInc = 0; statInc < numStructureStats; statInc++)
5137 		{
5138 			psStats = asStructureStats + statInc;
5139 			//loop until find the same name
5140 
5141 			if (psStats->id.compare(psSaveStructure->name) == 0)
5142 			{
5143 				found = true;
5144 				break;
5145 			}
5146 		}
5147 		//if haven't found the structure - ignore this record!
5148 		if (!found)
5149 		{
5150 			debug(LOG_ERROR, "This structure no longer exists - %s", getSaveStructNameV19((SAVE_STRUCTURE_V17 *)psSaveStructure));
5151 			//ignore this
5152 			continue;
5153 		}
5154 
5155 		//for modules - need to check the base structure exists
5156 		if (IsStatExpansionModule(psStats))
5157 		{
5158 			psStructure = getTileStructure(map_coord(psSaveStructure->x), map_coord(psSaveStructure->y));
5159 			if (psStructure == nullptr)
5160 			{
5161 				debug(LOG_ERROR, "No owning structure for module - %s for player - %d", getSaveStructNameV19((SAVE_STRUCTURE_V17 *)psSaveStructure), psSaveStructure->player);
5162 				//ignore this module
5163 				continue;
5164 			}
5165 		}
5166 
5167 		//check not trying to build too near the edge
5168 		if (map_coord(psSaveStructure->x) < TOO_NEAR_EDGE || map_coord(psSaveStructure->x) > mapWidth - TOO_NEAR_EDGE)
5169 		{
5170 			debug(LOG_ERROR, "Structure %s, x coord too near the edge of the map. id - %d", getSaveStructNameV19((SAVE_STRUCTURE_V17 *)psSaveStructure), psSaveStructure->id);
5171 			//ignore this
5172 			continue;
5173 		}
5174 		if (map_coord(psSaveStructure->y) < TOO_NEAR_EDGE || map_coord(psSaveStructure->y) > mapHeight - TOO_NEAR_EDGE)
5175 		{
5176 			debug(LOG_ERROR, "Structure %s, y coord too near the edge of the map. id - %d", getSaveStructNameV19((SAVE_STRUCTURE_V17 *)psSaveStructure), psSaveStructure->id);
5177 			//ignore this
5178 			continue;
5179 		}
5180 
5181 		psStructure = buildStructureDir(psStats, psSaveStructure->x, psSaveStructure->y, DEG(psSaveStructure->direction), psSaveStructure->player, true);
5182 		ASSERT(psStructure, "Unable to create structure");
5183 		if (!psStructure)
5184 		{
5185 			continue;
5186 		}
5187 		// The original code here didn't work and so the scriptwriters worked round it by using the module ID - so making it work now will screw up
5188 		// the scripts -so in ALL CASES overwrite the ID!
5189 		psStructure->id = psSaveStructure->id > 0 ? psSaveStructure->id : 0xFEDBCA98; // hack to remove struct id zero
5190 		psStructure->periodicalDamage = psSaveStructure->periodicalDamage;
5191 		periodicalDamageTime = psSaveStructure->periodicalDamageStart;
5192 		psStructure->periodicalDamageStart = periodicalDamageTime;
5193 		psStructure->status = (STRUCT_STATES)psSaveStructure->status;
5194 		if (psStructure->status == SS_BUILT)
5195 		{
5196 			buildingComplete(psStructure);
5197 		}
5198 		if (psStructure->pStructureType->type == REF_HQ)
5199 		{
5200 			scriptSetStartPos(psSaveStructure->player, psStructure->pos.x, psStructure->pos.y);
5201 		}
5202 		else if (psStructure->pStructureType->type == REF_RESOURCE_EXTRACTOR)
5203 		{
5204 			scriptSetDerrickPos(psStructure->pos.x, psStructure->pos.y);
5205 		}
5206 	}
5207 
5208 	if (NumberOfSkippedStructures > 0)
5209 	{
5210 		debug(LOG_ERROR, "structureLoad: invalid player number in %d structures ... assigned to the last player!\n\n", NumberOfSkippedStructures);
5211 		return false;
5212 	}
5213 
5214 	return true;
5215 }
5216 
5217 // -----------------------------------------------------------------------------------------
5218 //return id of a research topic based on the name
getResearchIdFromName(const WzString & name)5219 static UDWORD getResearchIdFromName(const WzString &name)
5220 {
5221 	for (size_t inc = 0; inc < asResearch.size(); inc++)
5222 	{
5223 		if (asResearch[inc].id.compare(name) == 0)
5224 		{
5225 			return inc;
5226 		}
5227 	}
5228 	debug(LOG_ERROR, "Unknown research - %s", name.toUtf8().c_str());
5229 	return NULL_ID;
5230 }
5231 
loadScriptStructure(ScriptMapData const & data)5232 bool loadScriptStructure(ScriptMapData const &data)
5233 {
5234 	if (!data.valid)
5235 	{
5236 		return false;
5237 	}
5238 
5239 	freeAllFlagPositions();  // clear any flags put in during level loads
5240 
5241 	for (auto &structure : data.structures) {
5242 		auto psStats = std::find_if(asStructureStats, asStructureStats + numStructureStats, [&](STRUCTURE_STATS &stat) { return stat.id == structure.name; });
5243 		if (psStats == asStructureStats + numStructureStats)
5244 		{
5245 			debug(LOG_ERROR, "Structure type \"%s\" unknown", structure.name.toStdString().c_str());
5246 			continue;  // ignore this
5247 		}
5248 		//for modules - need to check the base structure exists
5249 		if (IsStatExpansionModule(psStats))
5250 		{
5251 			STRUCTURE *psStructure = getTileStructure(map_coord(structure.position.x), map_coord(structure.position.y));
5252 			if (psStructure == nullptr)
5253 			{
5254 				debug(LOG_ERROR, "No owning structure for module - %s for player - %d", structure.name.toStdString().c_str(), structure.player);
5255 				continue; // ignore this module
5256 			}
5257 		}
5258 		//check not trying to build too near the edge
5259 		if (map_coord(structure.position.x) < TOO_NEAR_EDGE || map_coord(structure.position.x) > mapWidth - TOO_NEAR_EDGE
5260 		 || map_coord(structure.position.y) < TOO_NEAR_EDGE || map_coord(structure.position.y) > mapHeight - TOO_NEAR_EDGE)
5261 		{
5262 			debug(LOG_ERROR, "Structure %s, coord too near the edge of the map", structure.name.toStdString().c_str());
5263 			continue; // skip it
5264 		}
5265 		int player = remapIntPlayerNumber(structure.player);
5266 		STRUCTURE *psStructure = buildStructureDir(psStats, structure.position.x, structure.position.y, structure.direction, player, true);
5267 		if (psStructure == nullptr)
5268 		{
5269 			debug(LOG_ERROR, "Structure %s couldn't be built (probably on top of another structure).", structure.name.toStdString().c_str());
5270 			continue;
5271 		}
5272 		if (structure.modules > 0)
5273 		{
5274 			auto moduleStat = getModuleStat(psStructure);
5275 			if (moduleStat == nullptr)
5276 			{
5277 				debug(LOG_ERROR, "Structure %s can't have modules.", structure.name.toStdString().c_str());
5278 				continue;
5279 			}
5280 			for (int i = 0; i < structure.modules; ++i)
5281 			{
5282 				buildStructure(moduleStat, structure.position.x, structure.position.y, player, true);
5283 			}
5284 		}
5285 		buildingComplete(psStructure);
5286 		if (psStructure->pStructureType->type == REF_HQ)
5287 		{
5288 			scriptSetStartPos(player, psStructure->pos.x, psStructure->pos.y);
5289 		}
5290 		else if (psStructure->pStructureType->type == REF_RESOURCE_EXTRACTOR)
5291 		{
5292 			scriptSetDerrickPos(psStructure->pos.x, psStructure->pos.y);
5293 		}
5294 	}
5295 
5296 	return true;
5297 }
5298 
5299 // -----------------------------------------------------------------------------------------
5300 /* code for versions after version 20 of a save structure */
loadSaveStructure2(const char * pFileName,STRUCTURE ** ppList)5301 static bool loadSaveStructure2(const char *pFileName, STRUCTURE **ppList)
5302 {
5303 	if (!PHYSFS_exists(pFileName))
5304 	{
5305 		debug(LOG_SAVE, "No %s found -- use fallback method", pFileName);
5306 		return false;	// try to use fallback method
5307 	}
5308 	WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadOnly);
5309 
5310 	freeAllFlagPositions();		//clear any flags put in during level loads
5311 
5312 	std::vector<WzString> list = ini.childGroups();
5313 	for (size_t i = 0; i < list.size(); ++i)
5314 	{
5315 		FACTORY *psFactory;
5316 		RESEARCH_FACILITY *psResearch;
5317 		REPAIR_FACILITY *psRepair;
5318 		REARM_PAD *psReArmPad;
5319 		STRUCTURE_STATS *psModule;
5320 		int capacity, researchId;
5321 		STRUCTURE *psStructure;
5322 
5323 		ini.beginGroup(list[i]);
5324 		int player = getPlayer(ini);
5325 		int id = ini.value("id", -1).toInt();
5326 		Position pos = ini.vector3i("position");
5327 		Rotation rot = ini.vector3i("rotation");
5328 		WzString name = ini.string("name");
5329 
5330 		//get the stats for this structure
5331 		auto psStats = std::find_if(asStructureStats, asStructureStats + numStructureStats, [&](STRUCTURE_STATS &stat) { return stat.id == name; });
5332 		//if haven't found the structure - ignore this record!
5333 		ASSERT(psStats != asStructureStats + numStructureStats, "This structure no longer exists - %s", name.toUtf8().c_str());
5334 		if (psStats == asStructureStats + numStructureStats)
5335 		{
5336 			ini.endGroup();
5337 			continue;	// ignore this
5338 		}
5339 		/*create the Structure */
5340 		//for modules - need to check the base structure exists
5341 		if (IsStatExpansionModule(psStats))
5342 		{
5343 			STRUCTURE *psTileStructure = getTileStructure(map_coord(pos.x), map_coord(pos.y));
5344 			if (psTileStructure == nullptr)
5345 			{
5346 				debug(LOG_ERROR, "No owning structure for module - %s for player - %d", name.toUtf8().c_str(), player);
5347 				ini.endGroup();
5348 				continue; // ignore this module
5349 			}
5350 		}
5351 		//check not trying to build too near the edge
5352 		if (map_coord(pos.x) < TOO_NEAR_EDGE || map_coord(pos.x) > mapWidth - TOO_NEAR_EDGE
5353 		    || map_coord(pos.y) < TOO_NEAR_EDGE || map_coord(pos.y) > mapHeight - TOO_NEAR_EDGE)
5354 		{
5355 			debug(LOG_ERROR, "Structure %s (%s), coord too near the edge of the map", name.toUtf8().c_str(), list[i].toUtf8().c_str());
5356 			ini.endGroup();
5357 			continue; // skip it
5358 		}
5359 		psStructure = buildStructureDir(psStats, pos.x, pos.y, rot.direction, player, true);
5360 		ASSERT(psStructure, "Unable to create structure");
5361 		if (!psStructure)
5362 		{
5363 			ini.endGroup();
5364 			continue;
5365 		}
5366 		if (id > 0)
5367 		{
5368 			psStructure->id = id;	// force correct ID
5369 		}
5370 
5371 		// common BASE_OBJECT info
5372 		loadSaveObject(ini, psStructure);
5373 
5374 		if (psStructure->pStructureType->type == REF_HQ)
5375 		{
5376 			scriptSetStartPos(player, psStructure->pos.x, psStructure->pos.y);
5377 		}
5378 		psStructure->resistance = ini.value("resistance", psStructure->resistance).toInt();
5379 		capacity = ini.value("modules", 0).toInt();
5380 		psStructure->capacity = 0; // increased when modules are built
5381 		switch (psStructure->pStructureType->type)
5382 		{
5383 		case REF_FACTORY:
5384 		case REF_VTOL_FACTORY:
5385 		case REF_CYBORG_FACTORY:
5386 			//if factory save the current build info
5387 			psFactory = ((FACTORY *)psStructure->pFunctionality);
5388 			psFactory->productionLoops = ini.value("Factory/productionLoops", psFactory->productionLoops).toUInt();
5389 			psFactory->timeStarted = ini.value("Factory/timeStarted", psFactory->timeStarted).toInt();
5390 			psFactory->buildPointsRemaining = ini.value("Factory/buildPointsRemaining", psFactory->buildPointsRemaining).toInt();
5391 			psFactory->timeStartHold = ini.value("Factory/timeStartHold", psFactory->timeStartHold).toInt();
5392 			psFactory->loopsPerformed = ini.value("Factory/loopsPerformed", psFactory->loopsPerformed).toInt();
5393 			// statusPending and pendingCount belong to the GUI, not the game state.
5394 			psFactory->secondaryOrder = ini.value("Factory/secondaryOrder", psFactory->secondaryOrder).toInt();
5395 			//adjust the module structures IMD
5396 			if (capacity)
5397 			{
5398 				psModule = getModuleStat(psStructure);
5399 				//build the appropriate number of modules
5400 				for (int moduleIdx = 0; moduleIdx < capacity; moduleIdx++)
5401 				{
5402 					buildStructure(psModule, psStructure->pos.x, psStructure->pos.y, psStructure->player, true);
5403 				}
5404 			}
5405 			if (ini.contains("Factory/template"))
5406 			{
5407 				int templId(ini.value("Factory/template").toInt());
5408 				psFactory->psSubject = getTemplateFromMultiPlayerID(templId);
5409 			}
5410 			if (ini.contains("Factory/assemblyPoint/pos"))
5411 			{
5412 				Position point = ini.vector3i("Factory/assemblyPoint/pos");
5413 				setAssemblyPoint(psFactory->psAssemblyPoint, point.x, point.y, player, true);
5414 				psFactory->psAssemblyPoint->selected = ini.value("Factory/assemblyPoint/selected", false).toBool();
5415 			}
5416 			if (ini.contains("Factory/assemblyPoint/number"))
5417 			{
5418 				psFactory->psAssemblyPoint->factoryInc = ini.value("Factory/assemblyPoint/number", 42).toInt();
5419 			}
5420 			if (player == productionPlayer)
5421 			{
5422 				for (int runNum = 0; runNum < ini.value("Factory/productionRuns", 0).toInt(); runNum++)
5423 				{
5424 					ProductionRunEntry currentProd;
5425 					currentProd.quantity = ini.value("Factory/Run/" + WzString::number(runNum) + "/quantity").toInt();
5426 					currentProd.built = ini.value("Factory/Run/" + WzString::number(runNum) + "/built").toInt();
5427 					if (ini.contains("Factory/Run/" + WzString::number(runNum) + "/template"))
5428 					{
5429 						int tid = ini.value("Factory/Run/" + WzString::number(runNum) + "/template").toInt();
5430 						DROID_TEMPLATE *psTempl = getTemplateFromMultiPlayerID(tid);
5431 						currentProd.psTemplate = psTempl;
5432 						ASSERT(psTempl, "No template found for template ID %d for %s (%d)", tid, objInfo(psStructure), id);
5433 					}
5434 					if (psFactory->psAssemblyPoint->factoryInc >= asProductionRun[psFactory->psAssemblyPoint->factoryType].size())
5435 					{
5436 						asProductionRun[psFactory->psAssemblyPoint->factoryType].resize(psFactory->psAssemblyPoint->factoryInc + 1);
5437 					}
5438 					asProductionRun[psFactory->psAssemblyPoint->factoryType][psFactory->psAssemblyPoint->factoryInc].push_back(currentProd);
5439 				}
5440 			}
5441 			break;
5442 		case REF_RESEARCH:
5443 			psResearch = ((RESEARCH_FACILITY *)psStructure->pFunctionality);
5444 			//adjust the module structures IMD
5445 			if (capacity)
5446 			{
5447 				psModule = getModuleStat(psStructure);
5448 				buildStructure(psModule, psStructure->pos.x, psStructure->pos.y, psStructure->player, true);
5449 			}
5450 			//clear subject
5451 			psResearch->psSubject = nullptr;
5452 			psResearch->timeStartHold = 0;
5453 			//set the subject
5454 			if (ini.contains("Research/target"))
5455 			{
5456 				researchId = getResearchIdFromName(ini.value("Research/target").toWzString());
5457 				if (researchId != NULL_ID)
5458 				{
5459 					psResearch->psSubject = &asResearch[researchId];
5460 					psResearch->timeStartHold = ini.value("Research/timeStartHold").toInt();
5461 				}
5462 				else
5463 				{
5464 					debug(LOG_ERROR, "Failed to look up research target %s", ini.value("Research/target").toWzString().toUtf8().c_str());
5465 				}
5466 			}
5467 			break;
5468 		case REF_POWER_GEN:
5469 			// adjust the module structures IMD
5470 			if (capacity)
5471 			{
5472 				psModule = getModuleStat(psStructure);
5473 				buildStructure(psModule, psStructure->pos.x, psStructure->pos.y, psStructure->player, true);
5474 			}
5475 			break;
5476 		case REF_RESOURCE_EXTRACTOR:
5477 			break;
5478 		case REF_REPAIR_FACILITY:
5479 			psRepair = ((REPAIR_FACILITY *)psStructure->pFunctionality);
5480 			if (ini.contains("Repair/deliveryPoint/pos"))
5481 			{
5482 				Position point = ini.vector3i("Repair/deliveryPoint/pos");
5483 				setAssemblyPoint(psRepair->psDeliveryPoint, point.x, point.y, player, true);
5484 				psRepair->psDeliveryPoint->selected = ini.value("Repair/deliveryPoint/selected", false).toBool();
5485 			}
5486 			break;
5487 		case REF_REARM_PAD:
5488 			psReArmPad = ((REARM_PAD *)psStructure->pFunctionality);
5489 			psReArmPad->timeStarted = ini.value("Rearm/timeStarted", psReArmPad->timeStarted).toInt();
5490 			psReArmPad->timeLastUpdated = ini.value("Rearm/timeLastUpdated", psReArmPad->timeLastUpdated).toInt();
5491 			break;
5492 		case REF_WALL:
5493 		case REF_GATE:
5494 			psStructure->pFunctionality->wall.type = ini.value("Wall/type").toInt();
5495 			psStructure->sDisplay.imd = psStructure->pStructureType->pIMD[std::min<unsigned>(psStructure->pFunctionality->wall.type, psStructure->pStructureType->pIMD.size() - 1)];
5496 			break;
5497 		default:
5498 			break;
5499 		}
5500 		psStructure->body = healthValue(ini, structureBody(psStructure));
5501 		psStructure->currentBuildPts = ini.value("currentBuildPts", structureBuildPointsToCompletion(*psStructure)).toInt();
5502 		if (psStructure->status == SS_BUILT)
5503 		{
5504 			switch (psStructure->pStructureType->type)
5505 			{
5506 			case REF_POWER_GEN:
5507 				checkForResExtractors(psStructure);
5508 				if (selectedPlayer == psStructure->player)
5509 				{
5510 					audio_PlayObjStaticTrack(psStructure, ID_SOUND_POWER_HUM);
5511 				}
5512 				break;
5513 			case REF_RESOURCE_EXTRACTOR:
5514 				checkForPowerGen(psStructure);
5515 				break;
5516 			default:
5517 				//do nothing for factories etc
5518 				break;
5519 			}
5520 		}
5521 		// weapons
5522 		for (int j = 0; j < psStructure->pStructureType->numWeaps; j++)
5523 		{
5524 			if (psStructure->asWeaps[j].nStat > 0)
5525 			{
5526 				psStructure->asWeaps[j].ammo = ini.value("ammo/" + WzString::number(j)).toInt();
5527 				psStructure->asWeaps[j].lastFired = ini.value("lastFired/" + WzString::number(j)).toInt();
5528 				psStructure->asWeaps[j].shotsFired = ini.value("shotsFired/" + WzString::number(j)).toInt();
5529 				psStructure->asWeaps[j].rot = ini.vector3i("rotation/" + WzString::number(j));
5530 			}
5531 		}
5532 		psStructure->status = (STRUCT_STATES)ini.value("status", SS_BUILT).toInt();
5533 		if (psStructure->status == SS_BUILT)
5534 		{
5535 			buildingComplete(psStructure);
5536 		}
5537 		ini.endGroup();
5538 	}
5539 	resetFactoryNumFlag();	//reset flags into the masks
5540 
5541 	return true;
5542 }
5543 
5544 // -----------------------------------------------------------------------------------------
5545 /*
5546 Writes some version info
5547 */
writeGameInfo(const char * pFileName)5548 bool writeGameInfo(const char *pFileName)
5549 {
5550 	WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
5551 	char ourtime[100] = {'\0'};
5552 	const time_t currentTime = time(nullptr);
5553 	std::string time(ctime(&currentTime));
5554 
5555 	ini.beginGroup("GameProperties");
5556 	ini.setValue("current_time", time.data());
5557 	getAsciiTime(ourtime, graphicsTime);
5558 	ini.setValue("graphics_time", ourtime);
5559 	getAsciiTime(ourtime, gameTime);
5560 	ini.setValue("game_time", ourtime);
5561 	getAsciiTime(ourtime, gameTime - missionData.missionStarted);
5562 	ini.setValue("playing_time", ourtime);
5563 	ini.setValue("version", version_getVersionString());
5564 	ini.setValue("full_version", version_getFormattedVersionString());
5565 	ini.setValue("cheated", Cheated);
5566 	ini.setValue("debug", getDebugMappingStatus());
5567 	ini.setValue("level/map", getLevelName());
5568 	ini.setValue("mods", !getModList().empty() ? getModList().c_str() : "None");
5569 	auto backendInfo = gfx_api::context::get().getBackendGameInfo();
5570 	for (auto& kv : backendInfo)
5571 	{
5572 		ini.setValue(WzString::fromUtf8(kv.first), WzString::fromUtf8(kv.second));
5573 	}
5574 	ini.endGroup();
5575 	return true;
5576 }
5577 
5578 /*
5579 Writes the linked list of structure for each player to a file
5580 */
writeStructFile(const char * pFileName)5581 bool writeStructFile(const char *pFileName)
5582 {
5583 	WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
5584 	int counter = 0;
5585 
5586 	for (int player = 0; player < MAX_PLAYERS; player++)
5587 	{
5588 		for (STRUCTURE *psCurr = apsStructLists[player]; psCurr != nullptr; psCurr = psCurr->psNext)
5589 		{
5590 			ini.beginGroup("structure_" + (WzString::number(counter++).leftPadToMinimumLength(WzUniCodepoint::fromASCII('0'), 10)));  // Zero padded so that alphabetical sort works.
5591 			ini.setValue("name", psCurr->pStructureType->id);
5592 
5593 			writeSaveObject(ini, psCurr);
5594 
5595 			if (psCurr->resistance > 0)
5596 			{
5597 				ini.setValue("resistance", psCurr->resistance);
5598 			}
5599 			if (psCurr->status != SS_BUILT)
5600 			{
5601 				ini.setValue("status", psCurr->status);
5602 			}
5603 			ini.setValue("weapons", psCurr->numWeaps);
5604 			for (unsigned j = 0; j < psCurr->numWeaps; j++)
5605 			{
5606 				ini.setValue("parts/weapon/" + WzString::number(j + 1), (asWeaponStats + psCurr->asWeaps[j].nStat)->id);
5607 				if (psCurr->asWeaps[j].nStat > 0)
5608 				{
5609 					ini.setValue("ammo/" + WzString::number(j), psCurr->asWeaps[j].ammo);
5610 					ini.setValue("lastFired/" + WzString::number(j), psCurr->asWeaps[j].lastFired);
5611 					ini.setValue("shotsFired/" + WzString::number(j), psCurr->asWeaps[j].shotsFired);
5612 					ini.setVector3i("rotation/" + WzString::number(j), toVector(psCurr->asWeaps[j].rot));
5613 				}
5614 			}
5615 			for (unsigned i = 0; i < psCurr->numWeaps; i++)
5616 			{
5617 				if (psCurr->psTarget[i] && !psCurr->psTarget[i]->died)
5618 				{
5619 					ini.setValue("target/" + WzString::number(i) + "/id", psCurr->psTarget[i]->id);
5620 					ini.setValue("target/" + WzString::number(i) + "/player", psCurr->psTarget[i]->player);
5621 					ini.setValue("target/" + WzString::number(i) + "/type", psCurr->psTarget[i]->type);
5622 #ifdef DEBUG
5623 					ini.setValue("target/" + WzString::number(i) + "/debugfunc", WzString::fromUtf8(psCurr->targetFunc[i]));
5624 					ini.setValue("target/" + WzString::number(i) + "/debugline", psCurr->targetLine[i]);
5625 #endif
5626 				}
5627 			}
5628 			ini.setValue("currentBuildPts", psCurr->currentBuildPts);
5629 			if (psCurr->pFunctionality)
5630 			{
5631 				if (psCurr->pStructureType->type == REF_FACTORY || psCurr->pStructureType->type == REF_CYBORG_FACTORY
5632 				    || psCurr->pStructureType->type == REF_VTOL_FACTORY)
5633 				{
5634 					FACTORY *psFactory = (FACTORY *)psCurr->pFunctionality;
5635 					ini.setValue("modules", psCurr->capacity);
5636 					ini.setValue("Factory/productionLoops", psFactory->productionLoops);
5637 					ini.setValue("Factory/timeStarted", psFactory->timeStarted);
5638 					ini.setValue("Factory/buildPointsRemaining", psFactory->buildPointsRemaining);
5639 					ini.setValue("Factory/timeStartHold", psFactory->timeStartHold);
5640 					ini.setValue("Factory/loopsPerformed", psFactory->loopsPerformed);
5641 					// statusPending and pendingCount belong to the GUI, not the game state.
5642 					ini.setValue("Factory/secondaryOrder", psFactory->secondaryOrder);
5643 
5644 					if (psFactory->psSubject != nullptr)
5645 					{
5646 						ini.setValue("Factory/template", psFactory->psSubject->multiPlayerID);
5647 					}
5648 					FLAG_POSITION *psFlag = ((FACTORY *)psCurr->pFunctionality)->psAssemblyPoint;
5649 					if (psFlag != nullptr)
5650 					{
5651 						ini.setVector3i("Factory/assemblyPoint/pos", psFlag->coords);
5652 						if (psFlag->selected)
5653 						{
5654 							ini.setValue("Factory/assemblyPoint/selected", psFlag->selected);
5655 						}
5656 						ini.setValue("Factory/assemblyPoint/number", psFlag->factoryInc);
5657 					}
5658 					if (psFactory->psCommander)
5659 					{
5660 						ini.setValue("Factory/commander/id", psFactory->psCommander->id);
5661 						ini.setValue("Factory/commander/player", psFactory->psCommander->player);
5662 					}
5663 					ini.setValue("Factory/secondaryOrder", psFactory->secondaryOrder);
5664 					if (player == productionPlayer)
5665 					{
5666 						ProductionRun emptyRun;
5667 						bool haveRun = psFactory->psAssemblyPoint->factoryInc < asProductionRun[psFactory->psAssemblyPoint->factoryType].size();
5668 						ProductionRun const &productionRun = haveRun ? asProductionRun[psFactory->psAssemblyPoint->factoryType][psFactory->psAssemblyPoint->factoryInc] : emptyRun;
5669 						ini.setValue("Factory/productionRuns", (int)productionRun.size());
5670 						for (size_t runNum = 0; runNum < productionRun.size(); runNum++)
5671 						{
5672 							ProductionRunEntry psCurrentProd = productionRun.at(runNum);
5673 							ini.setValue("Factory/Run/" + WzString::number(runNum) + "/quantity", psCurrentProd.quantity);
5674 							ini.setValue("Factory/Run/" + WzString::number(runNum) + "/built", psCurrentProd.built);
5675 							if (psCurrentProd.psTemplate) ini.setValue("Factory/Run/" + WzString::number(runNum) + "/template",
5676 										psCurrentProd.psTemplate->multiPlayerID);
5677 						}
5678 					}
5679 					else
5680 					{
5681 						ini.setValue("Factory/productionRuns", 0);
5682 					}
5683 				}
5684 				else if (psCurr->pStructureType->type == REF_RESEARCH)
5685 				{
5686 					ini.setValue("modules", psCurr->capacity);
5687 					ini.setValue("Research/timeStartHold", ((RESEARCH_FACILITY *)psCurr->pFunctionality)->timeStartHold);
5688 					if (((RESEARCH_FACILITY *)psCurr->pFunctionality)->psSubject)
5689 					{
5690 						ini.setValue("Research/target", ((RESEARCH_FACILITY *)psCurr->pFunctionality)->psSubject->id);
5691 					}
5692 				}
5693 				else if (psCurr->pStructureType->type == REF_POWER_GEN)
5694 				{
5695 					ini.setValue("modules", psCurr->capacity);
5696 				}
5697 				else if (psCurr->pStructureType->type == REF_REPAIR_FACILITY)
5698 				{
5699 					REPAIR_FACILITY *psRepair = ((REPAIR_FACILITY *)psCurr->pFunctionality);
5700 					if (psRepair->psObj)
5701 					{
5702 						ini.setValue("Repair/target/id", psRepair->psObj->id);
5703 						ini.setValue("Repair/target/player", psRepair->psObj->player);
5704 						ini.setValue("Repair/target/type", psRepair->psObj->type);
5705 					}
5706 					FLAG_POSITION *psFlag = psRepair->psDeliveryPoint;
5707 					if (psFlag)
5708 					{
5709 						ini.setVector3i("Repair/deliveryPoint/pos", psFlag->coords);
5710 						if (psFlag->selected)
5711 						{
5712 							ini.setValue("Repair/deliveryPoint/selected", psFlag->selected);
5713 						}
5714 					}
5715 				}
5716 				else if (psCurr->pStructureType->type == REF_REARM_PAD)
5717 				{
5718 					REARM_PAD *psReArmPad = ((REARM_PAD *)psCurr->pFunctionality);
5719 					ini.setValue("Rearm/timeStarted", psReArmPad->timeStarted);
5720 					ini.setValue("Rearm/timeLastUpdated", psReArmPad->timeLastUpdated);
5721 					if (psReArmPad->psObj)
5722 					{
5723 						ini.setValue("Rearm/target/id", psReArmPad->psObj->id);
5724 						ini.setValue("Rearm/target/player", psReArmPad->psObj->player);
5725 						ini.setValue("Rearm/target/type", psReArmPad->psObj->type);
5726 					}
5727 				}
5728 				else if (psCurr->pStructureType->type == REF_WALL || psCurr->pStructureType->type == REF_GATE)
5729 				{
5730 					ini.setValue("Wall/type", psCurr->pFunctionality->wall.type);
5731 				}
5732 			}
5733 			ini.endGroup();
5734 		}
5735 	}
5736 	return true;
5737 }
5738 
5739 // -----------------------------------------------------------------------------------------
loadSaveStructurePointers(const WzString & filename,STRUCTURE ** ppList)5740 bool loadSaveStructurePointers(const WzString& filename, STRUCTURE **ppList)
5741 {
5742 	WzConfig ini(filename, WzConfig::ReadOnly);
5743 	std::vector<WzString> list = ini.childGroups();
5744 
5745 	for (size_t i = 0; i < list.size(); ++i)
5746 	{
5747 		ini.beginGroup(list[i]);
5748 		STRUCTURE *psStruct;
5749 		int player = getPlayer(ini);
5750 		int id = ini.value("id", -1).toInt();
5751 		for (psStruct = ppList[player]; psStruct && psStruct->id != id; psStruct = psStruct->psNext) { }
5752 		if (!psStruct)
5753 		{
5754 			ini.endGroup();
5755 			continue;	// it is not unusual for a structure to 'disappear' like this; it can happen eg because of module upgrades
5756 		}
5757 		for (int j = 0; j < MAX_WEAPONS; j++)
5758 		{
5759 			objTrace(psStruct->id, "weapon %d, nStat %d", j, psStruct->asWeaps[j].nStat);
5760 			if (ini.contains("target/" + WzString::number(j) + "/id"))
5761 			{
5762 				int tid = ini.value("target/" + WzString::number(j) + "/id", -1).toInt();
5763 				int tplayer = ini.value("target/" + WzString::number(j) + "/player", -1).toInt();
5764 				OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value("target/" + WzString::number(j) + "/type", 0).toInt();
5765 				ASSERT(tid >= 0 && tplayer >= 0, "Bad ID");
5766 				setStructureTarget(psStruct, getBaseObjFromData(tid, tplayer, ttype), j, ORIGIN_UNKNOWN);
5767 				ASSERT(psStruct->psTarget[j], "Failed to find target");
5768 			}
5769 			if (ini.contains("Factory/commander/id"))
5770 			{
5771 				ASSERT(psStruct->pStructureType->type == REF_FACTORY || psStruct->pStructureType->type == REF_CYBORG_FACTORY
5772 				       || psStruct->pStructureType->type == REF_VTOL_FACTORY, "Bad type");
5773 				FACTORY *psFactory = (FACTORY *)psStruct->pFunctionality;
5774 				OBJECT_TYPE ttype = OBJ_DROID;
5775 				int tid = ini.value("Factory/commander/id", -1).toInt();
5776 				int tplayer = ini.value("Factory/commander/player", -1).toInt();
5777 				ASSERT(tid >= 0 && tplayer >= 0, "Bad commander ID %d for player %d for building %d", tid, tplayer, id);
5778 				DROID *psCommander = (DROID *)getBaseObjFromData(tid, tplayer, ttype);
5779 				ASSERT(psCommander, "Commander %d not found for building %d", tid, id);
5780 				if (ppList == mission.apsStructLists)
5781 				{
5782 					psFactory->psCommander = psCommander;
5783 				}
5784 				else
5785 				{
5786 					assignFactoryCommandDroid(psStruct, psCommander);
5787 				}
5788 			}
5789 			if (ini.contains("Repair/target/id"))
5790 			{
5791 				ASSERT(psStruct->pStructureType->type == REF_REPAIR_FACILITY, "Bad type");
5792 				REPAIR_FACILITY *psRepair = ((REPAIR_FACILITY *)psStruct->pFunctionality);
5793 				OBJECT_TYPE ttype = (OBJECT_TYPE)ini.value("Repair/target/type", OBJ_DROID).toInt();
5794 				int tid = ini.value("Repair/target/id", -1).toInt();
5795 				int tplayer = ini.value("Repair/target/player", -1).toInt();
5796 				ASSERT(tid >= 0 && tplayer >= 0, "Bad repair ID %d for player %d for building %d", tid, tplayer, id);
5797 				psRepair->psObj = getBaseObjFromData(tid, tplayer, ttype);
5798 				ASSERT(psRepair->psObj, "Repair target %d not found for building %d", tid, id);
5799 			}
5800 			if (ini.contains("Rearm/target/id"))
5801 			{
5802 				ASSERT(psStruct->pStructureType->type == REF_REARM_PAD, "Bad type");
5803 				REARM_PAD *psReArmPad = ((REARM_PAD *)psStruct->pFunctionality);
5804 				OBJECT_TYPE ttype = OBJ_DROID; // always, for now
5805 				int tid = ini.value("Rearm/target/id", -1).toInt();
5806 				int tplayer = ini.value("Rearm/target/player", -1).toInt();
5807 				ASSERT(tid >= 0 && tplayer >= 0, "Bad rearm ID %d for player %d for building %d", tid, tplayer, id);
5808 				psReArmPad->psObj = getBaseObjFromData(tid, tplayer, ttype);
5809 				ASSERT(psReArmPad->psObj, "Rearm target %d not found for building %d", tid, id);
5810 			}
5811 		}
5812 		ini.endGroup();
5813 	}
5814 	return true;
5815 }
5816 
5817 // -----------------------------------------------------------------------------------------
loadSaveFeature(char * pFileData,UDWORD filesize)5818 bool loadSaveFeature(char *pFileData, UDWORD filesize)
5819 {
5820 	FEATURE_SAVEHEADER		*psHeader;
5821 	SAVE_FEATURE_V14			*psSaveFeature;
5822 	FEATURE					*pFeature;
5823 	UDWORD					count, i, statInc;
5824 	FEATURE_STATS			*psStats = nullptr;
5825 	bool					found;
5826 	UDWORD					sizeOfSaveFeature;
5827 
5828 	/* Check the file type */
5829 	psHeader = (FEATURE_SAVEHEADER *)pFileData;
5830 	if (psHeader->aFileType[0] != 'f' || psHeader->aFileType[1] != 'e' ||
5831 	    psHeader->aFileType[2] != 'a' || psHeader->aFileType[3] != 't')
5832 	{
5833 		debug(LOG_ERROR, "loadSaveFeature: Incorrect file type");
5834 		return false;
5835 	}
5836 
5837 	/* FEATURE_SAVEHEADER */
5838 	endian_udword(&psHeader->version);
5839 	endian_udword(&psHeader->quantity);
5840 
5841 	debug(LOG_SAVE, "Feature file version is %u ", psHeader->version);
5842 
5843 	//increment to the start of the data
5844 	pFileData += FEATURE_HEADER_SIZE;
5845 
5846 	/* Check the file version */
5847 	if (psHeader->version < VERSION_7 || psHeader->version > VERSION_19)
5848 	{
5849 		debug(LOG_ERROR, "Unsupported save format version %u", psHeader->version);
5850 		return false;
5851 	}
5852 	if (psHeader->version < VERSION_14)
5853 	{
5854 		sizeOfSaveFeature = sizeof(SAVE_FEATURE_V2);
5855 	}
5856 	else
5857 	{
5858 		sizeOfSaveFeature = sizeof(SAVE_FEATURE_V14);
5859 	}
5860 	if ((sizeOfSaveFeature * psHeader->quantity + FEATURE_HEADER_SIZE) > filesize)
5861 	{
5862 		debug(LOG_ERROR, "featureLoad: unexpected end of file");
5863 		return false;
5864 	}
5865 
5866 	/* Load in the feature data */
5867 	for (count = 0; count < psHeader->quantity; count ++, pFileData += sizeOfSaveFeature)
5868 	{
5869 		psSaveFeature = (SAVE_FEATURE_V14 *) pFileData;
5870 
5871 		/* FEATURE_SAVE_V14 is FEATURE_SAVE_V2 */
5872 		/* FEATURE_SAVE_V2 is OBJECT_SAVE_V19 */
5873 		/* OBJECT_SAVE_V19 */
5874 		endian_udword(&psSaveFeature->id);
5875 		endian_udword(&psSaveFeature->x);
5876 		endian_udword(&psSaveFeature->y);
5877 		endian_udword(&psSaveFeature->z);
5878 		endian_udword(&psSaveFeature->direction);
5879 		endian_udword(&psSaveFeature->player);
5880 		endian_udword(&psSaveFeature->periodicalDamageStart);
5881 		endian_udword(&psSaveFeature->periodicalDamage);
5882 
5883 		//get the stats for this feature
5884 		found = false;
5885 
5886 		for (statInc = 0; statInc < numFeatureStats; statInc++)
5887 		{
5888 			psStats = asFeatureStats + statInc;
5889 			//loop until find the same name
5890 			if (psStats->id.compare(psSaveFeature->name) == 0)
5891 			{
5892 				found = true;
5893 				break;
5894 			}
5895 		}
5896 		//if haven't found the feature - ignore this record!
5897 		if (!found)
5898 		{
5899 			debug(LOG_ERROR, "This feature no longer exists - %s", psSaveFeature->name);
5900 			//ignore this
5901 			continue;
5902 		}
5903 		//create the Feature
5904 		pFeature = buildFeature(psStats, psSaveFeature->x, psSaveFeature->y, true);
5905 		if (!pFeature)
5906 		{
5907 			debug(LOG_ERROR, "Unable to create feature %s", psSaveFeature->name);
5908 			continue;
5909 		}
5910 		if (pFeature->psStats->subType == FEAT_OIL_RESOURCE)
5911 		{
5912 			scriptSetDerrickPos(pFeature->pos.x, pFeature->pos.y);
5913 		}
5914 		//restore values
5915 		pFeature->id = psSaveFeature->id;
5916 		pFeature->rot.direction = DEG(psSaveFeature->direction);
5917 		pFeature->periodicalDamage = psSaveFeature->periodicalDamage;
5918 		if (psHeader->version >= VERSION_14)
5919 		{
5920 			for (i = 0; i < MAX_PLAYERS; i++)
5921 			{
5922 				pFeature->visible[i] = psSaveFeature->visible[i];
5923 			}
5924 		}
5925 	}
5926 
5927 	return true;
5928 }
5929 
loadScriptFeature(ScriptMapData const & data)5930 static bool loadScriptFeature(ScriptMapData const &data)
5931 {
5932 	if (!data.valid)
5933 	{
5934 		return false;
5935 	}
5936 
5937 	for (auto &feature : data.features)
5938 	{
5939 		auto psStats = std::find_if(asFeatureStats, asFeatureStats + numFeatureStats, [&](FEATURE_STATS &stat) { return stat.id == feature.name; });
5940 		if (psStats == asFeatureStats + numFeatureStats)
5941 		{
5942 			debug(LOG_ERROR, "Feature type \"%s\" unknown", feature.name.toStdString().c_str());
5943 			continue;  // ignore this
5944 		}
5945 		// Create the Feature
5946 		auto pFeature = buildFeature(psStats, feature.position.x, feature.position.y, true);
5947 		if (!pFeature)
5948 		{
5949 			debug(LOG_ERROR, "Unable to create feature %s", feature.name.toStdString().c_str());
5950 			continue;
5951 		}
5952 		if (pFeature->psStats->subType == FEAT_OIL_RESOURCE)
5953 		{
5954 			scriptSetDerrickPos(pFeature->pos.x, pFeature->pos.y);
5955 		}
5956 		//restore values
5957 		pFeature->id = generateSynchronisedObjectId();
5958 		pFeature->rot.direction = feature.direction;
5959 	}
5960 
5961 	return true;
5962 }
5963 
loadSaveFeature2(const char * pFileName)5964 bool loadSaveFeature2(const char *pFileName)
5965 {
5966 	if (!PHYSFS_exists(pFileName))
5967 	{
5968 		debug(LOG_SAVE, "No %s found -- use fallback method", pFileName);
5969 		return false;
5970 	}
5971 	WzConfig ini(pFileName, WzConfig::ReadOnly);
5972 	std::vector<WzString> list = ini.childGroups();
5973 	debug(LOG_SAVE, "Loading new style features (%zu found)", list.size());
5974 
5975 	for (size_t i = 0; i < list.size(); ++i)
5976 	{
5977 		FEATURE *pFeature;
5978 		ini.beginGroup(list[i]);
5979 		WzString name = ini.string("name");
5980 		Position pos = ini.vector3i("position");
5981 		int statInc;
5982 		bool found = false;
5983 		FEATURE_STATS *psStats = nullptr;
5984 
5985 		//get the stats for this feature
5986 		for (statInc = 0; statInc < numFeatureStats; statInc++)
5987 		{
5988 			psStats = asFeatureStats + statInc;
5989 			//loop until find the same name
5990 			if (psStats->id.compare(name) == 0)
5991 			{
5992 				found = true;
5993 				break;
5994 			}
5995 		}
5996 		//if haven't found the feature - ignore this record!
5997 		if (!found)
5998 		{
5999 			debug(LOG_ERROR, "This feature no longer exists - %s", name.toUtf8().c_str());
6000 			//ignore this
6001 			continue;
6002 		}
6003 		//create the Feature
6004 		pFeature = buildFeature(psStats, pos.x, pos.y, true);
6005 		if (!pFeature)
6006 		{
6007 			debug(LOG_ERROR, "Unable to create feature %s", name.toUtf8().c_str());
6008 			continue;
6009 		}
6010 		if (pFeature->psStats->subType == FEAT_OIL_RESOURCE)
6011 		{
6012 			scriptSetDerrickPos(pFeature->pos.x, pFeature->pos.y);
6013 		}
6014 		//restore values
6015 		int id = ini.value("id", -1).toInt();
6016 		if (id > 0)
6017 		{
6018 			pFeature->id = id;
6019 		}
6020 		else
6021 		{
6022 			pFeature->id = generateSynchronisedObjectId();
6023 		}
6024 		pFeature->rot = ini.vector3i("rotation");
6025 		pFeature->player = ini.value("player", PLAYER_FEATURE).toInt();
6026 
6027 		// common BASE_OBJECT info
6028 		loadSaveObject(ini, pFeature);
6029 
6030 		pFeature->body = healthValue(ini, pFeature->psStats->body);
6031 
6032 		ini.endGroup();
6033 	}
6034 	return true;
6035 }
6036 
6037 // -----------------------------------------------------------------------------------------
6038 /*
6039 Writes the linked list of features to a file
6040 */
writeFeatureFile(const char * pFileName)6041 bool writeFeatureFile(const char *pFileName)
6042 {
6043 	WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
6044 	int counter = 0;
6045 
6046 	for (FEATURE *psCurr = apsFeatureLists[0]; psCurr != nullptr; psCurr = psCurr->psNext)
6047 	{
6048 		ini.beginGroup("feature_" + (WzString::number(counter++).leftPadToMinimumLength(WzUniCodepoint::fromASCII('0'), 10)));  // Zero padded so that alphabetical sort works.
6049 		ini.setValue("name", psCurr->psStats->id);
6050 		writeSaveObject(ini, psCurr);
6051 		ini.endGroup();
6052 	}
6053 	return true;
6054 }
6055 
6056 // -----------------------------------------------------------------------------------------
loadSaveTemplate(const char * pFileName)6057 bool loadSaveTemplate(const char *pFileName)
6058 {
6059 	WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadOnly);
6060 	std::vector<WzString> list = ini.childGroups();
6061 
6062 	auto loadTemplate = [&]() {
6063 		DROID_TEMPLATE t;
6064 		if (!loadTemplateCommon(ini, t))
6065 		{
6066 			debug(LOG_ERROR, "Stored template \"%s\" contains an unknown component.", ini.string("name").toUtf8().c_str());
6067 		}
6068 		t.name = ini.string("name");
6069 		t.multiPlayerID = ini.value("multiPlayerID", generateNewObjectId()).toInt();
6070 		t.enabled = ini.value("enabled", false).toBool();
6071 		t.stored = ini.value("stored", false).toBool();
6072 		t.prefab = ini.value("prefab", false).toBool();
6073 		ini.nextArrayItem();
6074 		return t;
6075 	};
6076 
6077 	int version = ini.value("version", 0).toInt();
6078 	if (version == 0)
6079 	{
6080 		return false;
6081 	}
6082 	for (size_t i = 0; i < list.size(); ++i)
6083 	{
6084 		ini.beginGroup(list[i]);
6085 		int player = getPlayer(ini);
6086 		ini.beginArray("templates");
6087 		while (ini.remainingArrayItems() > 0)
6088 		{
6089 			addTemplate(player, std::unique_ptr<DROID_TEMPLATE>(new DROID_TEMPLATE(loadTemplate())));
6090 		}
6091 		ini.endArray();
6092 		ini.endGroup();
6093 	}
6094 
6095 	if (ini.contains("localTemplates"))
6096 	{
6097 		ini.beginArray("localTemplates");
6098 		while (ini.remainingArrayItems() > 0)
6099 		{
6100 			localTemplates.emplace_back(loadTemplate());
6101 		}
6102 		ini.endArray();
6103 	}
6104 	else
6105 	{
6106 		// Old savegame compatibility, should remove this branch sometime.
6107 		enumerateTemplates(selectedPlayer, [](DROID_TEMPLATE * psTempl) {
6108 			localTemplates.push_back(*psTempl);
6109 			return true;
6110 		});
6111 	}
6112 
6113 	return true;
6114 }
6115 
writeTemplateFile(const char * pFileName)6116 bool writeTemplateFile(const char *pFileName)
6117 {
6118 	WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6119 
6120 	auto writeTemplate = [&](DROID_TEMPLATE *psCurr) {
6121 		saveTemplateCommon(ini, psCurr);
6122 		ini.setValue("ref", psCurr->ref);
6123 		ini.setValue("multiPlayerID", psCurr->multiPlayerID);
6124 		ini.setValue("enabled", psCurr->enabled);
6125 		ini.setValue("stored", psCurr->stored);
6126 		ini.setValue("prefab", psCurr->prefab);
6127 		ini.nextArrayItem();
6128 	};
6129 
6130 	ini.setValue("version", 1);
6131 	for (int player = 0; player < MAX_PLAYERS; player++)
6132 	{
6133 		if (!apsDroidLists[player] && !apsStructLists[player])	// only write out templates of players that are still 'alive'
6134 		{
6135 			continue;
6136 		}
6137 		ini.beginGroup("player_" + WzString::number(player));
6138 		setPlayer(ini, player);
6139 		ini.beginArray("templates");
6140 		enumerateTemplates(player, [&writeTemplate](DROID_TEMPLATE* psTemplate) {
6141 			writeTemplate(psTemplate);
6142 			return true;
6143 		});
6144 		ini.endArray();
6145 		ini.endGroup();
6146 	}
6147 	ini.beginArray("localTemplates");
6148 	for (auto &psCurr : localTemplates)
6149 	{
6150 		writeTemplate(&psCurr);
6151 	}
6152 	ini.endArray();
6153 	return true;
6154 }
6155 
6156 // -----------------------------------------------------------------------------------------
6157 // load up a terrain tile type map file
6158 // note: This function modifies pFileData directly while loading! (FIXME?)
loadTerrainTypeMap(char * pFileData,UDWORD filesize)6159 bool loadTerrainTypeMap(char *pFileData, UDWORD filesize)
6160 {
6161 	TILETYPE_SAVEHEADER	*psHeader;
6162 	UDWORD				i;
6163 	UWORD				*pType;
6164 
6165 	if (filesize < TILETYPE_HEADER_SIZE)
6166 	{
6167 		debug(LOG_ERROR, "loadTerrainTypeMap: file too small");
6168 		return false;
6169 	}
6170 
6171 	// Check the header
6172 	psHeader = (TILETYPE_SAVEHEADER *)pFileData;
6173 	if (psHeader->aFileType[0] != 't' || psHeader->aFileType[1] != 't' ||
6174 	    psHeader->aFileType[2] != 'y' || psHeader->aFileType[3] != 'p')
6175 	{
6176 		debug(LOG_ERROR, "loadTerrainTypeMap: Incorrect file type");
6177 
6178 		return false;
6179 	}
6180 
6181 	/* TILETYPE_SAVEHEADER */
6182 	endian_udword(&psHeader->version);
6183 	endian_udword(&psHeader->quantity);
6184 
6185 	// reset the terrain table
6186 	memset(terrainTypes, 0, sizeof(terrainTypes));
6187 
6188 	// Load the terrain type mapping
6189 	pType = (UWORD *)(pFileData + TILETYPE_HEADER_SIZE);
6190 	endian_uword(pType);
6191 	if (psHeader->quantity >= MAX_TILE_TEXTURES)
6192 	{
6193 		// Workaround for fugly map editor bug, since we can't fix the map editor
6194 		psHeader->quantity = MAX_TILE_TEXTURES - 1;
6195 	}
6196 	for (i = 0; i < psHeader->quantity; i++)
6197 	{
6198 		if (*pType > TER_MAX)
6199 		{
6200 			debug(LOG_ERROR, "loadTerrainTypeMap: terrain type out of range");
6201 
6202 			return false;
6203 		}
6204 
6205 		terrainTypes[i] = (UBYTE) * pType;
6206 		pType++;
6207 		endian_uword(pType);
6208 	}
6209 
6210 	return true;
6211 }
6212 
loadTerrainTypeMapOverride(unsigned int tileSet)6213 bool loadTerrainTypeMapOverride(unsigned int tileSet)
6214 {
6215 	resForceBaseDir("/data/base/");
6216 	WzString iniName = "tileset/tileTypes.json";
6217 	if (!PHYSFS_exists(iniName.toUtf8().c_str()))
6218 	{
6219 		return false;
6220 	}
6221 
6222 	WzConfig ini(iniName, WzConfig::ReadOnly);
6223 	WzString tileTypeKey;
6224 
6225 	if (tileSet == ARIZONA)
6226 	{
6227 		tileTypeKey = "Arizona";
6228 	}
6229 	else if (tileSet == URBAN)
6230 	{
6231 		tileTypeKey = "Urban";
6232 	}
6233 	else if (tileSet == ROCKIE)
6234 	{
6235 		tileTypeKey = "Rockies";
6236 	}
6237 	else
6238 	{
6239 		debug(LOG_ERROR, "Unknown tile type");
6240 		resForceBaseDir("");
6241 		return false;
6242 	}
6243 
6244 	std::vector<WzString> list = ini.childGroups();
6245 	for (size_t i = 0; i < list.size(); ++i)
6246 	{
6247 		if (list[i].compare(tileTypeKey) == 0)
6248 		{
6249 			ini.beginGroup(list[i]);
6250 
6251 			debug(LOG_TERRAIN, "Looking at tileset type: %s", tileTypeKey.toUtf8().c_str());
6252 			unsigned int counter = 0;
6253 
6254 			std::vector<WzString> keys = ini.childKeys();
6255 			for (size_t j = 0; j < keys.size(); ++j)
6256 			{
6257 				unsigned int tileType = ini.value(keys.at(j)).toUInt();
6258 
6259 				if (tileType > TER_MAX)
6260 				{
6261 					debug(LOG_ERROR, "loadTerrainTypeMapOverride: terrain type out of range");
6262 					resForceBaseDir("");
6263 					return false;
6264 				}
6265 				// Workaround for fugly map editor bug, since we can't fix the map editor
6266 				if (counter > (MAX_TILE_TEXTURES - 1))
6267 				{
6268 					debug(LOG_ERROR, "loadTerrainTypeMapOverride: too many textures!");
6269 					resForceBaseDir("");
6270 					return false;
6271 				}
6272 				// Log the output for the override value.
6273 				if (terrainTypes[counter] != tileType)
6274 				{
6275 					debug(LOG_TERRAIN, "Upgrading map tile %d (type %d) to type %d", counter, terrainTypes[counter], tileType);
6276 				}
6277 				terrainTypes[counter] = tileType;
6278 				++counter;
6279 				debug(LOG_TERRAIN, "Tile %d at value: %d", counter - 1, tileType);
6280 			}
6281 			ini.endGroup();
6282 		}
6283 	}
6284 
6285 	resForceBaseDir("");
6286 
6287 	return true;
6288 }
6289 
6290 // -----------------------------------------------------------------------------------------
6291 // Write out the terrain type map
writeTerrainTypeMapFile(char * pFileName)6292 static bool writeTerrainTypeMapFile(char *pFileName)
6293 {
6294 	TILETYPE_SAVEHEADER		*psHeader;
6295 	char *pFileData;
6296 	UDWORD					fileSize, i;
6297 	UWORD					*pType;
6298 
6299 	// Calculate the file size
6300 	fileSize = TILETYPE_HEADER_SIZE + sizeof(UWORD) * MAX_TILE_TEXTURES;
6301 	pFileData = (char *)malloc(fileSize);
6302 	if (!pFileData)
6303 	{
6304 		debug(LOG_FATAL, "writeTerrainTypeMapFile: Out of memory");
6305 		abort();
6306 		return false;
6307 	}
6308 
6309 	// Put the file header on the file
6310 	psHeader = (TILETYPE_SAVEHEADER *)pFileData;
6311 	psHeader->aFileType[0] = 't';
6312 	psHeader->aFileType[1] = 't';
6313 	psHeader->aFileType[2] = 'y';
6314 	psHeader->aFileType[3] = 'p';
6315 	psHeader->version = CURRENT_VERSION_NUM;
6316 	psHeader->quantity = MAX_TILE_TEXTURES;
6317 
6318 	pType = (UWORD *)(pFileData + TILETYPE_HEADER_SIZE);
6319 	for (i = 0; i < MAX_TILE_TEXTURES; i++)
6320 	{
6321 		*pType = terrainTypes[i];
6322 		endian_uword(pType);
6323 		pType += 1;
6324 	}
6325 
6326 	/* TILETYPE_SAVEHEADER */
6327 	endian_udword(&psHeader->version);
6328 	endian_udword(&psHeader->quantity);
6329 
6330 	if (!saveFile(pFileName, pFileData, fileSize))
6331 	{
6332 		return false;
6333 	}
6334 	free(pFileData);
6335 
6336 	return true;
6337 }
6338 
6339 // -----------------------------------------------------------------------------------------
loadSaveCompList(const char * pFileName)6340 bool loadSaveCompList(const char *pFileName)
6341 {
6342 	WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadOnly);
6343 
6344 	for (int player = 0; player < MAX_PLAYERS; player++)
6345 	{
6346 		ini.beginGroup("player_" + WzString::number(player));
6347 		std::vector<WzString> list = ini.childKeys();
6348 		for (size_t i = 0; i < list.size(); ++i)
6349 		{
6350 			WzString name = list[i];
6351 			int state = ini.value(name, UNAVAILABLE).toInt();
6352 			COMPONENT_STATS *psComp = getCompStatsFromName(name);
6353 			ASSERT_OR_RETURN(false, psComp, "Bad component %s", name.toUtf8().c_str());
6354 			ASSERT_OR_RETURN(false, psComp->compType >= 0 && psComp->compType != COMP_NUMCOMPONENTS, "Bad type %d", psComp->compType);
6355 			ASSERT_OR_RETURN(false, state == UNAVAILABLE || state == AVAILABLE || state == FOUND || state == REDUNDANT,
6356 			                 "Bad state %d for %s", state, name.toUtf8().c_str());
6357 			apCompLists[player][psComp->compType][psComp->index] = state;
6358 		}
6359 		ini.endGroup();
6360 	}
6361 	return true;
6362 }
6363 
6364 // -----------------------------------------------------------------------------------------
6365 // Write out the current state of the Comp lists per player
writeCompListFile(const char * pFileName)6366 static bool writeCompListFile(const char *pFileName)
6367 {
6368 	WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
6369 
6370 	// Save each type of struct type
6371 	for (int player = 0; player < MAX_PLAYERS; player++)
6372 	{
6373 		ini.beginGroup("player_" + WzString::number(player));
6374 		for (int i = 0; i < numBodyStats; i++)
6375 		{
6376 			COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asBodyStats + i);
6377 			const int state = apCompLists[player][COMP_BODY][i];
6378 			if (state != UNAVAILABLE)
6379 			{
6380 				ini.setValue(psStats->id, state);
6381 			}
6382 		}
6383 		for (int i = 0; i < numWeaponStats; i++)
6384 		{
6385 			COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asWeaponStats + i);
6386 			const int state = apCompLists[player][COMP_WEAPON][i];
6387 			if (state != UNAVAILABLE)
6388 			{
6389 				ini.setValue(psStats->id, state);
6390 			}
6391 		}
6392 		for (int i = 0; i < numConstructStats; i++)
6393 		{
6394 			COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asConstructStats + i);
6395 			const int state = apCompLists[player][COMP_CONSTRUCT][i];
6396 			if (state != UNAVAILABLE)
6397 			{
6398 				ini.setValue(psStats->id, state);
6399 			}
6400 		}
6401 		for (int i = 0; i < numECMStats; i++)
6402 		{
6403 			COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asECMStats + i);
6404 			const int state = apCompLists[player][COMP_ECM][i];
6405 			if (state != UNAVAILABLE)
6406 			{
6407 				ini.setValue(psStats->id, state);
6408 			}
6409 		}
6410 		for (int i = 0; i < numPropulsionStats; i++)
6411 		{
6412 			COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asPropulsionStats + i);
6413 			const int state = apCompLists[player][COMP_PROPULSION][i];
6414 			if (state != UNAVAILABLE)
6415 			{
6416 				ini.setValue(psStats->id, state);
6417 			}
6418 		}
6419 		for (int i = 0; i < numSensorStats; i++)
6420 		{
6421 			COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asSensorStats + i);
6422 			const int state = apCompLists[player][COMP_SENSOR][i];
6423 			if (state != UNAVAILABLE)
6424 			{
6425 				ini.setValue(psStats->id, state);
6426 			}
6427 		}
6428 		for (int i = 0; i < numRepairStats; i++)
6429 		{
6430 			COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asRepairStats + i);
6431 			const int state = apCompLists[player][COMP_REPAIRUNIT][i];
6432 			if (state != UNAVAILABLE)
6433 			{
6434 				ini.setValue(psStats->id, state);
6435 			}
6436 		}
6437 		for (int i = 0; i < numBrainStats; i++)
6438 		{
6439 			COMPONENT_STATS *psStats = (COMPONENT_STATS *)(asBrainStats + i);
6440 			const int state = apCompLists[player][COMP_BRAIN][i];
6441 			if (state != UNAVAILABLE)
6442 			{
6443 				ini.setValue(psStats->id, state);
6444 			}
6445 		}
6446 		ini.endGroup();
6447 	}
6448 	return true;
6449 }
6450 
6451 // -----------------------------------------------------------------------------------------
6452 // load up structure type list file
loadSaveStructTypeList(const char * pFileName)6453 static bool loadSaveStructTypeList(const char *pFileName)
6454 {
6455 	WzConfig ini(pFileName, WzConfig::ReadOnly);
6456 
6457 	for (int player = 0; player < MAX_PLAYERS; player++)
6458 	{
6459 		ini.beginGroup("player_" + WzString::number(player));
6460 		std::vector<WzString> list = ini.childKeys();
6461 		for (size_t i = 0; i < list.size(); ++i)
6462 		{
6463 			WzString name = list[i];
6464 			int state = ini.value(name, UNAVAILABLE).toInt();
6465 			int statInc;
6466 
6467 			ASSERT_OR_RETURN(false, state == UNAVAILABLE || state == AVAILABLE || state == FOUND || state == REDUNDANT,
6468 			                 "Bad state %d for %s", state, name.toUtf8().c_str());
6469 			for (statInc = 0; statInc < numStructureStats; statInc++) // loop until find the same name
6470 			{
6471 				STRUCTURE_STATS *psStats = asStructureStats + statInc;
6472 
6473 				if (name.compare(psStats->id) == 0)
6474 				{
6475 					apStructTypeLists[player][statInc] = state;
6476 					break;
6477 				}
6478 			}
6479 			ASSERT_OR_RETURN(false, statInc != numStructureStats, "Did not find structure %s", name.toUtf8().c_str());
6480 		}
6481 		ini.endGroup();
6482 	}
6483 	return true;
6484 }
6485 
6486 // -----------------------------------------------------------------------------------------
6487 // Write out the current state of the Struct Type List per player
writeStructTypeListFile(const char * pFileName)6488 static bool writeStructTypeListFile(const char *pFileName)
6489 {
6490 	WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6491 
6492 	// Save each type of struct type
6493 	for (int player = 0; player < MAX_PLAYERS; player++)
6494 	{
6495 		ini.beginGroup("player_" + WzString::number(player));
6496 		STRUCTURE_STATS *psStats = asStructureStats;
6497 		for (int i = 0; i < numStructureStats; i++, psStats++)
6498 		{
6499 			if (apStructTypeLists[player][i] != UNAVAILABLE)
6500 			{
6501 				ini.setValue(psStats->id, apStructTypeLists[player][i]);
6502 			}
6503 		}
6504 		ini.endGroup();
6505 	}
6506 	return true;
6507 }
6508 
6509 // -----------------------------------------------------------------------------------------
6510 // load up saved research file
loadSaveResearch(const char * pFileName)6511 bool loadSaveResearch(const char *pFileName)
6512 {
6513 	WzConfig ini(pFileName, WzConfig::ReadOnly);
6514 	const int players = game.maxPlayers;
6515 	std::vector<WzString> list = ini.childGroups();
6516 	for (size_t i = 0; i < list.size(); ++i)
6517 	{
6518 		ini.beginGroup(list[i]);
6519 		bool found = false;
6520 		WzString name = ini.value("name").toWzString();
6521 		int statInc;
6522 		for (statInc = 0; statInc < asResearch.size(); statInc++)
6523 		{
6524 			RESEARCH *psStats = &asResearch[statInc];
6525 			//loop until find the same name
6526 			if (psStats->id.compare(name) == 0)
6527 
6528 			{
6529 				found = true;
6530 				break;
6531 			}
6532 		}
6533 		if (!found)
6534 		{
6535 			//ignore this record
6536 			continue;
6537 		}
6538 		auto researchedList = ini.value("researched").jsonValue();
6539 		auto possiblesList = ini.value("possible").jsonValue();
6540 		auto pointsList = ini.value("currentPoints").jsonValue();
6541 		ASSERT(researchedList.is_array(), "Bad (non-array) researched list for %s", name.toUtf8().c_str());
6542 		ASSERT(possiblesList.is_array(), "Bad (non-array) possible list for %s", name.toUtf8().c_str());
6543 		ASSERT(pointsList.is_array(), "Bad (non-array) points list for %s", name.toUtf8().c_str());
6544 		ASSERT(researchedList.size() == players, "Bad researched list for %s", name.toUtf8().c_str());
6545 		ASSERT(possiblesList.size() == players, "Bad possible list for %s", name.toUtf8().c_str());
6546 		ASSERT(pointsList.size() == players, "Bad points list for %s", name.toUtf8().c_str());
6547 		for (int plr = 0; plr < players; plr++)
6548 		{
6549 			PLAYER_RESEARCH *psPlRes;
6550 			int researched = json_getValue(researchedList, plr).toInt();
6551 			int possible = json_getValue(possiblesList, plr).toInt();
6552 			int points = json_getValue(pointsList, plr).toInt();
6553 
6554 			psPlRes = &asPlayerResList[plr][statInc];
6555 			// Copy the research status
6556 			psPlRes->ResearchStatus = (researched & RESBITS);
6557 			SetResearchPossible(psPlRes, possible);
6558 			psPlRes->currentPoints = points;
6559 			//for any research that has been completed - perform so that upgrade values are set up
6560 			if (researched == RESEARCHED)
6561 			{
6562 				researchResult(statInc, plr, false, nullptr, false);
6563 			}
6564 		}
6565 		ini.endGroup();
6566 	}
6567 	return true;
6568 }
6569 
6570 // -----------------------------------------------------------------------------------------
6571 // Write out the current state of the Research per player
writeResearchFile(char * pFileName)6572 static bool writeResearchFile(char *pFileName)
6573 {
6574 	WzConfig ini(WzString::fromUtf8(pFileName), WzConfig::ReadAndWrite);
6575 
6576 	for (size_t i = 0; i < asResearch.size(); ++i)
6577 	{
6578 		RESEARCH *psStats = &asResearch[i];
6579 		bool valid = false;
6580 		std::vector<WzString> possibles, researched, points;
6581 		for (int player = 0; player < game.maxPlayers; player++)
6582 		{
6583 			possibles.push_back(WzString::number(GetResearchPossible(&asPlayerResList[player][i])));
6584 			researched.push_back(WzString::number(asPlayerResList[player][i].ResearchStatus & RESBITS));
6585 			points.push_back(WzString::number(asPlayerResList[player][i].currentPoints));
6586 			if (IsResearchPossible(&asPlayerResList[player][i]) || (asPlayerResList[player][i].ResearchStatus & RESBITS) || asPlayerResList[player][i].currentPoints)
6587 			{
6588 				valid = true;	// write this entry
6589 			}
6590 		}
6591 		if (valid)
6592 		{
6593 			ini.beginGroup("research_" + WzString::number(i));
6594 			ini.setValue("name", psStats->id);
6595 			ini.setValue("possible", possibles);
6596 			ini.setValue("researched", researched);
6597 			ini.setValue("currentPoints", points);
6598 			ini.endGroup();
6599 		}
6600 	}
6601 	return true;
6602 }
6603 
6604 
6605 // -----------------------------------------------------------------------------------------
6606 // load up saved message file
loadSaveMessage(const char * pFileName,LEVEL_TYPE levelType)6607 bool loadSaveMessage(const char* pFileName, LEVEL_TYPE levelType)
6608 {
6609 	// Only clear the messages if its a mid save game
6610 	if (gameType == GTYPE_SAVE_MIDMISSION)
6611 	{
6612 		freeMessages();
6613 	}
6614 	else if (gameType == GTYPE_SAVE_START)
6615 	{
6616 		// If we are loading in a CamStart or a CamChange then we are not interested in any saved messages
6617 		if (levelType == LEVEL_TYPE::LDS_CAMSTART || levelType == LEVEL_TYPE::LDS_CAMCHANGE)
6618 		{
6619 			return true;
6620 		}
6621 	}
6622 
6623 	WzConfig ini(pFileName, WzConfig::ReadOnly);
6624 	std::vector<WzString> list = ini.childGroups();
6625 	for (size_t i = 0; i < list.size(); ++i)
6626 	{
6627 		ini.beginGroup(list[i]);
6628 		MESSAGE_TYPE type = (MESSAGE_TYPE)ini.value("type").toInt();
6629 		bool bObj = ini.contains("obj/id");
6630 		int player = ini.value("player").toInt();
6631 		int id = ini.value("id").toInt();
6632 		int dataType = ini.value("dataType").toInt();
6633 
6634 		if (type == MSG_PROXIMITY)
6635 		{
6636 			//only load proximity if a mid-mission save game
6637 			if (gameType == GTYPE_SAVE_MIDMISSION)
6638 			{
6639 				if (bObj)
6640 				{
6641 					// Proximity object so create get the obj from saved idy
6642 					int objId = ini.value("obj/id").toInt();
6643 					int objPlayer = ini.value("obj/player").toInt();
6644 					OBJECT_TYPE objType = (OBJECT_TYPE)ini.value("obj/type").toInt();
6645 					MESSAGE *psMessage = addMessage(type, true, player);
6646 					if (psMessage)
6647 					{
6648 						psMessage->psObj = getBaseObjFromData(objId, objPlayer, objType);
6649 						ASSERT(psMessage->psObj, "Viewdata object id %d not found for message %d", objId, id);
6650 					}
6651 					else
6652 					{
6653 						debug(LOG_ERROR, "Proximity object could not be created (type=%d, player=%d, message=%d)", type, player, id);
6654 					}
6655 				}
6656 				else
6657 				{
6658 					VIEWDATA* psViewData = nullptr;
6659 
6660 					// Proximity position so get viewdata pointer from the name
6661 					MESSAGE* psMessage = addMessage(type, false, player);
6662 
6663 					if (psMessage)
6664 					{
6665 						if (dataType == MSG_DATA_BEACON)
6666 						{
6667 							//See addBeaconMessage(). psMessage->dataType is wrong here because
6668 							//addMessage() calls createMessage() which defaults dataType to MSG_DATA_DEFAULT.
6669 							//Later when findBeaconMsg() attempts to find a placed beacon it can't because
6670 							//the dataType is wrong.
6671 							psMessage->dataType = MSG_DATA_BEACON;
6672 							Vector2i pos = ini.vector2i("position");
6673 							int sender = ini.value("sender").toInt();
6674 							psViewData = CreateBeaconViewData(sender, pos.x, pos.y);
6675 							ASSERT(psViewData, "Could not create view data for message %d", id);
6676 							if (psViewData == nullptr)
6677 							{
6678 								// Skip this message
6679 								removeMessage(psMessage, player);
6680 								continue;
6681 							}
6682 						}
6683 						else if (ini.contains("name"))
6684 						{
6685 							psViewData = getViewData(ini.value("name").toWzString());
6686 							ASSERT(psViewData, "Failed to find view data for proximity position - skipping message %d", id);
6687 							if (psViewData == nullptr)
6688 							{
6689 								// Skip this message
6690 								removeMessage(psMessage, player);
6691 								continue;
6692 							}
6693 						}
6694 						else
6695 						{
6696 							debug(LOG_ERROR, "Proximity position with empty name skipped (message %d)", id);
6697 							removeMessage(psMessage, player);
6698 							continue;
6699 						}
6700 
6701 						psMessage->pViewData = psViewData;
6702 						// Check the z value is at least the height of the terrain
6703 						const int terrainHeight = map_Height(((VIEW_PROXIMITY*)psViewData->pData)->x, ((VIEW_PROXIMITY*)psViewData->pData)->y);
6704 						if (((VIEW_PROXIMITY*)psViewData->pData)->z < terrainHeight)
6705 						{
6706 							((VIEW_PROXIMITY*)psViewData->pData)->z = terrainHeight;
6707 						}
6708 					}
6709 					else
6710 					{
6711 						debug(LOG_ERROR, "Proximity position could not be created (type=%d, player=%d, message=%d)", type, player, id);
6712 					}
6713 				}
6714 			}
6715 		}
6716 		else
6717 		{
6718 			// Only load Campaign/Mission messages if a mid-mission save game; always load research messages
6719 			if (type == MSG_RESEARCH || gameType == GTYPE_SAVE_MIDMISSION)
6720 			{
6721 				MESSAGE* psMessage = addMessage(type, false, player);
6722 				ASSERT(psMessage, "Could not create message %d", id);
6723 				if (psMessage)
6724 				{
6725 					psMessage->pViewData = getViewData(ini.value("name").toWzString());
6726 					ASSERT(psMessage->pViewData, "Failed to find view data for message %d", id);
6727 				}
6728 			}
6729 		}
6730 		ini.endGroup();
6731 	}
6732 	jsDebugMessageUpdate();
6733 	return true;
6734 }
6735 
6736 // -----------------------------------------------------------------------------------------
6737 // Write out the current messages per player
writeMessageFile(const char * pFileName)6738 static bool writeMessageFile(const char *pFileName)
6739 {
6740 	WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6741 	int numMessages = 0;
6742 
6743 	// save each type of research
6744 	for (int player = 0; player < game.maxPlayers; player++)
6745 	{
6746 		for (MESSAGE *psMessage = apsMessages[player]; psMessage != nullptr; psMessage = psMessage->psNext)
6747 		{
6748 			ini.beginGroup("message_" + WzString::number(numMessages++));
6749 			ini.setValue("id", numMessages - 1);	// for future use
6750 			ini.setValue("player", player);
6751 			ini.setValue("type", psMessage->type);
6752 			ini.setValue("dataType", psMessage->dataType);
6753 			if (psMessage->type == MSG_PROXIMITY)
6754 			{
6755 				//get the matching proximity message
6756 				PROXIMITY_DISPLAY *psProx = nullptr;
6757 				for (psProx = apsProxDisp[player]; psProx != nullptr; psProx = psProx->psNext)
6758 				{
6759 					//compare the pointers
6760 					if (psProx->psMessage == psMessage)
6761 					{
6762 						break;
6763 					}
6764 				}
6765 				ASSERT(psProx != nullptr, "Save message; proximity display not found for message");
6766 				if (psProx && psProx->type == POS_PROXDATA)
6767 				{
6768 					//message has viewdata so store the name
6769 					VIEWDATA *pViewData = psMessage->pViewData;
6770 					ini.setValue("name", pViewData->name);
6771 
6772 					// save beacon data
6773 					if (psMessage->dataType == MSG_DATA_BEACON)
6774 					{
6775 						VIEW_PROXIMITY *viewData = (VIEW_PROXIMITY *)psMessage->pViewData->pData;
6776 						ini.setVector2i("position", Vector2i(viewData->x, viewData->y));
6777 						ini.setValue("sender", viewData->sender);
6778 					}
6779 				}
6780 				else
6781 				{
6782 					// message has object so store Object Id
6783 					const BASE_OBJECT *psObj = psMessage->psObj;
6784 					if (psObj)
6785 					{
6786 						ini.setValue("obj/id", psObj->id);
6787 						ini.setValue("obj/player", psObj->player);
6788 						ini.setValue("obj/type", psObj->type);
6789 					}
6790 					else
6791 					{
6792 						ASSERT(false, "Message type has no object data to save ?");
6793 					}
6794 				}
6795 			}
6796 			else
6797 			{
6798 				const VIEWDATA *pViewData = psMessage->pViewData;
6799 				ini.setValue("name", pViewData != nullptr ? pViewData->name : "NULL");
6800 			}
6801 			ini.setValue("read", psMessage->read);	// flag to indicate whether message has been read; not that this is/was _not_ read by loading code!?
6802 			ASSERT(player == psMessage->player, "Bad player number (%d == %d)", player, psMessage->player);
6803 			ini.endGroup();
6804 		}
6805 	}
6806 	return true;
6807 }
6808 
6809 // -----------------------------------------------------------------------------------------
loadSaveStructLimits(const char * pFileName)6810 bool loadSaveStructLimits(const char *pFileName)
6811 {
6812 	WzConfig ini(pFileName, WzConfig::ReadOnly);
6813 
6814 	for (int player = 0; player < game.maxPlayers; player++)
6815 	{
6816 		ini.beginGroup("player_" + WzString::number(player));
6817 		std::vector<WzString> list = ini.childKeys();
6818 		for (size_t i = 0; i < list.size(); ++i)
6819 		{
6820 			WzString name = list[i];
6821 			int limit = ini.value(name, 0).toInt();
6822 
6823 			if (name.compare("@Droid") == 0)
6824 			{
6825 				setMaxDroids(player, limit);
6826 			}
6827 			else if (name.compare("@Commander") == 0)
6828 			{
6829 				setMaxCommanders(player, limit);
6830 			}
6831 			else if (name.compare("@Constructor") == 0)
6832 			{
6833 				setMaxConstructors(player, limit);
6834 			}
6835 			else
6836 			{
6837 				unsigned statInc;
6838 				for (statInc = 0; statInc < numStructureStats; ++statInc)
6839 				{
6840 					STRUCTURE_STATS *psStats = asStructureStats + statInc;
6841 					if (name.compare(psStats->id) == 0)
6842 					{
6843 						asStructureStats[statInc].upgrade[player].limit = limit != 255 ? limit : LOTS_OF;
6844 						break;
6845 					}
6846 				}
6847 				ASSERT_OR_RETURN(false, statInc != numStructureStats, "Did not find structure %s", name.toUtf8().c_str());
6848 			}
6849 		}
6850 		ini.endGroup();
6851 	}
6852 	return true;
6853 }
6854 
6855 // -----------------------------------------------------------------------------------------
6856 /*
6857 Writes the list of structure limits to a file
6858 */
writeStructLimitsFile(const char * pFileName)6859 bool writeStructLimitsFile(const char *pFileName)
6860 {
6861 	WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6862 
6863 	// Save each type of struct type
6864 	for (int player = 0; player < game.maxPlayers; player++)
6865 	{
6866 		ini.beginGroup("player_" + WzString::number(player));
6867 
6868 		ini.setValue("@Droid", getMaxDroids(player));
6869 		ini.setValue("@Commander", getMaxCommanders(player));
6870 		ini.setValue("@Constructor", getMaxConstructors(player));
6871 
6872 		STRUCTURE_STATS *psStats = asStructureStats;
6873 		for (int i = 0; i < numStructureStats; i++, psStats++)
6874 		{
6875 			const int limit = MIN(asStructureStats[i].upgrade[player].limit, 255);
6876 			if (limit != 255)
6877 			{
6878 				ini.setValue(psStats->id, limit);
6879 			}
6880 		}
6881 		ini.endGroup();
6882 	}
6883 	return true;
6884 }
6885 
6886 /*!
6887  * Load the current fire-support designated commanders (the one who has fire-support enabled)
6888  */
readFiresupportDesignators(const char * pFileName)6889 bool readFiresupportDesignators(const char *pFileName)
6890 {
6891 	WzConfig ini(pFileName, WzConfig::ReadOnly);
6892 	std::vector<WzString> list = ini.childGroups();
6893 
6894 	for (size_t i = 0; i < list.size(); ++i)
6895 	{
6896 		uint32_t id = ini.value("Player_" + WzString::number(i) + "/id", NULL_ID).toInt();
6897 		if (id != NULL_ID)
6898 		{
6899 			cmdDroidSetDesignator((DROID *)getBaseObjFromId(id));
6900 		}
6901 	}
6902 	return true;
6903 }
6904 
6905 /*!
6906  * Save the current fire-support designated commanders (the one who has fire-support enabled)
6907  */
writeFiresupportDesignators(const char * pFileName)6908 bool writeFiresupportDesignators(const char *pFileName)
6909 {
6910 	int player;
6911 	WzConfig ini(pFileName, WzConfig::ReadAndWrite);
6912 
6913 	for (player = 0; player < MAX_PLAYERS; player++)
6914 	{
6915 		DROID *psDroid = cmdDroidGetDesignator(player);
6916 		if (psDroid != nullptr)
6917 		{
6918 			ini.setValue("Player_" + WzString::number(player) + "/id", psDroid->id);
6919 		}
6920 	}
6921 	return true;
6922 }
6923 
6924 
6925 // -----------------------------------------------------------------------------------------
6926 // write the event state to a file on disk
writeScriptState(const char * pFileName)6927 static bool	writeScriptState(const char *pFileName)
6928 {
6929 	char	jsFilename[PATH_MAX], *ext;
6930 
6931 	// The below belongs to the new javascript stuff
6932 	sstrcpy(jsFilename, pFileName);
6933 	ext = strrchr(jsFilename, '/');
6934 	*ext = '\0';
6935 	strcat(jsFilename, "/scriptstate.json");
6936 	saveScriptStates(jsFilename);
6937 
6938 	return true;
6939 }
6940 
6941 // -----------------------------------------------------------------------------------------
6942 // load the script state given a .gam name
loadScriptState(char * pFileName)6943 bool loadScriptState(char *pFileName)
6944 {
6945 	char	jsFilename[PATH_MAX];
6946 
6947 	pFileName[strlen(pFileName) - 4] = '\0';
6948 
6949 	// The below belongs to the new javascript stuff
6950 	sstrcpy(jsFilename, pFileName);
6951 	strcat(jsFilename, "/scriptstate.json");
6952 	loadScriptStates(jsFilename);
6953 
6954 	// change the file extension
6955 	strcat(pFileName, "/scriptstate.es");
6956 
6957 	return true;
6958 }
6959 
6960 
6961 // -----------------------------------------------------------------------------------------
6962 /* set the global scroll values to use for the save game */
setMapScroll()6963 static void setMapScroll()
6964 {
6965 	//if loading in a pre version5 then scroll values will not have been set up so set to max poss
6966 	if (width == 0 && height == 0)
6967 	{
6968 		scrollMinX = 0;
6969 		scrollMaxX = mapWidth;
6970 		scrollMinY = 0;
6971 		scrollMaxY = mapHeight;
6972 		return;
6973 	}
6974 	scrollMinX = startX;
6975 	scrollMinY = startY;
6976 	scrollMaxX = startX + width;
6977 	scrollMaxY = startY + height;
6978 	//check not going beyond width/height of map
6979 	if (scrollMaxX > (SDWORD)mapWidth)
6980 	{
6981 		scrollMaxX = mapWidth;
6982 		debug(LOG_NEVER, "scrollMaxX was too big It has been set to map width");
6983 	}
6984 	if (scrollMaxY > (SDWORD)mapHeight)
6985 	{
6986 		scrollMaxY = mapHeight;
6987 		debug(LOG_NEVER, "scrollMaxY was too big It has been set to map height");
6988 	}
6989 }
6990 
6991 
6992 // -----------------------------------------------------------------------------------------
6993 /*returns the current type of save game being loaded*/
getSaveGameType()6994 GAME_TYPE getSaveGameType()
6995 {
6996 	return gameType;
6997 }
6998 
plotBackdropPixel(char * backDropSprite,int xx,int yy,PIELIGHT const & colour)6999 static void plotBackdropPixel(char *backDropSprite, int xx, int yy, PIELIGHT const &colour)
7000 {
7001 	xx = clip(xx, 0, BACKDROP_HACK_WIDTH - 1);
7002 	yy = clip(yy, 0, BACKDROP_HACK_HEIGHT - 1);
7003 	char *pixel = backDropSprite + (yy * BACKDROP_HACK_WIDTH + xx) * 3;
7004 	pixel[0] = colour.byte.r;
7005 	pixel[1] = colour.byte.g;
7006 	pixel[2] = colour.byte.b;
7007 }
7008 
plotStructurePreviewScript(ScriptMapData const & data,char * backDropSprite,Vector2i playeridpos[])7009 bool plotStructurePreviewScript(ScriptMapData const &data, char *backDropSprite, Vector2i playeridpos[])
7010 {
7011 	ASSERT_OR_RETURN(false, data.valid, "Missing map data");
7012 
7013 	for (auto &structure : data.structures)
7014 	{
7015 		unsigned player = structure.player == -1? scavengerSlot() : structure.player;
7016 		if (player >= MAX_PLAYERS)
7017 		{
7018 			debug(LOG_ERROR, "Bad player");
7019 			continue;
7020 		}
7021 		bool HQ = structure.name.startsWith("A0CommandCentre");
7022 		Vector2i pos = map_coord(structure.position);
7023 		if (HQ)
7024 		{
7025 			playeridpos[player] = pos;
7026 		}
7027 		unsigned playerid = getPlayerColour(RemapPlayerNumber(player));
7028 		// kludge to fix black, so you can see it on some maps.
7029 		PIELIGHT color = playerid == 3? WZCOL_GREY : clanColours[playerid];
7030 		if (HQ)
7031 		{
7032 			// This shows where the HQ is on the map in a special color.
7033 			// We could do the same for anything else (oil/whatever) also.
7034 			// Possible future enhancement?
7035 			color = WZCOL_MAP_PREVIEW_HQ;
7036 		}
7037 		// and now we blit the color to the texture
7038 		plotBackdropPixel(backDropSprite, pos.x, pos.y, color);
7039 	}
7040 
7041 	plotScriptFeature(data, backDropSprite);
7042 
7043 	return true;
7044 }
7045 
7046 /**
7047  * \param[out] backDropSprite The premade map texture.
7048  * \param scale               Scale of the map texture.
7049  * \param[out] playeridpos    Will contain the position on the map where the player's HQ are located.
7050  *
7051  * Reads the current map and colours the map preview for any structures
7052  * present. Additionally we load the player's HQ location into playeridpos so
7053  * we know the player's starting location.
7054  */
plotStructurePreview16(char * backDropSprite,Vector2i playeridpos[])7055 bool plotStructurePreview16(char *backDropSprite, Vector2i playeridpos[])
7056 {
7057 	union
7058 	{
7059 		SAVE_STRUCTURE_V2 v2;
7060 		SAVE_STRUCTURE_V20 v20;
7061 	} sSave;  // close eyes now.
7062 	SAVE_STRUCTURE_V2 *psSaveStructure2   = &sSave.v2;
7063 	SAVE_STRUCTURE_V20 *psSaveStructure20 = &sSave.v20;
7064 	// ok you can open them again..
7065 
7066 	STRUCT_SAVEHEADER *psHeader;
7067 	char aFileName[256];
7068 	UDWORD xx, yy, count, fileSize, sizeOfSaveStructure = sizeof(SAVE_STRUCTURE);
7069 	UDWORD playerid = 0;
7070 	char *pFileData = nullptr;
7071 	LEVEL_DATASET *psLevel;
7072 	PIELIGHT color = WZCOL_BLACK ;
7073 	bool HQ = false;
7074 
7075 	psLevel = levFindDataSet(game.map, &game.hash);
7076 	ASSERT_OR_RETURN(false, psLevel, "No level found for %s", game.map);
7077 	sstrcpy(aFileName, psLevel->apDataFiles[0]);
7078 	aFileName[strlen(aFileName) - 4] = '\0';
7079 	strcat(aFileName, "/struct.bjo");
7080 
7081 	if (!PHYSFS_exists(aFileName))	// use new version of structure data
7082 	{
7083 		sstrcpy(aFileName, psLevel->apDataFiles[0]);
7084 		aFileName[strlen(aFileName) - 4] = '\0';
7085 		strcat(aFileName, "/struct.json");
7086 		WzConfig ini(aFileName, WzConfig::ReadOnly);
7087 		std::vector<WzString> list = ini.childGroups();
7088 		for (size_t i = 0; i < list.size(); ++i)
7089 		{
7090 			ini.beginGroup(list[i]);
7091 			WzString name = ini.value("name").toWzString();
7092 			Position pos = ini.vector3i("position");
7093 			playerid = ini.value("startpos", scavengerSlot()).toInt();  // No conversion should be going on, this is the map makers position when player X should be.
7094 			ASSERT_OR_RETURN(false, playerid < MAX_PLAYERS, "Invalid player number");
7095 			if (name.startsWith("A0CommandCentre"))
7096 			{
7097 				HQ = true;
7098 				xx = playeridpos[playerid].x = map_coord(pos.x);
7099 				yy = playeridpos[playerid].y = map_coord(pos.y);
7100 			}
7101 			else
7102 			{
7103 				HQ = false;
7104 				xx = map_coord(pos.x);
7105 				yy = map_coord(pos.y);
7106 			}
7107 			playerid = getPlayerColour(RemapPlayerNumber(playerid));
7108 			// kludge to fix black, so you can see it on some maps.
7109 			if (playerid == 3)	// in this case 3 = palette entry for black.
7110 			{
7111 				color = WZCOL_GREY;
7112 			}
7113 			else
7114 			{
7115 				color.rgba = clanColours[playerid].rgba;
7116 			}
7117 			if (HQ)
7118 			{
7119 				// This shows where the HQ is on the map in a special color.
7120 				// We could do the same for anything else (oil/whatever) also.
7121 				// Possible future enhancement?
7122 				color = WZCOL_MAP_PREVIEW_HQ;
7123 			}
7124 			// and now we blit the color to the texture
7125 			plotBackdropPixel(backDropSprite, xx, yy, color);
7126 			ini.endGroup();
7127 		}
7128 		// And now we need to show features.
7129 		plotFeature(backDropSprite);
7130 
7131 		return true;
7132 	}
7133 
7134 
7135 	pFileData = fileLoadBuffer;
7136 	if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
7137 	{
7138 		debug(LOG_NEVER, "Failed to load file to buffer.");
7139 	}
7140 
7141 	/* Check the file type */
7142 	psHeader = (STRUCT_SAVEHEADER *)pFileData;
7143 	if (psHeader->aFileType[0] != 's' || psHeader->aFileType[1] != 't' ||
7144 	    psHeader->aFileType[2] != 'r' || psHeader->aFileType[3] != 'u')
7145 	{
7146 		debug(LOG_ERROR, "Invalid file type.");
7147 		return false;
7148 	}
7149 
7150 	/* STRUCT_SAVEHEADER */
7151 	endian_udword(&psHeader->version);
7152 	endian_udword(&psHeader->quantity);
7153 
7154 	//increment to the start of the data
7155 	pFileData += STRUCT_HEADER_SIZE;
7156 
7157 	if (psHeader->version < VERSION_12)
7158 	{
7159 		sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V2);
7160 	}
7161 	else if (psHeader->version < VERSION_14)
7162 	{
7163 		sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V12);
7164 	}
7165 	else if (psHeader->version <= VERSION_14)
7166 	{
7167 		sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V14);
7168 	}
7169 	else if (psHeader->version <= VERSION_16)
7170 	{
7171 		sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V15);
7172 	}
7173 	else if (psHeader->version <= VERSION_19)
7174 	{
7175 		sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V17);
7176 	}
7177 	else if (psHeader->version <= VERSION_20)
7178 	{
7179 		sizeOfSaveStructure = sizeof(SAVE_STRUCTURE_V20);
7180 	}
7181 
7182 	/* Load in the structure data */
7183 	for (count = 0; count < psHeader->quantity; count++, pFileData += sizeOfSaveStructure)
7184 	{
7185 		// we are specifically looking for the HQ, and it seems this is the only way to
7186 		// find it via parsing map.
7187 		// We store the coordinates of the structure, into a array for as many players as are on the map.
7188 
7189 		if (psHeader->version <= VERSION_19)
7190 		{
7191 			// All versions up to 19 are compatible with V2.
7192 			memcpy(psSaveStructure2, pFileData, sizeof(SAVE_STRUCTURE_V2));
7193 
7194 			endian_udword(&psSaveStructure2->x);
7195 			endian_udword(&psSaveStructure2->y);
7196 			endian_udword(&psSaveStructure2->player);
7197 			playerid = psSaveStructure2->player;
7198 			ASSERT_OR_RETURN(false, playerid < MAX_PLAYERS, "Invalid player number");
7199 			if (strncmp(psSaveStructure2->name, "A0CommandCentre", 15) == 0)
7200 			{
7201 				HQ = true;
7202 				xx = playeridpos[playerid].x = map_coord(psSaveStructure2->x);
7203 				yy = playeridpos[playerid].y = map_coord(psSaveStructure2->y);
7204 			}
7205 			else
7206 			{
7207 				HQ = false;
7208 				xx = map_coord(psSaveStructure2->x);
7209 				yy = map_coord(psSaveStructure2->y);
7210 			}
7211 		}
7212 		else
7213 		{
7214 			// All newer versions are compatible with V20.
7215 			memcpy(psSaveStructure20, pFileData, sizeof(SAVE_STRUCTURE_V20));
7216 
7217 			endian_udword(&psSaveStructure20->x);
7218 			endian_udword(&psSaveStructure20->y);
7219 			endian_udword(&psSaveStructure20->player);
7220 			playerid = psSaveStructure20->player;
7221 			ASSERT_OR_RETURN(false, playerid < MAX_PLAYERS, "Invalid player number");
7222 			if (strncmp(psSaveStructure20->name, "A0CommandCentre", 15) == 0)
7223 			{
7224 				HQ = true;
7225 				xx = playeridpos[playerid].x = map_coord(psSaveStructure20->x);
7226 				yy = playeridpos[playerid].y = map_coord(psSaveStructure20->y);
7227 			}
7228 			else
7229 			{
7230 				HQ = false;
7231 				xx = map_coord(psSaveStructure20->x);
7232 				yy = map_coord(psSaveStructure20->y);
7233 			}
7234 		}
7235 		playerid = getPlayerColour(RemapPlayerNumber(playerid));
7236 		// kludge to fix black, so you can see it on some maps.
7237 		if (playerid == 3)	// in this case 3 = palette entry for black.
7238 		{
7239 			color = WZCOL_GREY;
7240 		}
7241 		else
7242 		{
7243 			color.rgba = clanColours[playerid].rgba;
7244 		}
7245 
7246 		if (HQ)
7247 		{
7248 			// This shows where the HQ is on the map in a special color.
7249 			// We could do the same for anything else (oil/whatever) also.
7250 			// Possible future enhancement?
7251 			color = WZCOL_MAP_PREVIEW_HQ;
7252 		}
7253 
7254 		// and now we blit the color to the texture
7255 		plotBackdropPixel(backDropSprite, xx, yy, color);
7256 	}
7257 
7258 	// And now we need to show features.
7259 	plotFeature(backDropSprite);
7260 	return true;
7261 }
7262 
plotScriptFeature(ScriptMapData const & data,char * backDropSprite)7263 static void plotScriptFeature(ScriptMapData const &data, char *backDropSprite) {
7264 	ASSERT_OR_RETURN(, data.valid, "Missing map data");
7265 
7266 	for (auto &feature : data.features)
7267 	{
7268 		PIELIGHT colour;
7269 		if (feature.name.startsWith("OilResource"))
7270 		{
7271 			colour = WZCOL_MAP_PREVIEW_OIL;
7272 		}
7273 		else if (feature.name.startsWith("OilDrum"))
7274 		{
7275 			colour = WZCOL_MAP_PREVIEW_BARREL;
7276 		}
7277 		else
7278 		{
7279 			continue;
7280 		}
7281 		// and now we blit the color to the texture
7282 		auto pos = map_coord(feature.position);
7283 		plotBackdropPixel(backDropSprite, pos.x, pos.y, colour);
7284 	}
7285 }
7286 
7287 // Show location of (at this time) oil on the map preview
plotFeature(char * backDropSprite)7288 static void plotFeature(char *backDropSprite)
7289 {
7290 	FEATURE_SAVEHEADER	*psHeader;
7291 	SAVE_FEATURE_V2	*psSaveFeature;
7292 	LEVEL_DATASET *psLevel;
7293 	UDWORD xx, yy, count, fileSize;
7294 	UDWORD sizeOfSaveFeature = 0;
7295 	char *pFileData = nullptr;
7296 	char aFileName[256];
7297 	const PIELIGHT colourOil = WZCOL_MAP_PREVIEW_OIL;
7298 	const PIELIGHT colourBarrel = WZCOL_MAP_PREVIEW_BARREL;
7299 
7300 	psLevel = levFindDataSet(game.map, &game.hash);
7301 	ASSERT_OR_RETURN(, psLevel, "No level found for %s", game.map);
7302 	sstrcpy(aFileName, psLevel->apDataFiles[0]);
7303 	aFileName[strlen(aFileName) - 4] = '\0';
7304 	strcat(aFileName, "/feat.bjo");
7305 	if (!PHYSFS_exists(aFileName))	// use new version of feature data
7306 	{
7307 		sstrcpy(aFileName, psLevel->apDataFiles[0]);
7308 		aFileName[strlen(aFileName) - 4] = '\0';
7309 		strcat(aFileName, "/feature.json");
7310 		WzConfig ini(aFileName, WzConfig::ReadOnly);
7311 		if (!ini.status())
7312 		{
7313 			debug(LOG_ERROR, "Could not open %s", aFileName);
7314 			return;
7315 		}
7316 		std::vector<WzString> list = ini.childGroups();
7317 		for (size_t i = 0; i < list.size(); ++i)
7318 		{
7319 			ini.beginGroup(list[i]);
7320 			WzString name = ini.value("name").toWzString();
7321 			Position pos = ini.vector3i("position");
7322 
7323 			// we only care about oil
7324 			PIELIGHT const *colour = nullptr;
7325 			if (name.startsWith("OilResource"))
7326 			{
7327 				colour = &colourOil;
7328 			}
7329 			else if (name.startsWith("OilDrum"))
7330 			{
7331 				colour = &colourBarrel;
7332 			}
7333 			if (colour != nullptr)
7334 			{
7335 				// and now we blit the color to the texture
7336 				xx = map_coord(pos.x);
7337 				yy = map_coord(pos.y);
7338 				plotBackdropPixel(backDropSprite, xx, yy, *colour);
7339 			}
7340 			ini.endGroup();
7341 		}
7342 		return;
7343 	}
7344 
7345 	// Load in the chosen file data/
7346 	pFileData = fileLoadBuffer;
7347 	if (!loadFileToBuffer(aFileName, pFileData, FILE_LOAD_BUFFER_SIZE, &fileSize))
7348 	{
7349 		debug(LOG_ERROR, "Unable to load file %s?", aFileName);
7350 		return;
7351 	}
7352 
7353 	// Check the file type
7354 	psHeader = (FEATURE_SAVEHEADER *)pFileData;
7355 	if (psHeader->aFileType[0] != 'f' || psHeader->aFileType[1] != 'e' || psHeader->aFileType[2] != 'a' || psHeader->aFileType[3] != 't')
7356 	{
7357 		debug(LOG_ERROR, "Incorrect file type, looking at %s", aFileName);
7358 		return;
7359 	}
7360 
7361 	endian_udword(&psHeader->version);
7362 	endian_udword(&psHeader->quantity);
7363 
7364 	//increment to the start of the data
7365 	pFileData += FEATURE_HEADER_SIZE;
7366 
7367 	sizeOfSaveFeature = sizeof(SAVE_FEATURE_V2);
7368 
7369 	if ((sizeOfSaveFeature * psHeader->quantity + FEATURE_HEADER_SIZE) > fileSize)
7370 	{
7371 		debug(LOG_ERROR, "Unexpected end of file ?");
7372 		return;
7373 	}
7374 
7375 	// Load in the feature data
7376 	for (count = 0; count < psHeader->quantity; count++, pFileData += sizeOfSaveFeature)
7377 	{
7378 		// All versions up to 19 are compatible with V2.
7379 		psSaveFeature = (SAVE_FEATURE_V2 *) pFileData;
7380 
7381 		// we only care about oil
7382 		PIELIGHT const *colour = nullptr;
7383 		if (strncmp(psSaveFeature->name, "OilResource", 11) == 0)
7384 		{
7385 			colour = &colourOil;
7386 		}
7387 		else if (strncmp(psSaveFeature->name, "OilDrum", 7) == 0)
7388 		{
7389 			colour = &colourBarrel;
7390 		}
7391 		else
7392 		{
7393 			continue;
7394 		}
7395 		endian_udword(&psSaveFeature->x);
7396 		endian_udword(&psSaveFeature->y);
7397 		xx = map_coord(psSaveFeature->x);
7398 		yy = map_coord(psSaveFeature->y);
7399 		// and now we blit the color to the texture
7400 		plotBackdropPixel(backDropSprite, xx, yy, *colour);
7401 	}
7402 }
7403