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