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