parent
12c42e2846
commit
4751cedb08
8 changed files with 154 additions and 27 deletions
|
@ -213,6 +213,21 @@ SaveStateList UltimaMetaEngine::listSaves(const char *target) const {
|
||||||
return saveList;
|
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 {
|
Common::KeymapArray UltimaMetaEngine::initKeymaps(const char *target) const {
|
||||||
const Common::String gameId = getGameId(target);
|
const Common::String gameId = getGameId(target);
|
||||||
if (gameId == "ultima4" || gameId == "ultima4_enh")
|
if (gameId == "ultima4" || gameId == "ultima4_enh")
|
||||||
|
|
|
@ -45,6 +45,11 @@ public:
|
||||||
*/
|
*/
|
||||||
SaveStateList listSaves(const char *target) const override;
|
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
|
* Initialize keymaps
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -20,27 +20,39 @@
|
||||||
*/
|
*/
|
||||||
|
|
||||||
#include "ultima/ultima8/filesys/savegame.h"
|
#include "ultima/ultima8/filesys/savegame.h"
|
||||||
|
#include "common/bufferedstream.h"
|
||||||
|
#include "common/compression/unzip.h"
|
||||||
|
|
||||||
namespace Ultima {
|
namespace Ultima {
|
||||||
namespace Ultima8 {
|
namespace Ultima8 {
|
||||||
|
|
||||||
#define SAVEGAME_IDENT MKTAG('V', 'M', 'U', '8')
|
#define SAVEGAME_IDENT MKTAG('V', 'M', 'U', '8')
|
||||||
|
#define PKZIP_IDENT MKTAG('P', 'K', 3, 4)
|
||||||
#define SAVEGAME_VERSION 6
|
#define SAVEGAME_VERSION 6
|
||||||
#define SAVEGAME_MIN_VERSION 2
|
#define SAVEGAME_MIN_VERSION 2
|
||||||
|
|
||||||
SavegameReader::SavegameReader(Common::SeekableReadStream *rs, bool metadataOnly) : _file(rs), _version(0) {
|
class FileEntryArchive : public Common::Archive {
|
||||||
if (!MetaEngine::readSavegameHeader(rs, &_header))
|
struct FileEntry {
|
||||||
return;
|
uint _offset;
|
||||||
|
uint _size;
|
||||||
|
FileEntry() : _offset(0), _size(0) {}
|
||||||
|
};
|
||||||
|
private:
|
||||||
|
Common::HashMap<Common::String, FileEntry> _index;
|
||||||
|
Common::SeekableReadStream *_file;
|
||||||
|
|
||||||
// Validate the identifier for a valid savegame
|
public:
|
||||||
uint32 ident = _file->readUint32LE();
|
FileEntryArchive(Common::SeekableReadStream *rs);
|
||||||
if (ident != SAVEGAME_IDENT)
|
~FileEntryArchive() override;
|
||||||
return;
|
|
||||||
|
|
||||||
_version = _file->readUint32LE();
|
// Common::Archive API implementation
|
||||||
if (metadataOnly)
|
bool hasFile(const Common::Path &path) const override;
|
||||||
return;
|
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
|
// Load the index
|
||||||
uint count = _file->readUint16LE();
|
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() {
|
SavegameReader::~SavegameReader() {
|
||||||
|
if (_archive)
|
||||||
|
delete _archive;
|
||||||
}
|
}
|
||||||
|
|
||||||
SavegameReader::State SavegameReader::isValid() const {
|
SavegameReader::State SavegameReader::isValid() const {
|
||||||
|
@ -73,14 +171,9 @@ SavegameReader::State SavegameReader::isValid() const {
|
||||||
}
|
}
|
||||||
|
|
||||||
Common::SeekableReadStream *SavegameReader::getDataSource(const Std::string &name) {
|
Common::SeekableReadStream *SavegameReader::getDataSource(const Std::string &name) {
|
||||||
assert(_index.contains(name));
|
assert(_archive);
|
||||||
|
|
||||||
const FileEntry &fe = _index[name];
|
return _archive->createReadStreamForMember(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);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
|
@ -35,15 +35,9 @@ class ZipFile;
|
||||||
class IDataSource;
|
class IDataSource;
|
||||||
|
|
||||||
class SavegameReader {
|
class SavegameReader {
|
||||||
struct FileEntry {
|
|
||||||
uint _offset;
|
|
||||||
uint _size;
|
|
||||||
FileEntry() : _offset(0), _size(0) {}
|
|
||||||
};
|
|
||||||
private:
|
private:
|
||||||
ExtendedSavegameHeader _header;
|
ExtendedSavegameHeader _header;
|
||||||
Common::HashMap<Common::String, FileEntry> _index;
|
Common::Archive *_archive;
|
||||||
Common::SeekableReadStream *_file;
|
|
||||||
uint32 _version;
|
uint32 _version;
|
||||||
public:
|
public:
|
||||||
explicit SavegameReader(Common::SeekableReadStream *rs, bool metadataOnly = false);
|
explicit SavegameReader(Common::SeekableReadStream *rs, bool metadataOnly = false);
|
||||||
|
|
|
@ -154,10 +154,10 @@ Std::string GameInfo::getPrintableMD5() const {
|
||||||
bool GameInfo::match(GameInfo &other, bool ignoreMD5) const {
|
bool GameInfo::match(GameInfo &other, bool ignoreMD5) const {
|
||||||
if (_type != other._type) return false;
|
if (_type != other._type) return false;
|
||||||
if (_language != other._language) return false;
|
if (_language != other._language) return false;
|
||||||
if (version != other.version) return false;
|
|
||||||
|
|
||||||
if (ignoreMD5) return true;
|
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);
|
return (memcmp(_md5, other._md5, 16) == 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,9 @@
|
||||||
#include "ultima/ultima8/metaengine.h"
|
#include "ultima/ultima8/metaengine.h"
|
||||||
#include "ultima/ultima8/misc/debugger.h"
|
#include "ultima/ultima8/misc/debugger.h"
|
||||||
#include "ultima/ultima8/ultima8.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 "common/translation.h"
|
||||||
#include "backends/keymapper/action.h"
|
#include "backends/keymapper/action.h"
|
||||||
#include "backends/keymapper/standard-actions.h"
|
#include "backends/keymapper/standard-actions.h"
|
||||||
|
@ -237,5 +240,17 @@ Common::String MetaEngine::getMethod(KeybindingAction keyAction, bool isPress) {
|
||||||
return Common::String();
|
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 Ultima8
|
||||||
} // End of namespace Ultima
|
} // End of namespace Ultima
|
||||||
|
|
|
@ -78,6 +78,11 @@ public:
|
||||||
* Execute an engine keymap release action
|
* Execute an engine keymap release action
|
||||||
*/
|
*/
|
||||||
static void releaseAction(KeybindingAction keyAction);
|
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
|
} // End of namespace Ultima8
|
||||||
|
|
|
@ -1239,7 +1239,7 @@ Common::Error Ultima8Engine::loadGameStream(Common::SeekableReadStream *stream)
|
||||||
return Common::Error(Common::kReadingFailed, "Invalid or corrupt savegame: missing GameInfo");
|
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";
|
Std::string message = "Game mismatch\n";
|
||||||
message += "Running _game: " + _gameInfo->getPrintDetails() + "\n";
|
message += "Running _game: " + _gameInfo->getPrintDetails() + "\n";
|
||||||
message += "Savegame : " + saveinfo.getPrintDetails();
|
message += "Savegame : " + saveinfo.getPrintDetails();
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue