[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