[Scummvm-git-logs] scummvm master -> ed44cbf287a9c75ca5925b1108a0c58a870191c1

bonki bonki at users.noreply.github.com
Fri Mar 16 20:26:31 CET 2018


This automated email contains information about 1 new commit which have been
pushed to the 'scummvm' repo located at https://github.com/scummvm/scummvm .

Summary:
ed44cbf287 TUCKER: Add savegame metadata and autosave support


Commit: ed44cbf287a9c75ca5925b1108a0c58a870191c1
    https://github.com/scummvm/scummvm/commit/ed44cbf287a9c75ca5925b1108a0c58a870191c1
Author: Adrian Frühwirth (bonki at users.noreply.github.com)
Date: 2018-03-16T20:18:32+01:00

Commit Message:
TUCKER: Add savegame metadata and autosave support

Changed paths:
    engines/tucker/detection.cpp
    engines/tucker/saveload.cpp
    engines/tucker/tucker.cpp
    engines/tucker/tucker.h


diff --git a/engines/tucker/detection.cpp b/engines/tucker/detection.cpp
index 2447e15..7d07eda 100644
--- a/engines/tucker/detection.cpp
+++ b/engines/tucker/detection.cpp
@@ -25,8 +25,8 @@
 #include "common/savefile.h"
 #include "common/system.h"
 #include "common/fs.h"
-
 #include "base/plugins.h"
+#include "graphics/thumbnail.h"
 
 #include "tucker/tucker.h"
 
@@ -132,6 +132,10 @@ public:
 		case kSupportsListSaves:
 		case kSupportsLoadingDuringStartup:
 		case kSupportsDeleteSave:
+		case kSavesSupportMetaInfo:
+		case kSavesSupportThumbnail:
+		case kSavesSupportCreationDate:
+		case kSavesSupportPlayTime:
 			return true;
 		default:
 			return false;
@@ -162,26 +166,24 @@ public:
 	virtual SaveStateList listSaves(const char *target) const {
 		Common::String pattern = Tucker::generateGameStateFileName(target, 0, true);
 		Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles(pattern);
-		bool slotsTable[Tucker::kLastSaveSlot + 1];
-		memset(slotsTable, 0, sizeof(slotsTable));
+		Tucker::TuckerEngine::SavegameHeader header;
 		SaveStateList saveList;
+
 		for (Common::StringArray::const_iterator file = filenames.begin(); file != filenames.end(); ++file) {
 			int slot;
 			const char *ext = strrchr(file->c_str(), '.');
 			if (ext && (slot = atoi(ext + 1)) >= 0 && slot <= Tucker::kLastSaveSlot) {
 				Common::InSaveFile *in = g_system->getSavefileManager()->openForLoading(*file);
 				if (in) {
-					slotsTable[slot] = true;
+					if (Tucker::TuckerEngine::readSavegameHeader(in, header, false) == Tucker::TuckerEngine::kSavegameNoError) {
+						saveList.push_back(SaveStateDescriptor(slot, header.description));
+					}
+
 					delete in;
 				}
 			}
 		}
-		for (int slot = 0; slot <= Tucker::kLastSaveSlot; ++slot) {
-			if (slotsTable[slot]) {
-				Common::String description = Common::String::format("savegm.%02d", slot);
-				saveList.push_back(SaveStateDescriptor(slot, description));
-			}
-		}
+
 		// Sort saves based on slot number.
 		Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
 		return saveList;
@@ -195,6 +197,59 @@ public:
 		Common::String filename = Tucker::generateGameStateFileName(target, slot);
 		g_system->getSavefileManager()->removeSavefile(filename);
 	}
