diff options
Diffstat (limited to 'engines/sci/engine/savegame.cpp')
-rw-r--r-- | engines/sci/engine/savegame.cpp | 466 |
1 files changed, 316 insertions, 150 deletions
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index fcf951e4f2..720f6783ee 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -48,8 +48,10 @@ #include "sci/sound/music.h" #ifdef ENABLE_SCI32 -#include "sci/graphics/palette32.h" +#include "sci/graphics/cursor32.h" #include "sci/graphics/frameout.h" +#include "sci/graphics/palette32.h" +#include "sci/graphics/remap32.h" #endif namespace Sci { @@ -60,24 +62,87 @@ namespace Sci { #pragma mark - -// Experimental hack: Use syncWithSerializer to sync. By default, this assume -// the object to be synced is a subclass of Serializable and thus tries to invoke -// the saveLoadWithSerializer() method. But it is possible to specialize this -// template function to handle stuff that is not implementing that interface. -template<typename T> -void syncWithSerializer(Common::Serializer &s, T &obj) { +// These are serialization functions for various objects. + +void syncWithSerializer(Common::Serializer &s, Common::Serializable &obj) { + obj.saveLoadWithSerializer(s); +} + +// FIXME: Object could implement Serializable to make use of the function +// above. +void syncWithSerializer(Common::Serializer &s, Object &obj) { obj.saveLoadWithSerializer(s); } +void syncWithSerializer(Common::Serializer &s, reg_t &obj) { + // Segment and offset are accessed directly here + s.syncAsUint16LE(obj._segment); + s.syncAsUint16LE(obj._offset); +} + +void syncWithSerializer(Common::Serializer &s, synonym_t &obj) { + s.syncAsUint16LE(obj.replaceant); + s.syncAsUint16LE(obj.replacement); +} + +void syncWithSerializer(Common::Serializer &s, Class &obj) { + s.syncAsSint32LE(obj.script); + syncWithSerializer(s, obj.reg); +} + +void syncWithSerializer(Common::Serializer &s, List &obj) { + syncWithSerializer(s, obj.first); + syncWithSerializer(s, obj.last); +} + +void syncWithSerializer(Common::Serializer &s, Node &obj) { + syncWithSerializer(s, obj.pred); + syncWithSerializer(s, obj.succ); + syncWithSerializer(s, obj.key); + syncWithSerializer(s, obj.value); +} + +#pragma mark - + // By default, sync using syncWithSerializer, which in turn can easily be overloaded. template<typename T> struct DefaultSyncer : Common::BinaryFunction<Common::Serializer, T, void> { - void operator()(Common::Serializer &s, T &obj) const { - //obj.saveLoadWithSerializer(s); + void operator()(Common::Serializer &s, T &obj, int) const { syncWithSerializer(s, obj); } }; +// Syncer for entries in a segment obj table +template<typename T> +struct SegmentObjTableEntrySyncer : Common::BinaryFunction<Common::Serializer, typename T::Entry &, void> { + void operator()(Common::Serializer &s, typename T::Entry &entry, int index) const { + s.syncAsSint32LE(entry.next_free); + + bool hasData; + if (s.getVersion() >= 37) { + if (s.isSaving()) { + hasData = entry.data != nullptr; + } + s.syncAsByte(hasData); + } else { + hasData = (entry.next_free == index); + } + + if (hasData) { + if (s.isLoading()) { + entry.data = new typename T::value_type; + } + syncWithSerializer(s, *entry.data); + } else if (s.isLoading()) { + if (s.getVersion() < 37) { + typename T::value_type dummy; + syncWithSerializer(s, dummy); + } + entry.data = nullptr; + } + } +}; + /** * Sync a Common::Array using a Common::Serializer. * When saving, this writes the length of the array, then syncs (writes) all entries. @@ -102,9 +167,8 @@ struct ArraySyncer : Common::BinaryFunction<Common::Serializer, T, void> { if (s.isLoading()) arr.resize(len); - typename Common::Array<T>::iterator i; - for (i = arr.begin(); i != arr.end(); ++i) { - sync(s, *i); + for (uint i = 0; i < len; ++i) { + sync(s, arr[i], i); } } }; @@ -116,18 +180,10 @@ void syncArray(Common::Serializer &s, Common::Array<T> &arr) { sync(s, arr); } - -template<> -void syncWithSerializer(Common::Serializer &s, reg_t &obj) { - // Segment and offset are accessed directly here - s.syncAsUint16LE(obj._segment); - s.syncAsUint16LE(obj._offset); -} - -template<> -void syncWithSerializer(Common::Serializer &s, synonym_t &obj) { - s.syncAsUint16LE(obj.replaceant); - s.syncAsUint16LE(obj.replacement); +template<typename T, class Syncer> +void syncArray(Common::Serializer &s, Common::Array<T> &arr) { + ArraySyncer<T, Syncer> sync; + sync(s, arr); } void SegManager::saveLoadWithSerializer(Common::Serializer &s) { @@ -176,9 +232,8 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { } else if (type == SEG_TYPE_ARRAY) { // Set the correct segment for SCI32 arrays _arraysSegId = i; - } else if (type == SEG_TYPE_STRING) { - // Set the correct segment for SCI32 strings - _stringSegId = i; + } else if (s.getVersion() >= 36 && type == SEG_TYPE_BITMAP) { + _bitmapSegId = i; #endif } @@ -247,12 +302,6 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { } -template<> -void syncWithSerializer(Common::Serializer &s, Class &obj) { - s.syncAsSint32LE(obj.script); - syncWithSerializer(s, obj.reg); -} - static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) { s.syncString(obj.name); s.syncVersion(CURRENT_SAVEGAME_VERSION); @@ -281,6 +330,28 @@ static void sync_SavegameMetadata(Common::Serializer &s, SavegameMetadata &obj) } s.syncAsUint32LE(obj.playTime); } + + // Some games require additional metadata to display their restore screens + // correctly + if (s.getVersion() >= 39) { + if (s.isSaving()) { + const reg_t *globals = g_sci->getEngineState()->variables[VAR_GLOBAL]; + if (g_sci->getGameId() == GID_SHIVERS) { + obj.lowScore = globals[kGlobalVarScore].toUint16(); + obj.highScore = globals[kGlobalVarShivers1Score].toUint16(); + obj.avatarId = 0; + } else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES) { + obj.lowScore = obj.highScore = 0; + obj.avatarId = readSelectorValue(g_sci->getEngineState()->_segMan, globals[kGlobalVarEgo], SELECTOR(view)); + } else { + obj.lowScore = obj.highScore = obj.avatarId = 0; + } + } + + s.syncAsUint16LE(obj.lowScore); + s.syncAsUint16LE(obj.highScore); + s.syncAsByte(obj.avatarId); + } } void EngineState::saveLoadWithSerializer(Common::Serializer &s) { @@ -310,8 +381,15 @@ void EngineState::saveLoadWithSerializer(Common::Serializer &s) { _segMan->saveLoadWithSerializer(s); g_sci->_soundCmd->syncPlayList(s); - // NOTE: This will be GfxPalette32 for SCI32 engine games - g_sci->_gfxPalette16->saveLoadWithSerializer(s); + +#ifdef ENABLE_SCI32 + if (getSciVersion() >= SCI_VERSION_2) { + g_sci->_gfxPalette32->saveLoadWithSerializer(s); + g_sci->_gfxRemap32->saveLoadWithSerializer(s); + g_sci->_gfxCursor32->saveLoadWithSerializer(s); + } else +#endif + g_sci->_gfxPalette16->saveLoadWithSerializer(s); } void Vocabulary::saveLoadWithSerializer(Common::Serializer &s) { @@ -331,102 +409,13 @@ void Object::saveLoadWithSerializer(Common::Serializer &s) { syncArray<reg_t>(s, _variables); } -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<Clone>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - syncWithSerializer<Object>(s, obj); -} - -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<List>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - syncWithSerializer(s, obj.first); - syncWithSerializer(s, obj.last); -} - -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<Node>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - syncWithSerializer(s, obj.pred); - syncWithSerializer(s, obj.succ); - syncWithSerializer(s, obj.key); - syncWithSerializer(s, obj.value); -} - -#ifdef ENABLE_SCI32 -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<SciArray<reg_t> >::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - byte type = 0; - uint32 size = 0; - - if (s.isSaving()) { - type = (byte)obj.getType(); - size = obj.getSize(); - } - s.syncAsByte(type); - s.syncAsUint32LE(size); - if (s.isLoading()) { - obj.setType((int8)type); - - // HACK: Skip arrays that have a negative type - if ((int8)type < 0) - return; - - obj.setSize(size); - } - - for (uint32 i = 0; i < size; i++) { - reg_t value; - - if (s.isSaving()) - value = obj.getValue(i); - - syncWithSerializer(s, value); - - if (s.isLoading()) - obj.setValue(i, value); - } -} - -template<> -void syncWithSerializer(Common::Serializer &s, SegmentObjTable<SciString>::Entry &obj) { - s.syncAsSint32LE(obj.next_free); - - uint32 size = 0; - - if (s.isSaving()) { - size = obj.getSize(); - s.syncAsUint32LE(size); - } else { - s.syncAsUint32LE(size); - obj.setSize(size); - } - - for (uint32 i = 0; i < size; i++) { - char value = 0; - - if (s.isSaving()) - value = obj.getValue(i); - - s.syncAsByte(value); - - if (s.isLoading()) - obj.setValue(i, value); - } -} -#endif template<typename T> void sync_Table(Common::Serializer &s, T &obj) { s.syncAsSint32LE(obj.first_free); s.syncAsSint32LE(obj.entries_used); - syncArray<typename T::Entry>(s, obj._table); + syncArray<typename T::Entry, SegmentObjTableEntrySyncer<T> >(s, obj._table); } void CloneTable::saveLoadWithSerializer(Common::Serializer &s) { @@ -643,28 +632,44 @@ void SoundCommandParser::syncPlayList(Common::Serializer &s) { } void SoundCommandParser::reconstructPlayList() { - Common::StackLock lock(_music->_mutex); + _music->_mutex.lock(); // We store all songs here because starting songs may re-shuffle their order MusicList songs; for (MusicList::iterator i = _music->getPlayListStart(); i != _music->getPlayListEnd(); ++i) songs.push_back(*i); + // Done with main playlist, so release lock + _music->_mutex.unlock(); + for (MusicList::iterator i = songs.begin(); i != songs.end(); ++i) { - initSoundResource(*i); + MusicEntry *entry = *i; + initSoundResource(entry); + +#ifdef ENABLE_SCI32 + if (_soundVersion >= SCI_VERSION_2_1_EARLY && entry->isSample) { + const reg_t &soundObj = entry->soundObj; + + if ((int)readSelectorValue(_segMan, soundObj, SELECTOR(loop)) != -1 && + readSelector(_segMan, soundObj, SELECTOR(handle)) != NULL_REG) { - if ((*i)->status == kSoundPlaying) { + writeSelector(_segMan, soundObj, SELECTOR(handle), NULL_REG); + processPlaySound(soundObj, entry->playBed); + } + } else +#endif + if (entry->status == kSoundPlaying) { // WORKAROUND: PQ3 (German?) scripts can set volume negative in the // sound object directly without going through DoSound. // Since we re-read this selector when re-playing the sound after loading, // this will lead to unexpected behaviour. As a workaround we // sync the sound object's selectors here. (See bug #5501) - writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(loop), (*i)->loop); - writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(priority), (*i)->priority); + writeSelectorValue(_segMan, entry->soundObj, SELECTOR(loop), entry->loop); + writeSelectorValue(_segMan, entry->soundObj, SELECTOR(priority), entry->priority); if (_soundVersion >= SCI_VERSION_1_EARLY) - writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(vol), (*i)->volume); + writeSelectorValue(_segMan, entry->soundObj, SELECTOR(vol), entry->volume); - processPlaySound((*i)->soundObj, (*i)->playBed); + processPlaySound(entry->soundObj, entry->playBed); } } } @@ -677,11 +682,60 @@ void ArrayTable::saveLoadWithSerializer(Common::Serializer &ser) { sync_Table<ArrayTable>(ser, *this); } -void StringTable::saveLoadWithSerializer(Common::Serializer &ser) { - if (ser.getVersion() < 18) +void SciArray::saveLoadWithSerializer(Common::Serializer &s) { + uint16 savedSize; + + if (s.isSaving()) { + savedSize = _size; + } + + s.syncAsByte(_type); + s.syncAsByte(_elementSize); + s.syncAsUint16LE(savedSize); + + if (s.isLoading()) { + resize(savedSize); + } + + switch (_type) { + case kArrayTypeInt16: + case kArrayTypeID: + for (int i = 0; i < savedSize; ++i) { + syncWithSerializer(s, ((reg_t *)_data)[i]); + } + break; + case kArrayTypeByte: + case kArrayTypeString: + s.syncBytes((byte *)_data, savedSize); + break; + default: + error("Attempt to sync invalid SciArray type %d", _type); + } +} + +void BitmapTable::saveLoadWithSerializer(Common::Serializer &ser) { + if (ser.getVersion() < 36) { return; + } + + sync_Table(ser, *this); +} + +void SciBitmap::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 36) { + return; + } - sync_Table<StringTable>(ser, *this); + s.syncAsByte(_gc); + s.syncAsUint32LE(_dataSize); + if (s.isLoading()) { + _data = (byte *)malloc(_dataSize); + } + s.syncBytes(_data, _dataSize); + + if (s.isLoading()) { + _buffer = Buffer(getWidth(), getHeight(), getPixels()); + } } #endif @@ -729,7 +783,7 @@ void GfxPalette::saveLoadWithSerializer(Common::Serializer &s) { } #ifdef ENABLE_SCI32 -void saveLoadPalette32(Common::Serializer &s, Palette *const palette) { +static void saveLoadPalette32(Common::Serializer &s, Palette *const palette) { s.syncAsUint32LE(palette->timestamp); for (int i = 0; i < ARRAYSIZE(palette->colors); ++i) { s.syncAsByte(palette->colors[i].used); @@ -739,7 +793,7 @@ void saveLoadPalette32(Common::Serializer &s, Palette *const palette) { } } -void saveLoadOptionalPalette32(Common::Serializer &s, Palette **const palette) { +static void saveLoadOptionalPalette32(Common::Serializer &s, Palette **const palette) { bool hasPalette; if (s.isSaving()) { hasPalette = (*palette != nullptr); @@ -760,6 +814,16 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) { if (s.isLoading()) { ++_version; + + for (int i = 0; i < kNumCyclers; ++i) { + delete _cyclers[i]; + _cyclers[i] = nullptr; + } + + delete _varyTargetPalette; + _varyTargetPalette = nullptr; + delete _varyStartPalette; + _varyStartPalette = nullptr; } s.syncAsSint16LE(_varyDirection); @@ -768,7 +832,7 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint16LE(_varyFromColor); s.syncAsSint16LE(_varyToColor); s.syncAsUint16LE(_varyNumTimesPaused); - s.syncAsByte(_versionUpdated); + s.syncAsByte(_needsUpdate); s.syncAsSint32LE(_varyTime); s.syncAsUint32LE(_varyLastTick); @@ -808,8 +872,62 @@ void GfxPalette32::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsUint16LE(cycler->numTimesPaused); } } +} + +void GfxRemap32::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 35) { + return; + } + + s.syncAsByte(_numActiveRemaps); + s.syncAsByte(_blockedRangeStart); + s.syncAsSint16LE(_blockedRangeCount); + + for (uint i = 0; i < _remaps.size(); ++i) { + SingleRemap &singleRemap = _remaps[i]; + s.syncAsByte(singleRemap._type); + if (s.isLoading() && singleRemap._type != kRemapNone) { + singleRemap.reset(); + } + s.syncAsByte(singleRemap._from); + s.syncAsByte(singleRemap._to); + s.syncAsByte(singleRemap._delta); + s.syncAsByte(singleRemap._percent); + s.syncAsByte(singleRemap._gray); + } + + if (s.isLoading()) { + _needsUpdate = true; + } +} + +void GfxCursor32::saveLoadWithSerializer(Common::Serializer &s) { + if (s.getVersion() < 38) { + return; + } + + int32 hideCount; + if (s.isSaving()) { + hideCount = _hideCount; + } + s.syncAsSint32LE(hideCount); + s.syncAsSint16LE(_restrictedArea.left); + s.syncAsSint16LE(_restrictedArea.top); + s.syncAsSint16LE(_restrictedArea.right); + s.syncAsSint16LE(_restrictedArea.bottom); + s.syncAsUint16LE(_cursorInfo.resourceId); + s.syncAsUint16LE(_cursorInfo.loopNo); + s.syncAsUint16LE(_cursorInfo.celNo); - // TODO: _clutTable + if (s.isLoading()) { + hide(); + setView(_cursorInfo.resourceId, _cursorInfo.loopNo, _cursorInfo.celNo); + if (!hideCount) { + show(); + } else { + _hideCount = hideCount; + } + } } #endif @@ -903,7 +1021,7 @@ void SegManager::reconstructClones() { if (!isUsed) continue; - CloneTable::Entry &seeker = ct->_table[j]; + CloneTable::value_type &seeker = ct->at(j); const Object *baseObj = getObject(seeker.getSpeciesSelector()); seeker.cloneFromObject(baseObj); if (!baseObj) { @@ -935,10 +1053,11 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin meta.gameObjectOffset = g_sci->getGameObject().getOffset(); // Checking here again - if (s->executionStackBase) { - warning("Cannot save from below kernel function"); - return false; - } +// TODO: This breaks Torin autosave, is there actually any reason for it? +// if (s->executionStackBase) { +// warning("Cannot save from below kernel function"); +// return false; +// } Common::Serializer ser(0, fh); sync_SavegameMetadata(ser, meta); @@ -1012,6 +1131,26 @@ void gamestate_afterRestoreFixUp(EngineState *s, int savegameId) { g_sci->_gfxMenu->kernelSetAttribute(1025 >> 8, 1025 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Statistics g_sci->_gfxMenu->kernelSetAttribute(1026 >> 8, 1026 & 0xFF, SCI_MENU_ATTRIBUTE_ENABLED, TRUE_REG); // Status -> Goals break; + case GID_KQ6: + if (g_sci->isCD()) { + // WORKAROUND: + // For the CD version of King's Quest 6, set global depending on current hires/lowres state + // The game sets a global at the start depending on it and some things check that global + // instead of checking platform like for example the game action menu. + // This never happened in the original interpreter, because the original DOS interpreter + // was only capable of lowres graphics and the original Windows 3.11 interpreter was only capable + // of hires graphics. Saved games were not compatible between those two. + // Which means saving during lowres mode, then going into hires mode and restoring that saved game, + // will result in some graphics being incorrect (lowres). + // That's why we are setting the global after restoring a saved game depending on hires/lowres state. + // The CD demo of KQ6 does the same and uses the exact same global. + if ((g_sci->getPlatform() == Common::kPlatformWindows) || (g_sci->forceHiresGraphics())) { + s->variables[VAR_GLOBAL][0xA9].setOffset(1); + } else { + s->variables[VAR_GLOBAL][0xA9].setOffset(0); + } + } + break; case GID_PQ2: // HACK: Same as above - enable the save game menu option when loading in PQ2 (bug #6875). // It gets disabled in the game's death screen. @@ -1063,13 +1202,31 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { if (g_sci->_gfxPorts) g_sci->_gfxPorts->reset(); // clear screen - if (g_sci->_gfxScreen) - g_sci->_gfxScreen->clearForRestoreGame(); + if (getSciVersion() <= SCI_VERSION_1_1) { + // Only do clearing the screen for SCI16 + // Both SCI16 + SCI32 did not clear the screen. + // We basically do it for SCI16, because of KQ6. + // When hires portraits are shown and the user restores during that time, the portraits + // wouldn't get fully removed. In original SCI, the user wasn't able to restore during that time, + // so this is basically a workaround, so that ScummVM features work properly. + // For SCI32, behavior was verified in DOSBox, that SCI32 does not clear and also not redraw the screen. + // It only redraws elements that have changed in comparison to the state before the restore. + // If we cleared the screen for SCI32, we would have issues because of this behavior. + if (g_sci->_gfxScreen) + g_sci->_gfxScreen->clearForRestoreGame(); + } #ifdef ENABLE_SCI32 - // Also clear any SCI32 planes/screen items currently showing so they - // don't show up after the load. - if (getSciVersion() >= SCI_VERSION_2) - g_sci->_gfxFrameout->clear(); + // Delete current planes/elements of actively loaded VM, only when our ScummVM dialogs are patched in + // We MUST NOT delete all planes/screen items. At least Space Quest 6 has a few in memory like for example + // the options plane, which are not re-added and are in memory all the time right from the start of the + // game. Sierra SCI32 did not clear planes, only scripts cleared the ones inside planes::elements. + if (getSciVersion() >= SCI_VERSION_2) { + if (!s->_delayedRestoreFromLauncher) { + // Only do it, when we are restoring regulary and not from launcher + // As it could result in option planes etc. on the screen (happens in gk1) + g_sci->_gfxFrameout->syncWithScripts(false); + } + } #endif s->reset(true); @@ -1094,6 +1251,13 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { if (g_sci->_gfxPorts) g_sci->_gfxPorts->saveLoadWithSerializer(ser); + // SCI32: + // Current planes/screen elements of freshly loaded VM are re-added by scripts in [gameID]::replay + // We don't have to do that in here. + // But we may have to do it ourselves in case we ever implement some soft-error handling in case + // a saved game can't be restored. That way we can restore the game screen. + // see _gfxFrameout->syncWithScripts() + Vocabulary *voc = g_sci->getVocabulary(); if (ser.getVersion() >= 30 && voc) voc->saveLoadWithSerializer(ser); @@ -1111,6 +1275,8 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { // signal restored game to game scripts s->gameIsRestarting = GAMEISRESTARTING_RESTORE; + + s->_delayedRestoreFromLauncher = false; } bool get_savegame_metadata(Common::SeekableReadStream *stream, SavegameMetadata *meta) { |