From bc4692563606f8ea798a4c1032872eae71833ae5 Mon Sep 17 00:00:00 2001 From: sluicebox <22204938+sluicebox@users.noreply.github.com> Date: Thu, 11 Jul 2024 22:28:17 -0700 Subject: [PATCH] SCI: Add support for KQ5 FM-Towns save/restore UI --- engines/sci/engine/kernel_tables.h | 2 +- engines/sci/engine/kfile.cpp | 91 +++++++++++++++++++++--------- engines/sci/engine/workarounds.cpp | 7 +++ engines/sci/engine/workarounds.h | 1 + 4 files changed, 73 insertions(+), 28 deletions(-) diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index b5dc5943192..63286383315 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -727,7 +727,7 @@ static SciKernelMapEntry s_kernelMap[] = { #ifdef ENABLE_SCI32 { "GetSaveFiles", kGetSaveFiles32, SIG_THRU_SCI21EARLY, SIGFOR_ALL, "rrr", NULL, NULL }, #endif - { MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL }, + { MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, kGetSaveFiles_workarounds }, { MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL }, { MAP_CALL(GlobalToLocal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, #ifdef ENABLE_SCI32 diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 1a84766f4e4..7e8db3a0be7 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -663,7 +663,7 @@ reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) { reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { Common::String name = s->_segMan->getString(argv[0]); Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - bool result; + bool result = false; // SQ4 floppy prepends /\ to the filenames if (name.hasPrefix("/\\")) { @@ -671,7 +671,7 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { name.deleteChar(0); } - if (name.hasPrefix("sq4sg.")) { + if (g_sci->getGameId() == GID_SQ4 && name.hasPrefix("sq4sg.")) { // Special case for SQ4 floppy: This game has hardcoded save game names. // They are named "sq4sg.xxx", where xxx is the virtual ID. We construct // the appropriate save game name and delete it. @@ -698,6 +698,17 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) { result = saveFileMan->removeSavefile(wrappedName); } #endif + } else if (g_sci->getGameId() == GID_KQ5 && + g_sci->getPlatform() == Common::kPlatformFMTowns && + name.hasPrefix("a:\\KQ5sg.")) { + // KQ5 FM-Towns uses a custom save/restore UI in script 764. + // It directly deletes save files using a hard-coded path. + int saveNo = 0; + sscanf(name.c_str(), "a:\\KQ5sg.%d", &saveNo); + if (1 <= saveNo && saveNo <= 10) { // UI has ten buttons + name = g_sci->getSavegameName(saveNo); + result = saveFileMan->removeSavefile(name); + } } else { const Common::String wrappedName = g_sci->wrapFilename(name); result = saveFileMan->removeSavefile(wrappedName); @@ -807,8 +818,6 @@ reg_t kFileIOFindNext(EngineState *s, int argc, reg_t *argv) { reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { Common::String name = s->_segMan->getString(argv[0]); - bool exists = false; - if (g_sci->getGameId() == GID_PEPPER) { // HACK: Special case for Pepper's Adventure in Time // The game checks like crazy for the file CDAUDIO when entering the game menu. @@ -820,36 +829,48 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { } #ifdef ENABLE_SCI32 - if (isSaveCatalogue(name)) { - return saveCatalogueExists(name) ? TRUE_REG : NULL_REG; - } - - int findSaveNo = -1; - - if (g_sci->getGameId() == GID_LSL7 && name == "autosvsg.000") { - // LSL7 checks to see if the autosave save exists when deciding whether - // to go to the main menu or not on startup - findSaveNo = kAutoSaveId; - } else if (g_sci->getGameId() == GID_RAMA) { - // RAMA checks to see if save game files exist before showing them in - // the native save/load dialogue - if (name == "autorama.sg") { - findSaveNo = kAutoSaveId; - } else if (sscanf(name.c_str(), "ramasg.%d", &findSaveNo) == 1) { - findSaveNo += kSaveIdShift; + if (getSciVersion() >= SCI_VERSION_2) { + if (isSaveCatalogue(name)) { + return saveCatalogueExists(name) ? TRUE_REG : NULL_REG; } - } - if (findSaveNo != -1) { - return g_sci->getSaveFileManager()->listSavefiles(g_sci->getSavegameName(findSaveNo)).empty() ? NULL_REG : TRUE_REG; + int findSaveNo = -1; + if (g_sci->getGameId() == GID_LSL7 && name == "autosvsg.000") { + // LSL7 checks to see if the autosave save exists when deciding whether + // to go to the main menu or not on startup + findSaveNo = kAutoSaveId; + } else if (g_sci->getGameId() == GID_RAMA) { + // RAMA checks to see if save game files exist before showing them in + // the native save/load dialogue + if (name == "autorama.sg") { + findSaveNo = kAutoSaveId; + } else if (sscanf(name.c_str(), "ramasg.%d", &findSaveNo) == 1) { + findSaveNo += kSaveIdShift; + } + } + + if (findSaveNo != -1) { + return g_sci->getSaveFileManager()->listSavefiles(g_sci->getSavegameName(findSaveNo)).empty() ? NULL_REG : TRUE_REG; + } + // TODO: It may apparently be worth caching the existence of + // phantsg.dir, and possibly even keeping it open persistently } #endif - // TODO: It may apparently be worth caching the existence of - // phantsg.dir, and possibly even keeping it open persistently + if (g_sci->getGameId() == GID_KQ5 && g_sci->getPlatform() == Common::kPlatformFMTowns) { + // KQ5 FM-Towns uses a custom save/restore UI in script 764. + // It directly tests for save files using a hard-coded path. + int saveNo = 0; + sscanf(name.c_str(), "a:\\KQ5sg.%d", &saveNo); + if (1 <= saveNo && saveNo <= 10) { // UI has ten buttons + Common::Array saves; + listSavegames(saves); + return (findSavegame(saves, saveNo) != -1) ? TRUE_REG : NULL_REG; + } + } // Check for regular file - exists = Common::File::exists(Common::Path(name)); + bool exists = Common::File::exists(Common::Path(name)); // Check for a savegame with the name Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); @@ -1122,6 +1143,15 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { // Jones has one save slot only savegameId = 0; break; + case GID_KQ5: + if (g_sci->getPlatform() == Common::kPlatformFMTowns) { + // KQ5 FM-Towns uses custom save/restore code. + // Use the provided id. + savegameId = virtualId; + // Use a default description, game passes path since it wasn't displayed. + game_description = Common::String::format("Save %d", savegameId); + } + break; case GID_QFG3: { // Auto-save system used by QFG3 reg_t autoSaveNameId; @@ -1227,6 +1257,9 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) { if (g_sci->getGameId() == GID_JONES) { // Jones has one save slot only savegameId = 0; + } else if (g_sci->getGameId() == GID_KQ5 && g_sci->getPlatform() == Common::kPlatformFMTowns) { + // KQ5 FM-Towns uses custom save/restore code. + // Use the provided id. } else { // Real call from script, we need to adjust ID if ((savegameId < SAVEGAMEID_OFFICIALRANGE_START) || (savegameId > SAVEGAMEID_OFFICIALRANGE_END)) { @@ -1286,6 +1319,10 @@ reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) { uint savegameId = 0; if (g_sci->getGameId() == GID_JONES) { // Jones has one save slot only + } else if (g_sci->getGameId() == GID_KQ5 && g_sci->getPlatform() == Common::kPlatformFMTowns) { + // KQ5 FM-Towns uses custom save/restore code. + // Use the provided id. + savegameId = virtualId; } else { // Find saved game if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END)) diff --git a/engines/sci/engine/workarounds.cpp b/engines/sci/engine/workarounds.cpp index e1e44dd00d0..f95e658045a 100644 --- a/engines/sci/engine/workarounds.cpp +++ b/engines/sci/engine/workarounds.cpp @@ -359,6 +359,7 @@ const SciWorkaroundEntry uninitializedReadWorkarounds[] = { { GID_KQ5, 25, 25, 0, "rm025", "doit", nullptr, 0, 0, { WORKAROUND_FAKE, 0 } }, // inside witch forest, when going to the room where the walking rock is { GID_KQ5, 55, 55, 0, "helpScript", "doit", nullptr, 0, 0, { WORKAROUND_FAKE, 0 } }, // when giving the tambourine to the monster in the labyrinth (only happens at one of the locations) - bug #5198 { GID_KQ5, -1, 755, 0, "gcWin", "open", nullptr, -1, -1, { WORKAROUND_FAKE, 0 } }, // when entering control menu in the FM-Towns version + { GID_KQ5, -1, 764, 0, "trash", "select", nullptr, -1, -1, { WORKAROUND_FAKE, 0 } }, // when clicking delete button on save/restore dialog in the FM-Towns version { GID_KQ6, -1, 30, 0, "rats", "changeState", nullptr, 0, 5, { WORKAROUND_FAKE, 0 } }, // rats in the catacombs (temps 0-5, all temps!) - bugs #4958, #4998, #5017 { GID_KQ6, 210, 210, 0, "rm210", "scriptCheck", nullptr, 0, 0, { WORKAROUND_FAKE, 1 } }, // using inventory in that room - bug #4953 { GID_KQ6, 500, 500, 0, "rm500", "init", nullptr, 0, 0, { WORKAROUND_FAKE, 0 } }, // going to island of the beast @@ -705,6 +706,12 @@ const SciWorkaroundEntry kGetCWD_workarounds[] = { SCI_WORKAROUNDENTRY_TERMINATOR }; +// gameID, room,script,lvl, object-name, method-name, local-call-signature, index-range, workaround +const SciWorkaroundEntry kGetSaveFiles_workarounds[] = { + { GID_KQ5, -1, 764, 0, "trash", "select", nullptr, 0, 0, { WORKAROUND_FAKE, 0 } }, // FM-Towns version when clicking delete save button, save-catalog code passes buffers by value instead of address + SCI_WORKAROUNDENTRY_TERMINATOR +}; + // gameID, room,script,lvl, object-name, method-name, local-call-signature, index-range, workaround const SciWorkaroundEntry kFileIOOpen_workarounds[] = { { GID_HOYLE5, -1, 64990, 0, "Restore", "doit", nullptr, 0, 0, { WORKAROUND_STILLCALL, 0 } }, // Missing second argument when checking for bridgesg.cat or poker.cat when showing restore dialog diff --git a/engines/sci/engine/workarounds.h b/engines/sci/engine/workarounds.h index 13772593b76..842be448854 100644 --- a/engines/sci/engine/workarounds.h +++ b/engines/sci/engine/workarounds.h @@ -81,6 +81,7 @@ extern const SciWorkaroundEntry kFrameOut_workarounds[]; extern const SciWorkaroundEntry kDeleteKey_workarounds[]; extern const SciWorkaroundEntry kGetAngle_workarounds[]; extern const SciWorkaroundEntry kGetCWD_workarounds[]; +extern const SciWorkaroundEntry kGetSaveFiles_workarounds[]; extern const SciWorkaroundEntry kGraphDrawLine_workarounds[]; extern const SciWorkaroundEntry kGraphSaveBox_workarounds[]; extern const SciWorkaroundEntry kGraphRestoreBox_workarounds[];