1 /* ScummVM - Graphic Adventure Engine
2  *
3  * ScummVM is the legal property of its developers, whose names
4  * are too numerous to list here. Please refer to the COPYRIGHT
5  * file distributed with this source distribution.
6  *
7  * This program is free software; you can redistribute it and/or
8  * modify it under the terms of the GNU General Public License
9  * as published by the Free Software Foundation; either version 2
10  * of the License, or (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
20  *
21  * Save and restore scene and game.
22  */
23 
24 #include "tinsel/actors.h"
25 #include "tinsel/config.h"
26 #include "tinsel/dialogs.h"
27 #include "tinsel/drives.h"
28 #include "tinsel/dw.h"
29 #include "tinsel/rince.h"
30 #include "tinsel/savescn.h"
31 #include "tinsel/timers.h"
32 #include "tinsel/tinlib.h"
33 #include "tinsel/tinsel.h"
34 
35 #include "common/serializer.h"
36 #include "common/savefile.h"
37 #include "common/textconsole.h"
38 #include "common/translation.h"
39 
40 #include "gui/message.h"
41 
42 namespace Tinsel {
43 
44 
45 /**
46  * The current savegame format version.
47  * Our save/load system uses an elaborate scheme to allow us to modify the
48  * savegame while keeping full backward compatibility, in the sense that newer
49  * ScummVM versions always are able to load old savegames.
50  * In order to achieve that, we store a version in the savegame files, and whenever
51  * the savegame layout is modified, the version is incremented.
52  *
53  * This roughly works by marking each savegame entry with a range of versions
54  * for which it is valid; the save/load code iterates over all entries, but
55  * only saves/loads those which are valid for the version of the savegame
56  * which is being loaded/saved currently.
57  */
58 #define CURRENT_VER 3
59 
60 //----------------- GLOBAL GLOBAL DATA --------------------
61 
62 int	g_thingHeld = 0;
63 int	g_restoreCD = 0;
64 SRSTATE g_SRstate = SR_IDLE;
65 
66 //----------------- EXTERN FUNCTIONS --------------------
67 
68 // in DOS_DW.C
69 extern void syncSCdata(Common::Serializer &s);
70 
71 // in PCODE.C
72 extern void syncGlobInfo(Common::Serializer &s);
73 
74 // in POLYGONS.C
75 extern void syncPolyInfo(Common::Serializer &s);
76 
77 extern int g_sceneCtr;
78 
79 extern bool g_ASceneIsSaved;
80 
81 //----------------- LOCAL DEFINES --------------------
82 
83 struct SaveGameHeader {
84 	uint32 id;
85 	uint32 size;
86 	uint32 ver;
87 	char desc[SG_DESC_LEN];
88 	TimeDate dateTime;
89 	uint32 playTime;
90 	bool scnFlag;
91 	byte language;
92 	uint16 numInterpreters;			// Savegame version 2 or later only
93 };
94 
95 enum {
96 	DW1_SAVEGAME_ID = 0x44575399,	// = 'DWSc' = "DiscWorld 1 ScummVM"
97 	DW2_SAVEGAME_ID = 0x44573253,	// = 'DW2S' = "DiscWorld 2 ScummVM"
98 	SAVEGAME_HEADER_SIZE = 4 + 4 + 4 + SG_DESC_LEN + 7 + 4 + 1 + 1 + 2
99 };
100 
101 #define SAVEGAME_ID (TinselV2 ? (uint32)DW2_SAVEGAME_ID : (uint32)DW1_SAVEGAME_ID)
102 
103 enum {
104 	// FIXME: Save file names in ScummVM can be longer than 8.3, overflowing the
105 	// name field in savedFiles. Raising it to 256 as a preliminary fix.
106 	FNAMELEN	= 256 // 8.3
107 };
108 
109 struct SFILES {
110 	char	name[FNAMELEN];
111 	char	desc[SG_DESC_LEN + 2];
112 	TimeDate dateTime;
113 };
114 
115 //----------------- LOCAL GLOBAL DATA --------------------
116 
117 // FIXME: Avoid non-const global vars
118 
119 static int	g_numSfiles = 0;
120 static SFILES	g_savedFiles[MAX_SAVED_FILES];
121 
122 static bool g_NeedLoad = true;
123 
124 static SAVED_DATA *g_srsd = 0;
125 static int g_RestoreGameNumber = 0;
126 static char *g_SaveSceneName = 0;
127 static const char *g_SaveSceneDesc = 0;
128 static int *g_SaveSceneSsCount = 0;
129 static SAVED_DATA *g_SaveSceneSsData = 0;	// points to 'SAVED_DATA ssdata[MAX_NEST]'
130 
131 //------------- SAVE/LOAD SUPPORT METHODS ----------------
132 
setNeedLoad()133 void setNeedLoad() {
134 	g_NeedLoad = true;
135 }
136 
syncTime(Common::Serializer & s,TimeDate & t)137 static void syncTime(Common::Serializer &s, TimeDate &t) {
138 	s.syncAsUint16LE(t.tm_year);
139 	s.syncAsByte(t.tm_mon);
140 	s.syncAsByte(t.tm_mday);
141 	s.syncAsByte(t.tm_hour);
142 	s.syncAsByte(t.tm_min);
143 	s.syncAsByte(t.tm_sec);
144 }
145 
syncSaveGameHeader(Common::Serializer & s,SaveGameHeader & hdr)146 static bool syncSaveGameHeader(Common::Serializer &s, SaveGameHeader &hdr) {
147 	s.syncAsUint32LE(hdr.id);
148 	s.syncAsUint32LE(hdr.size);
149 	s.syncAsUint32LE(hdr.ver);
150 
151 	s.syncBytes((byte *)hdr.desc, SG_DESC_LEN);
152 	hdr.desc[SG_DESC_LEN - 1] = 0;
153 
154 	syncTime(s, hdr.dateTime);
155 
156 	if (hdr.ver >= 3)
157 		s.syncAsUint32LE(hdr.playTime);
158 	else
159 		hdr.playTime = 0;
160 
161 	int tmp = hdr.size - s.bytesSynced();
162 
163 	// NOTE: We can't use SAVEGAME_ID here when attempting to remove a saved game from the launcher,
164 	// as there is no TinselEngine initialized then. This means that we can't check if this is a DW1
165 	// or DW2 savegame in this case, but it doesn't really matter, as the saved game is about to be
166 	// deleted anyway. Refer to bug #3387551.
167 	bool correctID = _vm ? (hdr.id == SAVEGAME_ID) : (hdr.id == DW1_SAVEGAME_ID || hdr.id == DW2_SAVEGAME_ID);
168 
169 	// Perform sanity check
170 	if (tmp < 0 || !correctID || hdr.ver > CURRENT_VER || hdr.size > 1024)
171 		return false;
172 
173 	if (tmp > 0) {
174 		// If there's header space left, handling syncing the Scn flag and game language
175 		s.syncAsByte(hdr.scnFlag);
176 		s.syncAsByte(hdr.language);
177 		tmp -= 2;
178 
179 		if (_vm && s.isLoading()) {
180 			// If the engine is loaded, ensure the Scn/Gra usage is correct, and it's the correct language
181 			if ((hdr.scnFlag != ((_vm->getFeatures() & GF_SCNFILES) != 0)) ||
182 					(hdr.language != _vm->_config->_language))
183 				return false;
184 		}
185 	}
186 
187 	// Handle the number of interpreter contexts that will be saved in the savegame
188 	if (tmp >= 2) {
189 		tmp -= 2;
190 		hdr.numInterpreters = NUM_INTERPRET;
191 		s.syncAsUint16LE(hdr.numInterpreters);
192 	} else {
193 		if(_vm) // See comment above about bug #3387551
194 			hdr.numInterpreters = (TinselV2 ? 70 : 64) - 20;
195 		else
196 			hdr.numInterpreters = 50; // This value doesn't matter since the saved game is being deleted.
197 	}
198 
199 	// Skip over any extra bytes
200 	s.skip(tmp);
201 	return true;
202 }
203 
syncSavedMover(Common::Serializer & s,SAVED_MOVER & sm)204 static void syncSavedMover(Common::Serializer &s, SAVED_MOVER &sm) {
205 	int i, j;
206 
207 	s.syncAsUint32LE(sm.bActive);
208 	s.syncAsSint32LE(sm.actorID);
209 	s.syncAsSint32LE(sm.objX);
210 	s.syncAsSint32LE(sm.objY);
211 	s.syncAsUint32LE(sm.hLastfilm);
212 
213 	// Sync walk reels
214 	for (i = 0; i < TOTAL_SCALES; ++i)
215 		for (j = 0; j < 4; ++j)
216 			s.syncAsUint32LE(sm.walkReels[i][j]);
217 
218 	// Sync stand reels
219 	for (i = 0; i < TOTAL_SCALES; ++i)
220 		for (j = 0; j < 4; ++j)
221 			s.syncAsUint32LE(sm.standReels[i][j]);
222 
223 	// Sync talk reels
224 	for (i = 0; i < TOTAL_SCALES; ++i)
225 		for (j = 0; j < 4; ++j)
226 			s.syncAsUint32LE(sm.talkReels[i][j]);
227 
228 
229 	if (TinselV2) {
230 		s.syncAsByte(sm.bHidden);
231 
232 		s.syncAsSint32LE(sm.brightness);
233 		s.syncAsSint32LE(sm.startColor);
234 		s.syncAsSint32LE(sm.paletteLength);
235 	}
236 }
237 
syncSavedActor(Common::Serializer & s,SAVED_ACTOR & sa)238 static void syncSavedActor(Common::Serializer &s, SAVED_ACTOR &sa) {
239 	s.syncAsUint16LE(sa.actorID);
240 	s.syncAsUint16LE(sa.zFactor);
241 	s.syncAsUint16LE(sa.bAlive);
242 	s.syncAsUint16LE(sa.bHidden);
243 	s.syncAsUint32LE(sa.presFilm);
244 	s.syncAsUint16LE(sa.presRnum);
245 	s.syncAsUint16LE(sa.presPlayX);
246 	s.syncAsUint16LE(sa.presPlayY);
247 }
248 
249 extern void syncAllActorsAlive(Common::Serializer &s);
250 
syncNoScrollB(Common::Serializer & s,NOSCROLLB & ns)251 static void syncNoScrollB(Common::Serializer &s, NOSCROLLB &ns) {
252 	s.syncAsSint32LE(ns.ln);
253 	s.syncAsSint32LE(ns.c1);
254 	s.syncAsSint32LE(ns.c2);
255 }
256 
syncZPosition(Common::Serializer & s,Z_POSITIONS & zp)257 static void syncZPosition(Common::Serializer &s, Z_POSITIONS &zp) {
258 	s.syncAsSint16LE(zp.actor);
259 	s.syncAsSint16LE(zp.column);
260 	s.syncAsSint32LE(zp.z);
261 }
262 
syncPolyVolatile(Common::Serializer & s,POLY_VOLATILE & p)263 static void syncPolyVolatile(Common::Serializer &s, POLY_VOLATILE &p) {
264 	s.syncAsByte(p.bDead);
265 	s.syncAsSint16LE(p.xoff);
266 	s.syncAsSint16LE(p.yoff);
267 }
268 
syncSoundReel(Common::Serializer & s,SOUNDREELS & sr)269 static void syncSoundReel(Common::Serializer &s, SOUNDREELS &sr) {
270 	s.syncAsUint32LE(sr.hFilm);
271 	s.syncAsSint32LE(sr.column);
272 	s.syncAsSint32LE(sr.actorCol);
273 }
274 
syncSavedData(Common::Serializer & s,SAVED_DATA & sd,int numInterp)275 static void syncSavedData(Common::Serializer &s, SAVED_DATA &sd, int numInterp) {
276 	s.syncAsUint32LE(sd.SavedSceneHandle);
277 	s.syncAsUint32LE(sd.SavedBgroundHandle);
278 	for (int i = 0; i < MAX_MOVERS; ++i)
279 		syncSavedMover(s, sd.SavedMoverInfo[i]);
280 	for (int i = 0; i < MAX_SAVED_ACTORS; ++i)
281 		syncSavedActor(s, sd.SavedActorInfo[i]);
282 
283 	s.syncAsSint32LE(sd.NumSavedActors);
284 	s.syncAsSint32LE(sd.SavedLoffset);
285 	s.syncAsSint32LE(sd.SavedToffset);
286 	for (int i = 0; i < numInterp; ++i)
287 		sd.SavedICInfo[i].syncWithSerializer(s);
288 	for (int i = 0; i < MAX_POLY; ++i)
289 		s.syncAsUint32LE(sd.SavedDeadPolys[i]);
290 	s.syncAsUint32LE(sd.SavedControl);
291 	s.syncAsUint32LE(sd.SavedMidi);
292 	s.syncAsUint32LE(sd.SavedLoop);
293 	s.syncAsUint32LE(sd.SavedNoBlocking);
294 
295 	// SavedNoScrollData
296 	for (int i = 0; i < MAX_VNOSCROLL; ++i)
297 		syncNoScrollB(s, sd.SavedNoScrollData.NoVScroll[i]);
298 	for (int i = 0; i < MAX_HNOSCROLL; ++i)
299 		syncNoScrollB(s, sd.SavedNoScrollData.NoHScroll[i]);
300 	s.syncAsUint32LE(sd.SavedNoScrollData.NumNoV);
301 	s.syncAsUint32LE(sd.SavedNoScrollData.NumNoH);
302 
303 	// Tinsel 2 fields
304 	if (TinselV2) {
305 		// SavedNoScrollData
306 		s.syncAsUint32LE(sd.SavedNoScrollData.xTrigger);
307 		s.syncAsUint32LE(sd.SavedNoScrollData.xDistance);
308 		s.syncAsUint32LE(sd.SavedNoScrollData.xSpeed);
309 		s.syncAsUint32LE(sd.SavedNoScrollData.yTriggerTop);
310 		s.syncAsUint32LE(sd.SavedNoScrollData.yTriggerBottom);
311 		s.syncAsUint32LE(sd.SavedNoScrollData.yDistance);
312 		s.syncAsUint32LE(sd.SavedNoScrollData.ySpeed);
313 
314 		for (int i = 0; i < NUM_ZPOSITIONS; ++i)
315 			syncZPosition(s, sd.zPositions[i]);
316 		s.syncBytes(sd.savedActorZ, MAX_SAVED_ACTOR_Z);
317 		for (int i = 0; i < MAX_POLY; ++i)
318 			syncPolyVolatile(s, sd.SavedPolygonStuff[i]);
319 		for (int i = 0; i < 3; ++i)
320 			s.syncAsUint32LE(sd.SavedTune[i]);
321 		s.syncAsByte(sd.bTinselDim);
322 		s.syncAsSint32LE(sd.SavedScrollFocus);
323 		for (int i = 0; i < SV_TOPVALID; ++i)
324 			s.syncAsSint32LE(sd.SavedSystemVars[i]);
325 		for (int i = 0; i < MAX_SOUNDREELS; ++i)
326 			syncSoundReel(s, sd.SavedSoundReels[i]);
327 	}
328 }
329 
330 /**
331  * Compare two TimeDate structs to see which one was earlier.
332  * Returns 0 if they are equal, a negative value if a is lower / first, and
333  * a positive value if b is lower / first.
334  */
cmpTimeDate(const TimeDate & a,const TimeDate & b)335 static int cmpTimeDate(const TimeDate &a, const TimeDate &b) {
336 	int tmp;
337 
338 	#define CMP_ENTRY(x) tmp = a.x - b.x; if (tmp != 0) return tmp
339 
340 	CMP_ENTRY(tm_year);
341 	CMP_ENTRY(tm_mon);
342 	CMP_ENTRY(tm_mday);
343 	CMP_ENTRY(tm_hour);
344 	CMP_ENTRY(tm_min);
345 	CMP_ENTRY(tm_sec);
346 
347 	#undef CMP_ENTRY
348 
349 	return 0;
350 }
351 
352 /**
353  * Compute a list of all available saved game files.
354  * Store the file details, ordered by time, in savedFiles[] and return
355  * the number of files found.
356  */
getList(Common::SaveFileManager * saveFileMan,const Common::String & target)357 int getList(Common::SaveFileManager *saveFileMan, const Common::String &target) {
358 	// No change since last call?
359 	// TODO/FIXME: Just always reload this data? Be careful about slow downs!!!
360 	if (!g_NeedLoad)
361 		return g_numSfiles;
362 
363 	int i;
364 
365 	const Common::String pattern = target +  ".???";
366 	Common::StringArray files = saveFileMan->listSavefiles(pattern);
367 
368 	g_numSfiles = 0;
369 
370 	for (Common::StringArray::const_iterator file = files.begin(); file != files.end(); ++file) {
371 		if (g_numSfiles >= MAX_SAVED_FILES)
372 			break;
373 
374 		const Common::String &fname = *file;
375 		Common::InSaveFile *f = saveFileMan->openForLoading(fname);
376 		if (f == NULL) {
377 			continue;
378 		}
379 
380 		// Try to load save game header
381 		Common::Serializer s(f, 0);
382 		SaveGameHeader hdr;
383 		bool validHeader = syncSaveGameHeader(s, hdr);
384 		delete f;
385 		if (!validHeader) {
386 			continue;	// Invalid header, or savegame too new -> skip it
387 			// TODO: In SCUMM, we still show an entry for the save, but with description
388 			// "incompatible version".
389 		}
390 
391 		i = g_numSfiles;
392 #ifndef DISABLE_SAVEGAME_SORTING
393 		for (i = 0; i < g_numSfiles; i++) {
394 			if (cmpTimeDate(hdr.dateTime, g_savedFiles[i].dateTime) > 0) {
395 				Common::copy_backward(&g_savedFiles[i], &g_savedFiles[g_numSfiles], &g_savedFiles[g_numSfiles + 1]);
396 				break;
397 			}
398 		}
399 #endif
400 
401 		Common::strlcpy(g_savedFiles[i].name, fname.c_str(), FNAMELEN);
402 		Common::strlcpy(g_savedFiles[i].desc, hdr.desc, SG_DESC_LEN);
403 		g_savedFiles[i].dateTime = hdr.dateTime;
404 
405 		++g_numSfiles;
406 	}
407 
408 	// Next getList() needn't do its stuff again
409 	g_NeedLoad = false;
410 
411 	return g_numSfiles;
412 }
413 
getList()414 int getList() {
415 	// No change since last call?
416 	// TODO/FIXME: Just always reload this data? Be careful about slow downs!!!
417 	if (!g_NeedLoad)
418 		return g_numSfiles;
419 
420 	return getList(_vm->getSaveFileMan(), _vm->getTargetName());
421 }
422 
ListEntry(int i,letype which)423 char *ListEntry(int i, letype which) {
424 	if (i == -1)
425 		i = g_numSfiles;
426 
427 	assert(i >= 0);
428 
429 	if (i < g_numSfiles)
430 		return which == LE_NAME ? g_savedFiles[i].name : g_savedFiles[i].desc;
431 	else
432 		return NULL;
433 }
434 
DoSync(Common::Serializer & s,int numInterp)435 static bool DoSync(Common::Serializer &s, int numInterp) {
436 	int	sg = 0;
437 
438 	if (TinselV2) {
439 		if (s.isSaving())
440 			g_restoreCD = GetCurrentCD();
441 		s.syncAsSint16LE(g_restoreCD);
442 	}
443 
444 	if (TinselV2 && s.isLoading())
445 		HoldItem(INV_NOICON);
446 
447 	syncSavedData(s, *g_srsd, numInterp);
448 	syncGlobInfo(s);		// Glitter globals
449 	syncInvInfo(s);			// Inventory data
450 
451 	// Held object
452 	if (s.isSaving())
453 		sg = WhichItemHeld();
454 	s.syncAsSint32LE(sg);
455 	if (s.isLoading()) {
456 		if (sg != -1 && !GetIsInvObject(sg))
457 			// Not a valid inventory object, so return false
458 			return false;
459 
460 		if (TinselV2)
461 			g_thingHeld = sg;
462 		else
463 			HoldItem(sg);
464 	}
465 
466 	syncTimerInfo(s);		// Timer data
467 	if (!TinselV2)
468 		syncPolyInfo(s);		// Dead polygon data
469 	syncSCdata(s);			// Hook Scene and delayed scene
470 
471 	s.syncAsSint32LE(*g_SaveSceneSsCount);
472 
473 	if (*g_SaveSceneSsCount != 0) {
474 		SAVED_DATA *sdPtr = g_SaveSceneSsData;
475 		for (int i = 0; i < *g_SaveSceneSsCount; ++i, ++sdPtr)
476 			syncSavedData(s, *sdPtr, numInterp);
477 
478 		// Flag that there is a saved scene to return to. Note that in this context 'saved scene'
479 		// is a stored scene to return to from another scene, such as from the Summoning Book close-up
480 		// in Discworld 1 to whatever scene Rincewind was in prior to that
481 		g_ASceneIsSaved = true;
482 	}
483 
484 	if (!TinselV2)
485 		syncAllActorsAlive(s);
486 
487 	return true;
488 }
489 
490 /**
491  * DoRestore
492  */
DoRestore()493 static bool DoRestore() {
494 	Common::InSaveFile *f =  _vm->getSaveFileMan()->openForLoading(g_savedFiles[g_RestoreGameNumber].name);
495 
496 	if (f == NULL) {
497 		return false;
498 	}
499 
500 	Common::Serializer s(f, 0);
501 	SaveGameHeader hdr;
502 	if (!syncSaveGameHeader(s, hdr)) {
503 		delete f;	// Invalid header, or savegame too new -> skip it
504 		return false;
505 	}
506 
507 	if (hdr.ver >= 3)
508 		_vm->setTotalPlayTime(hdr.playTime);
509 	else
510 		_vm->setTotalPlayTime(0);
511 
512 	// Load in the data. For older savegame versions, we potentially need to load the data twice, once
513 	// for pre 1.5 savegames, and if that fails, a second time for 1.5 savegames
514 	int numInterpreters = hdr.numInterpreters;
515 	int32 currentPos = f->pos();
516 	for (int tryNumber = 0; tryNumber < ((hdr.ver >= 2) ? 1 : 2); ++tryNumber) {
517 		// If it's the second loop iteration, try with the 1.5 savegame number of interpreter contexts
518 		if (tryNumber == 1) {
519 			f->seek(currentPos);
520 			numInterpreters = 80;
521 		}
522 
523 		// Load the savegame data
524 		if (DoSync(s, numInterpreters))
525 			// Data load was successful (or likely), so break out of loop
526 			break;
527 	}
528 
529 	uint32 id = f->readSint32LE();
530 	if (id != (uint32)0xFEEDFACE)
531 		error("Incompatible saved game");
532 
533 	bool failed = (f->eos() || f->err());
534 
535 	delete f;
536 
537 	if (failed) {
538 		GUI::MessageDialog dialog(_("Failed to load saved game from file."));
539 		dialog.runModal();
540 	}
541 
542 	return !failed;
543 }
544 
SaveFailure(Common::OutSaveFile * f)545 static void SaveFailure(Common::OutSaveFile *f) {
546 	if (f) {
547 		delete f;
548 		_vm->getSaveFileMan()->removeSavefile(g_SaveSceneName);
549 	}
550 	g_SaveSceneName = NULL;	// Invalidate save name
551 	GUI::MessageDialog dialog(_("Failed to save game to file."));
552 	dialog.runModal();
553 }
554 
555 /**
556  * DoSave
557  */
DoSave()558 static void DoSave() {
559 	Common::OutSaveFile *f;
560 	char tmpName[FNAMELEN];
561 
562 	// Next getList() must do its stuff again
563 	g_NeedLoad = true;
564 
565 	if (g_SaveSceneName == NULL) {
566 		// Generate a new unique save name
567 		int	i;
568 		int	ano = 1;	// Allocated number
569 
570 		while (1) {
571 			Common::String fname = _vm->getSavegameFilename(ano);
572 			Common::strlcpy(tmpName, fname.c_str(), FNAMELEN);
573 
574 			for (i = 0; i < g_numSfiles; i++)
575 				if (!strcmp(g_savedFiles[i].name, tmpName))
576 					break;
577 
578 			if (i == g_numSfiles)
579 				break;
580 			ano++;
581 		}
582 
583 		g_SaveSceneName = tmpName;
584 	}
585 
586 
587 	if (g_SaveSceneDesc[0] == 0)
588 		g_SaveSceneDesc = "unnamed";
589 
590 	f = _vm->getSaveFileMan()->openForSaving(g_SaveSceneName);
591 	Common::Serializer s(0, f);
592 
593 	if (f == NULL) {
594 		SaveFailure(f);
595 		return;
596 	}
597 
598 	// Write out a savegame header
599 	SaveGameHeader hdr;
600 	hdr.id = SAVEGAME_ID;
601 	hdr.size = SAVEGAME_HEADER_SIZE;
602 	hdr.ver = CURRENT_VER;
603 	memset(hdr.desc, 0, SG_DESC_LEN);
604 	Common::strlcpy(hdr.desc, g_SaveSceneDesc, SG_DESC_LEN);
605 	g_system->getTimeAndDate(hdr.dateTime);
606 	hdr.playTime = _vm->getTotalPlayTime();
607 	hdr.scnFlag = _vm->getFeatures() & GF_SCNFILES;
608 	hdr.language = _vm->_config->_language;
609 
610 	if (!syncSaveGameHeader(s, hdr) || f->err()) {
611 		SaveFailure(f);
612 		return;
613 	}
614 
615 	DoSync(s, hdr.numInterpreters);
616 
617 	// Write out the special Id for Discworld savegames
618 	f->writeUint32LE(0xFEEDFACE);
619 	if (f->err()) {
620 		SaveFailure(f);
621 		return;
622 	}
623 
624 	f->finalize();
625 	delete f;
626 	g_SaveSceneName = NULL;	// Invalidate save name
627 }
628 
629 /**
630  * ProcessSRQueue
631  */
ProcessSRQueue()632 void ProcessSRQueue() {
633 	switch (g_SRstate) {
634 	case SR_DORESTORE:
635 		// If a load has been done directly from title screens, set a larger value for scene ctr so the
636 		// code used to skip the title screens in Discworld 1 gets properly disabled
637 		if (g_sceneCtr < 10)
638 			g_sceneCtr = 10;
639 
640 		if (DoRestore()) {
641 			DoRestoreScene(g_srsd, false);
642 		}
643 		g_SRstate = SR_IDLE;
644 		break;
645 
646 	case SR_DOSAVE:
647 		DoSave();
648 		g_SRstate = SR_IDLE;
649 		break;
650 	default:
651 		break;
652 	}
653 }
654 
655 
RequestSaveGame(char * name,char * desc,SAVED_DATA * sd,int * pSsCount,SAVED_DATA * pSsData)656 void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
657 	assert(g_SRstate == SR_IDLE);
658 
659 	g_SaveSceneName = name;
660 	g_SaveSceneDesc = desc;
661 	g_SaveSceneSsCount = pSsCount;
662 	g_SaveSceneSsData = pSsData;
663 	g_srsd = sd;
664 	g_SRstate = SR_DOSAVE;
665 }
666 
RequestRestoreGame(int num,SAVED_DATA * sd,int * pSsCount,SAVED_DATA * pSsData)667 void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
668 	if (TinselV2) {
669 		if (num == -1)
670 			return;
671 		else if (num == -2) {
672 			// From CD change for restore
673 			num = g_RestoreGameNumber;
674 		}
675 	}
676 
677 	assert(num >= 0);
678 
679 	g_RestoreGameNumber = num;
680 	g_SaveSceneSsCount = pSsCount;
681 	g_SaveSceneSsData = pSsData;
682 	g_srsd = sd;
683 	g_SRstate = SR_DORESTORE;
684 }
685 
686 /**
687  * Returns the index of the most recently saved savegame. This will always be
688  * the file at the first index, since the list is sorted by date/time
689  */
NewestSavedGame()690 int NewestSavedGame() {
691 	int numFiles = getList();
692 
693 	return (numFiles == 0) ? -1 : 0;
694 }
695 
696 } // End of namespace Tinsel
697