From 64dc37cfe22b5cea0cfa4457dd149bf21787b5f9 Mon Sep 17 00:00:00 2001 From: Colin Snover Date: Fri, 9 Sep 2016 14:21:33 -0500 Subject: SCI32: Start implementing kSave for SCI32 --- engines/sci/detection.cpp | 6 +- engines/sci/engine/file.cpp | 11 ++- engines/sci/engine/file.h | 12 ++- engines/sci/engine/kernel.h | 8 +- engines/sci/engine/kernel_tables.h | 38 +++++--- engines/sci/engine/kfile.cpp | 179 +++++++++++++++++++++++++++++++------ engines/sci/engine/savegame.cpp | 9 +- engines/sci/engine/state.h | 4 - engines/sci/sci.cpp | 1 + 9 files changed, 212 insertions(+), 56 deletions(-) diff --git a/engines/sci/detection.cpp b/engines/sci/detection.cpp index ad2b0f31a5..256f312b4d 100644 --- a/engines/sci/detection.cpp +++ b/engines/sci/detection.cpp @@ -772,8 +772,7 @@ SaveStateList SciMetaEngine::listSaves(const char *target) const { SaveStateDescriptor descriptor(slotNr, meta.name); if (slotNr == 0) { - // ScummVM auto-save slot, not used by SCI - // SCI does not support auto-saving, but slot 0 is reserved for auto-saving in ScummVM. + // ScummVM auto-save slot descriptor.setWriteProtectedFlag(true); } else { descriptor.setWriteProtectedFlag(false); @@ -795,9 +794,8 @@ SaveStateDescriptor SciMetaEngine::querySaveMetaInfos(const char *target, int sl Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(fileName); SaveStateDescriptor descriptor(slotNr, ""); - // Do not allow save slot 0 (used for auto-saving) to be deleted or - // overwritten. SCI does not support auto-saving, but slot 0 is reserved for auto-saving in ScummVM. if (slotNr == 0) { + // ScummVM auto-save slot descriptor.setWriteProtectedFlag(true); descriptor.setDeletableFlag(false); } else { diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp index 8cecd8c82c..b0b753be1e 100644 --- a/engines/sci/engine/file.cpp +++ b/engines/sci/engine/file.cpp @@ -330,14 +330,23 @@ void listSavegames(Common::Array &saves) { } delete in; + const int id = strtol(filename.end() - 3, NULL, 10); + +#ifdef ENABLE_SCI32 + if (id == kNewGameId) { + continue; + } +#endif + SavegameDesc desc; - desc.id = strtol(filename.end() - 3, NULL, 10); + desc.id = id; desc.date = meta.saveDate; // We need to fix date in here, because we save DDMMYYYY instead of // YYYYMMDD, so sorting wouldn't work desc.date = ((desc.date & 0xFFFF) << 16) | ((desc.date & 0xFF0000) >> 8) | ((desc.date & 0xFF000000) >> 24); desc.time = meta.saveTime; desc.version = meta.version; + desc.gameVersion = meta.gameVersion; if (meta.name.lastChar() == '\n') meta.name.deleteLastChar(); diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h index 982d7b7823..7102b293aa 100644 --- a/engines/sci/engine/file.h +++ b/engines/sci/engine/file.h @@ -34,12 +34,17 @@ enum { _K_FILE_MODE_CREATE = 2 }; -/* Maximum length of a savegame name (including terminator character). */ -#define SCI_MAX_SAVENAME_LENGTH 0x24 +enum { + SCI_MAX_SAVENAME_LENGTH = 36, ///< Maximum length of a savegame name (including terminator character). + MAX_SAVEGAME_NR = 20 ///< Maximum number of savegames +}; +#ifdef ENABLE_SCI32 enum { - MAX_SAVEGAME_NR = 20 /**< Maximum number of savegames */ + kAutoSaveId = 0, + kNewGameId = 100 }; +#endif #define VIRTUALFILE_HANDLE_START 32000 #define VIRTUALFILE_HANDLE_SCI32SAVE 32100 @@ -53,6 +58,7 @@ struct SavegameDesc { int time; int version; char name[SCI_MAX_SAVENAME_LENGTH]; + Common::String gameVersion; }; class FileHandle { diff --git a/engines/sci/engine/kernel.h b/engines/sci/engine/kernel.h index c1f33f57cc..f37d878d57 100644 --- a/engines/sci/engine/kernel.h +++ b/engines/sci/engine/kernel.h @@ -483,6 +483,12 @@ reg_t kShowMovieWinGetDuration(EngineState *s, int argc, reg_t *argv); reg_t kShowMovieWinPlayUntilEvent(EngineState *s, int argc, reg_t *argv); reg_t kShowMovieWinInitDouble(EngineState *s, int argc, reg_t *argv); +reg_t kSave(EngineState *s, int argc, reg_t *argv); +reg_t kSaveSave32(EngineState *s, int argc, reg_t *argv); +reg_t kSaveRestore32(EngineState *s, int argc, reg_t *argv); +reg_t kSaveList32(EngineState *s, int argc, reg_t *argv); +reg_t kSaveCheck32(EngineState *s, int argc, reg_t *argv); + reg_t kSetHotRectangles(EngineState *s, int argc, reg_t *argv); reg_t kIsHiRes(EngineState *s, int argc, reg_t *argv); reg_t kListAt(EngineState *s, int argc, reg_t *argv); @@ -615,8 +621,6 @@ reg_t kMorphOn(EngineState *s, int argc, reg_t *argv); reg_t kText(EngineState *s, int argc, reg_t *argv); reg_t kTextSize32(EngineState *s, int argc, reg_t *argv); reg_t kTextWidth(EngineState *s, int argc, reg_t *argv); -reg_t kSave(EngineState *s, int argc, reg_t *argv); -reg_t kAutoSave(EngineState *s, int argc, reg_t *argv); reg_t kList(EngineState *s, int argc, reg_t *argv); reg_t kCD(EngineState *s, int argc, reg_t *argv); reg_t kCheckCD(EngineState *s, int argc, reg_t *argv); diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index 3275ca0dcf..d234edb67b 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -351,15 +351,19 @@ static const SciKernelMapSubEntry kPalCycle_subops[] = { // version, subId, function-mapping, signature, workarounds static const SciKernelMapSubEntry kSave_subops[] = { - { SIG_SCI32, 0, MAP_CALL(SaveGame), "[r0]i[r0](r0)", NULL }, - { SIG_SCI32, 1, MAP_CALL(RestoreGame), "[r0]i[r0]", NULL }, - { SIG_SCI32, 2, MAP_CALL(GetSaveDir), "(r*)", NULL }, - { SIG_SCI32, 3, MAP_CALL(CheckSaveGame), ".*", NULL }, + { SIG_SCI32, 0, MAP_CALL(SaveSave32), "rir[r0]", NULL }, + { SIG_SCI32, 1, MAP_CALL(SaveRestore32), "ri[r0]", NULL }, + // System script 64994 in several SCI2.1mid games (KQ7 2.00b, Phant1, + // PQ:SWAT, SQ6, Torin) calls GetSaveDir with an extra unused argument, and + // it is easier to just handle it here than to bother with creating + // workarounds + { SIG_SCI32, 2, MAP_CALL(GetSaveDir), "(r)", NULL }, + { SIG_SCI32, 3, MAP_CALL(SaveCheck32), "ri[r0]", NULL }, // Subop 4 hasn't been encountered yet - { SIG_SCI32, 5, MAP_CALL(GetSaveFiles), "rrr", NULL }, + { SIG_SCI32, 5, MAP_CALL(SaveList32), "rrr", NULL }, { SIG_SCI32, 6, MAP_CALL(MakeSaveCatName), "rr", NULL }, { SIG_SCI32, 7, MAP_CALL(MakeSaveFileName), "rri", NULL }, - { SIG_SCI32, 8, MAP_CALL(AutoSave), "[o0]", NULL }, + { SIG_SCI32, 8, MAP_EMPTY(GameIsRestarting), ".*", NULL }, SCI_SUBOPENTRY_TERMINATOR }; @@ -628,7 +632,10 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(CheckFreeSpace), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "r(i)", NULL, NULL }, { MAP_CALL(CheckFreeSpace), SIG_SCI11, SIGFOR_ALL, "r(i)", NULL, NULL }, { MAP_CALL(CheckFreeSpace), SIG_SCI16, SIGFOR_ALL, "r", NULL, NULL }, - { MAP_CALL(CheckSaveGame), SIG_EVERYWHERE, ".*", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "CheckSaveGame", kSaveCheck32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]", NULL, NULL }, +#endif + { MAP_CALL(CheckSaveGame), SIG_SCI16, SIGFOR_ALL, ".*", NULL, NULL }, { MAP_CALL(Clone), SIG_EVERYWHERE, "o", NULL, NULL }, { MAP_CALL(CoordPri), SIG_EVERYWHERE, "i(i)", NULL, NULL }, { MAP_CALL(CosDiv), SIG_EVERYWHERE, "ii", NULL, NULL }, @@ -678,8 +685,13 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(GetMenu), SIG_EVERYWHERE, "i.", NULL, NULL }, { MAP_CALL(GetMessage), SIG_EVERYWHERE, "iiir", NULL, NULL }, { MAP_CALL(GetPort), SIG_EVERYWHERE, "", NULL, NULL }, - { MAP_CALL(GetSaveDir), SIG_SCI32, SIGFOR_ALL, "(r*)", NULL, NULL }, - { MAP_CALL(GetSaveDir), SIG_EVERYWHERE, "", NULL, NULL }, +#ifdef ENABLE_SCI32 + { MAP_CALL(GetSaveDir), SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "", NULL, NULL }, +#endif + { MAP_CALL(GetSaveDir), SIG_SCI16, SIGFOR_ALL, "", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "GetSaveFiles", kSaveList32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rrr", NULL, NULL }, +#endif { MAP_CALL(GetSaveFiles), SIG_EVERYWHERE, "rrr", NULL, NULL }, { MAP_CALL(GetTime), SIG_EVERYWHERE, "(i)", NULL, NULL }, { MAP_CALL(GlobalToLocal), SIG_SCI16, SIGFOR_ALL, "o", NULL, NULL }, @@ -741,9 +753,15 @@ static SciKernelMapEntry s_kernelMap[] = { { MAP_CALL(ResCheck), SIG_EVERYWHERE, "ii(iiii)", NULL, NULL }, { MAP_CALL(RespondsTo), SIG_EVERYWHERE, ".i", NULL, NULL }, { MAP_CALL(RestartGame), SIG_EVERYWHERE, "", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "RestoreGame", kSaveRestore32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "ri[r0]", NULL, NULL }, +#endif { MAP_CALL(RestoreGame), SIG_EVERYWHERE, "[r0]i[r0]", NULL, NULL }, { MAP_CALL(Said), SIG_EVERYWHERE, "[r0]", NULL, NULL }, - { MAP_CALL(SaveGame), SIG_EVERYWHERE, "[r0]i[r0](r0)", NULL, NULL }, +#ifdef ENABLE_SCI32 + { "SaveGame", kSaveSave32, SIG_UNTIL_SCI21EARLY, SIGFOR_ALL, "rir[r0]", NULL, NULL }, +#endif + { MAP_CALL(SaveGame), SIG_SCI16, SIGFOR_ALL, "[r0]i[r0](r0)", NULL, NULL }, { MAP_CALL(ScriptID), SIG_EVERYWHERE, "[io](i)", NULL, NULL }, { MAP_CALL(SetCursor), SIG_SCI11, SIGFOR_ALL, "i(i)(i)(i)(iiiiii)", NULL, NULL }, { MAP_CALL(SetCursor), SIG_SCI16, SIGFOR_ALL, "i(i)(i)(i)(i)", NULL, kSetCursor_workarounds }, diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index 4d151de0e8..00138ae224 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -257,17 +257,22 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { // Torin's autosave system checks for the presence of autosave.cat // by opening it. Since we don't use .cat files, we instead check - // for autosave.000 or autosave.001. + // for the autosave game. // - // The same logic is being followed for torinsg.cat - this shows - // the "Open..." button when continuing a game. + // Similar logic is needed for torinsg.cat - this shows the "Open..." button + // when continuing a game if it exists. // - // This has the added benefit of not detecting an SSCI autosave.cat - // accompanying SSCI autosave files that we wouldn't be able to load. + // TODO: Other games with autosave built in should be included here if (g_sci->getGameId() == GID_TORIN && (name == "autosave.cat" || name == "torinsg.cat")) { Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); - const Common::String pattern = (name == "autosave.cat") ? g_sci->wrapFilename("autosave.###") : g_sci->getSavegamePattern(); - bool exists = !saveFileMan->listSavefiles(pattern).empty(); + + bool exists; + if (name == "autosave.cat") { + exists = !saveFileMan->listSavefiles(g_sci->getSavegameName(0)).empty(); + } else { + exists = !saveFileMan->listSavefiles(g_sci->getSavegamePattern()).empty(); + } + if (exists) { // Dummy handle. Torin only checks if this is SIGNAL_REG, // and calls kFileIOClose on it. @@ -297,12 +302,10 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { // because they didn't want to modify the save/load code to add the extra // information. // Each slot in the book then has two strings, the save description and a - // description of the current spot that the player is at. Currently, the - // spot strings are always empty (probably related to the unimplemented - // kString subop 14, which gets called right before this call). + // description of the current spot that the player is at. // For now, we don't allow the creation of these files, which means that - // all the spot descriptions next to each slot description will be empty - // (they are empty anyway). Until a viable solution is found to handle these + // all the spot descriptions next to each slot description will be empty. + // Until a viable solution is found to handle these // extra files and until the spot description strings are initialized // correctly, we resort to virtual files in order to make the load screen // useable. Without this code it is unusable, as the extra information is @@ -325,7 +328,8 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { Common::Array saves; listSavegames(saves); - int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START); + int savegameNr = findSavegame(saves, slotNumber); + assert(savegameNr >= 0); int size = strlen(saves[savegameNr].name) + 2; char *buf = (char *)malloc(size); @@ -709,6 +713,13 @@ reg_t kSave(EngineState *s, int argc, reg_t *argv) { #endif reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) { + // slot 0 is the ScummVM auto-save slot, which is not used by us, but is + // still reserved + enum { + SAVEGAMESLOT_FIRST = 1, + SAVEGAMESLOT_LAST = 99 + }; + Common::String game_id = !argv[0].isNull() ? s->_segMan->getString(argv[0]) : ""; int16 virtualId = argv[1].toSint16(); int16 savegameId = -1; @@ -986,10 +997,6 @@ reg_t kCheckSaveGame(EngineState *s, int argc, reg_t *argv) { } reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { - Common::String game_id = s->_segMan->getString(argv[0]); - - debug(3, "kGetSaveFiles(%s)", game_id.c_str()); - // Scripts ask for current save files, we can assume that if afterwards they ask us to create a new slot they really // mean new slot instead of overwriting the old one s->_lastSaveVirtualId = SAVEGAMEID_OFFICIALRANGE_START; @@ -998,6 +1005,10 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { listSavegames(saves); uint totalSaves = MIN(saves.size(), MAX_SAVEGAME_NR); + Common::String game_id = s->_segMan->getString(argv[0]); + + debug(3, "kGetSaveFiles(%s)", game_id.c_str()); + reg_t *slot = s->_segMan->derefRegPtr(argv[2], totalSaves); if (!slot) { @@ -1024,6 +1035,129 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) { } #ifdef ENABLE_SCI32 +reg_t kSaveSave32(EngineState *s, int argc, reg_t *argv) { + const Common::String gameName = s->_segMan->getString(argv[0]); + int16 saveNo = argv[1].toSint16(); + const Common::String saveDescription = s->_segMan->getString(argv[2]); + const Common::String gameVersion = argv[3].isNull() ? "" : s->_segMan->getString(argv[3]); + + // Auto-save system used by Torin and LSL7 + if (gameName == "Autosave" || gameName == "Autosv") { + if (saveNo == 0) { + // Autosave slot 0 is the autosave + } else { + // Autosave slot 1 is a "new game" save + saveNo = kNewGameId; + } + } + + assert(gameName == "Autosave" || gameName == "Autosv" || saveNo > 0); + + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + const Common::String filename = g_sci->getSavegameName(saveNo); + Common::OutSaveFile *saveStream = saveFileMan->openForSaving(filename); + + if (saveStream == nullptr) { + warning("Error opening savegame \"%s\" for writing", filename.c_str()); + return NULL_REG; + } + + if (!gamestate_save(s, saveStream, saveDescription, gameVersion)) { + warning("Saving the game failed"); + saveStream->finalize(); + delete saveStream; + return NULL_REG; + } + + saveStream->finalize(); + if (saveStream->err()) { + warning("Writing the savegame failed"); + delete saveStream; + return NULL_REG; + } + + delete saveStream; + return TRUE_REG; +} + +reg_t kSaveRestore32(EngineState *s, int argc, reg_t *argv) { + const Common::String gameName = s->_segMan->getString(argv[0]); + int16 saveNo = argv[1].toSint16(); + const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); + + if (gameName == "Autosave" || gameName == "Autosv") { + if (saveNo == 0) { + // Autosave slot 0 is the autosave + } else { + // Autosave slot 1 is a "new game" save + saveNo = kNewGameId; + } + } + + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + const Common::String filename = g_sci->getSavegameName(saveNo); + Common::SeekableReadStream *saveStream = saveFileMan->openForLoading(filename); + + if (saveStream == nullptr) { + warning("Savegame #%d not found", saveNo); + return NULL_REG; + } + + gamestate_restore(s, saveStream); + delete saveStream; + + gamestate_afterRestoreFixUp(s, saveNo); + return TRUE_REG; +} + +reg_t kSaveCheck32(EngineState *s, int argc, reg_t *argv) { + const Common::String gameName = s->_segMan->getString(argv[0]); + int16 saveNo = argv[1].toSint16(); + const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); + + Common::Array saves; + listSavegames(saves); + + const int16 saveIndex = findSavegame(saves, saveNo); + if (saveIndex == -1) { + return NULL_REG; + } + + const SavegameDesc &save = saves[saveIndex]; + + if (save.version < MINIMUM_SAVEGAME_VERSION || + save.version > CURRENT_SAVEGAME_VERSION || + save.gameVersion != gameVersion) { + + return NULL_REG; + } + + return TRUE_REG; +} + +reg_t kSaveList32(EngineState *s, int argc, reg_t *argv) { + // argv[0] is gameName, used in SSCI as the name of the save game catalogue + // but unused here since ScummVM does not support multiple catalogues + SciArray &descriptions = *s->_segMan->lookupArray(argv[1]); + SciArray &saveIds = *s->_segMan->lookupArray(argv[2]); + + Common::Array saves; + listSavegames(saves); + + // Normally SSCI limits to 20 games per directory, but ScummVM allows more + // than that + descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1); + saveIds.resize(saves.size()); + + for (uint i = 0; i < saves.size(); ++i) { + const SavegameDesc &save = saves[i]; + char *target = &descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * i); + Common::strlcpy(target, save.name, SCI_MAX_SAVENAME_LENGTH); + saveIds.int16At(i) = save.id; + } + + return make_reg(0, saves.size()); +} reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) { // Normally, this creates the name of the save catalogue/directory to save into. @@ -1054,17 +1188,6 @@ reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) { return argv[0]; } -reg_t kAutoSave(EngineState *s, int argc, reg_t *argv) { - // TODO - // This is a timer callback, with 1 parameter: the timer object - // (e.g. "timers"). - // It's used for auto-saving (i.e. save every X minutes, by checking - // the elapsed time from the timer object) - - // This function has to return something other than 0 to proceed - return TRUE_REG; -} - #endif } // End of namespace Sci diff --git a/engines/sci/engine/savegame.cpp b/engines/sci/engine/savegame.cpp index 9def79c918..5a73526fb7 100644 --- a/engines/sci/engine/savegame.cpp +++ b/engines/sci/engine/savegame.cpp @@ -1009,10 +1009,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); diff --git a/engines/sci/engine/state.h b/engines/sci/engine/state.h index f1ec5d3f10..36ae784260 100644 --- a/engines/sci/engine/state.h +++ b/engines/sci/engine/state.h @@ -57,10 +57,6 @@ enum AbortGameState { kAbortQuitGame = 3 }; -// slot 0 is the ScummVM auto-save slot, which is not used by us, but is still reserved -#define SAVEGAMESLOT_FIRST 1 -#define SAVEGAMESLOT_LAST 99 - // We assume that scripts give us savegameId 0->99 for creating a new save slot // and savegameId 100->199 for existing save slots. Refer to kfile.cpp enum { diff --git a/engines/sci/sci.cpp b/engines/sci/sci.cpp index 6ae858ec72..eb314a9208 100644 --- a/engines/sci/sci.cpp +++ b/engines/sci/sci.cpp @@ -569,6 +569,7 @@ void SciEngine::patchGameSaveRestore() { case GID_MOTHERGOOSE256: // mother goose saves/restores directly and has no save/restore dialogs case GID_PHANTASMAGORIA: // has custom save/load code case GID_SHIVERS: // has custom save/load code + case GID_PQSWAT: // has custom save/load code return; default: break; -- cgit v1.2.3