+
+	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const {
+		Common::String fileName = Common::String::format("%s.%d", target, slot);
+		Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(fileName);
+
+		if (!file) {
+			return SaveStateDescriptor();
+		}
+
+		Tucker::TuckerEngine::SavegameHeader header;
+		Tucker::TuckerEngine::SavegameError savegameError = Tucker::TuckerEngine::readSavegameHeader(file, header, true);
+		if (savegameError) {
+			delete file;
+			return SaveStateDescriptor();
+		}
+
+		SaveStateDescriptor desc(slot, header.description);
+
+		if (slot == Tucker::kAutoSaveSlot) {
+			bool autosaveAllowed = Tucker::TuckerEngine::isAutosaveAllowed(target);
+			desc.setDeletableFlag(!autosaveAllowed);
+			desc.setWriteProtectedFlag(autosaveAllowed);
+		}
+
+		if (header.version >= 2) {
+			// creation/play time
+			if (header.saveDate) {
+				int day   = (header.saveDate >> 24) & 0xFF;
+				int month = (header.saveDate >> 16) & 0xFF;
+				int year  =  header.saveDate        & 0xFFFF;
+				desc.setSaveDate(year, month, day);
+			}
+
+			if (header.saveTime) {
+				int hour    = (header.saveTime >> 16) & 0xFF;
+				int minutes = (header.saveTime >>  8) & 0xFF;
+				desc.setSaveTime(hour, minutes);
+			}
+
+			if (header.playTime) {
+				desc.setPlayTime(header.playTime * 1000);
+			}
+
+			// thumbnail
+			if (header.thumbnail) {
+				desc.setThumbnail(header.thumbnail);
+			}
+		}
+
+		delete file;
+		return desc;
+	}
+
 };
 
 #if PLUGIN_ENABLED_DYNAMIC(TUCKER)
diff --git a/engines/tucker/saveload.cpp b/engines/tucker/saveload.cpp
index 08a4dfa..4178041 100644
--- a/engines/tucker/saveload.cpp
+++ b/engines/tucker/saveload.cpp
@@ -21,20 +21,29 @@
  */
 
 #include "common/savefile.h"
+#include "common/system.h"
 #include "common/textconsole.h"
