File: | client/cgame/campaign/cp_save.cpp |
Location: | line 346, column 2 |
Description: | Value stored to 'res' is never read |
1 | /** |
2 | * @file |
3 | * @brief Implements savegames |
4 | */ |
5 | |
6 | /* |
7 | Copyright (C) 2002-2011 UFO: Alien Invasion. |
8 | |
9 | This program is free software; you can redistribute it and/or |
10 | modify it under the terms of the GNU General Public License |
11 | as published by the Free Software Foundation; either version 2 |
12 | of the License, or (at your option) any later version. |
13 | |
14 | This program is distributed in the hope that it will be useful, |
15 | but WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | |
18 | See the GNU General Public License for more details. |
19 | |
20 | You should have received a copy of the GNU General Public License |
21 | along with this program; if not, write to the Free Software |
22 | Foundation, 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 | |
36 | typedef 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 | |
48 | static saveSubsystems_t saveSubsystems[MAX_SAVESUBSYSTEMS32]; |
49 | static int saveSubsystemsAmount; |
50 | static cvar_t* save_compressed; |
51 | static cvar_t *cl_lastsave; |
52 | |
53 | /** |
54 | * @brief Perform actions after loading a game for single player campaign |
55 | * @sa SAV_GameLoad |
56 | */ |
57 | static 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 | */ |
78 | static 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 | */ |
128 | bool 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 | */ |
248 | static 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 | */ |
357 | static 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 | */ |
394 | static 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 | */ |
427 | static 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 | */ |
445 | static 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 | */ |
476 | static 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 | */ |
503 | bool 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 | */ |
522 | static 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 | */ |
559 | bool 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 | */ |
578 | static 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 | */ |
597 | static 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 | */ |
612 | static 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 | */ |
635 | void 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 | } |