diff options
| -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  | 
