diff options
author | Colin Snover | 2017-09-23 01:08:09 -0500 |
---|---|---|
committer | Colin Snover | 2017-09-23 20:56:48 -0500 |
commit | b038432952d84e605b121bf72ebde246a639b244 (patch) | |
tree | ca45dc87f4020734c569e82202681207b3e8fdce /engines/sci/engine | |
parent | 9298f6a81e6218e7745a0ab6e530ecb5fabc460b (diff) | |
download | scummvm-rg350-b038432952d84e605b121bf72ebde246a639b244.tar.gz scummvm-rg350-b038432952d84e605b121bf72ebde246a639b244.tar.bz2 scummvm-rg350-b038432952d84e605b121bf72ebde246a639b244.zip |
SCI32: Support RAMA's custom save games through kFileIO
Diffstat (limited to 'engines/sci/engine')
-rw-r--r-- | engines/sci/engine/file.cpp | 68 | ||||
-rw-r--r-- | engines/sci/engine/file.h | 8 | ||||
-rw-r--r-- | engines/sci/engine/kernel_tables.h | 4 | ||||
-rw-r--r-- | engines/sci/engine/kfile.cpp | 187 | ||||
-rw-r--r-- | engines/sci/engine/script_patches.cpp | 38 | ||||
-rw-r--r-- | engines/sci/engine/selector.cpp | 2 | ||||
-rw-r--r-- | engines/sci/engine/selector.h | 2 | ||||
-rw-r--r-- | engines/sci/engine/vm.h | 1 |
8 files changed, 263 insertions, 47 deletions
diff --git a/engines/sci/engine/file.cpp b/engines/sci/engine/file.cpp index d1015cc4be..7f737036d2 100644 --- a/engines/sci/engine/file.cpp +++ b/engines/sci/engine/file.cpp @@ -374,6 +374,74 @@ int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId) { return -1; } +#ifdef ENABLE_SCI32 +Common::MemoryReadStream *makeCatalogue(const uint maxNumSaves, const uint gameNameSize, const Common::String &fileNamePattern, const bool ramaFormat) { + enum { + kGameIdSize = sizeof(int16), + kNumSavesSize = sizeof(int16), + kFreeSlotSize = sizeof(int16), + kTerminatorSize = kGameIdSize, + kTerminator = 0xFFFF + }; + + Common::Array<SavegameDesc> games; + listSavegames(games); + + const uint numSaves = MIN(games.size(), maxNumSaves); + const uint fileNameSize = fileNamePattern.empty() ? 0 : 12; + const uint entrySize = kGameIdSize + fileNameSize + gameNameSize; + uint dataSize = numSaves * entrySize + kTerminatorSize; + if (ramaFormat) { + dataSize += kNumSavesSize + kFreeSlotSize * maxNumSaves; + } + + byte *out = (byte *)malloc(dataSize); + const byte *const data = out; + + Common::Array<bool> usedSlots; + if (ramaFormat) { + WRITE_LE_UINT16(out, numSaves); + out += kNumSavesSize; + usedSlots.resize(maxNumSaves); + } + + for (uint i = 0; i < numSaves; ++i) { + const SavegameDesc &save = games[i]; + const uint16 id = save.id - kSaveIdShift; + if (!ramaFormat) { + WRITE_LE_UINT16(out, id); + out += kGameIdSize; + } + if (fileNameSize) { + const Common::String fileName = Common::String::format(fileNamePattern.c_str(), id); + strncpy(reinterpret_cast<char *>(out), fileName.c_str(), fileNameSize); + out += fileNameSize; + } + // Game names can be up to exactly gameNameSize + strncpy(reinterpret_cast<char *>(out), save.name, gameNameSize); + out += gameNameSize; + if (ramaFormat) { + WRITE_LE_UINT16(out, id); + out += kGameIdSize; + + assert(id >= 0 && id < maxNumSaves); + usedSlots[id] = true; + } + } + + if (ramaFormat) { + // A table indicating which save game slots are occupied + for (uint i = 0; i < usedSlots.size(); ++i) { + WRITE_LE_UINT16(out, !usedSlots[i]); + out += kFreeSlotSize; + } + } + + WRITE_LE_UINT16(out, kTerminator); + + return new Common::MemoryReadStream(data, dataSize, DisposeAfterUse::YES); +} +#endif FileHandle::FileHandle() : _in(0), _out(0) { } diff --git a/engines/sci/engine/file.h b/engines/sci/engine/file.h index 28a3c5801b..b94caa039f 100644 --- a/engines/sci/engine/file.h +++ b/engines/sci/engine/file.h @@ -168,6 +168,14 @@ void listSavegames(Common::Array<SavegameDesc> &saves); int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId); bool fillSavegameDesc(const Common::String &filename, SavegameDesc &desc); +#ifdef ENABLE_SCI32 +/** + * Constructs an in-memory stream from the ScummVM save game list that is + * compatible with game scripts' game catalogue readers. + */ +Common::MemoryReadStream *makeCatalogue(const uint maxNumSaves, const uint gameNameSize, const Common::String &fileNamePattern, const bool ramaFormat); +#endif + } // End of namespace Sci #endif // SCI_ENGINE_FILE_H diff --git a/engines/sci/engine/kernel_tables.h b/engines/sci/engine/kernel_tables.h index d7a83d9dbf..884a499542 100644 --- a/engines/sci/engine/kernel_tables.h +++ b/engines/sci/engine/kernel_tables.h @@ -67,6 +67,7 @@ struct SciKernelMapSubEntry { #define SIG_SINCE_SCI11 SCI_VERSION_1_1, SCI_VERSION_NONE #define SIG_SCI2 SCI_VERSION_2, SCI_VERSION_2 #define SIG_SCI21EARLY SCI_VERSION_2_1_EARLY, SCI_VERSION_2_1_EARLY +#define SIG_SCI21MID_LATE SCI_VERSION_2_1_MIDDLE, SCI_VERSION_2_1_LATE #define SIG_THRU_SCI21EARLY SCI_VERSION_2, SCI_VERSION_2_1_EARLY #define SIG_THRU_SCI21MID SCI_VERSION_2, SCI_VERSION_2_1_MIDDLE #define SIG_SINCE_SCI21 SCI_VERSION_2_1_EARLY, SCI_VERSION_3 @@ -324,7 +325,8 @@ static const SciKernelMapSubEntry kFileIO_subops[] = { { SIG_SINCE_SCI21MID, 13, MAP_CALL(FileIOReadByte), "i", NULL }, { SIG_SINCE_SCI21MID, 14, MAP_CALL(FileIOWriteByte), "ii", NULL }, { SIG_SINCE_SCI21MID, 15, MAP_CALL(FileIOReadWord), "i", NULL }, - { SIG_SINCE_SCI21MID, 16, MAP_CALL(FileIOWriteWord), "ii", NULL }, + { SIG_SCI21MID_LATE, 16, MAP_CALL(FileIOWriteWord), "ii", NULL }, + { SIG_SCI3, 16, MAP_CALL(FileIOWriteWord), "i.", NULL }, { SIG_SINCE_SCI21MID, 17, "FileIOCheckFreeSpace", kCheckFreeSpace, "i(r)", NULL }, { SIG_SINCE_SCI21MID, 18, MAP_CALL(FileIOGetCWD), "r", NULL }, { SIG_SINCE_SCI21MID, 19, MAP_CALL(FileIOIsValidDirectory), "[ro]", NULL }, diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index b5aac43d10..d8ef919324 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -41,6 +41,7 @@ #include "sci/sound/audio.h" #include "sci/console.h" #ifdef ENABLE_SCI32 +#include "graphics/thumbnail.h" #include "sci/engine/guest_additions.h" #include "sci/engine/message.h" #include "sci/resource.h" @@ -262,6 +263,34 @@ static bool saveCatalogueExists(const Common::String &name) { return exists; } + +static Common::String getRamaSaveName(EngineState *s, const uint saveNo) { + const reg_t catalogId = s->variables[VAR_GLOBAL][kGlobalVarRamaCatalogFile]; + if (catalogId.isNull()) { + error("Could not find CatalogFile object to retrieve save game name"); + } + + const List *list = s->_segMan->lookupList(readSelector(s->_segMan, catalogId, SELECTOR(elements))); + if (!list) { + error("Could not read CatalogFile object list"); + } + + Node *node = s->_segMan->lookupNode(list->first); + while (node) { + const reg_t entryId = node->value; + if (readSelectorValue(s->_segMan, entryId, SELECTOR(fileNumber)) == saveNo) { + reg_t description = readSelector(s->_segMan, entryId, SELECTOR(description)); + if (s->_segMan->isObject(description)) { + description = readSelector(s->_segMan, description, SELECTOR(data)); + } + return s->_segMan->getString(description); + } + + node = s->_segMan->lookupNode(node->succ); + } + + error("Could not find a save name for save %u", saveNo); +} #endif reg_t kFileIO(EngineState *s, int argc, reg_t *argv) { @@ -403,39 +432,6 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { if (name == "temp.tmp") { return make_reg(0, kVirtualFileHandleSci32Save); } - - // KQ7 tries to read out game information from catalogues directly - // instead of using the standard kSaveGetFiles function - if (name == "kq7cdsg.cat") { - if (mode == kFileOpenModeOpenOrCreate || mode == kFileOpenModeCreate) { - // Suppress creation of the catalogue file, since it is not necessary - debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str()); - return SIGNAL_REG; - } else if (mode == kFileOpenModeOpenOrFail) { - Common::Array<SavegameDesc> saves; - listSavegames(saves); - - const uint recordSize = sizeof(int16) + kMaxSaveNameLength; - const uint numSaves = MIN<uint>(saves.size(), 10); - const uint size = numSaves * recordSize + /* terminator */ 2; - byte *const buffer = (byte *)malloc(size); - - byte *out = buffer; - for (uint i = 0; i < numSaves; ++i) { - WRITE_UINT16(out, saves[i].id - kSaveIdShift); - strncpy((char *)out + sizeof(int16), saves[i].name, kMaxSaveNameLength); - out += recordSize; - } - WRITE_UINT16(out, 0xFFFF); - - const uint handle = findFreeFileHandle(s); - s->_fileHandles[handle]._in = new Common::MemoryReadStream(buffer, size, DisposeAfterUse::YES); - s->_fileHandles[handle]._out = nullptr; - s->_fileHandles[handle]._name = ""; - - return make_reg(0, handle); - } - } } else if (g_sci->getGameId() == GID_PQSWAT) { // PQSWAT tries to create subdirectories for each game profile for (Common::String::iterator it = name.begin(); it != name.end(); ++it) { @@ -446,6 +442,52 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { } else if (g_sci->getGameId() == GID_PHANTASMAGORIA2 && name == "RESDUK.PAT") { // Ignore the censorship password file in lieu of our game option return SIGNAL_REG; + } else if (g_sci->getGameId() == GID_RAMA) { + int saveNo = -1; + if (name == "autorama.sg") { + saveNo = kAutoSaveId; + } else if (sscanf(name.c_str(), "ramasg.%i", &saveNo) == 1) { + saveNo += kSaveIdShift; + } + + if (saveNo != -1) { + Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + const Common::String fileName = g_sci->getSavegameName(saveNo); + Common::SeekableReadStream *in = nullptr; + Common::OutSaveFile *out = nullptr; + bool valid = false; + + if (mode == kFileOpenModeOpenOrFail) { + in = saveFileMan->openForLoading(fileName); + if (in) { + SavegameMetadata meta; + if (get_savegame_metadata(in, meta)) { + Graphics::skipThumbnail(*in); + valid = true; + } + } + } else { + out = saveFileMan->openForSaving(fileName); + if (out) { + Common::String saveName; + if (saveNo != kAutoSaveId) { + saveName = getRamaSaveName(s, saveNo - kSaveIdShift); + } + Common::ScopedPtr<Common::SeekableReadStream> versionFile(SearchMan.createReadStreamForMember("VERSION")); + const Common::String gameVersion = versionFile->readLine(); + set_savegame_metadata(out, saveName, gameVersion); + valid = true; + } + } + + if (valid) { + uint handle = findFreeFileHandle(s); + s->_fileHandles[handle]._in = in; + s->_fileHandles[handle]._out = out; + s->_fileHandles[handle]._name = "-scummvm-save-"; + return make_reg(0, handle); + } + } } // See kMakeSaveCatName @@ -456,6 +498,27 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) { if (isSaveCatalogue(name)) { const bool exists = saveCatalogueExists(name); if (exists) { + // KQ7 & RAMA read out game information from catalogues directly + // instead of using the standard kSaveGetFiles function + if (name == "kq7cdsg.cat" || name == "ramasg.cat") { + if (mode == kFileOpenModeOpenOrCreate || mode == kFileOpenModeCreate) { + // Suppress creation of the catalogue file, since it is not necessary + debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str()); + return SIGNAL_REG; + } else if (mode == kFileOpenModeOpenOrFail) { + const uint handle = findFreeFileHandle(s); + + if (name == "kq7cdsg.cat") { + s->_fileHandles[handle]._in = makeCatalogue(10, kMaxSaveNameLength, "", false); + } else { + s->_fileHandles[handle]._in = makeCatalogue(100, 20, "ramasg.%d", true); + } + s->_fileHandles[handle]._out = nullptr; + s->_fileHandles[handle]._name = ""; + return make_reg(0, handle); + } + } + // Dummy handle is used to represent the catalogue and ignore any // direct game script writes return make_reg(0, kVirtualFileHandleSci32Save); @@ -753,10 +816,24 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) { return saveCatalogueExists(name) ? TRUE_REG : NULL_REG; } - // LSL7 checks to see if the autosave save exists when deciding whether to - // go to the main menu or not on startup + int findSaveNo = -1; + if (g_sci->getGameId() == GID_LSL7 && name == "autosvsg.000") { - return g_sci->getSaveFileManager()->listSavefiles(g_sci->getSavegameName(0)).empty() ? NULL_REG : TRUE_REG; + // 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.%i", &findSaveNo) == 1) { + findSaveNo += kSaveIdShift; + } + } + + if (findSaveNo != -1) { + return g_sci->getSaveFileManager()->listSavefiles(g_sci->getSavegameName(findSaveNo)).empty() ? NULL_REG : TRUE_REG; } #endif @@ -859,24 +936,48 @@ reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv) { } reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) { - FileHandle *f = getFileFromHandle(s, argv[0].toUint16()); + const uint16 handle = argv[0].toUint16(); + FileHandle *f = getFileFromHandle(s, handle); if (!f) - return NULL_REG; - return make_reg(0, f->_in->readUint16LE()); + return s->r_acc; + + reg_t value; + if (s->_fileHandles[handle]._name == "-scummvm-save-") { + value._segment = f->_in->readUint16LE(); + value._offset = f->_in->readUint16LE(); + } else { + value = make_reg(0, f->_in->readUint16LE()); + } + + if (f->_in->err()) { + return s->r_acc; + } + + return value; } reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv) { - uint16 handle = argv[0].toUint16(); + const uint16 handle = argv[0].toUint16(); -#ifdef ENABLE_SCI32 if (handle == kVirtualFileHandleSci32Save) { return s->r_acc; } -#endif FileHandle *f = getFileFromHandle(s, handle); - if (f) + if (!f) { + return s->r_acc; + } + + if (s->_fileHandles[handle]._name == "-scummvm-save-") { + f->_out->writeUint16LE(argv[1]._segment); + f->_out->writeUint16LE(argv[1]._offset); + } else { + if (argv[1].isPointer()) { + error("Attempt to write non-number %04x:%04x", PRINT_REG(argv[1])); + } f->_out->writeUint16LE(argv[1].toUint16()); + } + return s->r_acc; } diff --git a/engines/sci/engine/script_patches.cpp b/engines/sci/engine/script_patches.cpp index a5d2a67b94..b12b63bdee 100644 --- a/engines/sci/engine/script_patches.cpp +++ b/engines/sci/engine/script_patches.cpp @@ -138,6 +138,8 @@ static const char *const selectorNameTable[] = { "readWord", // LSL7, Phant1, Torin "flag", // PQ4 "select", // PQ4 + "handle", // RAMA + "saveFilePtr", // RAMA #endif NULL }; @@ -199,7 +201,9 @@ enum ScriptPatcherSelectors { SELECTOR_setScaler, SELECTOR_readWord, SELECTOR_flag, - SELECTOR_select + SELECTOR_select, + SELECTOR_handle, + SELECTOR_saveFilePtr #endif }; @@ -6218,9 +6222,37 @@ static const uint16 ramaBenchmarkPatch[] = { PATCH_END }; -// script, description, signature patch +// RAMA uses a custom save game format that game scripts read and write +// manually. The save game format serialises object references, which in the +// original engine could be done just by writing int16s (since object references +// were just 16-bit indexes), but in ScummVM we have to write the full 32-bit +// reg_t. We hijack kFileIOReadWord/kFileIOWriteWord to do this for us, but we +// need the game to agree to use those kFileIO calls instead of doing raw reads +// and creating its own numbers, as it tries to do here in +// `SaveManager::readWord`. +static const uint16 ramaSerializeRegTSignature1[] = { + SIG_MAGICDWORD, + 0x38, SIG_SELECTOR16(newWith), // pushi $10b (newWith) + 0x7a, // push2 + 0x7a, // push2 + 0x72, SIG_UINT16(0x00), // lofsa "" + 0x36, // push + 0x51, 0x0f, // class Str + SIG_END +}; + +static const uint16 ramaSerializeRegTPatch1[] = { + 0x38, PATCH_SELECTOR16(readWord), // pushi readWord + 0x76, // push0 + 0x62, PATCH_SELECTOR16(saveFilePtr), // pToa saveFilePtr + 0x4a, PATCH_UINT16(0x04), // send 4 + 0x48, // ret + PATCH_END +}; + static const SciScriptPatcherEntry ramaSignatures[] = { - { true, 64908, "disable video benchmarking", 1, ramaBenchmarkSignature, ramaBenchmarkPatch }, + { true, 85, "fix SaveManager to use normal readWord calls", 1, ramaSerializeRegTSignature1, ramaSerializeRegTPatch1 }, + { true, 64908, "disable video benchmarking", 1, ramaBenchmarkSignature, ramaBenchmarkPatch }, SCI_SIGNATUREENTRY_TERMINATOR }; diff --git a/engines/sci/engine/selector.cpp b/engines/sci/engine/selector.cpp index d310b0fafd..d4d170fdf0 100644 --- a/engines/sci/engine/selector.cpp +++ b/engines/sci/engine/selector.cpp @@ -227,6 +227,8 @@ void Kernel::mapSelectors() { FIND_SELECTOR(num); FIND_SELECTOR(reallyRestore); FIND_SELECTOR(bookMark); + FIND_SELECTOR(fileNumber); + FIND_SELECTOR(description); #endif } diff --git a/engines/sci/engine/selector.h b/engines/sci/engine/selector.h index 977411429a..98fa06c81c 100644 --- a/engines/sci/engine/selector.h +++ b/engines/sci/engine/selector.h @@ -184,6 +184,8 @@ struct SelectorCache { Selector num; // for Phant2 restore from launcher Selector reallyRestore; // for Phant2 restore from launcher Selector bookMark; // for Phant2 auto-save + Selector fileNumber; // for RAMA save/load + Selector description; // for RAMA save/load #endif }; diff --git a/engines/sci/engine/vm.h b/engines/sci/engine/vm.h index 792287ba53..83550f8aa6 100644 --- a/engines/sci/engine/vm.h +++ b/engines/sci/engine/vm.h @@ -155,6 +155,7 @@ enum GlobalVar { kGlobalVarTextSpeed = 94, // SCI32; 0 is fastest, 8 is slowest kGlobalVarGK1Music1 = 102, // 0 to 127 kGlobalVarGK1Music2 = 103, // 0 to 127 + kGlobalVarRamaCatalogFile = 130, kGlobalVarLSL6HiresGameFlags = 137, kGlobalVarGK1NarratorMode = 166, // 0 for text, 1 for speech kGlobalVarPhant1MusicVolume = 187, // 0 to 15 |