aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorColin Snover2017-09-23 01:08:09 -0500
committerColin Snover2017-09-23 20:56:48 -0500
commitb038432952d84e605b121bf72ebde246a639b244 (patch)
treeca45dc87f4020734c569e82202681207b3e8fdce
parent9298f6a81e6218e7745a0ab6e530ecb5fabc460b (diff)
downloadscummvm-rg350-b038432952d84e605b121bf72ebde246a639b244.tar.gz
scummvm-rg350-b038432952d84e605b121bf72ebde246a639b244.tar.bz2
scummvm-rg350-b038432952d84e605b121bf72ebde246a639b244.zip
SCI32: Support RAMA's custom save games through kFileIO
-rw-r--r--engines/sci/engine/file.cpp68
-rw-r--r--engines/sci/engine/file.h8
-rw-r--r--engines/sci/engine/kernel_tables.h4
-rw-r--r--engines/sci/engine/kfile.cpp187
-rw-r--r--engines/sci/engine/script_patches.cpp38
-rw-r--r--engines/sci/engine/selector.cpp2
-rw-r--r--engines/sci/engine/selector.h2
-rw-r--r--engines/sci/engine/vm.h1
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