diff options
Diffstat (limited to 'engines/sci/engine/kfile.cpp')
| -rw-r--r-- | engines/sci/engine/kfile.cpp | 633 | 
1 files changed, 455 insertions, 178 deletions
diff --git a/engines/sci/engine/kfile.cpp b/engines/sci/engine/kfile.cpp index e8b9d0461d..6aad256664 100644 --- a/engines/sci/engine/kfile.cpp +++ b/engines/sci/engine/kfile.cpp @@ -51,6 +51,7 @@ extern FileHandle *getFileFromHandle(EngineState *s, uint handle);  extern int fgets_wrapper(EngineState *s, char *dest, int maxsize, int handle);  extern void listSavegames(Common::Array<SavegameDesc> &saves);  extern int findSavegame(Common::Array<SavegameDesc> &saves, int16 savegameId); +extern bool fillSavegameDesc(const Common::String &filename, SavegameDesc *desc);  /**   * Writes the cwd to the supplied address and returns the address in acc. @@ -158,33 +159,37 @@ reg_t kDeviceInfo(EngineState *s, int argc, reg_t *argv) {  }  reg_t kCheckFreeSpace(EngineState *s, int argc, reg_t *argv) { -	if (argc > 1) { -		// SCI1.1/SCI32 -		// TODO: don't know if those are right for SCI32 as well -		// Please note that sierra sci supported both calls either w/ or w/o opcode in SCI1.1 -		switch (argv[1].toUint16()) { -		case 0: // return saved game size -			return make_reg(0, 0); // we return 0 - -		case 1: // return free harddisc space (shifted right somehow) -			return make_reg(0, 0x7fff); // we return maximum - -		case 2: // same as call w/o opcode -			break; -			return make_reg(0, 1); - -		default: -			error("kCheckFreeSpace: called with unknown sub-op %d", argv[1].toUint16()); -		} +	// A file path to test is also passed to this function as a separate +	// argument, but we do not actually check anything, so it is unused + +	enum { +		kSaveGameSize      = 0, +		kFreeDiskSpace     = 1, +		kEnoughSpaceToSave = 2 +	}; + +	int16 subop; +	// In SCI2.1mid, the call is moved into kFileIO and the arguments are +	// flipped +	if (getSciVersion() >= SCI_VERSION_2_1_MIDDLE) { +		subop = argc > 0 ? argv[0].toSint16() : 2; +	} else { +		subop = argc > 1 ? argv[1].toSint16() : 2;  	} -	Common::String path = s->_segMan->getString(argv[0]); +	switch (subop) { +	case kSaveGameSize: +		return make_reg(0, 0); -	debug(3, "kCheckFreeSpace(%s)", path.c_str()); -	// We simply always pretend that there is enough space. The alternative -	// would be to write a big test file, which is not nice on systems where -	// doing so is very slow. -	return make_reg(0, 1); +	case kFreeDiskSpace: // in KiB; up to 32MiB maximum +		return make_reg(0, 0x7fff); + +	case kEnoughSpaceToSave: +		return make_reg(0, 1); + +	default: +		error("kCheckFreeSpace: called with unknown sub-op %d", subop); +	}  }  reg_t kValidPath(EngineState *s, int argc, reg_t *argv) { @@ -224,6 +229,45 @@ reg_t kGetSavedCD(EngineState *s, int argc, reg_t *argv) {  // ---- FileIO operations ----------------------------------------------------- +#ifdef ENABLE_SCI32 +static bool isSaveCatalogue(const Common::String &name) { +	return name == "autosave.cat" || name.hasSuffix("sg.cat"); +} + +// SCI32 save game scripts check for, and write directly to, the save game +// catalogue. Since ScummVM does not use these catalogues, when looking for a +// catalogue, we instead check for save games within ScummVM that are logically +// equivalent to the behaviour of SSCI. +static bool saveCatalogueExists(const Common::String &name) { +	bool exists = false; +	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager(); + +	// There will always be one save game in some games, the "new game" +	// game, which should be ignored when deciding if there are any save +	// games available +	uint numPermanentSaves; +	switch (g_sci->getGameId()) { +	case GID_TORIN: +	case GID_LSL7: +	case GID_LIGHTHOUSE: +		numPermanentSaves = 1; +		break; +	default: +		numPermanentSaves = 0; +		break; +	} + +	// Torin uses autosave.cat; LSL7 uses autosvsg.cat +	if (name == "autosave.cat" || name == "autosvsg.cat") { +		exists = !saveFileMan->listSavefiles(g_sci->getSavegameName(0)).empty(); +	} else { +		exists = saveFileMan->listSavefiles(g_sci->getSavegamePattern()).size() > numPermanentSaves; +	} + +	return exists; +} +#endif +  reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {  	if (!s)  		return make_reg(0, getSciVersion()); @@ -233,9 +277,13 @@ reg_t kFileIO(EngineState *s, int argc, reg_t *argv) {  reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {  	Common::String name = s->_segMan->getString(argv[0]); -	// SCI32 can call K_FILEIO_OPEN with only one argument. It seems to -	// just be checking if it exists. -	int mode = (argc < 2) ? (int)_K_FILE_MODE_OPEN_OR_FAIL : argv[1].toUint16(); +	if (name.empty()) { +		// Happens many times during KQ1 (e.g. when typing something) +		debugC(kDebugLevelFile, "Attempted to open a file with an empty filename"); +		return SIGNAL_REG; +	} + +	int mode = argv[1].toUint16();  	bool unwrapFilename = true;  	// SQ4 floppy prepends /\ to the filenames @@ -253,94 +301,158 @@ reg_t kFileIOOpen(EngineState *s, int argc, reg_t *argv) {  		return SIGNAL_REG;  	} -	// 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. -	// -	// The same logic is being followed for torinsg.cat - this shows -	// the "Open..." button when continuing a game. -	// -	// This has the added benefit of not detecting an SSCI autosave.cat -	// accompanying SSCI autosave files that we wouldn't be able to load. -	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(); -		if (exists) { -			// Dummy handle. Torin only checks if this is SIGNAL_REG, -			// and calls kFileIOClose on it. -			return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); -		} else { -			return SIGNAL_REG; +#ifdef ENABLE_SCI32 +	// GK1, GK2, KQ7, LSL6hires, Phant1, PQ4, PQ:SWAT, and SQ6 read in +	// their game version from the VERSION file +	if (name.compareToIgnoreCase("version") == 0) { +		unwrapFilename = false; + +		// LSL6hires version is in a file with an empty extension +		if (Common::File::exists(name + ".")) { +			name += ".";  		}  	} -	if (name.empty()) { -		// Happens many times during KQ1 (e.g. when typing something) -		debugC(kDebugLevelFile, "Attempted to open a file with an empty filename"); -		return SIGNAL_REG; -	} -	debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); +	if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) { +		// Shivers stores the name and score of save games in separate %d.SG +		// files, which are used by the save/load screen +		if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) { +			// Suppress creation of the SG file, since it is not necessary +			debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str()); +			return SIGNAL_REG; +		} else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) { +			// Create a virtual file containing the save game description +			// and slot number, as the game scripts expect. +			int saveNo; +			sscanf(name.c_str(), "%d.SG", &saveNo); +			saveNo += kSaveIdShift; + +			SavegameDesc save; +			fillSavegameDesc(g_sci->getSavegameName(saveNo), &save); + +			Common::String score; +			if (!save.highScore) { +				score = Common::String::format("%u", save.lowScore); +			} else { +				score = Common::String::format("%u%03u", save.highScore, save.lowScore); +			} -	if (name.hasPrefix("sciAudio\\")) { -		// fan-made sciAudio extension, don't create those files and instead return a virtual handle -		return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO); -	} +			const uint nameLength = strlen(save.name); +			const uint size = nameLength + /* \r\n */ 2 + score.size(); +			char *buffer = (char *)malloc(size); +			memcpy(buffer, save.name, nameLength); +			buffer[nameLength] = '\r'; +			buffer[nameLength + 1] = '\n'; +			memcpy(buffer + nameLength + 2, score.c_str(), score.size()); -#ifdef ENABLE_SCI32 -	// Shivers is trying to store savegame descriptions and current spots in -	// separate .SG files, which are hardcoded in the scripts. -	// Essentially, there is a normal save file, created by the executable -	// and an extra hardcoded save file, created by the game scripts, probably -	// 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). -	// 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 -	// 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 -	// always saved to 0.SG for some reason, but on restore the correct file is -	// used. Perhaps the virtual ID is not taken into account when saving. -	// -	// Future TODO: maintain spot descriptions and show them too, ideally without -	// having to return to this logic of extra hardcoded files. -	if (g_sci->getGameId() == GID_SHIVERS && name.hasSuffix(".SG")) { +			const uint handle = findFreeFileHandle(s); + +			s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES); +			s->_fileHandles[handle]._out = nullptr; +			s->_fileHandles[handle]._name = ""; + +			return make_reg(0, handle); +		} +	} else if (g_sci->getGameId() == GID_MOTHERGOOSEHIRES && name.hasSuffix(".DTA")) { +		// MGDX stores the name and avatar ID in separate %d.DTA files, which +		// are used by the save/load screen  		if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) { -			// Game scripts are trying to create a file with the save -			// description, stop them here +			// Suppress creation of the DTA file, since it is not necessary  			debugC(kDebugLevelFile, "Not creating unused file %s", name.c_str());  			return SIGNAL_REG;  		} else if (mode == _K_FILE_MODE_OPEN_OR_FAIL) {  			// Create a virtual file containing the save game description  			// and slot number, as the game scripts expect. -			int slotNumber; -			sscanf(name.c_str(), "%d.SG", &slotNumber); +			int saveNo; +			sscanf(name.c_str(), "%d.DTA", &saveNo); +			saveNo += kSaveIdShift; -			Common::Array<SavegameDesc> saves; -			listSavegames(saves); -			int savegameNr = findSavegame(saves, slotNumber - SAVEGAMEID_OFFICIALRANGE_START); +			SavegameDesc save; +			fillSavegameDesc(g_sci->getSavegameName(saveNo), &save); -			int size = strlen(saves[savegameNr].name) + 2; -			char *buf = (char *)malloc(size); -			strcpy(buf, saves[savegameNr].name); -			buf[size - 1] = 0; // Spot description (empty) +			const Common::String avatarId = Common::String::format("%02d", save.avatarId); +			const uint nameLength = strlen(save.name); +			const uint size = nameLength + /* \r\n */ 2 + avatarId.size() + 1; +			char *buffer = (char *)malloc(size); +			memcpy(buffer, save.name, nameLength); +			buffer[nameLength] = '\r'; +			buffer[nameLength + 1] = '\n'; +			memcpy(buffer + nameLength + 2, avatarId.c_str(), avatarId.size() + 1); -			uint handle = findFreeFileHandle(s); +			const uint handle = findFreeFileHandle(s); -			s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buf, size, DisposeAfterUse::YES); +			s->_fileHandles[handle]._in = new Common::MemoryReadStream((byte *)buffer, size, DisposeAfterUse::YES);  			s->_fileHandles[handle]._out = nullptr;  			s->_fileHandles[handle]._name = "";  			return make_reg(0, handle);  		} +	} else if (g_sci->getGameId() == GID_KQ7) { +		// KQ7 creates a temp.tmp file to perform an atomic rewrite of the +		// catalogue, but since we do not create catalogues for most SCI32 +		// games, ignore the write +		if (name == "temp.tmp") { +			return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); +		} + +		// KQ7 tries to read out game information from catalogues directly +		// instead of using the standard kSaveGetFiles function +		if (name == "kq7cdsg.cat") { +			if (mode == _K_FILE_MODE_OPEN_OR_CREATE || mode == _K_FILE_MODE_CREATE) { +				// 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 == _K_FILE_MODE_OPEN_OR_FAIL) { +				Common::Array<SavegameDesc> saves; +				listSavegames(saves); + +				const uint recordSize = sizeof(int16) + SCI_MAX_SAVENAME_LENGTH; +				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); +					Common::strlcpy((char *)out + sizeof(int16), saves[i].name, SCI_MAX_SAVENAME_LENGTH); +					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); +			} +		} +	} + +	// See kMakeSaveCatName +	if (name == "fake.cat") { +		return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); +	} + +	if (isSaveCatalogue(name)) { +		const bool exists = saveCatalogueExists(name); +		if (exists) { +			// Dummy handle is used to represent the catalogue and ignore any +			// direct game script writes +			return make_reg(0, VIRTUALFILE_HANDLE_SCI32SAVE); +		} else { +			return SIGNAL_REG; +		}  	}  #endif +	debugC(kDebugLevelFile, "kFileIO(open): %s, 0x%x", name.c_str(), mode); + +	if (name.hasPrefix("sciAudio\\")) { +		// fan-made sciAudio extension, don't create those files and instead return a virtual handle +		return make_reg(0, VIRTUALFILE_HANDLE_SCIAUDIO); +	} +  	// QFG import rooms get a virtual filelisting instead of an actual one  	if (g_sci->inQfGImportRoom()) {  		// We need to find out what the user actually selected, "savedHeroes" is @@ -363,7 +475,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {  	if (handle >= VIRTUALFILE_HANDLE_START) {  		// it's a virtual handle? ignore it -		return SIGNAL_REG; +		return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG;  	}  	FileHandle *f = getFileFromHandle(s, handle); @@ -371,7 +483,7 @@ reg_t kFileIOClose(EngineState *s, int argc, reg_t *argv) {  		f->close();  		if (getSciVersion() <= SCI_VERSION_0_LATE)  			return s->r_acc;	// SCI0 semantics: no value returned -		return SIGNAL_REG; +		return getSciVersion() >= SCI_VERSION_2 ? TRUE_REG : SIGNAL_REG;  	}  	if (getSciVersion() <= SCI_VERSION_0_LATE) @@ -403,18 +515,37 @@ reg_t kFileIOReadRaw(EngineState *s, int argc, reg_t *argv) {  reg_t kFileIOWriteRaw(EngineState *s, int argc, reg_t *argv) {  	uint16 handle = argv[0].toUint16();  	uint16 size = argv[2].toUint16(); + +#ifdef ENABLE_SCI32 +	if (handle == VIRTUALFILE_HANDLE_SCI32SAVE) { +		return make_reg(0, size); +	} +#endif +  	char *buf = new char[size]; +	uint bytesWritten = 0;  	bool success = false;  	s->_segMan->memcpy((byte *)buf, argv[1], size);  	debugC(kDebugLevelFile, "kFileIO(writeRaw): %d, %d", handle, size);  	FileHandle *f = getFileFromHandle(s, handle);  	if (f) { -		f->_out->write(buf, size); -		success = true; +		bytesWritten = f->_out->write(buf, size); +		success = !f->_out->err();  	}  	delete[] buf; + +#ifdef ENABLE_SCI32 +	if (getSciVersion() >= SCI_VERSION_2) { +		if (!success) { +			return SIGNAL_REG; +		} + +		return make_reg(0, bytesWritten); +	} +#endif +  	if (success)  		return NULL_REG;  	return make_reg(0, 6); // DOS - invalid handle @@ -444,19 +575,35 @@ reg_t kFileIOUnlink(EngineState *s, int argc, reg_t *argv) {  		int savedir_nr = saves[slotNum].id;  		name = g_sci->getSavegameName(savedir_nr);  		result = saveFileMan->removeSavefile(name); +#ifdef ENABLE_SCI32  	} else if (getSciVersion() >= SCI_VERSION_2) { +		// Special case for KQ7, basically identical to the SQ4 case above, +		// where the game hardcodes its save game names +		if (name.hasPrefix("kq7cdsg.")) { +			int saveNo = atoi(name.c_str() + name.size() - 3); +			name = g_sci->getSavegameName(saveNo + kSaveIdShift); +		} +  		// The file name may be already wrapped, so check both cases  		result = saveFileMan->removeSavefile(name);  		if (!result) {  			const Common::String wrappedName = g_sci->wrapFilename(name);  			result = saveFileMan->removeSavefile(wrappedName);  		} +#endif  	} else {  		const Common::String wrappedName = g_sci->wrapFilename(name);  		result = saveFileMan->removeSavefile(wrappedName);  	}  	debugC(kDebugLevelFile, "kFileIO(unlink): %s", name.c_str()); + +#ifdef ENABLE_SCI32 +	if (getSciVersion() >= SCI_VERSION_2) { +		return make_reg(0, result); +	} +#endif +  	if (result)  		return NULL_REG;  	return make_reg(0, 2); // DOS - file not found error code @@ -567,6 +714,12 @@ reg_t kFileIOExists(EngineState *s, int argc, reg_t *argv) {  			return NULL_REG;  	} +#ifdef ENABLE_SCI32 +	if (isSaveCatalogue(name)) { +		return saveCatalogueExists(name) ? TRUE_REG : NULL_REG; +	} +#endif +  	// TODO: It may apparently be worth caching the existence of  	// phantsg.dir, and possibly even keeping it open persistently @@ -662,7 +815,7 @@ reg_t kFileIOWriteByte(EngineState *s, int argc, reg_t *argv) {  	FileHandle *f = getFileFromHandle(s, argv[0].toUint16());  	if (f)  		f->_out->writeByte(argv[1].toUint16() & 0xff); -	return s->r_acc; // FIXME: does this really not return anything? +	return s->r_acc;  }  reg_t kFileIOReadWord(EngineState *s, int argc, reg_t *argv) { @@ -676,27 +829,13 @@ reg_t kFileIOWriteWord(EngineState *s, int argc, reg_t *argv) {  	FileHandle *f = getFileFromHandle(s, argv[0].toUint16());  	if (f)  		f->_out->writeUint16LE(argv[1].toUint16()); -	return s->r_acc; // FIXME: does this really not return anything? +	return s->r_acc;  } -reg_t kFileIOCreateSaveSlot(EngineState *s, int argc, reg_t *argv) { -	// Used in Shivers when the user enters his name on the guest book -	// in the beginning to start the game. - -	// Creates a new save slot, and returns if the operation was successful - -	// Argument 0 denotes the save slot as a negative integer, 2 means "0" -	// Argument 1 is a string, with the file name, obtained from kSave(5). -	// The interpreter checks if it can be written to (by checking for free -	// disk space and write permissions) - -	// We don't really use or need any of this... - -	uint16 saveSlot = argv[0].toUint16(); -	char* fileName = s->_segMan->lookupString(argv[1])->getRawData(); -	warning("kFileIOCreateSaveSlot(%d, '%s')", saveSlot, fileName); - -	return TRUE_REG;	// slot creation was successful +reg_t kFileIOGetCWD(EngineState *s, int argc, reg_t *argv) { +	SciArray &fileName = *s->_segMan->lookupArray(argv[0]); +	fileName.fromString("C:\\SIERRA\\"); +	return argv[0];  }  reg_t kFileIOIsValidDirectory(EngineState *s, int argc, reg_t *argv) { @@ -721,6 +860,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; @@ -736,13 +882,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {  		return NULL_REG;  	} -	// Torin has two sets of saves: autosave.### and torinsg.###, both with -	// their own slots and .cat file. -	// The autosave system uses autosave.000 and autosave.001. -	// It also checks the presence of autosave.cat to determine if it should -	// show the chapter selection menu on startup. (See kFileIOOpen.) -	bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave"; -  	if (argv[0].isNull()) {  		// Direct call, from a patched Game::save  		if ((argv[1] != SIGNAL_REG) || (!argv[2].isNull())) @@ -761,14 +900,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {  		g_sci->_soundCmd->pauseAll(false); // unpause music (we can't have it paused during save)  		if (savegameId < 0)  			return NULL_REG; - -	} else if (torinAutosave) { -		if (argv[2].isNull()) -			error("kSaveGame: called with description being NULL"); -		game_description = s->_segMan->getString(argv[2]); -		savegameId = virtualId; - -		debug(3, "kSaveGame(%s,%d,%s,%s) [Torin autosave]", game_id.c_str(), virtualId, game_description.c_str(), version.c_str());  	} else {  		// Real call from script  		if (argv[2].isNull()) @@ -844,10 +975,6 @@ reg_t kSaveGame(EngineState *s, int argc, reg_t *argv) {  	Common::SaveFileManager *saveFileMan = g_sci->getSaveFileManager();  	Common::OutSaveFile *out; -	if (torinAutosave) { -		filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId)); -	} -  	out = saveFileMan->openForSaving(filename);  	if (!out) {  		warning("Error opening savegame \"%s\" for writing", filename.c_str()); @@ -876,10 +1003,6 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {  	debug(3, "kRestoreGame(%s,%d)", game_id.c_str(), savegameId); - -	// See comment in kSaveGame -	bool torinAutosave = g_sci->getGameId() == GID_TORIN && game_id == "Autosave"; -  	if (argv[0].isNull()) {  		// Direct call, either from launcher or from a patched Game::restore  		if (savegameId == -1) { @@ -895,7 +1018,7 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {  			pausedMusic = true;  		}  		// don't adjust ID of the saved game, it's already correct -	} else if (!torinAutosave) { +	} else {  		if (g_sci->getGameId() == GID_JONES) {  			// Jones has one save slot only  			savegameId = 0; @@ -912,9 +1035,8 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {  	s->r_acc = NULL_REG; // signals success  	Common::Array<SavegameDesc> saves; -	if (!torinAutosave) -		listSavegames(saves); -	if (!torinAutosave && findSavegame(saves, savegameId) == -1) { +	listSavegames(saves); +	if (findSavegame(saves, savegameId) == -1) {  		s->r_acc = TRUE_REG;  		warning("Savegame ID %d not found", savegameId);  	} else { @@ -922,10 +1044,6 @@ reg_t kRestoreGame(EngineState *s, int argc, reg_t *argv) {  		Common::String filename = g_sci->getSavegameName(savegameId);  		Common::SeekableReadStream *in; -		if (torinAutosave) { -			filename = g_sci->wrapFilename(Common::String::format("autosave.%03d", savegameId)); -		} -  		in = saveFileMan->openForLoading(filename);  		if (in) {  			// found a savegame file @@ -998,10 +1116,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; @@ -1010,6 +1124,10 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {  	listSavegames(saves);  	uint totalSaves = MIN<uint>(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) { @@ -1037,46 +1155,205 @@ reg_t kGetSaveFiles(EngineState *s, int argc, reg_t *argv) {  #ifdef ENABLE_SCI32 -reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) { -	// Normally, this creates the name of the save catalogue/directory to save into. -	// First parameter is the string to save the result into. Second is a string -	// with game parameters. We don't have a use for this at all, as we have our own -	// savegame directory management, thus we always return an empty string. -	return argv[0]; +reg_t kSaveGame32(EngineState *s, int argc, reg_t *argv) { +	const bool isScummVMSave = argv[0].isNull(); +	Common::String gameName = ""; +	int16 saveNo; +	Common::String saveDescription; +	Common::String gameVersion = (argc <= 3 || argv[3].isNull()) ? "" : s->_segMan->getString(argv[3]); + +	if (isScummVMSave) { +		// ScummVM call, from a patched Game::save +		g_sci->_soundCmd->pauseAll(true); +		GUI::SaveLoadChooser dialog(_("Save game:"), _("Save"), true); +		saveNo = dialog.runModalWithCurrentTarget(); +		g_sci->_soundCmd->pauseAll(false); + +		if (saveNo < 0) { +			// User cancelled save +			return NULL_REG; +		} + +		saveDescription = dialog.getResultString(); +		if (saveDescription.empty()) { +			saveDescription = dialog.createDefaultSaveDescription(saveNo); +		} +	} else { +		// Native script call +		gameName = s->_segMan->getString(argv[0]); +		saveNo = argv[1].toSint16(); +		saveDescription = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); +	} + +	debugC(kDebugLevelFile, "Game name %s save %d desc %s ver %s", gameName.c_str(), saveNo, saveDescription.c_str(), gameVersion.c_str()); + +	// 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; +		} +	} else if (!isScummVMSave) { +		// ScummVM save screen will give a pre-corrected save number, but native +		// save-load will not +		saveNo += kSaveIdShift; +	} + +	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 kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) { -	// Creates a savegame name from a slot number. Used when deleting saved games. -	// Param 0: the output buffer (same as in kMakeSaveCatName) -	// Param 1: a string with game parameters, ignored -	// Param 2: the selected slot +reg_t kRestoreGame32(EngineState *s, int argc, reg_t *argv) { +	const bool isScummVMRestore = argv[0].isNull(); +	Common::String gameName = ""; +	int16 saveNo = argv[1].toSint16(); +	const Common::String gameVersion = argv[2].isNull() ? "" : s->_segMan->getString(argv[2]); + +	if (isScummVMRestore && saveNo == -1) { +		// ScummVM call, either from lancher or a patched Game::restore +		g_sci->_soundCmd->pauseAll(true); +		GUI::SaveLoadChooser dialog(_("Restore game:"), _("Restore"), false); +		saveNo = dialog.runModalWithCurrentTarget(); +		g_sci->_soundCmd->pauseAll(false); + +		if (saveNo < 0) { +			// User cancelled restore +			return s->r_acc; +		} +	} else { +		gameName = s->_segMan->getString(argv[0]); +	} -	SciString *resultString = s->_segMan->lookupString(argv[0]); -	uint16 virtualId = argv[2].toUint16(); -	if ((virtualId < SAVEGAMEID_OFFICIALRANGE_START) || (virtualId > SAVEGAMEID_OFFICIALRANGE_END)) -		error("kMakeSaveFileName: invalid savegame ID specified"); -	uint saveSlot = virtualId - SAVEGAMEID_OFFICIALRANGE_START; +	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; +		} +	} else if (!isScummVMRestore) { +		// ScummVM save screen will give a pre-corrected save number, but native +		// save-load will not +		saveNo += kSaveIdShift; +	} + +	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 kCheckSaveGame32(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<SavegameDesc> saves;  	listSavegames(saves); -	Common::String filename = g_sci->getSavegameName(saveSlot); -	resultString->fromString(filename); +	if (gameName == "Autosave" || gameName == "Autosv") { +		if (saveNo == 1) { +			saveNo = kNewGameId; +		} +	} else { +		saveNo += kSaveIdShift; +	} -	return argv[0]; -} +	SavegameDesc save; +	if (!fillSavegameDesc(g_sci->getSavegameName(saveNo), &save)) { +		return NULL_REG; +	} + +	if (save.version < MINIMUM_SAVEGAME_VERSION || +		save.version > CURRENT_SAVEGAME_VERSION || +		save.gameVersion != gameVersion) { -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) +		return NULL_REG; +	} -	// This function has to return something other than 0 to proceed  	return TRUE_REG;  } +reg_t kGetSaveFiles32(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<SavegameDesc> saves; +	listSavegames(saves); + +	// Normally SSCI limits to 20 games per directory, but ScummVM allows more +	// than that with games that use the standard save-load dialogue +	descriptions.resize(SCI_MAX_SAVENAME_LENGTH * saves.size() + 1, true); +	saveIds.resize(saves.size() + 1, true); + +	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.setFromInt16(i, save.id - kSaveIdShift); +	} + +	descriptions.charAt(SCI_MAX_SAVENAME_LENGTH * saves.size()) = '\0'; +	saveIds.setFromInt16(saves.size(), 0); + +	return make_reg(0, saves.size()); +} + +reg_t kMakeSaveCatName(EngineState *s, int argc, reg_t *argv) { +	// ScummVM does not use SCI catalogues for save games, but game scripts try +	// to write out catalogues manually after a save game is deleted, so we need +	// to be able to identify and ignore these IO operations by always giving +	// back a fixed catalogue name and then ignoring it in kFileIO +	SciArray &outCatName = *s->_segMan->lookupArray(argv[0]); +	outCatName.fromString("fake.cat"); +	return argv[0]; +} + +reg_t kMakeSaveFileName(EngineState *s, int argc, reg_t *argv) { +	SciArray &outFileName = *s->_segMan->lookupArray(argv[0]); +	// argv[1] is the game name, which is not used by ScummVM +	const int16 saveNo = argv[2].toSint16(); +	outFileName.fromString(g_sci->getSavegameName(saveNo + kSaveIdShift)); +	return argv[0]; +} +  #endif  } // End of namespace Sci  | 
