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  */
22 
23 #include "common/savefile.h"
24 #include "common/stream.h"
25 #include "common/system.h"
26 #include "common/func.h"
27 #include "common/serializer.h"
28 #include "common/translation.h"
29 #include "graphics/thumbnail.h"
30 
31 #include "sci/sci.h"
32 #include "sci/event.h"
33 
34 #include "sci/engine/features.h"
35 #include "sci/engine/kernel.h"
36 #include "sci/engine/state.h"
37 #include "sci/engine/message.h"
38 #include "sci/engine/savegame.h"
39 #include "sci/engine/selector.h"
40 #include "sci/engine/vm_types.h"
41 #include "sci/engine/script.h"	// for SCI_OBJ_EXPORTS and SCI_OBJ_SYNONYMS
42 #include "sci/graphics/helpers.h"
43 #include "sci/graphics/menu.h"
44 #include "sci/graphics/palette.h"
45 #include "sci/graphics/ports.h"
46 #include "sci/graphics/screen.h"
47 #include "sci/parser/vocabulary.h"
48 #include "sci/sound/audio.h"
49 #include "sci/sound/music.h"
50 
51 #ifdef ENABLE_SCI32
52 #include "common/config-manager.h"
53 #include "common/gui_options.h"
54 #include "sci/engine/guest_additions.h"
55 #include "sci/graphics/cursor32.h"
56 #include "sci/graphics/frameout.h"
57 #include "sci/graphics/palette32.h"
58 #include "sci/graphics/remap32.h"
59 #include "sci/graphics/video32.h"
60 #endif
61 
62 namespace Sci {
63 
64 // These are serialization functions for various objects.
65 
syncWithSerializer(Common::Serializer & s,Common::Serializable & obj)66 void syncWithSerializer(Common::Serializer &s, Common::Serializable &obj) {
67 	obj.saveLoadWithSerializer(s);
68 }
69 
syncWithSerializer(Common::Serializer & s,ResourceId & obj)70 void syncWithSerializer(Common::Serializer &s, ResourceId &obj) {
71 	s.syncAsByte(obj._type);
72 	s.syncAsUint16LE(obj._number);
73 	s.syncAsUint32LE(obj._tuple);
74 }
75 
syncWithSerializer(Common::Serializer & s,reg_t & obj)76 void syncWithSerializer(Common::Serializer &s, reg_t &obj) {
77 	s.syncAsUint16LE(obj._segment);
78 	s.syncAsUint16LE(obj._offset);
79 }
80 
syncWithSerializer(Common::Serializer & s,synonym_t & obj)81 void syncWithSerializer(Common::Serializer &s, synonym_t &obj) {
82 	s.syncAsUint16LE(obj.replaceant);
83 	s.syncAsUint16LE(obj.replacement);
84 }
85 
syncWithSerializer(Common::Serializer & s,Class & obj)86 void syncWithSerializer(Common::Serializer &s, Class &obj) {
87 	s.syncAsSint32LE(obj.script);
88 	syncWithSerializer(s, obj.reg);
89 }
90 
syncWithSerializer(Common::Serializer & s,List & obj)91 void syncWithSerializer(Common::Serializer &s, List &obj) {
92 	syncWithSerializer(s, obj.first);
93 	syncWithSerializer(s, obj.last);
94 }
95 
syncWithSerializer(Common::Serializer & s,Node & obj)96 void syncWithSerializer(Common::Serializer &s, Node &obj) {
97 	syncWithSerializer(s, obj.pred);
98 	syncWithSerializer(s, obj.succ);
99 	syncWithSerializer(s, obj.key);
100 	syncWithSerializer(s, obj.value);
101 }
102 
103 #pragma mark -
104 
105 // By default, sync using syncWithSerializer, which in turn can easily be overloaded.
106 template<typename T>
107 struct DefaultSyncer : Common::BinaryFunction<Common::Serializer, T, void> {
operator ()Sci::DefaultSyncer108 	void operator()(Common::Serializer &s, T &obj, int) const {
109 		syncWithSerializer(s, obj);
110 	}
111 };
112 
113 // Syncer for entries in a segment obj table
114 template<typename T>
115 struct SegmentObjTableEntrySyncer : Common::BinaryFunction<Common::Serializer, typename T::Entry &, void> {
operator ()Sci::SegmentObjTableEntrySyncer116 	void operator()(Common::Serializer &s, typename T::Entry &entry, int index) const {
117 		s.syncAsSint32LE(entry.next_free);
118 
119 		bool hasData = false;
120 		if (s.getVersion() >= 37) {
121 			if (s.isSaving()) {
122 				hasData = entry.data != nullptr;
123 			}
124 			s.syncAsByte(hasData);
125 		} else {
126 			hasData = (entry.next_free == index);
127 		}
128 
129 		if (hasData) {
130 			if (s.isLoading()) {
131 				entry.data = new typename T::value_type;
132 			}
133 			syncWithSerializer(s, *entry.data);
134 		} else if (s.isLoading()) {
135 			if (s.getVersion() < 37) {
136 				typename T::value_type dummy;
137 				syncWithSerializer(s, dummy);
138 			}
139 			entry.data = nullptr;
140 		}
141 	}
142 };
143 
144 /**
145  * Sync a Common::Array using a Common::Serializer.
146  * When saving, this writes the length of the array, then syncs (writes) all entries.
147  * When loading, it loads the length of the array, then resizes it accordingly, before
148  * syncing all entries.
149  *
150  * Note: This shouldn't be in common/array.h nor in common/serializer.h, after
151  * all, not all code using arrays wants to use the serializer, and vice versa.
152  * But we could put this into a separate header file in common/ at some point.
153  * Something like common/serializer-extras.h or so.
154  *
155  * TODO: Add something like this for lists, queues....
156  */
157 template<typename T, class Syncer = DefaultSyncer<T> >
158 struct ArraySyncer : Common::BinaryFunction<Common::Serializer, T, void> {
operator ()Sci::ArraySyncer159 	void operator()(Common::Serializer &s, Common::Array<T> &arr) const {
160 		uint len = arr.size();
161 		s.syncAsUint32LE(len);
162 		Syncer sync;
163 
164 		// Resize the array if loading.
165 		if (s.isLoading())
166 			arr.resize(len);
167 
168 		for (uint i = 0; i < len; ++i) {
169 			sync(s, arr[i], i);
170 		}
171 	}
172 };
173 
174 // Convenience wrapper
175 template<typename T>
syncArray(Common::Serializer & s,Common::Array<T> & arr)176 void syncArray(Common::Serializer &s, Common::Array<T> &arr) {
177 	ArraySyncer<T> sync;
178 	sync(s, arr);
179 }
180 
181 template<typename T, class Syncer>
syncArray(Common::Serializer & s,Common::Array<T> & arr)182 void syncArray(Common::Serializer &s, Common::Array<T> &arr) {
183 	ArraySyncer<T, Syncer> sync;
184 	sync(s, arr);
185 }
186 
saveLoadWithSerializer(Common::Serializer & s)187 void SegManager::saveLoadWithSerializer(Common::Serializer &s) {
188 	if (s.isLoading()) {
189 		resetSegMan();
190 
191 		// Reset _scriptSegMap, to be restored below
192 		_scriptSegMap.clear();
193 	}
194 
195 	s.skip(4, VER(14), VER(18));		// OBSOLETE: Used to be _exportsAreWide
196 
197 	uint sync_heap_size = _heap.size();
198 	s.syncAsUint32LE(sync_heap_size);
199 	_heap.resize(sync_heap_size);
200 	for (uint i = 0; i < sync_heap_size; ++i) {
201 		SegmentObj *&mobj = _heap[i];
202 
203 		// Sync the segment type
204 		SegmentType type = (s.isSaving() && mobj) ? mobj->getType() : SEG_TYPE_INVALID;
205 		s.syncAsUint32LE(type);
206 
207 		if (type == SEG_TYPE_HUNK) {
208 			// Don't save or load HunkTable segments
209 			continue;
210 		} else if (type == SEG_TYPE_INVALID) {
211 			// If we were saving and mobj == 0, or if we are loading and this is an
212 			// entry marked as empty -> skip to next
213 			continue;
214 		} else if (type == 5) {
215 			// Don't save or load the obsolete system string segments
216 			if (s.isSaving()) {
217 				continue;
218 			} else {
219 				// Old saved game. Skip the data.
220 				Common::String tmp;
221 				for (int j = 0; j < 4; j++) {
222 					s.syncString(tmp);	// OBSOLETE: name
223 					s.skip(4);			// OBSOLETE: maxSize
224 					s.syncString(tmp);	// OBSOLETE: value
225 				}
226 				_heap[i] = NULL;	// set as freed
227 				continue;
228 			}
229 #ifdef ENABLE_SCI32
230 		} else if (type == SEG_TYPE_ARRAY) {
231 			_arraysSegId = i;
232 		} else if (s.getVersion() >= 36 && type == SEG_TYPE_BITMAP) {
233 			_bitmapSegId = i;
234 #endif
235 		}
236 
237 		if (s.isLoading())
238 			mobj = SegmentObj::createSegmentObj(type);
239 
240 		assert(mobj);
241 
242 		// Let the object sync custom data. Scripts are loaded at this point.
243 		mobj->saveLoadWithSerializer(s);
244 
245 		if (type == SEG_TYPE_SCRIPT) {
246 			Script *scr = (Script *)mobj;
247 
248 			if (s.isLoading()) {
249 				_scriptSegMap[scr->getScriptNumber()] = i;
250 			}
251 
252 			if (s.getVersion() >= 28)
253 				scr->syncStringHeap(s);
254 		}
255 	}
256 
257 	s.syncAsSint32LE(_clonesSegId);
258 	s.syncAsSint32LE(_listsSegId);
259 	s.syncAsSint32LE(_nodesSegId);
260 
261 	syncArray<Class>(s, _classTable);
262 
263 	if (s.isLoading()) {
264 		// Now that all scripts are loaded, init their objects.
265 		// Just like in Script::initializeObjectsSci0, we do two passes
266 		// in case an object is loaded before its base.
267 		int passes = getSciVersion() < SCI_VERSION_1_1 ? 2 : 1;
268 		for (int pass = 1; pass <= passes; ++pass) {
269 			for (uint i = 0; i < _heap.size(); i++) {
270 				if (!_heap[i] ||  _heap[i]->getType() != SEG_TYPE_SCRIPT)
271 					continue;
272 
273 				Script *scr = (Script *)_heap[i];
274 				scr->syncLocalsBlock(this);
275 
276 				ObjMap &objects = scr->getObjectMap();
277 				for (ObjMap::iterator it = objects.begin(); it != objects.end(); ++it) {
278 					reg_t addr = it->_value.getPos();
279 					if (pass == 1) {
280 						scr->scriptObjInit(addr, false);
281 					} else {
282 						Object *obj = scr->getObject(addr.getOffset());
283 						// When a game disposes a script with kDisposeScript,
284 						// the script is marked as deleted and its lockers are
285 						// set to 0, which makes the GC stop using the script
286 						// as a retainer of its own objects. Most of the time,
287 						// this means that the script and all of its objects are
288 						// cleaned up on the next GC cycle, but occasionally a
289 						// game will retain a reference to an object within a
290 						// disposed script somewhere else, which keeps the
291 						// script (and all of its objects) alive. This does not
292 						// prevent the GC from safely collecting other objects
293 						// that had only been retained by now-unreachable script
294 						// objects, so references held by these unreachable
295 						// objects may be invalidated. If the superclass of one
296 						// of these objects is GC'd (because it was the only
297 						// retainer of the superclass), and a save game is
298 						// created after kDisposeScript is called but before
299 						// the script actually becomes collectable, it will
300 						// cause the `initBaseObject` call to fail on restore,
301 						// but this is fine because the object isn't reachable
302 						// anyway (it is just waiting to be GC'd).
303 						//
304 						// For example, in EcoQuest floppy, after opening the
305 						// gate for Delphineus at the beginning of the game,
306 						// the game calls to dispose script 380, but there are
307 						// still reachable references to the script 380 object
308 						// `outsideGateLever` at `CueObj::client` and
309 						// `OnMeAndLowY::theObj`, so script 380 (and all of its
310 						// objects) are retained. However, the now-unreachable
311 						// `fJump` object had been the only retainer of its
312 						// superclass `JumpTo`, so the `JumpTo` class gets
313 						// GC'd, and the `fJump` object is left with no valid
314 						// superclass. If the game is saved and restored at this
315 						// point, `initBaseObject` will fail on `fJump` because
316 						// it has no superclass (but, again, this is fine
317 						// because this is an unreachable object). Later,
318 						// `outsideGateLever` becomes unreachable as the
319 						// `CueObj::client` and `OnMeAndLowY::theObj` properties
320 						// are changed, which means that all script 380 objects
321 						// are finally unreachable and the script and its
322 						// objects get fully disposed.
323 						//
324 						// All that said, if a script has lockers and the base
325 						// object necessary for restoring the object is still
326 						// missing, that is probably a real bug.
327 						if (!obj->initBaseObject(this, addr, false) && scr->getLockers()) {
328 							warning("Failed to locate base object %04x:%04x for object %04x:%04x (%s); skipping", PRINT_REG(obj->getSpeciesSelector()), PRINT_REG(addr), getObjectName(addr));
329 						}
330 					}
331 				}
332 
333 #ifdef ENABLE_SCI32
334 				if (pass == passes) {
335 					g_sci->_guestAdditions->segManSaveLoadScriptHook(*scr);
336 				}
337 #endif
338 			}
339 		}
340 	}
341 }
342 
343 
sync_SavegameMetadata(Common::Serializer & s,SavegameMetadata & obj)344 static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) {
345 	s.syncString(obj.name);
346 	s.syncVersion(CURRENT_SAVEGAME_VERSION);
347 	obj.version = s.getVersion();
348 	s.syncString(obj.gameVersion);
349 	s.syncAsSint32LE(obj.saveDate);
350 	s.syncAsSint32LE(obj.saveTime);
351 	if (s.getVersion() < 22) {
352 		obj.gameObjectOffset = 0;
353 		obj.script0Size = 0;
354 	} else {
355 		s.syncAsUint16LE(obj.gameObjectOffset);
356 		s.syncAsUint16LE(obj.script0Size);
357 	}
358 
359 	// Playtime
360 	obj.playTime = 0;
361 	if (s.isLoading()) {
362 		if (s.getVersion() >= 26)
363 			s.syncAsUint32LE(obj.playTime);
364 	} else {
365 		if (s.getVersion() >= 34) {
366 			obj.playTime = g_sci->getTickCount();
367 		} else {
368 			obj.playTime = g_engine->getTotalPlayTime() / 1000;
369 		}
370 		s.syncAsUint32LE(obj.playTime);
371 	}
372 
373 	// Some games require additional metadata to display their restore screens
374 	// correctly
375 	if (s.getVersion() >= 39) {
376 		if (s.isSaving()) {
377 			const reg_t *globals = g_sci->getEngineState()->variables[VAR_GLOBAL];
378 			if (g_sci->getGameId() == GID_SHIVERS) {
379 				obj.lowScore = globals[kGlobalVarScore].toUint16();
380 				obj.highScore = globals[kGlobalVarShivers1Score].toUint16();
381 				obj.avatarId = 0;
382 			} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) {
383 				obj.lowScore = obj.highScore = 0;
384 				obj.avatarId = readSelectorValue(g_sci->getEngineState()->_segMan, globals[kGlobalVarEgo], SELECTOR(view));
385 			} else {
386 				obj.lowScore = obj.highScore = obj.avatarId = 0;
387 			}
388 		}
389 
390 		s.syncAsUint16LE(obj.lowScore);
391 		s.syncAsUint16LE(obj.highScore);
392 		s.syncAsByte(obj.avatarId);
393 	}
394 }
395 
saveLoadWithSerializer(Common::Serializer & s)396 void EngineState::saveLoadWithSerializer(Common::Serializer &s) {
397 	Common::String tmp;
398 	s.syncString(tmp, VER(14), VER(23));			// OBSOLETE: Used to be gameVersion
399 
400 	if (getSciVersion() <= SCI_VERSION_1_1) {
401 		// Save/Load picPort as well for SCI0-SCI1.1. Necessary for Castle of Dr. Brain,
402 		// as the picPort has been changed when loading during the intro
403 		int16 picPortTop, picPortLeft;
404 		Common::Rect picPortRect;
405 
406 		if (s.isSaving())
407 			picPortRect = g_sci->_gfxPorts->kernelGetPicWindow(picPortTop, picPortLeft);
408 
409 		s.syncAsSint16LE(picPortRect.top);
410 		s.syncAsSint16LE(picPortRect.left);
411 		s.syncAsSint16LE(picPortRect.bottom);
412 		s.syncAsSint16LE(picPortRect.right);
413 		s.syncAsSint16LE(picPortTop);
414 		s.syncAsSint16LE(picPortLeft);
415 
416 		if (s.isLoading())
417 			g_sci->_gfxPorts->kernelSetPicWindow(picPortRect, picPortTop, picPortLeft, false);
418 	}
419 
420 #ifdef ENABLE_SCI32
421 	if (getSciVersion() >= SCI_VERSION_2) {
422 		g_sci->_video32->beforeSaveLoadWithSerializer(s);
423 	}
424 
425 	if (getSciVersion() >= SCI_VERSION_2 &&
426 		s.isLoading() &&
427 		g_sci->getPlatform() == Common::kPlatformMacintosh) {
428 		g_sci->_gfxFrameout->deletePlanesForMacRestore();
429 	}
430 #endif
431 
432 	_segMan->saveLoadWithSerializer(s);
433 
434 	g_sci->_soundCmd->syncPlayList(s);
435 
436 	if (getSciVersion() >= SCI_VERSION_2) {
437 #ifdef ENABLE_SCI32
438 		g_sci->_gfxPalette32->saveLoadWithSerializer(s);
439 		g_sci->_gfxRemap32->saveLoadWithSerializer(s);
440 		g_sci->_gfxCursor32->saveLoadWithSerializer(s);
441 		g_sci->_audio32->saveLoadWithSerializer(s);
442 		g_sci->_video32->saveLoadWithSerializer(s);
443 #endif
444 	} else {
445 		g_sci->_gfxPalette16->saveLoadWithSerializer(s);
446 	}
447 
448 	// Stop any currently playing audio when loading.
449 	// Loading is not normally allowed while audio is being played in SCI games.
450 	// Stopping sounds is needed in ScummVM, as the player may load via
451 	// Control - F5 at any point, even while a sound is playing.
452 	if (s.isLoading()) {
453 		if (getSciVersion() >= SCI_VERSION_2) {
454 #ifdef ENABLE_SCI32
455 			g_sci->_audio32->stop(kAllChannels);
456 #endif
457 		} else {
458 			g_sci->_audio->stopAllAudio();
459 		}
460 	}
461 }
462 
saveLoadWithSerializer(Common::Serializer & s)463 void Vocabulary::saveLoadWithSerializer(Common::Serializer &s) {
464 	syncArray<synonym_t>(s, _synonyms);
465 }
466 
saveLoadWithSerializer(Common::Serializer & s)467 void LocalVariables::saveLoadWithSerializer(Common::Serializer &s) {
468 	s.syncAsSint32LE(script_id);
469 	syncArray<reg_t>(s, _locals);
470 }
471 
saveLoadWithSerializer(Common::Serializer & s)472 void Object::saveLoadWithSerializer(Common::Serializer &s) {
473 	s.syncAsSint32LE(_isFreed);
474 	syncWithSerializer(s, _pos);
475 	s.syncAsSint32LE(_methodCount);		// that's actually a uint16
476 
477 	syncArray<reg_t>(s, _variables);
478 
479 #ifdef ENABLE_SCI32
480 	if (s.getVersion() >= 42 && getSciVersion() == SCI_VERSION_3) {
481 		// Obsolete mustSetViewVisible array
482 		if (s.getVersion() == 42 && s.isLoading()) {
483 			uint32 len;
484 			s.syncAsUint32LE(len);
485 			s.skip(len);
486 		}
487 		syncWithSerializer(s, _superClassPosSci3);
488 		syncWithSerializer(s, _speciesSelectorSci3);
489 		syncWithSerializer(s, _infoSelectorSci3);
490 	}
491 #endif
492 }
493 
494 
495 template<typename T>
sync_Table(Common::Serializer & s,T & obj)496 void sync_Table(Common::Serializer &s, T &obj) {
497 	s.syncAsSint32LE(obj.first_free);
498 	s.syncAsSint32LE(obj.entries_used);
499 
500 	syncArray<typename T::Entry, SegmentObjTableEntrySyncer<T> >(s, obj._table);
501 }
502 
saveLoadWithSerializer(Common::Serializer & s)503 void CloneTable::saveLoadWithSerializer(Common::Serializer &s) {
504 	sync_Table<CloneTable>(s, *this);
505 }
506 
saveLoadWithSerializer(Common::Serializer & s)507 void NodeTable::saveLoadWithSerializer(Common::Serializer &s) {
508 	sync_Table<NodeTable>(s, *this);
509 }
510 
saveLoadWithSerializer(Common::Serializer & s)511 void ListTable::saveLoadWithSerializer(Common::Serializer &s) {
512 	sync_Table<ListTable>(s, *this);
513 }
514 
saveLoadWithSerializer(Common::Serializer & s)515 void HunkTable::saveLoadWithSerializer(Common::Serializer &s) {
516 	// Do nothing, hunk tables are not actually saved nor loaded.
517 }
518 
syncStringHeap(Common::Serializer & s)519 void Script::syncStringHeap(Common::Serializer &s) {
520 	if (getSciVersion() < SCI_VERSION_1_1) {
521 		// Sync all of the SCI_OBJ_STRINGS blocks
522 		SciSpan<byte> buf = *_buf;
523 		bool oldScriptHeader = (getSciVersion() == SCI_VERSION_0_EARLY);
524 
525 		if (oldScriptHeader)
526 			buf += 2;
527 
528 		for (;;) {
529 			int blockType = buf.getUint16LEAt(0);
530 			int blockSize;
531 			if (blockType == 0)
532 				break;
533 
534 			blockSize = buf.getUint16LEAt(2);
535 			assert(blockSize > 0);
536 
537 			if (blockType == SCI_OBJ_STRINGS)
538 				s.syncBytes(buf.getUnsafeDataAt(0, blockSize), blockSize);
539 
540 			buf += blockSize;
541 		}
542 
543 	} else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1_LATE){
544 		// Strings in SCI1.1 come after the object instances
545 		SciSpan<byte> buf = _heap.subspan(4 + _heap.getUint16SEAt(2) * 2);
546 
547 		// Skip all of the objects
548 		while (buf.getUint16SEAt(0) == SCRIPT_OBJECT_MAGIC_NUMBER)
549 			buf += buf.getUint16SEAt(2) * 2;
550 
551 		// Now, sync everything till the end of the buffer
552 		const int length = _heap.size() - (buf - _heap);
553 		s.syncBytes(buf.getUnsafeDataAt(0, length), length);
554 	} else if (getSciVersion() == SCI_VERSION_3) {
555 		const int stringOffset = _buf->getInt32SEAt(4);
556 		const int length = _buf->getInt32SEAt(8) - stringOffset;
557 		s.syncBytes(_buf->getUnsafeDataAt(stringOffset, length), length);
558 	}
559 }
560 
saveLoadWithSerializer(Common::Serializer & s)561 void Script::saveLoadWithSerializer(Common::Serializer &s) {
562 	s.syncAsSint32LE(_nr);
563 
564 	if (s.isLoading())
565 		load(_nr, g_sci->getResMan(), g_sci->getScriptPatcher());
566 	s.skip(4, VER(14), VER(22));		// OBSOLETE: Used to be _bufSize
567 	s.skip(4, VER(14), VER(22));		// OBSOLETE: Used to be _scriptSize
568 	s.skip(4, VER(14), VER(22));		// OBSOLETE: Used to be _heapSize
569 
570 	s.skip(4, VER(14), VER(19));		// OBSOLETE: Used to be _numExports
571 	s.skip(4, VER(14), VER(19));		// OBSOLETE: Used to be _numSynonyms
572 	s.syncAsSint32LE(_lockers);
573 
574 	// Sync _objects. This is a hashmap, and we use the following on disk format:
575 	// First we store the number of items in the hashmap, then we store each
576 	// object (which is an 'Object' instance). For loading, we take advantage
577 	// of the fact that the key of each Object obj is just obj._pos.offset !
578 	// By "chance" this format is identical to the format used to sync Common::Array<>,
579 	// hence we can still old savegames with identical code :).
580 
581 	uint numObjs = _objects.size();
582 	s.syncAsUint32LE(numObjs);
583 
584 	if (s.isLoading()) {
585 		_objects.clear();
586 		Object tmp;
587 		for (uint i = 0; i < numObjs; ++i) {
588 			syncWithSerializer(s, tmp);
589 			_objects[tmp.getPos().getOffset()] = tmp;
590 		}
591 	} else {
592 		ObjMap::iterator it;
593 		const ObjMap::iterator end = _objects.end();
594 		for (it = _objects.begin(); it != end; ++it) {
595 			syncWithSerializer(s, it->_value);
596 		}
597 	}
598 
599 	s.skip(4, VER(14), VER(20));		// OBSOLETE: Used to be _localsOffset
600 	s.syncAsSint32LE(_localsSegment);
601 
602 	s.syncAsSint32LE(_markedAsDeleted);
603 }
604 
saveLoadWithSerializer(Common::Serializer & s)605 void DynMem::saveLoadWithSerializer(Common::Serializer &s) {
606 	s.syncAsSint32LE(_size);
607 	s.syncString(_description);
608 	if (!_buf && _size) {
609 		_buf = (byte *)calloc(_size, 1);
610 	}
611 	if (_size)
612 		s.syncBytes(_buf, _size);
613 }
614 
saveLoadWithSerializer(Common::Serializer & s)615 void DataStack::saveLoadWithSerializer(Common::Serializer &s) {
616 	s.syncAsUint32LE(_capacity);
617 	if (s.isLoading()) {
618 		free(_entries);
619 		_entries = (reg_t *)calloc(_capacity, sizeof(reg_t));
620 	}
621 }
622 
623 #pragma mark -
624 
saveLoadWithSerializer(Common::Serializer & s)625 void SciMusic::saveLoadWithSerializer(Common::Serializer &s) {
626 	// Sync song lib data. When loading, the actual song lib will be initialized
627 	// afterwards in gamestate_restore()
628 	int songcount = 0;
629 	byte masterVolume = soundGetMasterVolume();
630 	byte reverb = _pMidiDrv->getReverb();
631 
632 	if (s.isSaving()) {
633 		s.syncAsByte(_soundOn);
634 		s.syncAsByte(masterVolume);
635 		s.syncAsByte(reverb, VER(17));
636 	} else if (s.isLoading()) {
637 		if (s.getVersion() >= 15) {
638 			s.syncAsByte(_soundOn);
639 			s.syncAsByte(masterVolume);
640 			reverb = 0;
641 			s.syncAsByte(reverb, VER(17));
642 		} else {
643 			_soundOn = true;
644 			masterVolume = 15;
645 			reverb = 0;
646 		}
647 
648 		soundSetSoundOn(_soundOn);
649 		soundSetMasterVolume(masterVolume);
650 		setGlobalReverb(reverb);
651 	}
652 
653 	if (s.isSaving())
654 		songcount = _playList.size();
655 	s.syncAsUint32LE(songcount);
656 
657 	if (s.isLoading())
658 		clearPlayList();
659 
660 	Common::StackLock lock(_mutex);
661 
662 	if (s.isLoading()) {
663 		for (int i = 0; i < songcount; i++) {
664 			MusicEntry *curSong = new MusicEntry();
665 			curSong->saveLoadWithSerializer(s);
666 			_playList.push_back(curSong);
667 		}
668 	} else {
669 		for (int i = 0; i < songcount; i++) {
670 			_playList[i]->saveLoadWithSerializer(s);
671 		}
672 	}
673 }
674 
saveLoadWithSerializer(Common::Serializer & s)675 void MusicEntry::saveLoadWithSerializer(Common::Serializer &s) {
676 	syncWithSerializer(s, soundObj);
677 	s.syncAsSint16LE(resourceId);
678 	s.syncAsSint16LE(dataInc);
679 	s.syncAsSint16LE(ticker);
680 	s.syncAsSint16LE(signal, VER(17));
681 	if (s.getVersion() >= 31) // FE sound/music.h -> priority
682 		s.syncAsSint16LE(priority);
683 	else
684 		s.syncAsByte(priority);
685 	s.syncAsSint16LE(loop, VER(17));
686 	s.syncAsByte(volume);
687 	s.syncAsByte(hold, VER(17));
688 	s.syncAsByte(fadeTo);
689 	s.syncAsSint16LE(fadeStep);
690 	s.syncAsSint32LE(fadeTicker);
691 	s.syncAsSint32LE(fadeTickerStep);
692 	s.syncAsByte(stopAfterFading, VER(45));
693 	s.syncAsByte(status);
694 	if (s.getVersion() >= 32)
695 		s.syncAsByte(playBed);
696 	else if (s.isLoading())
697 		playBed = false;
698 	if (s.getVersion() >= 33)
699 		s.syncAsByte(overridePriority);
700 	else if (s.isLoading())
701 		overridePriority = false;
702 
703 	// pMidiParser and pStreamAud will be initialized when the
704 	// sound list is reconstructed in gamestate_restore()
705 	if (s.isLoading()) {
706 		soundRes = 0;
707 		pMidiParser = 0;
708 		pStreamAud = 0;
709 		reverb = -1;	// invalid reverb, will be initialized in processInitSound()
710 	}
711 }
712 
syncPlayList(Common::Serializer & s)713 void SoundCommandParser::syncPlayList(Common::Serializer &s) {
714 	_music->saveLoadWithSerializer(s);
715 }
716 
reconstructPlayList()717 void SoundCommandParser::reconstructPlayList() {
718 	_music->_mutex.lock();
719 
720 	// We store all songs here because starting songs may re-shuffle their order
721 	MusicList songs;
722 	for (MusicList::iterator i = _music->getPlayListStart(); i != _music->getPlayListEnd(); ++i)
723 		songs.push_back(*i);
724 
725 	// Done with main playlist, so release lock
726 	_music->_mutex.unlock();
727 
728 	for (MusicList::iterator i = songs.begin(); i != songs.end(); ++i) {
729 		MusicEntry *entry = *i;
730 		initSoundResource(entry);
731 
732 #ifdef ENABLE_SCI32
733 		if (_soundVersion >= SCI_VERSION_2 && entry->isSample) {
734 			const reg_t &soundObj = entry->soundObj;
735 
736 			if (readSelectorValue(_segMan, soundObj, SELECTOR(loop)) == 0xFFFF &&
737 				readSelector(_segMan, soundObj, SELECTOR(handle)) != NULL_REG) {
738 
739 				writeSelector(_segMan, soundObj, SELECTOR(handle), NULL_REG);
740 				processPlaySound(soundObj, entry->playBed);
741 			}
742 		} else
743 #endif
744 		if (entry->status == kSoundPlaying) {
745 			// WORKAROUND: PQ3 (German?) scripts can set volume negative in the
746 			// sound object directly without going through DoSound.
747 			// Since we re-read this selector when re-playing the sound after loading,
748 			// this will lead to unexpected behaviour. As a workaround we
749 			// sync the sound object's selectors here. (See bug #5501)
750 			writeSelectorValue(_segMan, entry->soundObj, SELECTOR(loop), entry->loop);
751 			writeSelectorValue(_segMan, entry->soundObj, SELECTOR(priority), entry->priority);
752 			if (_soundVersion >= SCI_VERSION_1_EARLY)
753 				writeSelectorValue(_segMan, entry->soundObj, SELECTOR(vol), entry->volume);
754 
755 			processPlaySound(entry->soundObj, entry->playBed, true);
756 		}
757 	}
758 
759 	// Emulate the original SCI0 behavior: If no sound with status kSoundPlaying was found we
760 	// look for the first sound with status kSoundPaused and start that. It relies on a correctly
761 	// sorted playlist, but we have that...
762 	if (_soundVersion <= SCI_VERSION_0_LATE && !_music->getFirstSlotWithStatus(kSoundPlaying)) {
763 		if (MusicEntry *pSnd = _music->getFirstSlotWithStatus(kSoundPaused)) {
764 			writeSelectorValue(_segMan, pSnd->soundObj, SELECTOR(loop), pSnd->loop);
765 			writeSelectorValue(_segMan, pSnd->soundObj, SELECTOR(priority), pSnd->priority);
766 			processPlaySound(pSnd->soundObj, pSnd->playBed, true);
767 		}
768 	}
769 }
770 
771 #ifdef ENABLE_SCI32
saveLoadWithSerializer(Common::Serializer & ser)772 void ArrayTable::saveLoadWithSerializer(Common::Serializer &ser) {
773 	if (ser.getVersion() < 18)
774 		return;
775 
776 	sync_Table<ArrayTable>(ser, *this);
777 }
778 
saveLoadWithSerializer(Common::Serializer & s)779 void SciArray::saveLoadWithSerializer(Common::Serializer &s) {
780 	uint16 savedSize;
781 
782 	if (s.isSaving()) {
783 		savedSize = _size;
784 	}
785 
786 	s.syncAsByte(_type);
787 	s.syncAsByte(_elementSize);
788 	s.syncAsUint16LE(savedSize);
789 
790 	if (s.isLoading()) {
791 		resize(savedSize);
792 	}
793 
794 	switch (_type) {
795 	case kArrayTypeInt16:
796 	case kArrayTypeID:
797 		for (int i = 0; i < savedSize; ++i) {
798 			syncWithSerializer(s, ((reg_t *)_data)[i]);
799 		}
800 		break;
801 	case kArrayTypeByte:
802 	case kArrayTypeString:
803 		s.syncBytes((byte *)_data, savedSize);
804 		break;
805 	default:
806 		error("Attempt to sync invalid SciArray type %d", _type);
807 	}
808 }
809 
saveLoadWithSerializer(Common::Serializer & ser)810 void BitmapTable::saveLoadWithSerializer(Common::Serializer &ser) {
811 	if (ser.getVersion() < 36) {
812 		return;
813 	}
814 
815 	sync_Table(ser, *this);
816 }
817 
saveLoadWithSerializer(Common::Serializer & s)818 void SciBitmap::saveLoadWithSerializer(Common::Serializer &s) {
819 	if (s.getVersion() < 36) {
820 		return;
821 	}
822 
823 	s.syncAsByte(_gc);
824 	s.syncAsUint32LE(_dataSize);
825 	if (s.isLoading()) {
826 		_data = (byte *)malloc(_dataSize);
827 	}
828 	s.syncBytes(_data, _dataSize);
829 
830 	if (s.isLoading()) {
831 		_buffer.init(getWidth(), getHeight(), getWidth(), getPixels(), Graphics::PixelFormat::createFormatCLUT8());
832 	}
833 }
834 #endif
835 
palVarySaveLoadPalette(Common::Serializer & s,Palette * palette)836 void GfxPalette::palVarySaveLoadPalette(Common::Serializer &s, Palette *palette) {
837 	s.syncBytes(palette->mapping, 256);
838 	s.syncAsUint32LE(palette->timestamp);
839 	for (int i = 0; i < 256; i++) {
840 		s.syncAsByte(palette->colors[i].used);
841 		s.syncAsByte(palette->colors[i].r);
842 		s.syncAsByte(palette->colors[i].g);
843 		s.syncAsByte(palette->colors[i].b);
844 	}
845 	s.syncBytes(palette->intensity, 256);
846 }
847 
saveLoadWithSerializer(Common::Serializer & s)848 void GfxPalette::saveLoadWithSerializer(Common::Serializer &s) {
849 	if (s.getVersion() >= 25) {
850 		// We need to save intensity of the _sysPalette at least for kq6 when entering the dark cave (room 390)
851 		//  from room 340. scripts will set intensity to 60 for this room and restore them when leaving.
852 		//  Sierra SCI is also doing this (although obviously not for SCI0->SCI01 games, still it doesn't hurt
853 		//  to save it everywhere). Refer to bug #5383
854 		s.syncBytes(_sysPalette.intensity, 256);
855 	}
856 	if (s.getVersion() >= 24) {
857 		if (s.isLoading() && _palVaryResourceId != -1)
858 			palVaryRemoveTimer();
859 
860 		s.syncAsSint32LE(_palVaryResourceId);
861 		if (_palVaryResourceId != -1 || s.getVersion() >= 40) {
862 			if (_palVaryResourceId != -1) {
863 				palVarySaveLoadPalette(s, &_palVaryOriginPalette);
864 				palVarySaveLoadPalette(s, &_palVaryTargetPalette);
865 			}
866 			s.syncAsSint16LE(_palVaryStep);
867 			s.syncAsSint16LE(_palVaryStepStop);
868 			s.syncAsSint16LE(_palVaryDirection);
869 			s.syncAsUint16LE(_palVaryTicks);
870 			s.syncAsSint32LE(_palVaryPaused);
871 			if (s.getVersion() >= 40)
872 				s.syncAsSint32LE(_palVarySignal);
873 		}
874 
875 		if (s.isLoading() && s.getVersion() < 40) {
876 			// Reset _palVaryPaused to 0 when loading an old savegame.
877 			// Before version 40, we didn't restore or reset _palVaryPaused.
878 			// In QfG3 this could get it stuck at positive values (bug #9674).
879 			//
880 			// Other SCI11 games don't appear to use palVaryPaused at all.
881 			// (Looked at eq2, freddy, kq6, lb2, mgoose11, pq1, qg1, sq4, sq5)
882 			_palVaryPaused = 0;
883 
884 			// Clear any pending updates, since _palVarySignal also wasn't saved
885 			// before version 40.
886 			_palVarySignal = 0;
887 		}
888 
889 		if (s.isLoading() && _palVaryResourceId != -1) {
890 			palVaryInstallTimer();
891 		}
892 	}
893 }
894 
895 #ifdef ENABLE_SCI32
saveLoadPalette32(Common::Serializer & s,Palette & palette)896 static void saveLoadPalette32(Common::Serializer &s, Palette &palette) {
897 	s.syncAsUint32LE(palette.timestamp);
898 	for (int i = 0; i < ARRAYSIZE(palette.colors); ++i) {
899 		s.syncAsByte(palette.colors[i].used);
900 		s.syncAsByte(palette.colors[i].r);
901 		s.syncAsByte(palette.colors[i].g);
902 		s.syncAsByte(palette.colors[i].b);
903 	}
904 }
905 
saveLoadOptionalPalette32(Common::Serializer & s,Common::ScopedPtr<Palette> & palette)906 static void saveLoadOptionalPalette32(Common::Serializer &s, Common::ScopedPtr<Palette> &palette) {
907 	bool hasPalette = false;
908 	if (s.isSaving()) {
909 		hasPalette = palette;
910 	}
911 	s.syncAsByte(hasPalette);
912 	if (hasPalette) {
913 		if (s.isLoading()) {
914 			palette.reset(new Palette);
915 		}
916 		saveLoadPalette32(s, *palette);
917 	}
918 }
919 
saveLoadWithSerializer(Common::Serializer & s)920 void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) {
921 	if (s.getVersion() < 34) {
922 		return;
923 	}
924 
925 	if (s.isLoading()) {
926 		++_version;
927 
928 		for (int i = 0; i < kNumCyclers; ++i) {
929 			_cyclers[i].reset();
930 		}
931 
932 		_varyTargetPalette.reset();
933 		_varyStartPalette.reset();
934 	}
935 
936 	s.syncAsSint16LE(_varyDirection);
937 	s.syncAsSint16LE(_varyPercent);
938 	s.syncAsSint16LE(_varyTargetPercent);
939 	s.syncAsSint16LE(_varyFromColor);
940 	s.syncAsSint16LE(_varyToColor);
941 	s.syncAsUint16LE(_varyNumTimesPaused);
942 	s.syncAsByte(_needsUpdate);
943 	s.syncAsSint32LE(_varyTime);
944 	s.syncAsUint32LE(_varyLastTick);
945 
946 	for (int i = 0; i < ARRAYSIZE(_fadeTable); ++i) {
947 		s.syncAsByte(_fadeTable[i]);
948 	}
949 	for (int i = 0; i < ARRAYSIZE(_cycleMap); ++i) {
950 		s.syncAsByte(_cycleMap[i]);
951 	}
952 
953 	if (g_sci->_features->hasLatePaletteCode() && s.getVersion() >= 41) {
954 		s.syncAsSint16LE(_gammaLevel);
955 		saveLoadPalette32(s, _sourcePalette);
956 		++_version;
957 		_needsUpdate = true;
958 		_gammaChanged = true;
959 	}
960 
961 	saveLoadOptionalPalette32(s, _varyTargetPalette);
962 	saveLoadOptionalPalette32(s, _varyStartPalette);
963 
964 	// _nextPalette is not saved by SSCI
965 
966 	for (int i = 0; i < ARRAYSIZE(_cyclers); ++i) {
967 		PalCycler *cycler = nullptr;
968 
969 		bool hasCycler = false;
970 		if (s.isSaving()) {
971 			cycler = _cyclers[i].get();
972 			hasCycler = (cycler != nullptr);
973 		}
974 		s.syncAsByte(hasCycler);
975 
976 		if (hasCycler) {
977 			if (s.isLoading()) {
978 				cycler = new PalCycler;
979 				_cyclers[i].reset(cycler);
980 			}
981 
982 			s.syncAsByte(cycler->fromColor);
983 			s.syncAsUint16LE(cycler->numColorsToCycle);
984 			s.syncAsByte(cycler->currentCycle);
985 			s.syncAsByte(cycler->direction);
986 			s.syncAsUint32LE(cycler->lastUpdateTick);
987 			s.syncAsSint16LE(cycler->delay);
988 			s.syncAsUint16LE(cycler->numTimesPaused);
989 		}
990 	}
991 }
992 
saveLoadWithSerializer(Common::Serializer & s)993 void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) {
994 	if (s.getVersion() < 35) {
995 		return;
996 	}
997 
998 	s.syncAsByte(_numActiveRemaps);
999 	s.syncAsByte(_blockedRangeStart);
1000 	s.syncAsSint16LE(_blockedRangeCount);
1001 
1002 	for (uint i = 0; i < _remaps.size(); ++i) {
1003 		SingleRemap &singleRemap = _remaps[i];
1004 		s.syncAsByte(singleRemap._type);
1005 		if (s.isLoading() && singleRemap._type != kRemapNone) {
1006 			singleRemap.reset();
1007 		}
1008 		s.syncAsByte(singleRemap._from);
1009 		s.syncAsByte(singleRemap._to);
1010 		s.syncAsByte(singleRemap._delta);
1011 		s.syncAsByte(singleRemap._percent);
1012 		s.syncAsByte(singleRemap._gray);
1013 	}
1014 
1015 	if (s.isLoading()) {
1016 		_needsUpdate = true;
1017 	}
1018 }
1019 
saveLoadWithSerializer(Common::Serializer & s)1020 void GfxCursor32::saveLoadWithSerializer(Common::Serializer &s) {
1021 	if (s.getVersion() < 38) {
1022 		return;
1023 	}
1024 
1025 	int32 hideCount;
1026 	if (s.isSaving()) {
1027 		hideCount = _hideCount;
1028 	}
1029 	s.syncAsSint32LE(hideCount);
1030 	s.syncAsSint16LE(_restrictedArea.left);
1031 	s.syncAsSint16LE(_restrictedArea.top);
1032 	s.syncAsSint16LE(_restrictedArea.right);
1033 	s.syncAsSint16LE(_restrictedArea.bottom);
1034 	s.syncAsUint16LE(_cursorInfo.resourceId);
1035 	s.syncAsUint16LE(_cursorInfo.loopNo);
1036 	s.syncAsUint16LE(_cursorInfo.celNo);
1037 
1038 	if (s.isLoading()) {
1039 		hide();
1040 		setView(_cursorInfo.resourceId, _cursorInfo.loopNo, _cursorInfo.celNo);
1041 		if (!hideCount) {
1042 			show();
1043 		} else {
1044 			_hideCount = hideCount;
1045 		}
1046 	}
1047 }
1048 
saveLoadWithSerializer(Common::Serializer & s)1049 void Audio32::saveLoadWithSerializer(Common::Serializer &s) {
1050 	if (!g_sci->_features->hasSci3Audio() || s.getVersion() < 44) {
1051 		return;
1052 	}
1053 
1054 	syncArray(s, _lockedResourceIds);
1055 }
1056 
beforeSaveLoadWithSerializer(Common::Serializer & s)1057 void Video32::beforeSaveLoadWithSerializer(Common::Serializer &s) {
1058 	if (getSciVersion() < SCI_VERSION_3 || s.isSaving()) {
1059 		return;
1060 	}
1061 
1062 	_robotPlayer.close();
1063 }
1064 
saveLoadWithSerializer(Common::Serializer & s)1065 void Video32::saveLoadWithSerializer(Common::Serializer &s) {
1066 	if (getSciVersion() < SCI_VERSION_3) {
1067 		return;
1068 	}
1069 
1070 	bool robotExists = _robotPlayer.getStatus() != RobotDecoder::kRobotStatusUninitialized;
1071 	s.syncAsByte(robotExists);
1072 	if (robotExists) {
1073 		GuiResourceId robotId;
1074 		reg_t planeId;
1075 		Common::Point position;
1076 		int16 priority, scale;
1077 		int frameNo;
1078 
1079 		if (s.isSaving()) {
1080 			robotId = _robotPlayer.getResourceId();
1081 			planeId = _robotPlayer.getPlaneId();
1082 			priority = _robotPlayer.getPriority();
1083 			position = _robotPlayer.getPosition();
1084 			scale = _robotPlayer.getScale();
1085 			frameNo = _robotPlayer.getFrameNo();
1086 		}
1087 
1088 		s.syncAsUint16LE(robotId);
1089 		syncWithSerializer(s, planeId);
1090 		s.syncAsSint16LE(priority);
1091 		s.syncAsSint16LE(position.x);
1092 		s.syncAsSint16LE(position.y);
1093 		s.syncAsSint16LE(scale);
1094 		s.syncAsSint32LE(frameNo);
1095 
1096 		if (s.isLoading()) {
1097 			_robotPlayer.open(robotId, planeId, priority, position.x, position.y, scale);
1098 			_robotPlayer.showFrame(frameNo, position.x, position.y, priority);
1099 		}
1100 	}
1101 }
1102 
1103 #endif
1104 
saveLoadWithSerializer(Common::Serializer & s)1105 void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) {
1106 	// reset() is called directly way earlier in gamestate_restore()
1107 	if (s.getVersion() >= 27) {
1108 		uint windowCount = 0;
1109 		uint id = PORTS_FIRSTSCRIPTWINDOWID;
1110 		if (s.isSaving()) {
1111 			while (id < _windowsById.size()) {
1112 				if (_windowsById[id])
1113 					windowCount++;
1114 				id++;
1115 			}
1116 		}
1117 		// Save/Restore window count
1118 		s.syncAsUint32LE(windowCount);
1119 
1120 		if (s.isSaving()) {
1121 			id = PORTS_FIRSTSCRIPTWINDOWID;
1122 			while (id < _windowsById.size()) {
1123 				if (_windowsById[id]) {
1124 					Window *window = (Window *)_windowsById[id];
1125 					window->saveLoadWithSerializer(s);
1126 				}
1127 				id++;
1128 			}
1129 		} else {
1130 			id = PORTS_FIRSTSCRIPTWINDOWID;
1131 			while (windowCount) {
1132 				Window *window = new Window(0);
1133 				window->saveLoadWithSerializer(s);
1134 
1135 				// add enough entries inside _windowsById as needed
1136 				while (id <= window->id) {
1137 					_windowsById.push_back(0);
1138 					id++;
1139 				}
1140 				_windowsById[window->id] = window;
1141 				// _windowList may not be 100% correct using that way of restoring
1142 				//  saving/restoring ports won't work perfectly anyway, because the contents
1143 				//  of the window can only get repainted by the scripts and they dont do that
1144 				//  so we will get empty, transparent windows instead. So perfect window order
1145 				//  shouldn't really matter
1146 				if (window->counterTillFree) {
1147 					_freeCounter++;
1148 				} else {
1149 					// we don't put the saved script windows into _windowList[], so that they aren't used
1150 					//  by kernel functions. This is important and would cause issues otherwise.
1151 					//  see Conquests of Camelot - bug #6744 - when saving on the map screen (room 103),
1152 					//                                restoring would result in a black window in place
1153 					//                                where the area name was displayed before
1154 					//                                In Sierra's SCI the behaviour is identical to us
1155 					//                                 Sierra's SCI won't show those windows after restoring
1156 					// If this should cause issues in another game, we would have to add a flag to simply
1157 					//  avoid any drawing operations for such windows
1158 					// We still have to restore script windows, because for example Conquests of Camelot
1159 					//  will immediately delete windows, that were created before saving the game.
1160 				}
1161 
1162 				windowCount--;
1163 			}
1164 		}
1165 	}
1166 }
1167 
reconstructStack(EngineState * s)1168 void SegManager::reconstructStack(EngineState *s) {
1169 	DataStack *stack = (DataStack *)(_heap[findSegmentByType(SEG_TYPE_STACK)]);
1170 	s->stack_base = stack->_entries;
1171 	s->stack_top = s->stack_base + stack->_capacity;
1172 }
1173 
reconstructClones()1174 void SegManager::reconstructClones() {
1175 	for (uint i = 0; i < _heap.size(); i++) {
1176 		SegmentObj *mobj = _heap[i];
1177 		if (mobj && mobj->getType() == SEG_TYPE_CLONES) {
1178 			CloneTable *ct = (CloneTable *)mobj;
1179 
1180 			for (uint j = 0; j < ct->_table.size(); j++) {
1181 				// Check if the clone entry is used
1182 				uint entryNum = (uint)ct->first_free;
1183 				bool isUsed = true;
1184 				while (entryNum != ((uint) CloneTable::HEAPENTRY_INVALID)) {
1185 					if (entryNum == j) {
1186 						isUsed = false;
1187 						break;
1188 					}
1189 					entryNum = ct->_table[entryNum].next_free;
1190 				}
1191 
1192 				if (!isUsed)
1193 					continue;
1194 
1195 				CloneTable::value_type &seeker = ct->at(j);
1196 				const Object *baseObj = getObject(seeker.getSpeciesSelector());
1197 				seeker.cloneFromObject(baseObj);
1198 				if (!baseObj) {
1199 					// Can happen when loading some KQ6 savegames
1200 					warning("Clone entry without a base class: %d", j);
1201 				}
1202 			}	// end for
1203 		}	// end if
1204 	}	// end for
1205 }
1206 
1207 
1208 #pragma mark -
1209 
gamestate_save(EngineState * s,int saveId,const Common::String & savename,const Common::String & version)1210 bool gamestate_save(EngineState *s, int saveId, const Common::String &savename, const Common::String &version) {
1211 	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
1212 	const Common::String filename = g_sci->getSavegameName(saveId);
1213 
1214 	Common::OutSaveFile *saveStream = saveFileMan->openForSaving(filename);
1215 	if (saveStream == nullptr) {
1216 		warning("Error opening savegame \"%s\" for writing", filename.c_str());
1217 		return false;
1218 	}
1219 
1220 	if (!gamestate_save(s, saveStream, savename, version)) {
1221 		warning("Saving the game failed");
1222 		saveStream->finalize();
1223 		delete saveStream;
1224 		return false;
1225 	}
1226 
1227 	saveStream->finalize();
1228 	if (saveStream->err()) {
1229 		warning("Writing the savegame failed");
1230 		delete saveStream;
1231 		return false;
1232 	}
1233 
1234 	delete saveStream;
1235 	return true;
1236 }
1237 
gamestate_save(EngineState * s,Common::WriteStream * fh,const Common::String & savename,const Common::String & version)1238 bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::String &savename, const Common::String &version) {
1239 	Common::Serializer ser(nullptr, fh);
1240 	set_savegame_metadata(ser, fh, savename, version);
1241 	s->saveLoadWithSerializer(ser);		// FIXME: Error handling?
1242 	if (g_sci->_gfxPorts)
1243 		g_sci->_gfxPorts->saveLoadWithSerializer(ser);
1244 	Vocabulary *voc = g_sci->getVocabulary();
1245 	if (voc)
1246 		voc->saveLoadWithSerializer(ser);
1247 
1248 	// TODO: SSCI (at least JonesCD, presumably more) also stores the Menu state
1249 
1250 	return true;
1251 }
1252 
1253 extern int showScummVMDialog(const Common::U32String &message, const Common::U32String &altButton = Common::U32String(), bool alignCenter = true);
1254 
gamestate_afterRestoreFixUp(EngineState * s,int savegameId)1255 void gamestate_afterRestoreFixUp(EngineState *s, int savegameId) {
1256 	switch (g_sci->getGameId()) {
1257 	case GID_CAMELOT: {
1258 		// WORKAROUND: CAMELOT depends on its dynamic menu state persisting. The menu items'
1259 		//  enabled states determines when the player can draw or sheathe their sword and
1260 		//  open a purse. If these aren't updated then the player may be unable to perform
1261 		//  necessary actions, or may be able to perform unexpected ones that break the game.
1262 		//  Since we don't persist menu state (yet) we need to recreate it from game state.
1263 		//
1264 		// - Action \ Open Purse: Enabled while one of the purses is in inventory.
1265 		// - Action \ Draw Sword: Enabled while flag 3 is set, unless disabled by room scripts.
1266 		// * The text "Draw Sword" toggles to "Sheathe Sword" depending on global 124,
1267 		//   but this is only cosmetic. Exported proc #1 in script 997 refreshes this
1268 		//   when the sword status or room changes.
1269 		//
1270 		// After evaluating all the scripts that disable the sword, we enforce the few
1271 		//  that prevent breaking the game: room 50 under the aqueduct and sitting with
1272 		//  the scholar while in room 82 (ego view 84).
1273 		//
1274 		// FIXME: Save and restore full menu state as SSCI did and don't apply these
1275 		//  workarounds when restoring saves that contain menu state.
1276 
1277 		// Action \ Open Purse
1278 		reg_t enablePurse = NULL_REG;
1279 		Common::Array<reg_t> purses = s->_segMan->findObjectsByName("purse");
1280 		reg_t ego = s->variables[VAR_GLOBAL][0];
1281 		for (uint i = 0; i < purses.size(); ++i) {
1282 			reg_t purseOwner = readSelector(s->_segMan, purses[i], SELECTOR(owner));
1283 			if (purseOwner == ego) {
1284 				enablePurse = TRUE_REG;
1285 				break;
1286 			}
1287 		}
1288 		g_sci->_gfxMenu->kernelSetAttribute(1281 >> 8, 1281 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, enablePurse);
1289 
1290 		// Action \ Draw Sword
1291 		bool hasSword = (s->variables[VAR_GLOBAL][250].getOffset() & 0x1000); // flag 3
1292 		bool underAqueduct = (s->variables[VAR_GLOBAL][11].getOffset() == 50);
1293 		bool sittingWithScholar = (readSelectorValue(s->_segMan, ego, SELECTOR(view)) == 84);
1294 		reg_t enableSword = (hasSword && !underAqueduct && !sittingWithScholar) ? TRUE_REG : NULL_REG;
1295 		g_sci->_gfxMenu->kernelSetAttribute(1283 >> 8, 1283 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, enableSword);
1296 		break;
1297 	}
1298 	case GID_MOTHERGOOSE:
1299 		// WORKAROUND: Mother Goose SCI0
1300 		//  Script 200 / rm200::newRoom will set global C5h directly right after creating a child to the
1301 		//   current number of children plus 1.
1302 		//  We can't trust that global, that's why we set the actual savedgame id right here directly after
1303 		//   restoring a saved game.
1304 		//  If we didn't, the game would always save to a new slot
1305 		s->variables[VAR_GLOBAL][0xC5].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId);
1306 		break;
1307 	case GID_MOTHERGOOSE256:
1308 		// WORKAROUND: Mother Goose SCI1/SCI1.1 does some weird things for
1309 		//  saving a previously restored game.
1310 		// We set the current savedgame-id directly and remove the script
1311 		//  code concerning this via script patch.
1312 		// We also set this in kSaveGame so that the global is correct even if no restoring occurs,
1313 		//  otherwise the auto-delete script at the end of the SCI1.1 floppy version breaks if
1314 		//  the game is played from start to finish. (bug #5294)
1315 		s->variables[VAR_GLOBAL][0xB3].setOffset(SAVEGAMEID_OFFICIALRANGE_START + savegameId);
1316 		break;
1317 	case GID_JONES:
1318 		// HACK: The code that enables certain menu items isn't called when a game is restored from the
1319 		// launcher, or the "Restore game" option in the game's main menu - bugs #6537 and #6723.
1320 		// These menu entries are disabled when the game is launched, and are enabled when a new game is
1321 		// started. The code for enabling these entries is is all in script 1, room1::init, but that code
1322 		// path is never followed in these two cases (restoring game from the menu, or restoring a game
1323 		// from the ScummVM launcher). Thus, we perform the calls to enable the menus ourselves here.
1324 		// These two are needed when restoring from the launcher
1325 		// FIXME: The original interpreter saves and restores the menu state, so these attributes
1326 		// are automatically reset there. We may want to do the same.
1327 		g_sci->_gfxMenu->kernelSetAttribute(257 >> 8, 257 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG);    // Sierra -> About Jones
1328 		g_sci->_gfxMenu->kernelSetAttribute(258 >> 8, 258 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG);    // Sierra -> Help
1329 		// The rest are normally enabled from room1::init
1330 		g_sci->_gfxMenu->kernelSetAttribute(769 >> 8, 769 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG);    // Options -> Delete current player
1331 		g_sci->_gfxMenu->kernelSetAttribute(513 >> 8, 513 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG);    // Game -> Save Game
1332 		g_sci->_gfxMenu->kernelSetAttribute(515 >> 8, 515 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG);    // Game -> Restore Game
1333 		g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG);  // Status -> Statistics
1334 		g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG);  // Status -> Goals
1335 		break;
1336 	case GID_KQ5:
1337 		// WORKAROUND: We allow users to choose if they want the older KQ5 CD Windows cursors. These
1338 		//  are black and white Cursor resources instead of the color View resources in the DOS version.
1339 		//  This setting affects how KQCursor objects are initialized and might have changed since the
1340 		//  game was saved. The scripts don't expect this since it wasn't an option in the original.
1341 		//  In order for the cursors to correctly use the current setting, we need to clear the "number"
1342 		//  property of every KQCursor when restoring when Windows cursors are disabled.
1343 		if (g_sci->isCD() && !g_sci->_features->useWindowsCursors()) {
1344 			Common::Array<reg_t> cursors = s->_segMan->findObjectsBySuperClass("KQCursor");
1345 			for (uint i = 0; i < cursors.size(); ++i) {
1346 				writeSelector(s->_segMan, cursors[i], SELECTOR(number), NULL_REG);
1347 			}
1348 		}
1349 		break;
1350 	case GID_KQ6:
1351 		if (g_sci->isCD()) {
1352 			// WORKAROUND:
1353 			// For the CD version of King's Quest 6, set global depending on current hires/lowres state
1354 			// The game sets a global at the start depending on it and some things check that global
1355 			// instead of checking platform like for example the game action menu.
1356 			// This never happened in the original interpreter, because the original DOS interpreter
1357 			// was only capable of lowres graphics and the original Windows 3.11 interpreter was only capable
1358 			// of hires graphics. Saved games were not compatible between those two.
1359 			// Which means saving during lowres mode, then going into hires mode and restoring that saved game,
1360 			// will result in some graphics being incorrect (lowres).
1361 			// That's why we are setting the global after restoring a saved game depending on hires/lowres state.
1362 			// The CD demo of KQ6 does the same and uses the exact same global.
1363 			if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) {
1364 				s->variables[VAR_GLOBAL][0xA9].setOffset(1);
1365 			} else {
1366 				s->variables[VAR_GLOBAL][0xA9].setOffset(0);
1367 			}
1368 		}
1369 		break;
1370 	case GID_PQ2:
1371 		// HACK: Same as in Jones - enable the save game menu option when loading in
1372 		// PQ2 (bug #6875). It gets disabled in the game's death screen.
1373 		g_sci->_gfxMenu->kernelSetAttribute(2, 1, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG);	// Game -> Save Game
1374 		break;
1375 #ifdef ENABLE_SCI32
1376 	case GID_KQ7:
1377 		if (Common::checkGameGUIOption(GAMEOPTION_UPSCALE_VIDEOS, ConfMan.get("guioptions"))) {
1378 			uint16 value = ConfMan.getBool("enable_video_upscale") ? 32 : 0;
1379 			s->variables[VAR_GLOBAL][kGlobalVarKQ7UpscaleVideos] = make_reg(0, value);
1380 		}
1381 		break;
1382 	case GID_PHANTASMAGORIA2:
1383 		if (Common::checkGameGUIOption(GAMEOPTION_ENABLE_CENSORING, ConfMan.get("guioptions"))) {
1384 			s->variables[VAR_GLOBAL][kGlobalVarPhant2CensorshipFlag] = make_reg(0, ConfMan.getBool("enable_censoring"));
1385 		}
1386 		break;
1387 	case GID_SHIVERS:
1388 		// WORKAROUND: When loading a saved game from the GMM in the same scene in
1389 		// Shivers, we end up with the same draw list, but the scene palette is not
1390 		// set properly. Normally, Shivers does a room change when showing the saved
1391 		// game list, which does not occur when loading directly from the GMM. When
1392 		// loading from the GMM, at this point all of the visible planes and items
1393 		// are deleted, so calling frameOut here helps reset the game palette
1394 		// properly, like when changing a room.
1395 		g_sci->_gfxFrameout->frameOut(true);
1396 		break;
1397 #endif
1398 	default:
1399 		break;
1400 	}
1401 }
1402 
gamestate_restore(EngineState * s,int saveId)1403 bool gamestate_restore(EngineState *s, int saveId) {
1404 	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();
1405 	const Common::String filename = g_sci->getSavegameName(saveId);
1406 	Common::SeekableReadStream *saveStream = saveFileMan->openForLoading(filename);
1407 
1408 	if (saveStream == nullptr) {
1409 		warning("Savegame #%d not found", saveId);
1410 		return false;
1411 	}
1412 
1413 	gamestate_restore(s, saveStream);
1414 	delete saveStream;
1415 
1416 	gamestate_afterRestoreFixUp(s, saveId);
1417 	return true;
1418 }
1419 
gamestate_restore(EngineState * s,Common::SeekableReadStream * fh)1420 void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) {
1421 	SavegameMetadata meta;
1422 
1423 	Common::Serializer ser(fh, 0);
1424 	sync_SavegameMetadata(ser, meta);
1425 
1426 	if (fh->eos()) {
1427 		s->r_acc = TRUE_REG;	// signal failure
1428 		return;
1429 	}
1430 
1431 	// In SCI32 these checks are all in kCheckSaveGame32
1432 	if (getSciVersion() < SCI_VERSION_2) {
1433 		if ((meta.version < MINIMUM_SAVEGAME_VERSION) || (meta.version > CURRENT_SAVEGAME_VERSION)) {
1434 			if (meta.version < MINIMUM_SAVEGAME_VERSION) {
1435 				showScummVMDialog(_("The format of this saved game is obsolete, unable to load it"));
1436 			} else {
1437 				Common::U32String msg = Common::U32String::format(_("Savegame version is %d, maximum supported is %0d"), meta.version, CURRENT_SAVEGAME_VERSION);
1438 				showScummVMDialog(msg);
1439 			}
1440 
1441 			s->r_acc = TRUE_REG;	// signal failure
1442 			return;
1443 		}
1444 
1445 		if (meta.gameObjectOffset > 0 && meta.script0Size > 0) {
1446 			Resource *script0 = g_sci->getResMan()->findResource(ResourceId(kResourceTypeScript, 0), false);
1447 			if (script0->size() != meta.script0Size || g_sci->getGameObject().getOffset() != meta.gameObjectOffset) {
1448 				showScummVMDialog(_("This saved game was created with a different version of the game, unable to load it"));
1449 
1450 				s->r_acc = TRUE_REG;	// signal failure
1451 				return;
1452 			}
1453 		}
1454 	}
1455 
1456 	// We don't need the thumbnail here, so just read it and discard it
1457 	Graphics::skipThumbnail(*fh);
1458 
1459 	// reset ports is one of the first things we do, because that may free() some hunk memory
1460 	//  and we don't want to do that after we read in the saved game hunk memory
1461 	if (g_sci->_gfxPorts)
1462 		g_sci->_gfxPorts->reset();
1463 	// clear screen
1464 	if (getSciVersion() <= SCI_VERSION_1_1) {
1465 		// Only do clearing the screen for SCI16
1466 		// Both SCI16 + SCI32 did not clear the screen.
1467 		// We basically do it for SCI16, because of KQ6.
1468 		// When hires portraits are shown and the user restores during that time, the portraits
1469 		// wouldn't get fully removed. In original SCI, the user wasn't able to restore during that time,
1470 		// so this is basically a workaround, so that ScummVM features work properly.
1471 		// For SCI32, behavior was verified in DOSBox, that SCI32 does not clear and also not redraw the screen.
1472 		// It only redraws elements that have changed in comparison to the state before the restore.
1473 		// If we cleared the screen for SCI32, we would have issues because of this behavior.
1474 		if (g_sci->_gfxScreen)
1475 			g_sci->_gfxScreen->clearForRestoreGame();
1476 	}
1477 
1478 	s->reset(true);
1479 	s->saveLoadWithSerializer(ser);	// FIXME: Error handling?
1480 
1481 	// Now copy all current state information
1482 
1483 	s->_segMan->reconstructStack(s);
1484 	s->_segMan->reconstructClones();
1485 	s->initGlobals();
1486 	s->gcCountDown = GC_INTERVAL - 1;
1487 
1488 	// Time state:
1489 	s->lastWaitTime = g_system->getMillis();
1490 	s->_screenUpdateTime = g_system->getMillis();
1491 	if (meta.version >= 34) {
1492 		g_sci->setTickCount(meta.playTime);
1493 	} else {
1494 		g_engine->setTotalPlayTime(meta.playTime * 1000);
1495 	}
1496 
1497 	if (g_sci->_gfxPorts)
1498 		g_sci->_gfxPorts->saveLoadWithSerializer(ser);
1499 
1500 	Vocabulary *voc = g_sci->getVocabulary();
1501 	if (ser.getVersion() >= 30 && voc)
1502 		voc->saveLoadWithSerializer(ser);
1503 
1504 	g_sci->_soundCmd->reconstructPlayList();
1505 
1506 	// Message state:
1507 	delete s->_msgState;
1508 	s->_msgState = new MessageState(s->_segMan);
1509 
1510 	// System strings:
1511 	s->_segMan->initSysStrings();
1512 
1513 	s->abortScriptProcessing = kAbortLoadGame;
1514 
1515 	// signal restored game to game scripts
1516 	s->gameIsRestarting = GAMEISRESTARTING_RESTORE;
1517 }
1518 
set_savegame_metadata(Common::Serializer & ser,Common::WriteStream * fh,const Common::String & savename,const Common::String & version)1519 void set_savegame_metadata(Common::Serializer &ser, Common::WriteStream *fh, const Common::String &savename, const Common::String &version) {
1520 	TimeDate curTime;
1521 	g_system->getTimeAndDate(curTime);
1522 
1523 	SavegameMetadata meta;
1524 	meta.version = CURRENT_SAVEGAME_VERSION;
1525 	meta.name = savename;
1526 	meta.gameVersion = version;
1527 	meta.saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
1528 	meta.saveTime = ((curTime.tm_hour & 0xFF) << 16) | (((curTime.tm_min) & 0xFF) << 8) | ((curTime.tm_sec) & 0xFF);
1529 
1530 	Resource *script0 = g_sci->getResMan()->findResource(ResourceId(kResourceTypeScript, 0), false);
1531 	assert(script0);
1532 	meta.script0Size = script0->size();
1533 	meta.gameObjectOffset = g_sci->getGameObject().getOffset();
1534 
1535 	sync_SavegameMetadata(ser, meta);
1536 	Graphics::saveThumbnail(*fh);
1537 }
1538 
set_savegame_metadata(Common::WriteStream * fh,const Common::String & savename,const Common::String & version)1539 void set_savegame_metadata(Common::WriteStream *fh, const Common::String &savename, const Common::String &version) {
1540 	Common::Serializer ser(nullptr, fh);
1541 	set_savegame_metadata(ser, fh, savename, version);
1542 }
1543 
get_savegame_metadata(Common::SeekableReadStream * stream,SavegameMetadata & meta)1544 bool get_savegame_metadata(Common::SeekableReadStream *stream, SavegameMetadata &meta) {
1545 	assert(stream);
1546 
1547 	Common::Serializer ser(stream, nullptr);
1548 	sync_SavegameMetadata(ser, meta);
1549 
1550 	if (stream->eos())
1551 		return false;
1552 
1553 	if ((meta.version < MINIMUM_SAVEGAME_VERSION) ||
1554 	    (meta.version > CURRENT_SAVEGAME_VERSION)) {
1555 		if (meta.version < MINIMUM_SAVEGAME_VERSION)
1556 			warning("Old savegame version detected- can't load");
1557 		else
1558 			warning("Savegame version is %d- maximum supported is %0d", meta.version, CURRENT_SAVEGAME_VERSION);
1559 
1560 		return false;
1561 	}
1562 
1563 	return true;
1564 }
1565 
1566 } // End of namespace Sci
1567