[Scummvm-git-logs] scummvm master -> 4751cedb086483f0e873b216fcc89e788e42f89f

OMGPizzaGuy noreply at scummvm.org
Thu Jun 29 04:33:14 UTC 2023


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:
4751cedb08 ULTIMA8: Support reading of Pentagram save game files


Commit: 4751cedb086483f0e873b216fcc89e788e42f89f
    https://github.com/scummvm/scummvm/commit/4751cedb086483f0e873b216fcc89e788e42f89f
Author: Matthew Jimenez (matthew.jimenez at outlook.com)
Date: 2023-06-28T23:33:09-05:00

Commit Message:
ULTIMA8: Support reading of Pentagram save game files
Fixes bug #14043

Changed paths:
    engines/ultima/metaengine.cpp
    engines/ultima/metaengine.h
    engines/ultima/ultima8/filesys/savegame.cpp
    engines/ultima/ultima8/filesys/savegame.h
    engines/ultima/ultima8/games/game_info.cpp
    engines/ultima/ultima8/metaengine.cpp
    engines/ultima/ultima8/metaengine.h
    engines/ultima/ultima8/ultima8.cpp


diff --git a/engines/ultima/metaengine.cpp b/engines/ultima/metaengine.cpp
index a4bb371a8f3..0cd7d624b5b 100644
--- a/engines/ultima/metaengine.cpp
+++ b/engines/ultima/metaengine.cpp
@@ -213,6 +213,21 @@ SaveStateList UltimaMetaEngine::listSaves(const char *target) const {
 	return saveList;
 }
 
