diff options
Diffstat (limited to 'engines/sci/engine/savegame.cpp')
-rw-r--r-- | engines/sci/engine/savegame.cpp | 155 |
1 files changed, 102 insertions, 53 deletions
diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index c8076ec819..93b3a997cc 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -8,18 +8,19 @@ * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. - + * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. - + * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. * */ +#include "common/savefile.h" #include "common/stream.h" #include "common/system.h" #include "common/func.h" @@ -40,6 +41,7 @@ #include "sci/graphics/helpers.h" #include "sci/graphics/palette.h" #include "sci/graphics/ports.h" +#include "sci/graphics/screen.h" #include "sci/parser/vocabulary.h" #include "sci/sound/audio.h" #include "sci/sound/music.h" @@ -132,13 +134,6 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { // Reset _scriptSegMap, to be restored below _scriptSegMap.clear(); - -#ifdef ENABLE_SCI32 - // Clear any planes/screen items currently showing so they - // don't show up after the load. - if (getSciVersion() >= SCI_VERSION_2) - g_sci->_gfxFrameout->clear(); -#endif } s.skip(4, VER(14), VER(18)); // OBSOLETE: Used to be _exportsAreWide @@ -219,25 +214,30 @@ void SegManager::saveLoadWithSerializer(Common::Serializer &s) { syncArray<Class>(s, _classTable); - // Now that all scripts are loaded, init their objects - for (uint i = 0; i < _heap.size(); i++) { - if (!_heap[i] || _heap[i]->getType() != SEG_TYPE_SCRIPT) - continue; + // Now that all scripts are loaded, init their objects. + // Just like in Script::initializeObjectsSci0, we do two passes + // in case an object is loaded before its base. + int passes = getSciVersion() < SCI_VERSION_1_1 ? 2 : 1; + for (int pass = 1; pass <= passes; ++pass) { + for (uint i = 0; i < _heap.size(); i++) { + if (!_heap[i] || _heap[i]->getType() != SEG_TYPE_SCRIPT) + continue; - Script *scr = (Script *)_heap[i]; - scr->syncLocalsBlock(this); + Script *scr = (Script *)_heap[i]; + scr->syncLocalsBlock(this); - ObjMap objects = scr->getObjectMap(); - for (ObjMap::iterator it = objects.begin(); it != objects.end(); ++it) { - reg_t addr = it->_value.getPos(); - Object *obj = scr->scriptObjInit(addr, false); + ObjMap objects = scr->getObjectMap(); + for (ObjMap::iterator it = objects.begin(); it != objects.end(); ++it) { + reg_t addr = it->_value.getPos(); + Object *obj = scr->scriptObjInit(addr, false); - if (getSciVersion() < SCI_VERSION_1_1) { - if (!obj->initBaseObject(this, addr, false)) { - // TODO/FIXME: This should not be happening at all. It might indicate a possible issue - // with the garbage collector. It happens for example in LSL5 (German, perhaps English too). - warning("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr)); - objects.erase(addr.toUint16()); + if (pass == 2) { + if (!obj->initBaseObject(this, addr, false)) { + // TODO/FIXME: This should not be happening at all. It might indicate a possible issue + // with the garbage collector. It happens for example in LSL5 (German, perhaps English too). + warning("Failed to locate base object for object at %04X:%04X; skipping", PRINT_REG(addr)); + objects.erase(addr.toUint16()); + } } } } @@ -465,7 +465,7 @@ void Script::syncStringHeap(Common::Serializer &s) { break; } while (1); - } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1){ + } else if (getSciVersion() >= SCI_VERSION_1_1 && getSciVersion() <= SCI_VERSION_2_1){ // Strings in SCI1.1 come after the object instances byte *buf = _heapStart + 4 + READ_SCI11ENDIAN_UINT16(_heapStart + 2) * 2; @@ -484,7 +484,7 @@ void Script::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint32LE(_nr); if (s.isLoading()) - load(_nr, g_sci->getResMan()); + load(_nr, g_sci->getResMan(), g_sci->getScriptPatcher()); s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _bufSize s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _scriptSize s.skip(4, VER(14), VER(22)); // OBSOLETE: Used to be _heapSize @@ -600,7 +600,10 @@ void MusicEntry::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint16LE(dataInc); s.syncAsSint16LE(ticker); s.syncAsSint16LE(signal, VER(17)); - s.syncAsByte(priority); + if (s.getVersion() >= 31) // FE sound/music.h -> priority + s.syncAsSint16LE(priority); + else + s.syncAsByte(priority); s.syncAsSint16LE(loop, VER(17)); s.syncAsByte(volume); s.syncAsByte(hold, VER(17)); @@ -609,6 +612,14 @@ void MusicEntry::saveLoadWithSerializer(Common::Serializer &s) { s.syncAsSint32LE(fadeTicker); s.syncAsSint32LE(fadeTickerStep); s.syncAsByte(status); + if (s.getVersion() >= 32) + s.syncAsByte(playBed); + else if (s.isLoading()) + playBed = false; + if (s.getVersion() >= 33) + s.syncAsByte(overridePriority); + else if (s.isLoading()) + overridePriority = false; // pMidiParser and pStreamAud will be initialized when the // sound list is reconstructed in gamestate_restore() @@ -627,20 +638,26 @@ void SoundCommandParser::syncPlayList(Common::Serializer &s) { void SoundCommandParser::reconstructPlayList() { Common::StackLock lock(_music->_mutex); - const MusicList::iterator end = _music->getPlayListEnd(); - for (MusicList::iterator i = _music->getPlayListStart(); i != end; ++i) { + // 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); + + for (MusicList::iterator i = songs.begin(); i != songs.end(); ++i) { initSoundResource(*i); if ((*i)->status == kSoundPlaying) { - // Sync the sound object's selectors related to playing with the stored - // ones in the playlist, as they may have been invalidated when loading. - // Refer to bug #3104624. + // 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); if (_soundVersion >= SCI_VERSION_1_EARLY) writeSelectorValue(_segMan, (*i)->soundObj, SELECTOR(vol), (*i)->volume); - processPlaySound((*i)->soundObj); + processPlaySound((*i)->soundObj, (*i)->playBed); } } } @@ -705,9 +722,7 @@ void GfxPalette::saveLoadWithSerializer(Common::Serializer &s) { } void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) { - if (s.isLoading()) - reset(); // remove all script generated windows - + // reset() is called directly way earlier in gamestate_restore() if (s.getVersion() >= 27) { uint windowCount = 0; uint id = PORTS_FIRSTSCRIPTWINDOWID; @@ -750,10 +765,17 @@ void GfxPorts::saveLoadWithSerializer(Common::Serializer &s) { if (window->counterTillFree) { _freeCounter++; } else { - if (window->wndStyle & SCI_WINDOWMGR_STYLE_TOPMOST) - _windowList.push_front(window); - else - _windowList.push_back(window); + // we don't put the saved script windows into _windowList[], so that they aren't used + // by kernel functions. This is important and would cause issues otherwise. + // see Conquests of Camelot - bug #6744 - when saving on the map screen (room 103), + // restoring would result in a black window in place + // where the area name was displayed before + // In Sierra's SCI the behaviour is identical to us + // Sierra's SCI won't show those windows after restoring + // If this should cause issues in another game, we would have to add a flag to simply + // avoid any drawing operations for such windows + // We still have to restore script windows, because for example Conquests of Camelot + // will immediately delete windows, that were created before saving the game. } windowCount--; @@ -836,11 +858,29 @@ bool gamestate_save(EngineState *s, Common::WriteStream *fh, const Common::Strin if (voc) voc->saveLoadWithSerializer(ser); + // TODO: SSCI (at least JonesCD, presumably more) also stores the Menu state + return true; } extern void showScummVMDialog(const Common::String &message); +void gamestate_delayedrestore(EngineState *s) { + Common::String fileName = g_sci->getSavegameName(s->_delayedRestoreGameId); + Common::SeekableReadStream *in = g_sci->getSaveFileManager()->openForLoading(fileName); + + if (in) { + // found a savegame file + gamestate_restore(s, in); + delete in; + if (s->r_acc != make_reg(0, 1)) { + return; + } + } + + error("Restoring gamestate '%s' failed", fileName.c_str()); +} + void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { SavegameMetadata meta; @@ -852,16 +892,13 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { return; } - if ((meta.version < MINIMUM_SAVEGAME_VERSION) || - (meta.version > CURRENT_SAVEGAME_VERSION)) { - /* - if (meta.version < MINIMUM_SAVEGAME_VERSION) - warning("Old savegame version detected, unable to load it"); - else - warning("Savegame version is %d, maximum supported is %0d", meta.version, CURRENT_SAVEGAME_VERSION); - */ - - showScummVMDialog("The format of this saved game is obsolete, unable to load it"); + if ((meta.version < MINIMUM_SAVEGAME_VERSION) || (meta.version > CURRENT_SAVEGAME_VERSION)) { + if (meta.version < MINIMUM_SAVEGAME_VERSION) { + showScummVMDialog("The format of this saved game is obsolete, unable to load it"); + } else { + Common::String msg = Common::String::format("Savegame version is %d, maximum supported is %0d", meta.version, CURRENT_SAVEGAME_VERSION); + showScummVMDialog(msg); + } s->r_acc = TRUE_REG; // signal failure return; @@ -870,8 +907,6 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { if (meta.gameObjectOffset > 0 && meta.script0Size > 0) { Resource *script0 = g_sci->getResMan()->findResource(ResourceId(kResourceTypeScript, 0), false); if (script0->size != meta.script0Size || g_sci->getGameObject().getOffset() != meta.gameObjectOffset) { - //warning("This saved game was created with a different version of the game, unable to load it"); - showScummVMDialog("This saved game was created with a different version of the game, unable to load it"); s->r_acc = TRUE_REG; // signal failure @@ -882,6 +917,20 @@ void gamestate_restore(EngineState *s, Common::SeekableReadStream *fh) { // We don't need the thumbnail here, so just read it and discard it Graphics::skipThumbnail(*fh); + // reset ports is one of the first things we do, because that may free() some hunk memory + // and we don't want to do that after we read in the saved game hunk memory + if (g_sci->_gfxPorts) + g_sci->_gfxPorts->reset(); + // clear screen + 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(); +#endif + s->reset(true); s->saveLoadWithSerializer(ser); // FIXME: Error handling? |