1 /*
2 ===========================================================================
3 Copyright (C) 2000 - 2013, Raven Software, Inc.
4 Copyright (C) 2001 - 2013, Activision, Inc.
5 Copyright (C) 2013 - 2015, OpenJK contributors
6
7 This file is part of the OpenJK source code.
8
9 OpenJK is free software; you can redistribute it and/or modify it
10 under the terms of the GNU General Public License version 2 as
11 published by the Free Software Foundation.
12
13 This program is distributed in the hope that it will be useful,
14 but WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 GNU General Public License for more details.
17
18 You should have received a copy of the GNU General Public License
19 along with this program; if not, see <http://www.gnu.org/licenses/>.
20 ===========================================================================
21 */
22
23 // Filename:- sv_savegame.cpp
24 #include <memory>
25 #include "../server/exe_headers.h"
26
27 #define JPEG_IMAGE_QUALITY 95
28
29 //#define USE_LAST_SAVE_FROM_THIS_MAP // enable this if you want to use the last explicity-loaded savegame from this map
30 // when respawning after dying, else it'll just load "auto" regardless
31 // (EF1 behaviour). I should maybe time/date check them though?
32
33 #include "server.h"
34 #include "../qcommon/stringed_ingame.h"
35 #include "../game/statindex.h"
36 #include "../game/weapons.h"
37 #include "../game/g_items.h"
38
39 #include <map>
40
41 #include "qcommon/ojk_saved_game.h"
42 #include "qcommon/ojk_saved_game_helper.h"
43
44
45 static char saveGameComment[iSG_COMMENT_SIZE];
46
47 //#define SG_PROFILE // enable for debug save stats if you want
48
49 int giSaveGameVersion; // filled in when a savegame file is opened
50
51 SavedGameJustLoaded_e eSavedGameJustLoaded = eNO;
52
53 char sLastSaveFileLoaded[MAX_QPATH]={0};
54
55 #ifdef JK2_MODE
56 #define iSG_MAPCMD_SIZE (MAX_TOKEN_CHARS)
57 #else
58 #define iSG_MAPCMD_SIZE (MAX_QPATH)
59 #endif // JK2_MODE
60
61 static char *SG_GetSaveGameMapName(const char *psPathlessBaseName);
62
63
64 #ifdef SG_PROFILE
65
66 class CChid
67 {
68 private:
69 int m_iCount;
70 int m_iSize;
71 public:
CChid()72 CChid()
73 {
74 m_iCount = 0;
75 m_iSize = 0;
76 }
Add(int iLength)77 void Add(int iLength)
78 {
79 m_iCount++;
80 m_iSize += iLength;
81 }
GetCount()82 int GetCount()
83 {
84 return m_iCount;
85 }
GetSize()86 int GetSize()
87 {
88 return m_iSize;
89 }
90 };
91
92 typedef map<unsigned int, CChid> CChidInfo_t;
93 CChidInfo_t save_info;
94 #endif
95
GetString_FailedToOpenSaveGame(const char * psFilename,qboolean bOpen)96 static const char *GetString_FailedToOpenSaveGame(const char *psFilename, qboolean bOpen)
97 {
98 static char sTemp[256];
99
100 strcpy(sTemp,S_COLOR_RED);
101
102 #ifdef JK2_MODE
103 const char *psReference = bOpen ? "MENUS3_FAILED_TO_OPEN_SAVEGAME" : "MENUS3_FAILED_TO_CREATE_SAVEGAME";
104 #else
105 const char *psReference = bOpen ? "MENUS_FAILED_TO_OPEN_SAVEGAME" : "MENUS3_FAILED_TO_CREATE_SAVEGAME";
106 #endif
107 Q_strncpyz(sTemp + strlen(sTemp), va( SE_GetString(psReference), psFilename),sizeof(sTemp));
108 strcat(sTemp,"\n");
109 return sTemp;
110 }
111
SG_WipeSavegame(const char * psPathlessBaseName)112 void SG_WipeSavegame(
113 const char* psPathlessBaseName)
114 {
115 ojk::SavedGame::remove(
116 psPathlessBaseName);
117 }
118
119 // called from the ERR_DROP stuff just in case the error occured during loading of a saved game, because if
120 // we didn't do this then we'd run out of quake file handles after the 8th load fail...
121 //
SG_Shutdown()122 void SG_Shutdown()
123 {
124 ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
125
126 saved_game.close();
127
128 eSavedGameJustLoaded = eNO;
129 // important to do this if we ERR_DROP during loading, else next map you load after
130 // a bad save-file you'll arrive at dead :-)
131
132 // and this bit stops people messing up the laoder by repeatedly stabbing at the load key during loads...
133 //
134 extern qboolean gbAlreadyDoingLoad;
135 gbAlreadyDoingLoad = qfalse;
136 }
137
SV_WipeGame_f(void)138 void SV_WipeGame_f(void)
139 {
140 if (Cmd_Argc() != 2)
141 {
142 Com_Printf (S_COLOR_RED "USAGE: wipe <name>\n");
143 return;
144 }
145 if (!Q_stricmp (Cmd_Argv(1), "auto") )
146 {
147 Com_Printf (S_COLOR_RED "Can't wipe 'auto'\n");
148 return;
149 }
150 SG_WipeSavegame(Cmd_Argv(1));
151 // Com_Printf("%s has been wiped\n", Cmd_Argv(1)); // wurde gel�scht in german, but we've only got one string
152 // Com_Printf("Ok\n"); // no localization of this
153 }
154
155 /*
156 // Store given string in saveGameComment for later use when game is
157 // actually saved
158 */
SG_StoreSaveGameComment(const char * sComment)159 void SG_StoreSaveGameComment(const char *sComment)
160 {
161 memmove(saveGameComment,sComment,iSG_COMMENT_SIZE);
162 }
163
SV_TryLoadTransition(const char * mapname)164 qboolean SV_TryLoadTransition( const char *mapname )
165 {
166 char *psFilename = va( "hub/%s", mapname );
167
168 Com_Printf (S_COLOR_CYAN "Restoring game \"%s\"...\n", psFilename);
169
170 if ( !SG_ReadSavegame( psFilename ) )
171 {//couldn't load a savegame
172 return qfalse;
173 }
174 #ifdef JK2_MODE
175 Com_Printf (S_COLOR_CYAN "Done.\n");
176 #else
177 Com_Printf (S_COLOR_CYAN "%s.\n",SE_GetString("MENUS_DONE"));
178 #endif
179
180 return qtrue;
181 }
182
183 qboolean gbAlreadyDoingLoad = qfalse;
SV_LoadGame_f(void)184 void SV_LoadGame_f(void)
185 {
186 if (gbAlreadyDoingLoad)
187 {
188 Com_DPrintf ("( Already loading, ignoring extra 'load' commands... )\n");
189 return;
190 }
191
192 // // check server is running
193 // //
194 // if ( !com_sv_running->integer )
195 // {
196 // Com_Printf( "Server is not running\n" );
197 // return;
198 // }
199
200 if (Cmd_Argc() != 2)
201 {
202 Com_Printf ("USAGE: load <filename>\n");
203 return;
204 }
205
206 const char *psFilename = Cmd_Argv(1);
207 if (strstr (psFilename, "..") || strstr (psFilename, "/") || strstr (psFilename, "\\") )
208 {
209 Com_Printf (S_COLOR_RED "Bad loadgame name.\n");
210 return;
211 }
212
213 if (!Q_stricmp (psFilename, "current"))
214 {
215 Com_Printf (S_COLOR_RED "Can't load from \"current\"\n");
216 return;
217 }
218
219 // special case, if doing a respawn then check that the available auto-save (if any) is from the same map
220 // as we're currently on (if in a map at all), if so, load that "auto", else re-load the last-loaded file...
221 //
222 if (!Q_stricmp(psFilename, "*respawn"))
223 {
224 psFilename = "auto"; // default to standard respawn behaviour
225
226 // see if there's a last-loaded file to even check against as regards loading...
227 //
228 if ( sLastSaveFileLoaded[0] )
229 {
230 const char *psServerInfo = sv.configstrings[CS_SERVERINFO];
231 const char *psMapName = Info_ValueForKey( psServerInfo, "mapname" );
232
233 char *psMapNameOfAutoSave = SG_GetSaveGameMapName("auto");
234
235 if ( !Q_stricmp(psMapName,"_brig") )
236 {//if you're in the brig and there is no autosave, load the last loaded savegame
237 if ( !psMapNameOfAutoSave )
238 {
239 psFilename = sLastSaveFileLoaded;
240 }
241 }
242 else
243 {
244 #ifdef USE_LAST_SAVE_FROM_THIS_MAP
245 // if the map name within the name of the last save file we explicitly loaded is the same
246 // as the current map, then use that...
247 //
248 char *psMapNameOfLastSaveFileLoaded = SG_GetSaveGameMapName(sLastSaveFileLoaded);
249
250 if (!Q_stricmp(psMapName,psMapNameOfLastSaveFileLoaded)))
251 {
252 psFilename = sLastSaveFileLoaded;
253 }
254 else
255 #endif
256 if (!(psMapName && psMapNameOfAutoSave && !Q_stricmp(psMapName,psMapNameOfAutoSave)))
257 {
258 // either there's no auto file, or it's from a different map to the one we've just died on...
259 //
260 psFilename = sLastSaveFileLoaded;
261 }
262 }
263 }
264 //default will continue to load auto
265 }
266 #ifdef JK2_MODE
267 Com_Printf (S_COLOR_CYAN "Loading game \"%s\"...\n", psFilename);
268 #else
269 Com_Printf (S_COLOR_CYAN "%s\n",va(SE_GetString("MENUS_LOADING_MAPNAME"), psFilename));
270 #endif
271
272 gbAlreadyDoingLoad = qtrue;
273 if (!SG_ReadSavegame(psFilename)) {
274 gbAlreadyDoingLoad = qfalse; // do NOT do this here now, need to wait until client spawn, unless the load failed.
275 } else
276 {
277 #ifdef JK2_MODE
278 Com_Printf (S_COLOR_CYAN "Done.\n");
279 #else
280 Com_Printf (S_COLOR_CYAN "%s.\n",SE_GetString("MENUS_DONE"));
281 #endif
282 }
283 }
284
285 qboolean SG_GameAllowedToSaveHere(qboolean inCamera);
286
287
288 //JLF notes
289 // save game will be in charge of creating a new directory
SV_SaveGame_f(void)290 void SV_SaveGame_f(void)
291 {
292 // check server is running
293 //
294 if ( !com_sv_running->integer )
295 {
296 Com_Printf( S_COLOR_RED "Server is not running\n" );
297 return;
298 }
299
300 if (sv.state != SS_GAME)
301 {
302 Com_Printf (S_COLOR_RED "You must be in a game to save.\n");
303 return;
304 }
305
306 // check args...
307 //
308 if ( Cmd_Argc() != 2 )
309 {
310 Com_Printf( "USAGE: save <filename>\n" );
311 return;
312 }
313
314
315 if (svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0)
316 {
317 #ifdef JK2_MODE
318 Com_Printf (S_COLOR_RED "\nCan't savegame while dead!\n");
319 #else
320 Com_Printf (S_COLOR_RED "\n%s\n", SE_GetString("SP_INGAME_CANT_SAVE_DEAD"));
321 #endif
322 return;
323 }
324
325 //this check catches deaths even the instant you die, like during a slo-mo death!
326 gentity_t *svent;
327 svent = SV_GentityNum(0);
328 if (svent->client->stats[STAT_HEALTH]<=0)
329 {
330 #ifdef JK2_MODE
331 Com_Printf (S_COLOR_RED "\nCan't savegame while dead!\n");
332 #else
333 Com_Printf (S_COLOR_RED "\n%s\n", SE_GetString("SP_INGAME_CANT_SAVE_DEAD"));
334 #endif
335 return;
336 }
337
338 const char *psFilename = Cmd_Argv(1);
339 char filename[MAX_QPATH] = {0};
340
341 Q_strncpyz(filename, psFilename, sizeof(filename));
342
343 if (!Q_stricmp (filename, "current"))
344 {
345 Com_Printf (S_COLOR_RED "Can't save to 'current'\n");
346 return;
347 }
348
349 if (strstr (filename, "..") || strstr (filename, "/") || strstr (filename, "\\") )
350 {
351 Com_Printf (S_COLOR_RED "Bad savegame name.\n");
352 return;
353 }
354
355 if (!SG_GameAllowedToSaveHere(qfalse)) //full check
356 return; // this prevents people saving via quick-save now during cinematics.
357
358 #ifdef JK2_MODE
359 if ( !Q_stricmp (filename, "quik*") || !Q_stricmp (filename, "auto*") )
360 {
361 SCR_PrecacheScreenshot();
362 if ( filename[4]=='*' )
363 filename[4]=0; //remove the *
364 SG_StoreSaveGameComment(""); // clear previous comment/description, which will force time/date comment.
365 }
366 #else
367 if ( !Q_stricmp (filename, "auto") )
368 {
369 SG_StoreSaveGameComment(""); // clear previous comment/description, which will force time/date comment.
370 }
371 #endif
372
373 #ifdef JK2_MODE
374 Com_Printf (S_COLOR_CYAN "Saving game \"%s\"...\n", filename);
375 #else
376 Com_Printf (S_COLOR_CYAN "%s \"%s\"...\n", SE_GetString("CON_TEXT_SAVING_GAME"), filename);
377 #endif
378
379 if (SG_WriteSavegame(filename, qfalse))
380 {
381 #ifdef JK2_MODE
382 Com_Printf (S_COLOR_CYAN "Done.\n");
383 #else
384 Com_Printf (S_COLOR_CYAN "%s.\n",SE_GetString("MENUS_DONE"));
385 #endif
386 }
387 else
388 {
389 #ifdef JK2_MODE
390 Com_Printf (S_COLOR_RED "Failed.\n");
391 #else
392 Com_Printf (S_COLOR_RED "%s.\n",SE_GetString("MENUS_FAILED_TO_OPEN_SAVEGAME"));
393 #endif
394 }
395 }
396
397
398
399 //---------------
WriteGame(qboolean autosave)400 static void WriteGame(qboolean autosave)
401 {
402 ojk::SavedGameHelper saved_game(
403 &ojk::SavedGame::get_instance());
404
405 saved_game.write_chunk<int32_t>(
406 INT_ID('G', 'A', 'M', 'E'),
407 autosave);
408
409 if (autosave)
410 {
411 // write out player ammo level, health, etc...
412 //
413 extern void SV_Player_EndOfLevelSave(void);
414 SV_Player_EndOfLevelSave(); // this sets up the various cvars needed, so we can then write them to disk
415 //
416 char s[MAX_STRING_CHARS];
417
418 // write health/armour etc...
419 //
420 memset(s,0,sizeof(s));
421 Cvar_VariableStringBuffer( sCVARNAME_PLAYERSAVE, s, sizeof(s) );
422
423 saved_game.write_chunk(
424 INT_ID('C', 'V', 'S', 'V'),
425 s);
426
427 // write ammo...
428 //
429 memset(s,0,sizeof(s));
430 Cvar_VariableStringBuffer( "playerammo", s, sizeof(s) );
431
432 saved_game.write_chunk(
433 INT_ID('A', 'M', 'M', 'O'),
434 s);
435
436 // write inventory...
437 //
438 memset(s,0,sizeof(s));
439 Cvar_VariableStringBuffer( "playerinv", s, sizeof(s) );
440
441 saved_game.write_chunk(
442 INT_ID('I', 'V', 'T', 'Y'),
443 s);
444
445 // the new JK2 stuff - force powers, etc...
446 //
447 memset(s,0,sizeof(s));
448 Cvar_VariableStringBuffer( "playerfplvl", s, sizeof(s) );
449
450 saved_game.write_chunk(
451 INT_ID('F', 'P', 'L', 'V'),
452 s);
453 }
454 }
455
ReadGame(void)456 static qboolean ReadGame (void)
457 {
458 qboolean qbAutoSave = qfalse;
459
460 ojk::SavedGameHelper saved_game(
461 &ojk::SavedGame::get_instance());
462
463 saved_game.read_chunk<int32_t>(
464 INT_ID('G', 'A', 'M', 'E'),
465 qbAutoSave);
466
467 if (qbAutoSave)
468 {
469 char s[MAX_STRING_CHARS]={0};
470
471 // read health/armour etc...
472 //
473 memset(s,0,sizeof(s));
474
475 saved_game.read_chunk(
476 INT_ID('C', 'V', 'S', 'V'),
477 s);
478
479 Cvar_Set( sCVARNAME_PLAYERSAVE, s );
480
481 // read ammo...
482 //
483 memset(s,0,sizeof(s));
484
485 saved_game.read_chunk(
486 INT_ID('A', 'M', 'M', 'O'),
487 s);
488
489 Cvar_Set( "playerammo", s);
490
491 // read inventory...
492 //
493 memset(s,0,sizeof(s));
494
495 saved_game.read_chunk(
496 INT_ID('I', 'V', 'T', 'Y'),
497 s);
498
499 Cvar_Set( "playerinv", s);
500
501 // read force powers...
502 //
503 memset(s,0,sizeof(s));
504
505 saved_game.read_chunk(
506 INT_ID('F', 'P', 'L', 'V'),
507 s);
508
509 Cvar_Set( "playerfplvl", s );
510 }
511
512 return qbAutoSave;
513 }
514
515 //---------------
516
517
518 // write all CVAR_SAVEGAME cvars
519 // these will be things like model, name, ...
520 //
521 extern cvar_t *cvar_vars; // I know this is really unpleasant, but I need access for scanning/writing latched cvars during save games
522
SG_WriteCvars(void)523 void SG_WriteCvars(void)
524 {
525 cvar_t *var;
526 int iCount = 0;
527
528 ojk::SavedGameHelper saved_game(
529 &ojk::SavedGame::get_instance());
530
531 // count the cvars...
532 //
533 for (var = cvar_vars; var; var = var->next)
534 {
535 #ifdef JK2_MODE
536 if (!(var->flags & (CVAR_SAVEGAME|CVAR_USERINFO)))
537 #else
538 if (!(var->flags & CVAR_SAVEGAME))
539 #endif
540 {
541 continue;
542 }
543 iCount++;
544 }
545
546 // store count...
547 //
548 saved_game.write_chunk<int32_t>(
549 INT_ID('C', 'V', 'C', 'N'),
550 iCount);
551
552 // write 'em...
553 //
554 for (var = cvar_vars; var; var = var->next)
555 {
556 #ifdef JK2_MODE
557 if (!(var->flags & (CVAR_SAVEGAME|CVAR_USERINFO)))
558 #else
559 if (!(var->flags & CVAR_SAVEGAME))
560 #endif
561 {
562 continue;
563 }
564
565 saved_game.write_chunk(
566 INT_ID('C', 'V', 'A', 'R'),
567 var->name,
568 static_cast<int>(strlen(var->name) + 1));
569
570 saved_game.write_chunk(
571 INT_ID('V', 'A', 'L', 'U'),
572 var->string,
573 static_cast<int>(strlen(var->string) + 1));
574 }
575 }
576
SG_ReadCvars()577 void SG_ReadCvars()
578 {
579 int iCount = 0;
580 std::string psName;
581 const char* psValue;
582
583 ojk::SavedGameHelper saved_game(
584 &ojk::SavedGame::get_instance());
585
586 saved_game.read_chunk<int32_t>(
587 INT_ID('C', 'V', 'C', 'N'),
588 iCount);
589
590 for (int i = 0; i < iCount; ++i)
591 {
592 saved_game.read_chunk(
593 INT_ID('C', 'V', 'A', 'R'));
594
595 psName = reinterpret_cast<const char*>(
596 saved_game.get_buffer_data());
597
598
599 saved_game.read_chunk(
600 INT_ID('V', 'A', 'L', 'U'));
601
602 psValue = reinterpret_cast<const char*>(
603 saved_game.get_buffer_data());
604
605 ::Cvar_Set(psName.c_str(), psValue);
606 }
607 }
608
SG_WriteServerConfigStrings()609 void SG_WriteServerConfigStrings()
610 {
611 ojk::SavedGameHelper saved_game(
612 &ojk::SavedGame::get_instance());
613
614 int iCount = 0;
615 int i; // not in FOR statement in case compiler goes weird by reg-optimising it then failing to get the address later
616
617 // count how many non-blank server strings there are...
618 //
619 for ( i=0; i<MAX_CONFIGSTRINGS; i++)
620 {
621 if (i!=CS_SYSTEMINFO)
622 {
623 if (sv.configstrings[i] && strlen(sv.configstrings[i])) // paranoia... <g>
624 {
625 iCount++;
626 }
627 }
628 }
629
630 saved_game.write_chunk<int32_t>(
631 INT_ID('C', 'S', 'C', 'N'),
632 iCount);
633
634 // now write 'em...
635 //
636 for (i=0; i<MAX_CONFIGSTRINGS; i++)
637 {
638 if (i!=CS_SYSTEMINFO)
639 {
640 if (sv.configstrings[i] && strlen(sv.configstrings[i]))
641 {
642 saved_game.write_chunk<int32_t>(
643 INT_ID('C', 'S', 'I', 'N'),
644 i);
645
646 saved_game.write_chunk(
647 INT_ID('C', 'S', 'D', 'A'),
648 ::sv.configstrings[i],
649 static_cast<int>(strlen(::sv.configstrings[i]) + 1));
650 }
651 }
652 }
653 }
654
SG_ReadServerConfigStrings(void)655 void SG_ReadServerConfigStrings( void )
656 {
657 // trash the whole table...
658 //
659 for (int i=0; i<MAX_CONFIGSTRINGS; i++)
660 {
661 if (i!=CS_SYSTEMINFO)
662 {
663 if ( sv.configstrings[i] )
664 {
665 Z_Free( sv.configstrings[i] );
666 }
667 sv.configstrings[i] = CopyString("");
668 }
669 }
670
671 // now read the replacement ones...
672 //
673 int iCount = 0;
674
675 ojk::SavedGameHelper saved_game(
676 &ojk::SavedGame::get_instance());
677
678 saved_game.read_chunk<int32_t>(
679 INT_ID('C', 'S', 'C', 'N'),
680 iCount);
681
682 Com_DPrintf( "Reading %d configstrings...\n",iCount);
683
684 for (int i = 0; i<iCount; i++)
685 {
686 int iIndex = 0;
687 const char *psName;
688
689 saved_game.read_chunk<int32_t>(
690 INT_ID('C', 'S', 'I', 'N'),
691 iIndex);
692
693 saved_game.read_chunk(
694 INT_ID('C', 'S', 'D', 'A'));
695
696 psName = reinterpret_cast<const char*>(
697 saved_game.get_buffer_data());
698
699 Com_DPrintf( "Cfg str %d = %s\n",iIndex, psName);
700
701 //sv.configstrings[iIndex] = psName;
702 SV_SetConfigstring(iIndex, psName);
703 }
704 }
705
SG_UnixTimestamp(const time_t & t)706 static unsigned int SG_UnixTimestamp ( const time_t& t )
707 {
708 return static_cast<unsigned int>(t);
709 }
710
SG_WriteComment(qboolean qbAutosave,const char * psMapName)711 static void SG_WriteComment(qboolean qbAutosave, const char *psMapName)
712 {
713 ojk::SavedGameHelper saved_game(
714 &ojk::SavedGame::get_instance());
715
716 char sComment[iSG_COMMENT_SIZE];
717
718 if ( qbAutosave || !*saveGameComment)
719 {
720 Com_sprintf( sComment, sizeof(sComment), "---> %s", psMapName );
721 }
722 else
723 {
724 Q_strncpyz(sComment,saveGameComment, sizeof(sComment));
725 }
726
727 saved_game.write_chunk(
728 INT_ID('C', 'O', 'M', 'M'),
729 sComment);
730
731 // Add Date/Time/Map stamp
732 unsigned int timestamp = SG_UnixTimestamp (time (NULL));
733
734 saved_game.write_chunk<uint32_t>(
735 INT_ID('C', 'M', 'T', 'M'),
736 timestamp);
737
738 Com_DPrintf("Saving: current (%s)\n", sComment);
739 }
740
SG_GetTime(unsigned int timestamp)741 static time_t SG_GetTime ( unsigned int timestamp )
742 {
743 return static_cast<time_t>(timestamp);
744 }
745
746 // Test to see if the given file name is in the save game directory
747 // then grab the comment if it's there
748 //
SG_GetSaveGameComment(const char * psPathlessBaseName,char * sComment,char * sMapName)749 int SG_GetSaveGameComment(
750 const char* psPathlessBaseName,
751 char* sComment,
752 char* sMapName)
753 {
754 int ret = 0;
755
756 ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
757
758 ojk::SavedGameHelper sgh(
759 &ojk::SavedGame::get_instance());
760
761 if (!saved_game.open(
762 psPathlessBaseName))
763 {
764 return 0;
765 }
766
767 bool is_succeed = true;
768
769 // Read description
770 //
771 is_succeed = sgh.try_read_chunk(
772 INT_ID('C', 'O', 'M', 'M'));
773
774 if (is_succeed)
775 {
776 if (sComment)
777 {
778 if (sgh.get_buffer_size() == iSG_COMMENT_SIZE)
779 {
780 std::uninitialized_copy_n(
781 static_cast<const char*>(sgh.get_buffer_data()),
782 iSG_COMMENT_SIZE,
783 sComment);
784 }
785 else
786 {
787 sComment[0] = '\0';
788 }
789 }
790 }
791
792 // Read timestamp
793 //
794 time_t tFileTime = ::SG_GetTime(0);
795
796 if (is_succeed)
797 {
798 unsigned int fileTime = 0;
799
800 is_succeed = sgh.try_read_chunk<uint32_t>(
801 INT_ID('C', 'M', 'T', 'M'),
802 fileTime);
803
804 if (is_succeed)
805 {
806 tFileTime = ::SG_GetTime(
807 fileTime);
808 }
809 }
810
811 #ifdef JK2_MODE
812 // Read screenshot
813 //
814
815 if (is_succeed)
816 {
817 size_t iScreenShotLength;
818
819 is_succeed = sgh.try_read_chunk<uint32_t>(
820 INT_ID('S', 'H', 'L', 'N'),
821 iScreenShotLength);
822 }
823
824 if (is_succeed)
825 {
826 is_succeed = sgh.try_read_chunk(
827 INT_ID('S', 'H', 'O', 'T'));
828 }
829 #endif
830
831 // Read mapname
832 //
833 if (is_succeed)
834 {
835 is_succeed = sgh.try_read_chunk(
836 INT_ID('M', 'P', 'C', 'M'));
837
838 if (is_succeed)
839 {
840 if (sMapName)
841 {
842 if (sgh.get_buffer_size() == iSG_MAPCMD_SIZE)
843 {
844 std::uninitialized_copy_n(
845 static_cast<const char*>(sgh.get_buffer_data()),
846 iSG_MAPCMD_SIZE,
847 sMapName);
848 }
849 else
850 {
851 sMapName[0] = '\0';
852 }
853 }
854 }
855 }
856
857 ret = tFileTime;
858
859 saved_game.close();
860
861 return ret;
862 }
863
864
865 // read the mapname field from the supplied savegame file
866 //
867 // returns NULL if not found
868 //
SG_GetSaveGameMapName(const char * psPathlessBaseName)869 static char *SG_GetSaveGameMapName(const char *psPathlessBaseName)
870 {
871 static char sMapName[iSG_MAPCMD_SIZE]={0};
872 char *psReturn = NULL;
873 if (SG_GetSaveGameComment(psPathlessBaseName, NULL, sMapName))
874 {
875 psReturn = sMapName;
876 }
877
878 return psReturn;
879 }
880
881
882 // pass in qtrue to set as loading screen, else pass in pvDest to read it into there...
883 //
884 #ifdef JK2_MODE
SG_ReadScreenshot(bool set_as_loading_screen,void * screenshot_ptr)885 static bool SG_ReadScreenshot(
886 bool set_as_loading_screen,
887 void* screenshot_ptr)
888 {
889 bool is_succeed = true;
890
891 ojk::SavedGameHelper saved_game(
892 &ojk::SavedGame::get_instance());
893
894 // get JPG screenshot data length...
895 //
896 size_t screenshot_length = 0;
897
898 is_succeed = saved_game.try_read_chunk<uint32_t>(
899 INT_ID('S', 'H', 'L', 'N'),
900 screenshot_length);
901
902 //
903 // alloc enough space plus extra 4K for sloppy JPG-decode reader to not do memory access violation...
904 //
905 byte* jpeg_data = nullptr;
906
907 if (is_succeed)
908 {
909 jpeg_data = static_cast<byte*>(::Z_Malloc(
910 static_cast<int>(screenshot_length + 4096),
911 TAG_TEMP_WORKSPACE,
912 qfalse));
913 }
914
915 //
916 // now read the JPG data...
917 //
918 if (is_succeed)
919 {
920 is_succeed = saved_game.try_read_chunk(
921 INT_ID('S', 'H', 'O', 'T'),
922 jpeg_data,
923 static_cast<int>(screenshot_length));
924 }
925
926 //
927 // decompress JPG data...
928 //
929 byte* image = NULL;
930 int width;
931 int height;
932
933 if (is_succeed)
934 {
935 ::re.LoadJPGFromBuffer(
936 jpeg_data,
937 screenshot_length,
938 &image,
939 &width,
940 &height);
941
942 //
943 // if the loaded image is the same size as the game is expecting, then copy it to supplied arg (if present)...
944 //
945 if (width == SG_SCR_WIDTH && height == SG_SCR_HEIGHT)
946 {
947 if (screenshot_ptr)
948 {
949 ::memcpy(
950 screenshot_ptr,
951 image,
952 SG_SCR_WIDTH * SG_SCR_HEIGHT * 4);
953 }
954
955 if (set_as_loading_screen)
956 {
957 ::SCR_SetScreenshot(
958 image,
959 SG_SCR_WIDTH,
960 SG_SCR_HEIGHT);
961 }
962 }
963 else
964 {
965 is_succeed = false;
966 }
967 }
968
969 if (jpeg_data)
970 {
971 ::Z_Free(jpeg_data);
972 }
973
974 if (image)
975 {
976 ::Z_Free(image);
977 }
978
979 return is_succeed;
980 }
981 // Gets the savegame screenshot
982 //
SG_GetSaveImage(const char * base_name,void * image_ptr)983 qboolean SG_GetSaveImage(
984 const char* base_name,
985 void* image_ptr)
986 {
987 if (!base_name)
988 {
989 return qfalse;
990 }
991
992 ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
993
994 if (!saved_game.open(base_name))
995 {
996 return qfalse;
997 }
998
999 bool is_succeed = true;
1000
1001 ojk::SavedGameHelper sgh(
1002 &saved_game);
1003
1004 is_succeed = sgh.try_read_chunk(
1005 INT_ID('C', 'O', 'M', 'M'));
1006
1007 if (is_succeed)
1008 {
1009 is_succeed = sgh.try_read_chunk(
1010 INT_ID('C', 'M', 'T', 'M'));
1011 }
1012
1013 if (is_succeed)
1014 {
1015 is_succeed = SG_ReadScreenshot(
1016 false,
1017 image_ptr);
1018 }
1019
1020 saved_game.close();
1021
1022 return is_succeed ? qtrue : qfalse;
1023 }
1024
1025
SG_WriteScreenshot(qboolean qbAutosave,const char * psMapName)1026 static void SG_WriteScreenshot(qboolean qbAutosave, const char *psMapName)
1027 {
1028 ojk::SavedGameHelper saved_game(
1029 &ojk::SavedGame::get_instance());
1030
1031 byte *pbRawScreenShot = NULL;
1032 byte *byBlank = NULL;
1033
1034 if( qbAutosave )
1035 {
1036 // try to read a levelshot (any valid TGA/JPG etc named the same as the map)...
1037 //
1038 int iWidth = SG_SCR_WIDTH;
1039 int iHeight= SG_SCR_HEIGHT;
1040 const size_t bySize = SG_SCR_WIDTH * SG_SCR_HEIGHT * 4;
1041 byte *src, *dst;
1042
1043 byBlank = new byte[bySize];
1044 pbRawScreenShot = SCR_TempRawImage_ReadFromFile(va("levelshots/%s.tga",psMapName), &iWidth, &iHeight, byBlank, qtrue); // qtrue = vert flip
1045
1046 if (pbRawScreenShot)
1047 {
1048 for (int y = 0; y < iHeight; y++)
1049 {
1050 for (int x = 0; x < iWidth; x++)
1051 {
1052 src = pbRawScreenShot + 4 * (y * iWidth + x);
1053 dst = pbRawScreenShot + 3 * (y * iWidth + x);
1054 dst[0] = src[0];
1055 dst[1] = src[1];
1056 dst[2] = src[2];
1057 }
1058 }
1059 }
1060 }
1061
1062 if (!pbRawScreenShot)
1063 {
1064 pbRawScreenShot = SCR_GetScreenshot(0);
1065 }
1066
1067
1068 size_t iJPGDataSize = 0;
1069 size_t bufSize = SG_SCR_WIDTH * SG_SCR_HEIGHT * 3;
1070 byte *pJPGData = (byte *)Z_Malloc( static_cast<int>(bufSize), TAG_TEMP_WORKSPACE, qfalse, 4 );
1071
1072 #ifdef JK2_MODE
1073 bool flip_vertical = true;
1074 #else
1075 bool flip_vertical = false;
1076 #endif // JK2_MODE
1077
1078 iJPGDataSize = re.SaveJPGToBuffer(pJPGData, bufSize, JPEG_IMAGE_QUALITY, SG_SCR_WIDTH, SG_SCR_HEIGHT, pbRawScreenShot, 0, flip_vertical );
1079 if ( qbAutosave )
1080 delete[] byBlank;
1081
1082 saved_game.write_chunk<uint32_t>(
1083 INT_ID('S', 'H', 'L', 'N'),
1084 iJPGDataSize);
1085
1086 saved_game.write_chunk(
1087 INT_ID('S', 'H', 'O', 'T'),
1088 pJPGData,
1089 static_cast<int>(iJPGDataSize));
1090
1091 Z_Free(pJPGData);
1092 SCR_TempRawImage_CleanUp();
1093 }
1094 #endif
1095
1096
SG_GameAllowedToSaveHere(qboolean inCamera)1097 qboolean SG_GameAllowedToSaveHere(qboolean inCamera)
1098 {
1099 if (!inCamera) {
1100 if ( !com_sv_running || !com_sv_running->integer )
1101 {
1102 return qfalse; // Com_Printf( S_COLOR_RED "Server is not running\n" );
1103 }
1104
1105 if (CL_IsRunningInGameCinematic())
1106 {
1107 return qfalse; //nope, not during a video
1108 }
1109
1110 if (sv.state != SS_GAME)
1111 {
1112 return qfalse; // Com_Printf (S_COLOR_RED "You must be in a game to save.\n");
1113 }
1114
1115 //No savegames from "_" maps
1116 if ( !sv_mapname || (sv_mapname->string != NULL && sv_mapname->string[0] == '_') )
1117 {
1118 return qfalse; // Com_Printf (S_COLOR_RED "Cannot save on holodeck or brig.\n");
1119 }
1120
1121 if (svs.clients[0].frames[svs.clients[0].netchan.outgoingSequence & PACKET_MASK].ps.stats[STAT_HEALTH] <= 0)
1122 {
1123 return qfalse; // Com_Printf (S_COLOR_RED "\nCan't savegame while dead!\n");
1124 }
1125 }
1126 if (!ge)
1127 return inCamera; // only happens when called to test if inCamera
1128
1129 return ge->GameAllowedToSaveHere();
1130 }
1131
SG_WriteSavegame(const char * psPathlessBaseName,qboolean qbAutosave)1132 qboolean SG_WriteSavegame(const char *psPathlessBaseName, qboolean qbAutosave)
1133 {
1134 if (!qbAutosave && !SG_GameAllowedToSaveHere(qfalse)) //full check
1135 return qfalse; // this prevents people saving via quick-save now during cinematics
1136
1137 int iPrevTestSave = sv_testsave->integer;
1138 sv_testsave->integer = 0;
1139
1140 // Write out server data...
1141 //
1142 const char *psServerInfo = sv.configstrings[CS_SERVERINFO];
1143 const char *psMapName = Info_ValueForKey( psServerInfo, "mapname" );
1144 //JLF
1145 #ifdef JK2_MODE
1146 if ( !strcmp("quik",psPathlessBaseName))
1147 #else
1148 if ( !strcmp("quick",psPathlessBaseName))
1149 #endif
1150 {
1151 SG_StoreSaveGameComment(va("--> %s <--",psMapName));
1152 }
1153
1154 ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
1155
1156 if(!saved_game.create( "current" ))
1157 {
1158 Com_Printf (GetString_FailedToOpenSaveGame("current",qfalse));//S_COLOR_RED "Failed to create savegame\n");
1159 SG_WipeSavegame( "current" );
1160 sv_testsave->integer = iPrevTestSave;
1161 return qfalse;
1162 }
1163 //END JLF
1164
1165 ojk::SavedGameHelper sgh(
1166 &saved_game);
1167
1168 char sMapCmd[iSG_MAPCMD_SIZE]={0};
1169 Q_strncpyz( sMapCmd,psMapName, sizeof(sMapCmd)); // need as array rather than ptr because const strlen needed for MPCM chunk
1170
1171 SG_WriteComment(qbAutosave, sMapCmd);
1172 #ifdef JK2_MODE
1173 SG_WriteScreenshot(qbAutosave, sMapCmd);
1174 #endif
1175
1176 sgh.write_chunk(
1177 INT_ID('M', 'P', 'C', 'M'),
1178 sMapCmd);
1179
1180 SG_WriteCvars();
1181
1182 WriteGame (qbAutosave);
1183
1184 // Write out all the level data...
1185 //
1186 if (!qbAutosave)
1187 {
1188 sgh.write_chunk<int32_t>(
1189 INT_ID('T', 'I', 'M', 'E'),
1190 ::sv.time);
1191
1192 sgh.write_chunk<int32_t>(
1193 INT_ID('T', 'I', 'M', 'R'),
1194 ::sv.timeResidual);
1195
1196 CM_WritePortalState();
1197 SG_WriteServerConfigStrings();
1198 }
1199 ge->WriteLevel(qbAutosave); // always done now, but ent saver only does player if auto
1200
1201 bool is_write_failed = saved_game.is_failed();
1202
1203 saved_game.close();
1204
1205 if (is_write_failed)
1206 {
1207 Com_Printf (GetString_FailedToOpenSaveGame("current",qfalse));//S_COLOR_RED "Failed to write savegame!\n");
1208 SG_WipeSavegame( "current" );
1209 sv_testsave->integer = iPrevTestSave;
1210 return qfalse;
1211 }
1212
1213 ojk::SavedGame::rename(
1214 "current",
1215 psPathlessBaseName);
1216
1217 sv_testsave->integer = iPrevTestSave;
1218 return qtrue;
1219 }
1220
SG_ReadSavegame(const char * psPathlessBaseName)1221 qboolean SG_ReadSavegame(
1222 const char* psPathlessBaseName)
1223 {
1224 char sComment[iSG_COMMENT_SIZE];
1225 char sMapCmd[iSG_MAPCMD_SIZE];
1226
1227 #ifdef JK2_MODE
1228 Cvar_Set(
1229 "cg_missionstatusscreen",
1230 "0");
1231 #endif
1232
1233 ojk::SavedGame& saved_game = ojk::SavedGame::get_instance();
1234
1235 ojk::SavedGameHelper sgh(
1236 &saved_game);
1237
1238 const int iPrevTestSave = ::sv_testsave->integer;
1239
1240 ojk::ScopeGuard scope_guard(
1241 [&]()
1242 {
1243 ::sv_testsave->integer = 0;
1244 },
1245
1246 [&]()
1247 {
1248 saved_game.close();
1249
1250 ::sv_testsave->integer = iPrevTestSave;
1251 }
1252 );
1253
1254
1255 if (!saved_game.open(psPathlessBaseName))
1256 {
1257 //S_COLOR_RED "Failed to open savegame \"%s\"\n", psPathlessBaseName);
1258 ::Com_Printf(
1259 ::GetString_FailedToOpenSaveGame(
1260 psPathlessBaseName,
1261 qtrue));
1262
1263 return qfalse;
1264 }
1265
1266 // this check isn't really necessary, but it reminds me that these two strings may actually be the same physical one.
1267 //
1268 if (psPathlessBaseName != sLastSaveFileLoaded)
1269 {
1270 ::Q_strncpyz(
1271 ::sLastSaveFileLoaded,
1272 psPathlessBaseName,
1273 sizeof(sLastSaveFileLoaded));
1274 }
1275
1276 // Read in all the server data...
1277 //
1278 sgh.read_chunk(
1279 INT_ID('C', 'O', 'M', 'M'),
1280 sComment);
1281
1282 ::Com_DPrintf(
1283 "Reading: %s\n",
1284 sComment);
1285
1286 sgh.read_chunk(
1287 INT_ID('C', 'M', 'T', 'M'));
1288
1289 #ifdef JK2_MODE
1290 ::SG_ReadScreenshot(
1291 true,
1292 nullptr);
1293 #endif
1294
1295 sgh.read_chunk(
1296 INT_ID('M', 'P', 'C', 'M'),
1297 sMapCmd);
1298
1299 ::SG_ReadCvars();
1300
1301 // read game state
1302 const qboolean qbAutosave = ::ReadGame();
1303
1304 ::eSavedGameJustLoaded = (qbAutosave ? eAUTO : eFULL);
1305
1306 // note that this also trashes the whole G_Alloc pool as well (of course)
1307 ::SV_SpawnServer(
1308 sMapCmd,
1309 eForceReload_NOTHING,
1310 (::eSavedGameJustLoaded != eFULL ? qtrue : qfalse));
1311
1312 // read in all the level data...
1313 //
1314 if (!qbAutosave)
1315 {
1316 sgh.read_chunk<int32_t>(
1317 INT_ID('T', 'I', 'M', 'E'),
1318 ::sv.time);
1319
1320 sgh.read_chunk<int32_t>(
1321 INT_ID('T', 'I', 'M', 'R'),
1322 ::sv.timeResidual);
1323
1324 ::CM_ReadPortalState();
1325 ::SG_ReadServerConfigStrings();
1326 }
1327
1328 // always done now, but ent reader only does player if auto
1329 ::ge->ReadLevel(
1330 qbAutosave,
1331 qbLoadTransition);
1332
1333 return qtrue;
1334 }
1335
SG_TestSave(void)1336 void SG_TestSave(void)
1337 {
1338 if (sv_testsave->integer && sv.state == SS_GAME)
1339 {
1340 WriteGame(qfalse);
1341 ge->WriteLevel(qfalse);
1342 }
1343 }
1344