+SaveStateDescriptor UltimaMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+	SaveStateDescriptor desc = AdvancedMetaEngine::querySaveMetaInfos(target, slot);
+	if (!desc.isValid() && slot > 0) {
+		Common::String gameId = getGameId(target);
+		if (gameId == "ultima8") {
+			Common::String filename = getSavegameFile(slot, target);
+			desc = SaveStateDescriptor(this, slot, Common::U32String());
+			if (!Ultima::Ultima8::MetaEngine::querySaveMetaInfos(filename, desc))
+				return SaveStateDescriptor();
+		}
+	}
+
+	return desc;
+}
+
 Common::KeymapArray UltimaMetaEngine::initKeymaps(const char *target) const {
 	const Common::String gameId = getGameId(target);
 	if (gameId == "ultima4" || gameId == "ultima4_enh")
diff --git a/engines/ultima/metaengine.h b/engines/ultima/metaengine.h
index 08e6cb8173d..4318def2955 100644
--- a/engines/ultima/metaengine.h
+++ b/engines/ultima/metaengine.h
@@ -45,6 +45,11 @@ public:
 	 */
 	SaveStateList listSaves(const char *target) const override;
 
+	/**
+	 * Return meta information from the specified save state.
+	 */
+	SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const override;
+
 	/**
 	 * Initialize keymaps
 	 */
diff --git a/engines/ultima/ultima8/filesys/savegame.cpp b/engines/ultima/ultima8/filesys/savegame.cpp
index 66dc429935d..7d4603cbd9d 100644
--- a/engines/ultima/ultima8/filesys/savegame.cpp
+++ b/engines/ultima/ultima8/filesys/savegame.cpp
@@ -20,27 +20,39 @@
  */
 
 #include "ultima/ultima8/filesys/savegame.h"
+#include "common/bufferedstream.h"
+#include "common/compression/unzip.h"
 
 namespace Ultima {
 namespace Ultima8 {
 
 #define SAVEGAME_IDENT MKTAG('V', 'M', 'U', '8')
+#define PKZIP_IDENT MKTAG('P', 'K', 3, 4)
 #define SAVEGAME_VERSION 6
 #define SAVEGAME_MIN_VERSION 2
 
-SavegameReader::SavegameReader(Common::SeekableReadStream *rs, bool metadataOnly) : _file(rs), _version(0) {
-	if (!MetaEngine::readSavegameHeader(rs, &_header))
-		return;
-
-	// Validate the identifier for a valid savegame
-	uint32 ident = _file->readUint32LE();
-	if (ident != SAVEGAME_IDENT)
-		return;
-
-	_version = _file->readUint32LE();
-	if (metadataOnly)
-		return;
-
+class FileEntryArchive : public Common::Archive {
+	struct FileEntry {
+		uint _offset;
+		uint _size;
+		FileEntry() : _offset(0), _size(0) {}
+	};
+private:
+	Common::HashMap<Common::String, FileEntry> _index;
+	Common::SeekableReadStream *_file;
+
+public:
+	FileEntryArchive(Common::SeekableReadStream *rs);
+	~FileEntryArchive() override;
+
+	// Common::Archive API implementation
+	bool hasFile(const Common::Path &path) const override;
+	int listMembers(Common::ArchiveMemberList &list) const override;
+	const Common::ArchiveMemberPtr getMember(const Common::Path &path) const override;
+	Common::SeekableReadStream *createReadStreamForMember(const Common::Path &path) const override;
+};
+
+FileEntryArchive::FileEntryArchive(Common::SeekableReadStream *rs) : _file(rs) {
 	// Load the index
 	uint count = _file->readUint16LE();
 
@@ -58,7 +70,93 @@ SavegameReader::SavegameReader(Common::SeekableReadStream *rs, bool metadataOnly
 	}
 }
 
+FileEntryArchive::~FileEntryArchive() {
+}
+
+bool FileEntryArchive::hasFile(const Common::Path &path) const {
+	return _index.contains(path.toString());
+}
+
+int FileEntryArchive::listMembers(Common::ArchiveMemberList &list) const {
+	list.clear();
+	for (Common::HashMap<Common::String, FileEntry>::const_iterator it = _index.begin(); it != _index.end(); ++it)
+		list.push_back(Common::ArchiveMemberPtr(new Common::GenericArchiveMember(it->_key, this)));
+
+	return list.size();
+}
+
+const Common::ArchiveMemberPtr FileEntryArchive::getMember(const Common::Path &path) const {
+	if (!hasFile(path))
+		return nullptr;
+
+	Common::String name = path.toString();
+	return Common::ArchiveMemberPtr(new Common::GenericArchiveMember(name, this));
+}
+
+Common::SeekableReadStream *FileEntryArchive::createReadStreamForMember(const Common::Path &path) const {
+	assert(hasFile(path));
+
+	const FileEntry &fe = _index[path.toString()];
+	uint8 *data = (uint8 *)malloc(fe._size);
+	_file->seek(fe._offset);
+	_file->read(data, fe._size);
+
+	return new Common::MemoryReadStream(data, fe._size, DisposeAfterUse::YES);
+}
+
+SavegameReader::SavegameReader(Common::SeekableReadStream *rs, bool metadataOnly) : _archive(nullptr), _version(0) {
+	// Validate the identifier for a valid savegame
+	uint32 ident = rs->readUint32LE();
+	if (ident == SAVEGAME_IDENT) {
+		_version = rs->readUint32LE();
+
+		if (!MetaEngine::readSavegameHeader(rs, &_header))
+			return;
+
+		if (metadataOnly)
+			return;
+
+		_archive = new FileEntryArchive(rs);
+	} else if (SWAP_BYTES_32(ident) == PKZIP_IDENT) {
+		// Note: Pentagram save description is the zip global comment
+		_header.description = "Pentagram Save";
+
+		// Hack to pull the comment if length < 255
+		char data[256];
+		uint16 size = sizeof(data);
+		rs->seek(-size, SEEK_END);
+		rs->read(data, size);
+		for (uint16 i = size; i >= 2; i--) {
+			uint16 length = size - i;
+			if (data[i - 2] == length && data[i - 1] == 0) {
+				if (length > 0)
+					_header.description = Common::String(data + i, length);
+				break;
+			}
+		}
+
+		Common::SeekableReadStream *stream = wrapBufferedSeekableReadStream(rs, 4096, DisposeAfterUse::NO);
+		_archive = Common::makeZipArchive(stream);
+		if (!_archive)
+			return;
+
+		Common::ArchiveMemberPtr member = _archive->getMember("VERSION");
+		if (member) {
+			_version = member->createReadStream()->readUint32LE();
+			_header.version = _version;
+		}
+
+		if (metadataOnly) {
+			delete _archive;
+			_archive = nullptr;
+			return;
+		}
+	}
+}
+
 SavegameReader::~SavegameReader() {
+	if (_archive)
+		delete _archive;
 }
 
 SavegameReader::State SavegameReader::isValid() const {
@@ -73,14 +171,9 @@ SavegameReader::State SavegameReader::isValid() const {
 }
 
 Common::SeekableReadStream *SavegameReader::getDataSource(const Std::string &name) {
-	assert(_index.contains(name));
+	assert(_archive);
 
-	const FileEntry &fe = _index[name];
-	uint8 *data = (uint8 *)malloc(fe._size);
-	_file->seek(fe._offset);
-	_file->read(data, fe._size);
-
-	return new Common::MemoryReadStream(data, fe._size, DisposeAfterUse::YES);
+	return _archive->createReadStreamForMember(name);
 }
 
 
diff --git a/engines/ultima/ultima8/filesys/savegame.h b/engines/ultima/ultima8/filesys/savegame.h
index 1305683a53f..64595c6bc76 100644
--- a/engines/ultima/ultima8/filesys/savegame.h
+++ b/engines/ultima/ultima8/filesys/savegame.h
@@ -35,15 +35,9 @@ class ZipFile;
 class IDataSource;
 
 class SavegameReader {
-	struct FileEntry {
-		uint _offset;
-		uint _size;
-		FileEntry() : _offset(0), _size(0) {}
-	};
 private:
 	ExtendedSavegameHeader _header;
-	Common::HashMap<Common::String, FileEntry> _index;
-	Common::SeekableReadStream *_file;
+	Common::Archive *_archive;
 	uint32 _version;
 public:
 	explicit SavegameReader(Common::SeekableReadStream *rs, bool metadataOnly = false);
diff --git a/engines/ultima/ultima8/games/game_info.cpp b/engines/ultima/ultima8/games/game_info.cpp
index c01f8123cb4..e4f571a96be 100644
--- a/engines/ultima/ultima8/games/game_info.cpp
+++ b/engines/ultima/ultima8/games/game_info.cpp
@@ -154,10 +154,10 @@ Std::string GameInfo::getPrintableMD5() const {
 bool GameInfo::match(GameInfo &other, bool ignoreMD5) const {
 	if (_type != other._type) return false;
 	if (_language != other._language) return false;
-	if (version != other.version) return false;
-
 	if (ignoreMD5) return true;
 
+	// NOTE: Version and MD5 hash are not currently set
+	if (version != other.version) return false;
 	return (memcmp(_md5, other._md5, 16) == 0);
 }
 
diff --git a/engines/ultima/ultima8/metaengine.cpp b/engines/ultima/ultima8/metaengine.cpp
index a5f213117d2..88620602e2e 100644
--- a/engines/ultima/ultima8/metaengine.cpp
+++ b/engines/ultima/ultima8/metaengine.cpp
@@ -22,6 +22,9 @@
 #include "ultima/ultima8/metaengine.h"
 #include "ultima/ultima8/misc/debugger.h"
 #include "ultima/ultima8/ultima8.h"
+#include "ultima/ultima8/filesys/savegame.h"
+#include "common/savefile.h"
+#include "common/system.h"
 #include "common/translation.h"
 #include "backends/keymapper/action.h"
 #include "backends/keymapper/standard-actions.h"
@@ -237,5 +240,17 @@ Common::String MetaEngine::getMethod(KeybindingAction keyAction, bool isPress) {
 	return Common::String();
 }
 
+bool MetaEngine::querySaveMetaInfos(const Common::String &filename, SaveStateDescriptor& desc) {
+	Common::ScopedPtr<Common::InSaveFile> f(g_system->getSavefileManager()->openForLoading(filename));
+
+	if (f) {
+		SavegameReader sg(f.get(), true);
+		desc.setDescription(sg.getDescription());
+		return sg.isValid();
+	}
+
+	return false;
+}
+
 } // End of namespace Ultima8
 } // End of namespace Ultima
diff --git a/engines/ultima/ultima8/metaengine.h b/engines/ultima/ultima8/metaengine.h
index 4c67c2a77b0..d577cb9d9fb 100644
--- a/engines/ultima/ultima8/metaengine.h
+++ b/engines/ultima/ultima8/metaengine.h
@@ -78,6 +78,11 @@ public:
 	 * Execute an engine keymap release action
 	 */
 	static void releaseAction(KeybindingAction keyAction);
+
+	/**
+	 * Return meta information from the specified save state for saves that do not have ExtendedSavegameHeader
+	 */
+	static bool querySaveMetaInfos(const Common::String &filename, SaveStateDescriptor &desc);
 };
 
 } // End of namespace Ultima8
diff --git a/engines/ultima/ultima8/ultima8.cpp b/engines/ultima/ultima8/ultima8.cpp
index 0255bdbc997..bc1bf8216db 100644
--- a/engines/ultima/ultima8/ultima8.cpp
+++ b/engines/ultima/ultima8/ultima8.cpp
@@ -1239,7 +1239,7 @@ Common::Error Ultima8Engine::loadGameStream(Common::SeekableReadStream *stream)
 		return Common::Error(Common::kReadingFailed, "Invalid or corrupt savegame: missing GameInfo");
 	}
 
-	if (!_gameInfo->match(saveinfo)) {
+	if (!_gameInfo->match(saveinfo, true)) {
 		Std::string message = "Game mismatch\n";
 		message += "Running _game: " + _gameInfo->getPrintDetails()  + "\n";
 		message += "Savegame    : " + saveinfo.getPrintDetails();




More information about the Scummvm-git-logs mailing list