+#include "graphics/thumbnail.h"
 
 #include "tucker/tucker.h"
 
 namespace Tucker {
 
+#define kSavegameSignature MKTAG('T', 'C', 'K', 'R')
+
 enum {
-	kCurrentGameStateVersion = 1
+	kSavegameVersionCurrent = 2,
+	kSavegameVersionMinimum = 1
+};
+
+enum SavegameFlag {
+	kSavegameFlagAutosave = 1 << 0,
 };
 
 Common::String generateGameStateFileName(const char *target, int slot, bool prefixOnly) {
 	Common::String name(target);
 	if (prefixOnly) {
-		name += ".*";
+		name += ".#*";
 	} else {
 		name += Common::String::format(".%d", slot);
 	}
@@ -50,7 +59,7 @@ static void saveOrLoadInt(Common::ReadStream &stream, int &i) {
 }
 
 template<class S>
-void TuckerEngine::saveOrLoadGameStateData(S &s) {
+TuckerEngine::SavegameError TuckerEngine::saveOrLoadGameStateData(S &s) {
 	for (int i = 0; i < kFlagsTableSize; ++i) {
 		saveOrLoadInt(s, _flagsTable[i]);
 	}
@@ -71,59 +80,224 @@ void TuckerEngine::saveOrLoadGameStateData(S &s) {
 	saveOrLoadInt(s, _yPosCurrent);
 	saveOrLoadInt(s, _inventoryObjectsCount);
 	saveOrLoadInt(s, _inventoryObjectsOffset);
+
+	return s.err() ? kSavegameIoError : kSavegameNoError;
+}
+
+Common::Error TuckerEngine::loadGameState(int slot) {
+	Common::String fileName = generateGameStateFileName(_targetName.c_str(), slot);
+	Common::InSaveFile *file = _saveFileMan->openForLoading(fileName);
+
+	if (!file) {
+		return Common::kReadingFailed;
+	}
+
+	SavegameHeader header;
+	SavegameError savegameError = readSavegameHeader(file, header);
+
+	if (!savegameError) {
+		savegameError = saveOrLoadGameStateData(*file);
+	}
+
+	if (savegameError) {
+		switch (savegameError) {
+			case kSavegameInvalidTypeError:
+				warning("Invalid savegame '%s' (does not look like a ScummVM Tucker-engine savegame)", fileName.c_str());
+				break;
+
+			case kSavegameInvalidVersionError:
+				warning("Invalid savegame '%s' (expected savegame version v%i-v%i, got v%i)",
+					fileName.c_str(), kSavegameVersionMinimum, kSavegameVersionCurrent, header.version);
+				break;
+
+			default:
+				warning("Failed to load savegame '%s'", fileName.c_str());
+				break;
+		}
+
+		delete file;
+		return Common::kReadingFailed;
+	}
+
+	g_engine->setTotalPlayTime(header.playTime * 1000);
+
+	_nextLocationNum = _locationNum;
+	setBlackPalette();
+	loadBudSpr();
+	_forceRedrawPanelItems = true;
+
+	delete file;
+	return Common::kNoError;
+}
+
+
+TuckerEngine::SavegameError TuckerEngine::readSavegameHeader(const char *target, int slot, SavegameHeader &header) {
+	Common::String fileName = generateGameStateFileName(target, slot);
+	Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(fileName);
+
+	if (!file) {
+		return kSavegameNotFoundError;
+	}
+
+	SavegameError savegameError = readSavegameHeader(file, header);
+
+	delete file;
+	return savegameError;
 }
 
-Common::Error TuckerEngine::loadGameState(int num) {
-	Common::Error ret = Common::kNoError;
-	Common::String gameStateFileName = generateGameStateFileName(_targetName.c_str(), num);
-	Common::InSaveFile *f = _saveFileMan->openForLoading(gameStateFileName);
-	if (f) {
-		uint16 version = f->readUint16LE();
-		if (version < kCurrentGameStateVersion) {
-			warning("Unsupported gamestate version %d (slot %d)", version, num);
+TuckerEngine::SavegameError TuckerEngine::readSavegameHeader(Common::InSaveFile *file, SavegameHeader &header, bool loadThumbnail) {
+	header.version   = -1;
+	header.flags     = 0;
+	header.description.clear();
+	header.saveDate  = 0;
+	header.saveTime  = 0;
+	header.playTime  = 0;
+	header.thumbnail = nullptr;
+
+	if (file->readUint32BE() == kSavegameSignature) {
+		header.version = file->readUint16LE();
+	} else {
+		// possibly an old, headerless savegame
+
+		file->seek(0, SEEK_SET);
+
+		header.version = file->readUint16LE();
+		// old savegames are always version 1
+		if (header.version != 1) {
+			return kSavegameInvalidTypeError;
+		}
+
+		file->skip(2);
+	}
+
+	if (header.version > kSavegameVersionCurrent) {
+		return kSavegameInvalidVersionError;
+	}
+
+	if (header.version >= 2) {
+		// savegame flags
+		header.flags = file->readUint32LE();
+
+		char ch;
+		while ((ch = (char)file->readByte()) != '\0')
+			header.description += ch;
+
+		header.saveDate = file->readUint32LE();
+		header.saveTime = file->readUint32LE();
+		header.playTime = file->readUint32LE();
+
+		if (loadThumbnail) {
+			header.thumbnail = Graphics::loadThumbnail(*file);
 		} else {
-			f->skip(2);
-			saveOrLoadGameStateData(*f);
-			if (f->err() || f->eos()) {
-				warning("Can't read file '%s'", gameStateFileName.c_str());
-				ret = Common::kReadingFailed;
-			} else {
-				_nextLocationNum = _locationNum;
-				setBlackPalette();
-				loadBudSpr();
-				_forceRedrawPanelItems = true;
-			}
+			Graphics::skipThumbnail(*file);
 		}
-		delete f;
 	}
-	return ret;
-}
-
-Common::Error TuckerEngine::saveGameState(int num, const Common::String &description) {
-	Common::Error ret = Common::kNoError;
-	Common::String gameStateFileName = generateGameStateFileName(_targetName.c_str(), num);
-	Common::OutSaveFile *f = _saveFileMan->openForSaving(gameStateFileName);
-	if (f) {
-		f->writeUint16LE(kCurrentGameStateVersion);
-		f->writeUint16LE(0);
-		saveOrLoadGameStateData(*f);
-		f->finalize();
-		if (f->err()) {
-			warning("Can't write file '%s'", gameStateFileName.c_str());
-			ret = Common::kWritingFailed;
+
+	return ((file->err() || file->eos()) ? kSavegameIoError : kSavegameNoError);
+}
+
+TuckerEngine::SavegameError TuckerEngine::writeSavegameHeader(Common::OutSaveFile *file, SavegameHeader &header) {
+	// Tucker savegame signature
+	file->writeUint32BE(kSavegameSignature);
+
+	// version information
+	file->writeUint16LE(kSavegameVersionCurrent);
+
+	// savegame flags
+	file->writeUint32LE(header.flags);
+
+	// savegame name
+	file->writeString(header.description);
+	file->writeByte(0);
+
+	// creation/play time
+	TimeDate curTime;
+	_system->getTimeAndDate(curTime);
+	header.saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
+	header.saveTime = ((curTime.tm_hour & 0xFF) << 16) | (((curTime.tm_min)     & 0xFF) <<  8) | ((curTime.tm_sec)         & 0xFF);
+	header.playTime = g_engine->getTotalPlayTime() / 1000;
+	file->writeUint32LE(header.saveDate);
+	file->writeUint32LE(header.saveTime);
+	file->writeUint32LE(header.playTime);
+
+	// thumbnail
+	Graphics::saveThumbnail(*file);
+
+	return (file->err() ? kSavegameIoError : kSavegameNoError);
+}
+
+Common::Error TuckerEngine::saveGameState(int slot, const Common::String &description) {
+	return writeSavegame(slot, description, false);
+}
+
+Common::Error TuckerEngine::writeSavegame(int slot, const Common::String &description, bool autosave) {
+	Common::String fileName = generateGameStateFileName(_targetName.c_str(), slot);
+	Common::OutSaveFile *file = _saveFileMan->openForSaving(fileName);
+	SavegameHeader header;
+	SavegameError savegameError = kSavegameNoError;
+
+	if (!file)
+		savegameError = kSavegameIoError;
+
+	if (!savegameError) {
+		// savegame flags
+		if (autosave)
+			header.flags |= kSavegameFlagAutosave;
+
+		// description
+		header.description = description;
+
+		savegameError = writeSavegameHeader(file, header);
+	}
+
+	if (!savegameError)
+		savegameError = saveOrLoadGameStateData(*file);
+
+	if (!savegameError)
+		file->finalize();
+
+	delete file;
+
+	if (savegameError) {
+		warning("Error writing savegame '%s'", fileName.c_str());
+		return Common::kWritingFailed;
+	}
+
+	return Common::kNoError;
+}
+
+bool TuckerEngine::isAutosaveAllowed() {
+	return isAutosaveAllowed(_targetName.c_str());
+}
+
+bool TuckerEngine::isAutosaveAllowed(const char *target) {
+	SavegameHeader savegameHeader;
+	SavegameError savegameError = readSavegameHeader(target, kAutoSaveSlot, savegameHeader);
+	return (savegameError == kSavegameNotFoundError || (savegameHeader.flags & kSavegameFlagAutosave));
+}
+
+void TuckerEngine::writeAutosave() {
+	if (canSaveGameStateCurrently()) {
+		if (!isAutosaveAllowed()) {
+			warning("Refusing to overwrite non-autosave savegame in slot %i, skipping autosave", kAutoSaveSlot);
+			return;
 		}
-		delete f;
+
+		writeSavegame(kAutoSaveSlot, "Autosave", true);
+		_lastSaveTime = _system->getMillis();
 	}
-	return ret;
 }
 
+bool TuckerEngine::canLoadOrSave() const {
+	return !_player && _cursorState != kCursorStateDisabledHidden;
+}
 
 bool TuckerEngine::canLoadGameStateCurrently() {
-	return !_player && _cursorState != kCursorStateDisabledHidden;
+	return canLoadOrSave();
 }
 
 bool TuckerEngine::canSaveGameStateCurrently() {
-	return !_player && _cursorState != kCursorStateDisabledHidden;
+	return canLoadOrSave();
 }
 
 bool TuckerEngine::existsSavegame() {
diff --git a/engines/tucker/tucker.cpp b/engines/tucker/tucker.cpp
index 309e0f2..56247a1 100644
--- a/engines/tucker/tucker.cpp
+++ b/engines/tucker/tucker.cpp
@@ -351,6 +351,8 @@ void TuckerEngine::resetVariables() {
 	_updateLocationFlag = false;
 	_updateLocation70StringLen = 0;
 	memset(_updateLocation70String, 0, sizeof(_updateLocation70String));
+
+	_lastSaveTime = _system->getMillis();
 }
 
 void TuckerEngine::mainLoop() {
@@ -616,7 +618,15 @@ void TuckerEngine::mainLoop() {
 			handleCreditsSequence();
 			_quitGame = true;
 		}
+
+		if (shouldPerformAutoSave(_lastSaveTime)) {
+			writeAutosave();
+		}
 	} while (!_quitGame && _flagsTable[100] == 0);
+
+	// auto save on quit
+	writeAutosave();
+
 	if (_flagsTable[100] == 1) {
 		handleCongratulationsSequence();
 	}
diff --git a/engines/tucker/tucker.h b/engines/tucker/tucker.h
index a4cee74..cd12939 100644
--- a/engines/tucker/tucker.h
+++ b/engines/tucker/tucker.h
@@ -28,6 +28,7 @@
 #include "common/endian.h"
 #include "common/events.h"
 #include "common/random.h"
+#include "common/savefile.h"
 #include "common/stream.h"
 
 #include "video/flic_decoder.h"
@@ -254,7 +255,8 @@ enum {
 	kStartupLocationGame = 1,
 	kDefaultCharSpeechSoundCounter = 1,
 	kMaxSoundVolume = 127,
-	kLastSaveSlot = 99
+	kLastSaveSlot = 99,
+	kAutoSaveSlot = kLastSaveSlot
 };
 
 enum InputKey {
@@ -334,6 +336,24 @@ public:
 		kMaxDirtyRects = 32
 	};
 
+	struct SavegameHeader {
+		uint16 version;
+		uint32 flags;
+		Common::String description;
+		uint32 saveDate;
+		uint32 saveTime;
+		uint32 playTime;
+		Graphics::Surface *thumbnail;
+	};
+
+	enum SavegameError {
+		kSavegameNoError = 0,
+		kSavegameInvalidTypeError,
+		kSavegameInvalidVersionError,
+		kSavegameNotFoundError,
+		kSavegameIoError
+	};
+
 	TuckerEngine(OSystem *system, Common::Language language, uint32 flags);
 	virtual ~TuckerEngine();
 
@@ -341,6 +361,10 @@ public:
 	virtual bool hasFeature(EngineFeature f) const;
 	GUI::Debugger *getDebugger() { return _console; }
 
+	static SavegameError readSavegameHeader(Common::InSaveFile *file, SavegameHeader &header, bool loadThumbnail = false);
+	static SavegameError readSavegameHeader(const char *target, int slot, SavegameHeader &header);
+	bool isAutosaveAllowed();
+	static bool isAutosaveAllowed(const char *target);
 protected:
 
 	int getRandomNumber();
@@ -627,9 +651,13 @@ protected:
 	void updateSprite_locationNum81_1(int i);
 	void updateSprite_locationNum82(int i);
 
-	template<class S> void saveOrLoadGameStateData(S &s);
-	virtual Common::Error loadGameState(int num);
-	virtual Common::Error saveGameState(int num, const Common::String &description);
+	template<class S> SavegameError saveOrLoadGameStateData(S &s);
+	virtual Common::Error loadGameState(int slot);
+	virtual Common::Error saveGameState(int slot, const Common::String &description);
+	Common::Error writeSavegame(int slot, const Common::String &description, bool autosave = false);
+	SavegameError writeSavegameHeader(Common::OutSaveFile *file, SavegameHeader &header);
+	void writeAutosave();
+	bool canLoadOrSave() const;
 	virtual bool canLoadGameStateCurrently();
 	virtual bool canSaveGameStateCurrently();
 	virtual bool existsSavegame();
@@ -679,6 +707,7 @@ protected:
 	Common::Language _gameLang;
 	uint32 _gameFlags;
 	int _startSlot;
+	uint32 _lastSaveTime;
 
 	bool _quitGame;
 	bool _fastMode;





More information about the Scummvm-git-logs mailing list