From 12d37a352b74ee9652e83fdb0c71888a4b7aa5cd Mon Sep 17 00:00:00 2001 From: sluicebox <22204938+sluicebox@users.noreply.github.com> Date: Sun, 19 Apr 2020 23:46:13 -0700 Subject: [PATCH] SCI32: Implement KQ7/SHIVERS custom Mac saving Implements the custom Mac save and restore kPlatform subops for KQ7 and Shivers. Still TODO: Mothergoose and Lighthouse. --- engines/sci/engine/guest_additions.cpp | 15 +++ engines/sci/engine/kmisc.cpp | 134 +++++++++++++++++++++---- engines/sci/engine/state.cpp | 3 + engines/sci/engine/state.h | 4 + engines/sci/engine/vm.h | 1 + engines/sci/engine/workarounds.cpp | 1 + 6 files changed, 137 insertions(+), 21 deletions(-) diff --git a/engines/sci/engine/guest_additions.cpp b/engines/sci/engine/guest_additions.cpp index 017b41abee2..2219c4ee817 100644 --- a/engines/sci/engine/guest_additions.cpp +++ b/engines/sci/engine/guest_additions.cpp @@ -25,6 +25,7 @@ #include "common/gui_options.h" #include "common/savefile.h" #include "sci/engine/features.h" +#include "sci/engine/file.h" #include "sci/engine/guest_additions.h" #include "sci/engine/kernel.h" #include "sci/engine/savegame.h" @@ -789,11 +790,25 @@ bool GuestAdditions::restoreFromLauncher() const { reg_t args[] = { make_reg(0, _state->_delayedRestoreGameId - kSaveIdShift) }; invokeSelector(g_sci->getGameObject(), SELECTOR(restore), 1, args); } else { + int saveId = _state->_delayedRestoreGameId; + // When `Game::restore` is invoked, it will call to `Restore::doit` // which will automatically return the `_delayedRestoreGameId` instead // of prompting the user for a save game invokeSelector(g_sci->getGameObject(), SELECTOR(restore)); + // initialize KQ7 Mac's global save state by recording the save id + // and description. this is necessary for subsequent saves to work + // after restoring from launcher. + if (g_sci->getGameId() == GID_KQ7 || g_sci->getPlatform() == Common::kPlatformMacintosh) { + _state->_kq7MacSaveGameId = saveId; + + SavegameDesc savegameDesc; + if (fillSavegameDesc(g_sci->getSavegameName(saveId), savegameDesc)) { + _state->_kq7MacSaveGameDescription = savegameDesc.name; + } + } + // The normal save game system resets _delayedRestoreGameId with a // call to `EngineState::reset`, but RAMA uses a custom save game // system which does not reset the engine, so we need to clear the diff --git a/engines/sci/engine/kmisc.cpp b/engines/sci/engine/kmisc.cpp index ddd82c124e4..fa6f6d50879 100644 --- a/engines/sci/engine/kmisc.cpp +++ b/engines/sci/engine/kmisc.cpp @@ -21,6 +21,7 @@ */ #include "common/config-manager.h" +#include "common/savefile.h" #include "common/system.h" #include "sci/sci.h" @@ -29,10 +30,15 @@ #include "sci/engine/state.h" #include "sci/engine/kernel.h" #include "sci/engine/gc.h" +#ifdef ENABLE_SCI32 +#include "sci/engine/guest_additions.h" +#endif +#include "sci/engine/savegame.h" #include "sci/graphics/cursor.h" #include "sci/graphics/palette.h" #ifdef ENABLE_SCI32 #include "sci/graphics/cursor32.h" +#include "sci/graphics/frameout.h" #endif #include "sci/graphics/maciconbar.h" #include "sci/console.h" @@ -570,32 +576,118 @@ reg_t kMacPlatform(EngineState *s, int argc, reg_t *argv) { } #ifdef ENABLE_SCI32 +// kMacKq7InitializeSave is a subop of kMacPlatform32. +// KQ7 Mac would display a native Save dialog with the prompt "Who's game?" +// and store the result in a global variable inside the interpreter +// for subsequent calls to kMacKq7SaveGame. +reg_t kMacKq7InitializeSave(EngineState *s) { + s->_kq7MacSaveGameId = g_sci->_guestAdditions->runSaveRestore(true, s->_kq7MacSaveGameDescription); + s->_kq7MacSaveGameId = shiftSciToScummVMSaveId(s->_kq7MacSaveGameId); + return (s->_kq7MacSaveGameId != -1) ? TRUE_REG : NULL_REG; +} + +// kMacKq7SaveGame is a subop of kMacPlatform32. +// Saves the game using the current save id and description that's set +// when initializing or restoring a saved game. +reg_t kMacKq7SaveGame(EngineState *s) { + if (s->_kq7MacSaveGameId == -1) { + error("kMacKq7SaveGame: save game hasn't been initialized"); + } + + const reg_t version = s->variables[VAR_GLOBAL][kGlobalVarVersion]; + const Common::String versionString = s->_segMan->getString(version); + if (gamestate_save(s, s->_kq7MacSaveGameId, s->_kq7MacSaveGameDescription, versionString)) { + return TRUE_REG; + } + return NULL_REG; +} + +// kMacKq7RestoreGame is a subop of kMacPlatform32. +// KQ7 Mac would display a native Open dialog with the prompt "Who's game?" +// and store the result in a global variable inside the interpreter to +// use in subsequent calls to kMacKq7SaveGame before restoring. +reg_t kMacKq7RestoreGame(EngineState *s) { + s->_kq7MacSaveGameId = g_sci->_guestAdditions->runSaveRestore(false, s->_kq7MacSaveGameDescription); + s->_kq7MacSaveGameId = shiftSciToScummVMSaveId(s->_kq7MacSaveGameId); + if (s->_kq7MacSaveGameId == -1) { + return NULL_REG; + } + + // gamestate_restore() resets s->_kq7MacSaveGameId and + // s->_kq7MacSaveGameDescription so save and restore them. + int kq7MacSaveGameId = s->_kq7MacSaveGameId; + Common::String kq7MacSaveGameDescription = s->_kq7MacSaveGameDescription; + bool success = gamestate_restore(s, s->_kq7MacSaveGameId); + s->_kq7MacSaveGameId = kq7MacSaveGameId; + s->_kq7MacSaveGameDescription = kq7MacSaveGameDescription; + + return success ? TRUE_REG : NULL_REG; +} + +// kMacShiversInitializeSave is a subop of kMacPlatform32. +reg_t kMacShiversInitializeSave(EngineState *s, int argc, reg_t *argv) { + return TRUE_REG; // NULL_REG if i/o errors +} + +// kMacShiversSaveGame is a subop of kMacPlatform32. +reg_t kMacShiversSaveGame(EngineState *s, int argc, reg_t *argv) { + g_sci->_gfxFrameout->kernelFrameOut(true); // see kSaveGame32 + + const int saveId = shiftSciToScummVMSaveId(argv[1].toUint16()); + const Common::String description = s->_segMan->getString(argv[2]); + const reg_t version = s->variables[VAR_GLOBAL][kGlobalVarVersion]; + const Common::String versionString = s->_segMan->getString(version); + if (gamestate_save(s, saveId, description, versionString)) { + return TRUE_REG; + } + return NULL_REG; +} + +// kMacShiversRestoreGame is a subop of kMacPlatform32. +reg_t kMacShiversRestoreGame(EngineState *s, int argc, reg_t *argv) { + const int saveId = shiftSciToScummVMSaveId(argv[1].toUint16()); + if (gamestate_restore(s, saveId)) { + return TRUE_REG; + } + return NULL_REG; +} + reg_t kMacPlatform32(EngineState *s, int argc, reg_t *argv) { switch (argv[0].toUint16()) { case 0: // build cursor view map g_sci->_gfxCursor32->setMacCursorRemapList(argc - 1, argv + 1); - break; + return s->r_acc; case 1: // compact/purge mac memory case 2: // hands-off/hands-on for mac menus - break; + return s->r_acc; - // TODO: Save game handling in KQ7, Shivers, and Lighthouse. - // - KQ7 uses all three with no parameters; the interpreter would - // remember the current save file. - // - Shivers uses all three but passes parameters in a similar - // manner as the normal kSave\kRestore calls. - // - Lighthouse goes insane and only uses subop 3 but adds sub-subops - // which appear to do the three operations. - // Temporarily stubbing these out with success values so that KQ7 can start. - case 3: // initialize save game file - warning("Unimplemented kMacPlatform32(%d): Initialize save game file", argv[0].toUint16()); - return TRUE_REG; - case 4: // save game - warning("Unimplemented kMacPlatform32(%d): Save game", argv[0].toUint16()); - return TRUE_REG; - case 5: // restore game - warning("Unimplemented kMacPlatform32(%d): Restore game", argv[0].toUint16()); + // Subops 3-5 are used for custom saving and restoring but they + // changed completely between each game that uses them. + // + // KQ7: 3-5 with no parameters + // Shivers: 3-5 with parameters + // Lighthouse: 3 with sub-subops: -1, 0, and 1 (TODO) + case 3: + if (argc == 1) { + return kMacKq7InitializeSave(s); + } else if (argc == 3) { + return kMacShiversInitializeSave(s, argc - 1, argv + 1); + } + break; + case 4: + if (argc == 1) { + return kMacKq7SaveGame(s); + } else if (argc == 4) { + return kMacShiversSaveGame(s, argc - 1, argv + 1); + } + break; + case 5: + if (argc == 1) { + return kMacKq7RestoreGame(s); + } else if (argc == 3) { + return kMacShiversRestoreGame(s, argc - 1, argv + 1); + } break; // TODO: Mother Goose save game handling @@ -605,18 +697,18 @@ reg_t kMacPlatform32(EngineState *s, int argc, reg_t *argv) { case 9: case 10: case 11: - error("Unimplemented kMacPlatform32(%d) save game operation", argv[0].toUint16()); break; // TODO: Phantasmagoria music volume adjustment [ 0-15 ] case 12: warning("Unimplemented kMacPlatform32(%d): Set volume: %d", argv[0].toUint16(), argv[1].toUint16()); - break; + return s->r_acc; default: - error("Unknown kMacPlatform32(%d)", argv[0].toUint16()); + break; } + error("Unknown kMacPlatform32(%d)", argv[0].toUint16()); return s->r_acc; } #endif diff --git a/engines/sci/engine/state.cpp b/engines/sci/engine/state.cpp index 5bb13438399..6648458bd75 100644 --- a/engines/sci/engine/state.cpp +++ b/engines/sci/engine/state.cpp @@ -88,6 +88,9 @@ void EngineState::reset(bool isRestoring) { _delayedRestoreGameId = -1; + _kq7MacSaveGameId = -1; + _kq7MacSaveGameDescription.clear(); + executionStackBase = 0; _executionStackPosChanged = false; stack_base = 0; diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index a783f09d826..46161a31879 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -136,6 +136,10 @@ public: // see detection.cpp / SciEngine::loadGameState() int _delayedRestoreGameId; // the saved game id, that it supposed to get restored (triggered by ScummVM menu) + // see kmisc.cpp / kMacPlatform32 + int _kq7MacSaveGameId; // the saved game id to use when saving (might not exist yet) + Common::String _kq7MacSaveGameDescription; // description to use when saving game + uint _chosenQfGImportItem; // Remembers the item selected in QfG import rooms bool _cursorWorkaroundActive; // Refer to GfxCursor::setPosition() diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index c80070409b6..f1af11a2498 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -149,6 +149,7 @@ enum GlobalVar { kGlobalVarPreviousRoomNo = 12, kGlobalVarNewRoomNo = 13, kGlobalVarScore = 15, + kGlobalVarVersion = 27, kGlobalVarGK2MusicVolume = 76, // 0 to 127 kGlobalVarPhant2SecondaryVolume = 76, // 0 to 127 kGlobalVarFastCast = 84, // SCI16 diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index a9218337084..7050edbf9ca 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -446,6 +446,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_KQ7, 2450, 2450, 0, "maliciaComes", "handleEvent", NULL, 0, 0, { WORKAROUND_FAKE, 0 } }, // when malicia appears at the southeast exit of the main chamber near the end of chapter 2 { GID_KQ7, 5300, 5302, 0, "putOnMask", "handleEvent", NULL, 0, 0, { WORKAROUND_FAKE, 0 } }, // in chapter 3, after using the mask on Valanice, click the jackalope hair in inventory - bug Trac#9759 { GID_KQ7, 6060, 64964, 0, "DPath", "init", NULL, 1, 1, { WORKAROUND_FAKE, 0 } }, // after entering the harp crystal in chapter 5 + { GID_KQ7, -1, 64994, -1, "Game", "restore", NULL, 0, 0, { WORKAROUND_FAKE, 0 } }, // when restoring from ScummVM launcher in Mac version { GID_LAURABOW, 37, 0, 0, "CB1", "doit", NULL, 1, 1, { WORKAROUND_FAKE, 0 } }, // when going up the stairs - bug #5084 { GID_LAURABOW, -1, 967, 0, "myIcon", "cycle", NULL, 1, 1, { WORKAROUND_FAKE, 0 } }, // having any portrait conversation coming up - initial bug #4971 { GID_LAURABOW2, -1, 24, 0, "gcWin", "open", NULL, 5, 5, { WORKAROUND_FAKE, 0xf } }, // is used as priority for game menu