Bug Summary

File:client/cgame/campaign/cp_save.cpp
Location:line 346, column 2
Description:Value stored to 'res' is never read

Annotated Source Code

1/**
2 * @file
3 * @brief Implements savegames
4 */
5
6/*
7Copyright (C) 2002-2011 UFO: Alien Invasion.
8
9This program is free software; you can redistribute it and/or
10modify it under the terms of the GNU General Public License
11as published by the Free Software Foundation; either version 2
12of the License, or (at your option) any later version.
13
14This program is distributed in the hope that it will be useful,
15but WITHOUT ANY WARRANTY; without even the implied warranty of
16MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
17
18See the GNU General Public License for more details.
19
20You should have received a copy of the GNU General Public License
21along with this program; if not, write to the Free Software
22Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
23*/
24
25#include "../../cl_shared.h"
26#include "../cl_game.h" /* GAME_ReloadMode */
27#include "cp_campaign.h"
28#include "cp_save.h"
29#include "cp_time.h"
30#include "cp_popup.h"
31#include "save/save.h"
32#include "missions/cp_mission_baseattack.h"
33
34#define SAVEGAME_EXTENSION"savx" "savx"
35
36typedef struct saveFileHeader_s {
37 uint32_t version; /**< which savegame version */
38 uint32_t compressed; /**< is this file compressed via zlib */
39 uint32_t subsystems; /**< amount of subsystems that were saved in this savegame */
40 uint32_t dummy[13]; /**< maybe we have to extend this later */
41 char gameVersion[16]; /**< game version that was used to save this file */
42 char name[32]; /**< savefile name */
43 char gameDate[32]; /**< internal game date */
44 char realDate[32]; /**< real datestring when the user saved this game */
45 uint32_t xmlSize;
46} saveFileHeader_t;
47
48static saveSubsystems_t saveSubsystems[MAX_SAVESUBSYSTEMS32];
49static int saveSubsystemsAmount;
50static cvar_t* save_compressed;
51static cvar_t *cl_lastsave;
52
53/**
54 * @brief Perform actions after loading a game for single player campaign
55 * @sa SAV_GameLoad
56 */
57static bool SAV_GameActionsAfterLoad (void)
58{
59 bool result = true;
60
61 result = result && AIR_PostLoadInit();
62 result = result && B_PostLoadInit();
63 result = result && PR_PostLoadInit();
64
65 /* Make sure the date&time is displayed when loading. */
66 CP_UpdateTime();
67
68 /* Update number of UFO detected by radar */
69 RADAR_SetRadarAfterLoading();
70
71 return result;
72}
73
74/**
75 * @brief Tries to verify the Header of the savegame
76 * @param[in] header a pointer to the header to verify
77 */
78static bool SAV_VerifyHeader (saveFileHeader_t const * const header)
79{
80 size_t len;
81 /*check the length of the string*/
82 len = strlen(header->name);
83 if (len > sizeof(header->name)) {
84 Com_Printf("Name is " UFO_SIZE_T"%zu" " Bytes long, max is " UFO_SIZE_T"%zu" "\n", len, sizeof(header->name));
85 return false;
86 }
87 len = strlen(header->gameVersion);
88 if (len > sizeof(header->gameVersion)) {
89 Com_Printf("gameVersion is " UFO_SIZE_T"%zu" " Bytes long, max is " UFO_SIZE_T"%zu" "\n", len, sizeof(header->gameVersion));
90 return false;
91 }
92 len = strlen(header->gameDate);
93 if (len > sizeof(header->gameDate)) {
94 Com_Printf("gameDate is " UFO_SIZE_T"%zu" " Bytes long, max is " UFO_SIZE_T"%zu" "\n", len, sizeof(header->gameDate));
95 return false;
96 }
97 len = strlen(header->realDate);
98 if (len > sizeof(header->realDate)) {
99 Com_Printf("realDate is " UFO_SIZE_T"%zu" " Bytes long, max is " UFO_SIZE_T"%zu" "\n", len, sizeof(header->realDate));
100 return false;
101 }
102 if (header->subsystems != 0 && header->subsystems != saveSubsystemsAmount) {
103 Com_DPrintf(DEBUG_CLIENT0x20, "Savefile has incompatible amount of subsystems\n");
104 return false;
105 }
106
107 /* saved games should not be bigger than 15MB */
108 if (header->xmlSize > 15 * 1024 * 1024) {
109 Com_Printf("Save size seems to be to large (over 15 MB) %i.\n", header->xmlSize);
110 return false;
111 }
112 if (header->version == 0) {
113 Com_Printf("Version is invalid - must be greater than zero\n");
114 return false;
115 }
116 if (header->version > SAVE_FILE_VERSION4) {
117 Com_Printf("Savefile is newer than the game!\n");
118 }
119 return true;
120}
121
122/**
123 * @brief Loads the given savegame from an xml File.
124 * @return true on load success false on failures
125 * @param[in] file The Filename to load from (without extension)
126 * @param[out] error On failure an errormessage may be set.
127 */
128bool SAV_GameLoad (const char *file, const char **error)
129{
130 char filename[MAX_OSPATH256];
131 qFILE f;
132 int i, clen;
133 xmlNode_tmxml_node_t *topNode, *node;
134 saveFileHeader_t header;
135
136 Q_strncpyz(filename, file, sizeof(filename))Q_strncpyzDebug( filename, file, sizeof(filename), "src/client/cgame/campaign/cp_save.cpp"
, 136 )
;
137
138 /* open file */
139 FS_OpenFile(va("save/%s.%s", filename, SAVEGAME_EXTENSION"savx"), &f, FILE_READ);
140 if (!f.f) {
141 Com_Printf("Couldn't open file '%s'\n", filename);
142 *error = "File not found";
143 return false;
144 }
145
146 clen = FS_FileLength(&f);
147 byte* const cbuf = Mem_PoolAllocTypeN(byte, clen + 1 /* for '\0' if not compressed */, cp_campaignPool)static_cast<byte*>(_Mem_Alloc((sizeof(byte) * (clen + 1
)),true,((cp_campaignPool)),(0),"src/client/cgame/campaign/cp_save.cpp"
,147))
;
148 if (FS_Read(cbuf, clen, &f) != clen)
149 Com_Printf("Warning: Could not read %i bytes from savefile\n", clen);
150 FS_CloseFile(&f);
151 Com_Printf("Loading savegame xml (size %d)\n", clen);
152
153 memcpy(&header, cbuf, sizeof(header));
154 /* swap all int values if needed */
155 header.compressed = LittleLong(header.compressed)(int)(header.compressed);
156 header.version = LittleLong(header.version)(int)(header.version);
157 header.xmlSize = LittleLong(header.xmlSize)(int)(header.xmlSize);
158 /* doing some header verification */
159 if (!SAV_VerifyHeader(&header)) {
160 /* our header is not valid, we MUST abort loading the game! */
161 Com_Printf("The Header of the savegame '%s.%s' is corrupted. Loading aborted\n", filename, SAVEGAME_EXTENSION"savx");
162 Mem_Free(cbuf)_Mem_Free((cbuf),"src/client/cgame/campaign/cp_save.cpp",162);
163 *error = "Corrupted header";
164 return false;
165 }
166
167 Com_Printf("Loading savegame\n"
168 "...version: %i\n"
169 "...game version: %s\n"
170 "...xml Size: %i, compressed? %c\n",
171 header.version, header.gameVersion, header.xmlSize, header.compressed ? 'y' : 'n');
172
173 if (header.compressed) {
174 uLongf len = header.xmlSize + 1 /* for '\0' */;
175 byte* const buf = Mem_PoolAllocTypeN(byte, len /* sic, old savegames contain one (garbage) byte more than the header says. */, cp_campaignPool)static_cast<byte*>(_Mem_Alloc((sizeof(byte) * (len)),true
,((cp_campaignPool)),(0),"src/client/cgame/campaign/cp_save.cpp"
,175))
;
176 /* uncompress data, skipping comment header */
177 const int res = uncompress(buf, &len, cbuf + sizeof(header), clen - sizeof(header));
178 buf[header.xmlSize] = '\0'; /* Ensure '\0' termination. */
179 Mem_Free(cbuf)_Mem_Free((cbuf),"src/client/cgame/campaign/cp_save.cpp",179);
180
181 if (res != Z_OK0) {
182 Mem_Free(buf)_Mem_Free((buf),"src/client/cgame/campaign/cp_save.cpp",182);
183 *error = _("Error decompressing data")gettext("Error decompressing data");
184 Com_Printf("Error decompressing data in '%s'.\n", filename);
185 return false;
186 }
187 topNode = XML_Parse((const char*)buf);
188 if (!topNode) {
189 Mem_Free(buf)_Mem_Free((buf),"src/client/cgame/campaign/cp_save.cpp",189);
190 Com_Printf("Error: Failure in loading the xml data!\n");
191 *error = "Corrupted xml data";
192 return false;
193 }
194 Mem_Free(buf)_Mem_Free((buf),"src/client/cgame/campaign/cp_save.cpp",194);
195 } else {
196 topNode = XML_Parse((const char*)(cbuf + sizeof(header)));
197 Mem_Free(cbuf)_Mem_Free((cbuf),"src/client/cgame/campaign/cp_save.cpp",197);
198 if (!topNode) {
199 Com_Printf("Error: Failure in loading the xml data!\n");
200 *error = "Corrupted xml data";
201 return false;
202 }
203 }
204
205 /* doing a subsystem run */
206 GAME_ReloadMode();
207 node = XML_GetNode(topNode, SAVE_ROOTNODE"savegame");
208 if (!node) {
209 Com_Printf("Error: Failure in loading the xml data! (savegame node not found)\n");
210 mxmlDelete(topNode);
211 *error = "Invalid xml data";
212 return false;
213 }
214
215 Com_Printf("Load '%s' %d subsystems\n", filename, saveSubsystemsAmount);
216 for (i = 0; i < saveSubsystemsAmount; i++) {
217 Com_Printf("...Running subsystem '%s'\n", saveSubsystems[i].name);
218 if (!saveSubsystems[i].load(node)) {
219 Com_Printf("...subsystem '%s' returned false - savegame could not be loaded\n",
220 saveSubsystems[i].name);
221 *error = va("Could not load subsystem %s", saveSubsystems[i].name);
222 return false;
223 } else
224 Com_Printf("...subsystem '%s' - loaded.\n", saveSubsystems[i].name);
225 }
226 mxmlDelete(node);
227 mxmlDelete(topNode);
228
229 if (!SAV_GameActionsAfterLoad()) {
230 Com_Printf("Savegame postprocessing returned false - savegame could not be loaded\n");
231 *error = "Postprocessing failed";
232 return false;
233 }
234
235 Com_Printf("File '%s' successfully loaded from %s xml savegame.\n",
236 filename, header.compressed ? "compressed" : "");
237
238 cgi->UI_InitStack("geoscape", NULL__null, true, true);
239 return true;
240}
241
242/**
243 * @brief This is a savegame function which stores the game in xml-Format.
244 * @param[in] filename The Filename to save to (without extension)
245 * @param[in] comment Description of the savegame
246 * @param[out] error On failure an errormessage may be set.
247 */
248static bool SAV_GameSave (const char *filename, const char *comment, char **error)
249{
250 xmlNode_tmxml_node_t *topNode, *node;
251 char savegame[MAX_OSPATH256];
252 int res;
253 int requiredBufferLength;
254 uLongf bufLen;
255 saveFileHeader_t header;
256 char dummy[2];
257 int i;
258 dateLong_t date;
259 char message[30];
260 char timeStampBuffer[32];
261
262 if (!CP_IsRunning()) {
263 *error = _("No campaign active.")gettext("No campaign active.");
264 Com_Printf("Error: No campaign active.\n");
265 return false;
266 }
267
268 if (!B_AtLeastOneExists()(B_GetNext(__null) != __null)) {
269 *error = _("Nothing to save yet.")gettext("Nothing to save yet.");
270 Com_Printf("Error: Nothing to save yet.\n");
271 return false;
272 }
273
274 Com_MakeTimestamp(timeStampBuffer, sizeof(timeStampBuffer));
275 Com_sprintf(savegame, sizeof(savegame), "save/%s.%s", filename, SAVEGAME_EXTENSION"savx");
276 topNode = mxmlNewXML("1.0");
277 node = XML_AddNode(topNode, SAVE_ROOTNODE"savegame");
278 /* writing Header */
279 XML_AddInt(node, SAVE_SAVEVERSION"saveVersion", SAVE_FILE_VERSION4);
280 XML_AddString(node, SAVE_COMMENT"comment", comment);
281 XML_AddString(node, SAVE_UFOVERSION"gameVersion", UFO_VERSION"2.5-dev");
282 XML_AddString(node, SAVE_REALDATE"realDate", timeStampBuffer);
283 CP_DateConvertLong(&ccs.date, &date);
284 Com_sprintf(message, sizeof(message), _("%i %s %02i")gettext("%i %s %02i"),
285 date.year, Date_GetMonthName(date.month - 1), date.day);
286 XML_AddString(node, SAVE_GAMEDATE"gameDate", message);
287 /* working through all subsystems. perhaps we should redesign it, order is not important anymore */
288 Com_Printf("Calling subsystems\n");
289 for (i = 0; i < saveSubsystemsAmount; i++) {
290 if (!saveSubsystems[i].save(node))
291 Com_Printf("...subsystem '%s' failed to save the data\n", saveSubsystems[i].name);
292 else
293 Com_Printf("...subsystem '%s' - saved\n", saveSubsystems[i].name);
294 }
295
296 /* calculate the needed buffer size */
297 OBJZERO(header)(memset(&((header)), (0), sizeof((header))));
298 header.compressed = LittleLong(save_compressed->integer)(int)(save_compressed->integer);
299 header.version = LittleLong(SAVE_FILE_VERSION)(int)(4);
300 header.subsystems = LittleLong(saveSubsystemsAmount)(int)(saveSubsystemsAmount);
301 Q_strncpyz(header.name, comment, sizeof(header.name))Q_strncpyzDebug( header.name, comment, sizeof(header.name), "src/client/cgame/campaign/cp_save.cpp"
, 301 )
;
302 Q_strncpyz(header.gameVersion, UFO_VERSION, sizeof(header.gameVersion))Q_strncpyzDebug( header.gameVersion, "2.5-dev", sizeof(header
.gameVersion), "src/client/cgame/campaign/cp_save.cpp", 302 )
;
303 CP_DateConvertLong(&ccs.date, &date);
304 Com_sprintf(header.gameDate, sizeof(header.gameDate), _("%i %s %02i")gettext("%i %s %02i"),
305 date.year, Date_GetMonthName(date.month - 1), date.day);
306 Q_strncpyz(header.realDate, timeStampBuffer, sizeof(header.realDate))Q_strncpyzDebug( header.realDate, timeStampBuffer, sizeof(header
.realDate), "src/client/cgame/campaign/cp_save.cpp", 306 )
;
307
308 requiredBufferLength = mxmlSaveString(topNode, dummy, 2, MXML_NO_CALLBACK0);
309
310 header.xmlSize = LittleLong(requiredBufferLength)(int)(requiredBufferLength);
311 byte* const buf = Mem_PoolAllocTypeN(byte, requiredBufferLength + 1, cp_campaignPool)static_cast<byte*>(_Mem_Alloc((sizeof(byte) * (requiredBufferLength
+ 1)),true,((cp_campaignPool)),(0),"src/client/cgame/campaign/cp_save.cpp"
,311))
;
312 if (!buf) {
313 mxmlDelete(topNode);
314 *error = _("Could not allocate enough memory to save this game")gettext("Could not allocate enough memory to save this game");
315 Com_Printf("Error: Could not allocate enough memory to save this game\n");
316 return false;
317 }
318 res = mxmlSaveString(topNode, (char*)buf, requiredBufferLength + 1, MXML_NO_CALLBACK0);
319 mxmlDelete(topNode);
320 Com_Printf("XML Written to buffer (%d Bytes)\n", res);
321
322 if (header.compressed)
323 bufLen = compressBound(requiredBufferLength);
324 else
325 bufLen = requiredBufferLength;
326
327 byte* const fbuf = Mem_PoolAllocTypeN(byte, bufLen + sizeof(header), cp_campaignPool)static_cast<byte*>(_Mem_Alloc((sizeof(byte) * (bufLen +
sizeof(header))),true,((cp_campaignPool)),(0),"src/client/cgame/campaign/cp_save.cpp"
,327))
;
328 memcpy(fbuf, &header, sizeof(header));
329
330 if (header.compressed) {
331 res = compress(fbuf + sizeof(header), &bufLen, buf, requiredBufferLength);
332 Mem_Free(buf)_Mem_Free((buf),"src/client/cgame/campaign/cp_save.cpp",332);
333
334 if (res != Z_OK0) {
335 Mem_Free(fbuf)_Mem_Free((fbuf),"src/client/cgame/campaign/cp_save.cpp",335);
336 *error = _("Memory error compressing save-game data - set save_compressed cvar to 0")gettext("Memory error compressing save-game data - set save_compressed cvar to 0"
)
;
337 Com_Printf("Memory error compressing save-game data (%s) (Error: %i)!\n", comment, res);
338 return false;
339 }
340 } else {
341 memcpy(fbuf + sizeof(header), buf, requiredBufferLength);
342 Mem_Free(buf)_Mem_Free((buf),"src/client/cgame/campaign/cp_save.cpp",342);
343 }
344
345 /* last step - write data */
346 res = FS_WriteFile(fbuf, bufLen + sizeof(header), savegame);
Value stored to 'res' is never read
347 Mem_Free(fbuf)_Mem_Free((fbuf),"src/client/cgame/campaign/cp_save.cpp",347);
348
349 return true;
350}
351
352/**
353 * @brief Console command binding for save function
354 * @sa SAV_GameSave
355 * @note called via 'game_save' command
356 */
357static void SAV_GameSave_f (void)
358{
359 char comment[MAX_VAR64] = "";
360 char *error = NULL__null;
361 bool result;
362
363 /* get argument */
364 if (Cmd_Argc() < 2) {
365 Com_Printf("Usage: %s <filename> <comment|*cvar>\n", Cmd_Argv(0));
366 return;
367 }
368
369 if (!CP_IsRunning()) {
370 Com_Printf("No running game - no saving...\n");
371 return;
372 }
373
374 /* get comment */
375 if (Cmd_Argc() > 2) {
376 const char *arg = Cmd_Argv(2);
377 Q_strncpyz(comment, arg, sizeof(comment))Q_strncpyzDebug( comment, arg, sizeof(comment), "src/client/cgame/campaign/cp_save.cpp"
, 377 )
;
378 }
379
380 result = SAV_GameSave(Cmd_Argv(1), comment, &error);
381 if (!result) {
382 if (error)
383 CP_Popup(_("Note")gettext("Note"), "%s\n%s", _("Error saving game.")gettext("Error saving game."), error);
384 else
385 CP_Popup(_("Note")gettext("Note"), "%s\n%s", "%s\n", _("Error saving game.")gettext("Error saving game."));
386 }
387}
388
389/**
390 * @brief Init menu cvar for one savegame slot given by actual index.
391 * @param[in] idx the savegame slot to retrieve gamecomment for
392 * @sa SAV_GameReadGameComments_f
393 */
394static void SAV_GameReadGameComment (const int idx)
395{
396 saveFileHeader_t header;
397 qFILE f;
398
399 FS_OpenFile(va("save/slot%i.%s", idx, SAVEGAME_EXTENSION"savx"), &f, FILE_READ);
400 if (f.f || f.z) {
401 if (FS_Read(&header, sizeof(header), &f) != sizeof(header))
402 Com_Printf("Warning: Savefile header may be corrupted\n");
403
404 header.compressed = LittleLong(header.compressed)(int)(header.compressed);
405 header.version = LittleLong(header.version)(int)(header.version);
406 header.xmlSize = LittleLong(header.xmlSize)(int)(header.xmlSize);
407 header.subsystems = LittleLong(header.subsystems)(int)(header.subsystems);
408
409 if (!SAV_VerifyHeader(&header))
410 Com_Printf("Savegame header for slot%d is corrupted!\n", idx);
411 else
412 cgi->UI_ExecuteConfunc("update_save_game_info %i \"%s\" \"%s\" \"%s\"", idx, header.name, header.gameDate, header.realDate);
413
414 FS_CloseFile(&f);
415 }
416}
417
418/**
419 * @brief Console commands to read the comments from savegames
420 * @note The comment is the part of the savegame header that you type in at saving
421 * for reidentifying the savegame
422 * @sa SAV_GameLoad_f
423 * @sa SAV_GameLoad
424 * @sa SAV_GameSaveNameCleanup_f
425 * @sa SAV_GameReadGameComment
426 */
427static void SAV_GameReadGameComments_f (void)
428{
429 if (Cmd_Argc() == 2) {
430 int idx = atoi(Cmd_Argv(1));
431 SAV_GameReadGameComment(idx);
432 } else {
433 int i;
434 /* read all game comments */
435 for (i = 0; i < 8; i++) {
436 SAV_GameReadGameComment(i);
437 }
438 }
439}
440
441/**
442 * @brief Console command to load a savegame
443 * @sa SAV_GameLoad
444 */
445static void SAV_GameLoad_f (void)
446{
447 const char *error = NULL__null;
448
449 /* get argument */
450 if (Cmd_Argc() < 2) {
451 Com_Printf("Usage: %s <filename>\n", Cmd_Argv(0));
452 return;
453 }
454
455 /* Check if savegame exists */
456 if (FS_CheckFile("save/%s.%s", Cmd_Argv(1), SAVEGAME_EXTENSION"savx") <= 0) {
457 Com_Printf("savegame file '%s' doesn't exist or an empty file\n", Cmd_Argv(1));
458 return;
459 }
460
461 Com_DPrintf(DEBUG_CLIENT0x20, "load file '%s'\n", Cmd_Argv(1));
462
463 /* load and go to map */
464 if (!SAV_GameLoad(Cmd_Argv(1), &error)) {
465 Cbuf_Execute(); /* wipe outstanding campaign commands */
466 cgi->UI_Popup(_("Error")gettext("Error"), "%s\n%s", _("Error loading game.")gettext("Error loading game."), error ? error : "");
467 Cmd_ExecuteString("game_exit");
468 }
469}
470
471/**
472 * @brief Loads the last saved game
473 * @note At saving the archive cvar cl_lastsave was set to the latest savegame
474 * @sa SAV_GameLoad
475 */
476static void SAV_GameContinue_f (void)
477{
478 const char *error = NULL__null;
479
480 if (cgi->CL_OnBattlescape()) {
481 cgi->UI_PopWindow(false);
482 return;
483 }
484
485 if (!CP_IsRunning()) {
486 /* try to load the last saved campaign */
487 if (!SAV_GameLoad(cl_lastsave->string, &error)) {
488 Cbuf_Execute(); /* wipe outstanding campaign commands */
489 cgi->UI_Popup(_("Error")gettext("Error"), "%s\n%s", _("Error loading game.")gettext("Error loading game."), error ? error : "");
490 Cmd_ExecuteString("game_exit");
491 }
492 } else {
493 /* just continue the current running game */
494 cgi->UI_PopWindow(false);
495 }
496}
497
498/**
499 * @brief Adds a subsystem to the saveSubsystems array
500 * @note The order is _not_ important
501 * @sa SAV_Init
502 */
503bool SAV_AddSubsystem (saveSubsystems_t *subsystem)
504{
505 if (saveSubsystemsAmount >= MAX_SAVESUBSYSTEMS32)
506 return false;
507
508 saveSubsystems[saveSubsystemsAmount].name = subsystem->name;
509 saveSubsystems[saveSubsystemsAmount].load = subsystem->load;
510 saveSubsystems[saveSubsystemsAmount].save = subsystem->save;
511 saveSubsystemsAmount++;
512
513 Com_Printf("added %s subsystem\n", subsystem->name);
514 return true;
515}
516
517/**
518 * @brief Set the mn_slotX cvar to the comment (remove the date string) for clean
519 * editing of the save comment
520 * @sa SAV_GameReadGameComments_f
521 */
522static void SAV_GameSaveNameCleanup_f (void)
523{
524 int slotID;
525 char cvar[16];
526 qFILE f;
527 saveFileHeader_t header;
528
529 /* get argument */
530 if (Cmd_Argc() < 2) {
531 Com_Printf("Usage: %s <[0-7]>\n", Cmd_Argv(0));
532 return;
533 }
534
535 slotID = atoi(Cmd_Argv(1));
536 if (slotID < 0 || slotID > 7)
537 return;
538
539 FS_OpenFile(va("save/slot%i.%s", slotID, SAVEGAME_EXTENSION"savx"), &f, FILE_READ);
540 if (!f.f && !f.z)
541 return;
542
543 /* read the comment */
544 if (FS_Read(&header, sizeof(header), &f) != sizeof(header))
545 Com_Printf("Warning: Savefile header may be corrupted\n");
546
547 Com_sprintf(cvar, sizeof(cvar), "mn_slot%i", slotID);
548 if (SAV_VerifyHeader(&header))
549 Cvar_Set(cvar, header.name);
550 else
551 Cvar_Set(cvar, "");
552 FS_CloseFile(&f);
553}
554
555/**
556 * @brief Quick save the current campaign
557 * @sa CP_StartMissionMap
558 */
559bool SAV_QuickSave (void)
560{
561 char *error = NULL__null;
562 bool result;
563
564 if (cgi->CL_OnBattlescape())
565 return false;
566
567 result = SAV_GameSave("slotquick", _("QuickSave")gettext("QuickSave"), &error);
568 if (!result)
569 Com_Printf("Error saving the xml game: %s\n", error ? error : "");
570
571 return true;
572}
573
574/**
575 * @brief Checks whether there is a quicksave file and opens the quickload menu if there is one
576 * @note This does not work while we are in the battlescape
577 */
578static void SAV_GameQuickLoadInit_f (void)
579{
580 qFILE f;
581
582 if (cgi->CL_OnBattlescape()) {
583 return;
584 }
585
586 FS_OpenFile(va("save/slotquick.%s", SAVEGAME_EXTENSION"savx"), &f, FILE_READ);
587 if (f.f || f.z) {
588 cgi->UI_PushWindow("quickload");
589 FS_CloseFile(&f);
590 }
591}
592
593/**
594 * @brief Saves to the quick save slot
595 * @sa SAV_GameQuickLoad_f
596 */
597static void SAV_GameQuickSave_f (void)
598{
599 if (!CP_IsRunning())
600 return;
601
602 if (!SAV_QuickSave())
603 Com_Printf("Could not save the campaign\n");
604 else
605 MS_AddNewMessage(_("Quicksave")gettext("Quicksave"), _("Campaign was successfully saved.")gettext("Campaign was successfully saved."), MSG_INFO);
606}
607
608/**
609 * @brief Loads the quick save slot
610 * @sa SAV_GameQuickSave_f
611 */
612static void SAV_GameQuickLoad_f (void)
613{
614 const char *error = NULL__null;
615
616 if (cgi->CL_OnBattlescape()) {
617 Com_Printf("Could not load the campaign while you are on the battlefield\n");
618 return;
619 }
620
621 if (!SAV_GameLoad("slotquick", &error)) {
622 Cbuf_Execute(); /* wipe outstanding campaign commands */
623 CP_Popup(_("Error")gettext("Error"), "%s\n%s", _("Error loading game.")gettext("Error loading game."), error ? error : "");
624 } else {
625 MS_AddNewMessage(_("Campaign loaded")gettext("Campaign loaded"), _("Quicksave campaign was successfully loaded.")gettext("Quicksave campaign was successfully loaded."), MSG_INFO);
626 CP_CheckBaseAttacks();
627 }
628}
629
630/**
631 * @brief Register all save-subsystems and init some cvars and commands
632 * @sa SAV_GameSave
633 * @sa SAV_GameLoad
634 */
635void SAV_Init (void)
636{
637 static saveSubsystems_t cp_subsystemXML = {"campaign", CP_SaveXML, CP_LoadXML};
638 static saveSubsystems_t b_subsystemXML = {"base", B_SaveXML, B_LoadXML};
639 static saveSubsystems_t rs_subsystemXML = {"research", RS_SaveXML, RS_LoadXML};
640 static saveSubsystems_t hos_subsystemXML = {"hospital", HOS_SaveXML, HOS_LoadXML};
641 static saveSubsystems_t bs_subsystemXML = {"market", BS_SaveXML, BS_LoadXML};
642 static saveSubsystems_t e_subsystemXML = {"employee", E_SaveXML, E_LoadXML};
643 static saveSubsystems_t ac_subsystemXML = {"aliencont", AC_SaveXML, AC_LoadXML};
644 static saveSubsystems_t pr_subsystemXML = {"production", PR_SaveXML, PR_LoadXML};
645 static saveSubsystems_t air_subsystemXML = {"aircraft", AIR_SaveXML, AIR_LoadXML};
646 static saveSubsystems_t ab_subsystemXML = {"alien base", AB_SaveXML, AB_LoadXML};
647 static saveSubsystems_t int_subsystemXML = {"interest", INT_SaveXML, INT_LoadXML};
648 static saveSubsystems_t mis_subsystemXML = {"mission", MIS_SaveXML, MIS_LoadXML};
649 static saveSubsystems_t ms_subsystemXML = {"messagesystem", MS_SaveXML, MS_LoadXML};
650 static saveSubsystems_t stats_subsystemXML = {"stats", STATS_SaveXML, STATS_LoadXML};
651 static saveSubsystems_t na_subsystemXML = {"nations", NAT_SaveXML, NAT_LoadXML};
652 static saveSubsystems_t trans_subsystemXML = {"transfer", TR_SaveXML, TR_LoadXML};
653 static saveSubsystems_t xvi_subsystemXML = {"xvirate", XVI_SaveXML, XVI_LoadXML};
654 static saveSubsystems_t ins_subsystemXML = {"installation", INS_SaveXML, INS_LoadXML};
655 static saveSubsystems_t mso_subsystemXML = {"messageoptions", MSO_SaveXML, MSO_LoadXML};
656 static saveSubsystems_t us_subsystemXML = {"ufostores", US_SaveXML, US_LoadXML};
657
658 saveSubsystemsAmount = 0;
659 OBJZERO(saveSubsystems)(memset(&((saveSubsystems)), (0), sizeof((saveSubsystems)
)))
;
660
661 Com_Printf("\n--- save subsystem initialization --\n");
662
663 /* don't mess with the order */
664 SAV_AddSubsystem(&cp_subsystemXML);
665 SAV_AddSubsystem(&b_subsystemXML);
666 SAV_AddSubsystem(&rs_subsystemXML);
667 SAV_AddSubsystem(&hos_subsystemXML);
668 SAV_AddSubsystem(&bs_subsystemXML);
669 SAV_AddSubsystem(&e_subsystemXML);
670 SAV_AddSubsystem(&ac_subsystemXML);
671 SAV_AddSubsystem(&air_subsystemXML);
672 SAV_AddSubsystem(&ab_subsystemXML);
673 SAV_AddSubsystem(&int_subsystemXML);
674 SAV_AddSubsystem(&ins_subsystemXML);
675 SAV_AddSubsystem(&mis_subsystemXML);
676 SAV_AddSubsystem(&us_subsystemXML);
677 SAV_AddSubsystem(&pr_subsystemXML);
678 SAV_AddSubsystem(&ms_subsystemXML);
679 SAV_AddSubsystem(&stats_subsystemXML);
680 SAV_AddSubsystem(&na_subsystemXML);
681 SAV_AddSubsystem(&trans_subsystemXML);
682 SAV_AddSubsystem(&xvi_subsystemXML);
683 SAV_AddSubsystem(&mso_subsystemXML);
684
685 /* Check whether there is a quicksave at all */
686 Cmd_AddCommand("game_quickloadinit", SAV_GameQuickLoadInit_f, "Load the game from the quick save slot.");
687 Cmd_AddCommand("game_quicksave", SAV_GameQuickSave_f, "Save to the quick save slot.");
688 Cmd_AddCommand("game_quickload", SAV_GameQuickLoad_f, "Load the game from the quick save slot.");
689 Cmd_AddCommand("game_save", SAV_GameSave_f, "Saves to a given filename");
690 Cmd_AddCommand("game_load", SAV_GameLoad_f, "Loads a given filename");
691 Cmd_AddCommand("game_comments", SAV_GameReadGameComments_f, "Loads the savegame names");
692 Cmd_AddCommand("game_continue", SAV_GameContinue_f, "Continue with the last saved game");
693 Cmd_AddCommand("game_savenamecleanup", SAV_GameSaveNameCleanup_f, "Remove the date string from mn_slotX cvars");
694 save_compressed = Cvar_Get("save_compressed", "1", CVAR_ARCHIVE1, "Save the savefiles compressed if set to 1");
695 cl_lastsave = Cvar_Get("cl_lastsave", "", CVAR_ARCHIVE1, "Last saved slot - use for the continue-campaign function");
696}