MOHAWK: Save ScummVM specific metadata in the Riven saves
- Thumbnail - Save date - Save description - Total play time
This commit is contained in:
parent
792548f28f
commit
f78bb08b18
4 changed files with 272 additions and 38 deletions
|
@ -41,6 +41,7 @@
|
|||
|
||||
#ifdef ENABLE_RIVEN
|
||||
#include "mohawk/riven.h"
|
||||
#include "mohawk/riven_saveload.h"
|
||||
#endif
|
||||
|
||||
namespace Mohawk {
|
||||
|
@ -240,14 +241,18 @@ SaveStateList MohawkMetaEngine::listSaves(const char *target) const {
|
|||
}
|
||||
|
||||
Common::sort(saveList.begin(), saveList.end(), SaveStateDescriptorSlotComparator());
|
||||
} else
|
||||
}
|
||||
#endif
|
||||
#ifdef ENABLE_RIVEN
|
||||
if (strstr(target, "riven")) {
|
||||
filenames = g_system->getSavefileManager()->listSavefiles("*.rvn");
|
||||
|
||||
for (uint32 i = 0; i < filenames.size(); i++)
|
||||
saveList.push_back(SaveStateDescriptor(i, filenames[i]));
|
||||
for (uint32 i = 0; i < filenames.size(); i++) {
|
||||
Common::String description = Mohawk::RivenSaveLoad::querySaveDescription(filenames[i]);
|
||||
saveList.push_back(SaveStateDescriptor(i, description));
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
return saveList;
|
||||
}
|
||||
|
@ -270,6 +275,12 @@ SaveStateDescriptor MohawkMetaEngine::querySaveMetaInfos(const char *target, int
|
|||
#ifdef ENABLE_MYST
|
||||
if (strstr(target, "myst")) {
|
||||
return Mohawk::MystGameState::querySaveMetaInfos(slot);
|
||||
}
|
||||
#endif
|
||||
#ifdef ENABLE_RIVEN
|
||||
if (strstr(target, "riven")) {
|
||||
Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("*.rvn");
|
||||
return Mohawk::RivenSaveLoad::querySaveMetaInfos(filenames[slot].c_str());
|
||||
} else
|
||||
#endif
|
||||
{
|
||||
|
|
|
@ -68,6 +68,8 @@ namespace Mohawk {
|
|||
#define ID_VARS MKTAG('V','A','R','S') // Variable Values
|
||||
#define ID_VERS MKTAG('V','E','R','S') // Version Info
|
||||
#define ID_ZIPS MKTAG('Z','I','P','S') // Zip Mode Status
|
||||
#define ID_META MKTAG('M','E','T','A') // ScummVM save metadata
|
||||
#define ID_THMB MKTAG('T','H','M','B') // ScummVM save thumbnail
|
||||
|
||||
// Zoombini Resource FourCC's
|
||||
#define ID_SND MKTAG( 0 ,'S','N','D') // Standard Mohawk Sound
|
||||
|
|
|
@ -24,10 +24,38 @@
|
|||
#include "mohawk/riven.h"
|
||||
#include "mohawk/riven_saveload.h"
|
||||
|
||||
#include "common/util.h"
|
||||
#include "common/system.h"
|
||||
#include "graphics/thumbnail.h"
|
||||
|
||||
namespace Mohawk {
|
||||
|
||||
RivenSaveMetadata::RivenSaveMetadata() {
|
||||
saveDay = 0;
|
||||
saveMonth = 0;
|
||||
saveYear = 0;
|
||||
saveHour = 0;
|
||||
saveMinute = 0;
|
||||
totalPlayTime = 0;
|
||||
}
|
||||
|
||||
bool RivenSaveMetadata::sync(Common::Serializer &s) {
|
||||
static const Common::Serializer::Version kCurrentVersion = 1;
|
||||
|
||||
if (!s.syncVersion(kCurrentVersion)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
s.syncAsByte(saveDay);
|
||||
s.syncAsByte(saveMonth);
|
||||
s.syncAsUint16BE(saveYear);
|
||||
s.syncAsByte(saveHour);
|
||||
s.syncAsByte(saveMinute);
|
||||
s.syncString(saveDescription);
|
||||
s.syncAsUint32BE(totalPlayTime);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
RivenSaveLoad::RivenSaveLoad(MohawkEngine_Riven *vm, Common::SaveFileManager *saveFileMan) : _vm(vm), _saveFileMan(saveFileMan) {
|
||||
}
|
||||
|
||||
|
@ -38,6 +66,91 @@ Common::StringArray RivenSaveLoad::generateSaveGameList() {
|
|||
return _saveFileMan->listSavefiles("*.rvn");
|
||||
}
|
||||
|
||||
Common::String RivenSaveLoad::querySaveDescription(const Common::String &filename) {
|
||||
Common::InSaveFile *loadFile = g_system->getSavefileManager()->openForLoading(filename);
|
||||
if (!loadFile) {
|
||||
return "";
|
||||
}
|
||||
|
||||
MohawkArchive mhk;
|
||||
if (!mhk.openStream(loadFile)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
if (!mhk.hasResource(ID_META, 1)) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *metaStream = mhk.getResource(ID_META, 1);
|
||||
if (!metaStream) {
|
||||
return "";
|
||||
}
|
||||
|
||||
Common::Serializer serializer = Common::Serializer(metaStream, nullptr);
|
||||
|
||||
RivenSaveMetadata metadata;
|
||||
if (!metadata.sync(serializer)) {
|
||||
delete metaStream;
|
||||
return "";
|
||||
}
|
||||
|
||||
delete metaStream;
|
||||
|
||||
return metadata.saveDescription;
|
||||
}
|
||||
|
||||
SaveStateDescriptor RivenSaveLoad::querySaveMetaInfos(const Common::String &filename) {
|
||||
Common::InSaveFile *loadFile = g_system->getSavefileManager()->openForLoading(filename);
|
||||
if (!loadFile) {
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
MohawkArchive mhk;
|
||||
if (!mhk.openStream(loadFile)) {
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
if (!mhk.hasResource(ID_META, 1)) {
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *metaStream = mhk.getResource(ID_META, 1);
|
||||
if (!metaStream) {
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
Common::Serializer serializer = Common::Serializer(metaStream, nullptr);
|
||||
|
||||
RivenSaveMetadata metadata;
|
||||
if (!metadata.sync(serializer)) {
|
||||
delete metaStream;
|
||||
return SaveStateDescriptor();
|
||||
}
|
||||
|
||||
SaveStateDescriptor descriptor;
|
||||
descriptor.setDescription(metadata.saveDescription);
|
||||
descriptor.setPlayTime(metadata.totalPlayTime);
|
||||
descriptor.setSaveDate(metadata.saveYear, metadata.saveMonth, metadata.saveDay);
|
||||
descriptor.setSaveTime(metadata.saveHour, metadata.saveMinute);
|
||||
|
||||
delete metaStream;
|
||||
|
||||
if (!mhk.hasResource(ID_THMB, 1)) {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
Common::SeekableReadStream *thmbStream = mhk.getResource(ID_THMB, 1);
|
||||
if (!thmbStream) {
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
descriptor.setThumbnail(Graphics::loadThumbnail(*thmbStream));
|
||||
|
||||
delete thmbStream;
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
Common::Error RivenSaveLoad::loadGame(Common::String filename) {
|
||||
if (_vm->getFeatures() & GF_DEMO) // Don't load games in the demo
|
||||
return Common::kNoError;
|
||||
|
@ -147,6 +260,20 @@ Common::Error RivenSaveLoad::loadGame(Common::String filename) {
|
|||
}
|
||||
|
||||
delete zips;
|
||||
|
||||
// Load the ScummVM specific save metadata
|
||||
if (mhk->hasResource(ID_META, 1)) {
|
||||
Common::SeekableReadStream *metadataStream = mhk->getResource(ID_META, 1);
|
||||
Common::Serializer serializer = Common::Serializer(metadataStream, nullptr);
|
||||
|
||||
RivenSaveMetadata metadata;
|
||||
metadata.sync(serializer);
|
||||
|
||||
// Set the saved total play time
|
||||
_vm->setTotalPlayTime(metadata.totalPlayTime);
|
||||
|
||||
delete metadataStream;
|
||||
}
|
||||
delete mhk;
|
||||
|
||||
return Common::kNoError;
|
||||
|
@ -233,6 +360,34 @@ Common::MemoryWriteStreamDynamic *RivenSaveLoad::genZIPSSection() {
|
|||
return stream;
|
||||
}
|
||||
|
||||
Common::MemoryWriteStreamDynamic *RivenSaveLoad::genTHMBSection() const {
|
||||
Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic();
|
||||
|
||||
Graphics::saveThumbnail(*stream);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
Common::MemoryWriteStreamDynamic *RivenSaveLoad::genMETASection(const Common::String &desc) const {
|
||||
Common::MemoryWriteStreamDynamic *stream = new Common::MemoryWriteStreamDynamic();
|
||||
Common::Serializer serializer = Common::Serializer(nullptr, stream);
|
||||
|
||||
TimeDate t;
|
||||
_vm->_system->getTimeAndDate(t);
|
||||
|
||||
RivenSaveMetadata metadata;
|
||||
metadata.saveDay = t.tm_mday;
|
||||
metadata.saveMonth = t.tm_mon + 1;
|
||||
metadata.saveYear = t.tm_year + 1900;
|
||||
metadata.saveHour = t.tm_hour;
|
||||
metadata.saveMinute = t.tm_min;
|
||||
metadata.saveDescription = desc;
|
||||
metadata.totalPlayTime = _vm->getTotalPlayTime();
|
||||
metadata.sync(serializer);
|
||||
|
||||
return stream;
|
||||
}
|
||||
|
||||
Common::Error RivenSaveLoad::saveGame(Common::String filename) {
|
||||
// NOTE: This code is designed to only output a Mohawk archive
|
||||
// for a Riven saved game. It's hardcoded to do this because
|
||||
|
@ -255,16 +410,20 @@ Common::Error RivenSaveLoad::saveGame(Common::String filename) {
|
|||
|
||||
debug (0, "Saving game to \'%s\'", filename.c_str());
|
||||
|
||||
Common::MemoryWriteStreamDynamic *versSection = genVERSSection();
|
||||
Common::MemoryWriteStreamDynamic *metaSection = genMETASection(filename);
|
||||
Common::MemoryWriteStreamDynamic *nameSection = genNAMESection();
|
||||
Common::MemoryWriteStreamDynamic *thmbSection = genTHMBSection();
|
||||
Common::MemoryWriteStreamDynamic *varsSection = genVARSSection();
|
||||
Common::MemoryWriteStreamDynamic *versSection = genVERSSection();
|
||||
Common::MemoryWriteStreamDynamic *zipsSection = genZIPSSection();
|
||||
|
||||
// Let's calculate the file size!
|
||||
uint32 fileSize = 142;
|
||||
fileSize += versSection->size();
|
||||
uint32 fileSize = 194;
|
||||
fileSize += metaSection->size();
|
||||
fileSize += nameSection->size();
|
||||
fileSize += thmbSection->size();
|
||||
fileSize += varsSection->size();
|
||||
fileSize += versSection->size();
|
||||
fileSize += zipsSection->size();
|
||||
|
||||
// MHWK Header (8 bytes - total: 8)
|
||||
|
@ -277,93 +436,129 @@ Common::Error RivenSaveLoad::saveGame(Common::String filename) {
|
|||
saveFile->writeUint16BE(1); // Compaction -- original saves have this too
|
||||
saveFile->writeUint32BE(fileSize); // Subtract off the MHWK header size
|
||||
saveFile->writeUint32BE(28); // Absolute offset: right after both headers
|
||||
saveFile->writeUint16BE(70); // File Table Offset
|
||||
saveFile->writeUint16BE(44); // File Table Size (4 bytes count + 4 entries * 10 bytes per entry)
|
||||
saveFile->writeUint16BE(102); // File Table Offset
|
||||
saveFile->writeUint16BE(64); // File Table Size (4 bytes count + 6 entries * 10 bytes per entry)
|
||||
|
||||
// Type Table (4 bytes - total: 32)
|
||||
saveFile->writeUint16BE(36); // String table offset After the Type Table Entries
|
||||
saveFile->writeUint16BE(4); // 4 Type Table Entries
|
||||
saveFile->writeUint16BE(52); // String table offset After the Type Table Entries
|
||||
saveFile->writeUint16BE(6); // 6 Type Table Entries
|
||||
|
||||
// Hardcode Entries (32 bytes - total: 64)
|
||||
// Hardcode Entries (48 bytes - total: 80)
|
||||
// The original engine relies on the entries being sorted by tag alphabetical order
|
||||
// to optimize its lookup algorithm.
|
||||
saveFile->writeUint32BE(ID_META);
|
||||
saveFile->writeUint16BE(66); // Resource table offset
|
||||
saveFile->writeUint16BE(54); // String table offset
|
||||
|
||||
saveFile->writeUint32BE(ID_NAME);
|
||||
saveFile->writeUint16BE(46); // Resource table offset
|
||||
saveFile->writeUint16BE(38); // String table offset
|
||||
saveFile->writeUint16BE(72);
|
||||
saveFile->writeUint16BE(56);
|
||||
|
||||
saveFile->writeUint32BE(ID_THMB);
|
||||
saveFile->writeUint16BE(78);
|
||||
saveFile->writeUint16BE(58);
|
||||
|
||||
saveFile->writeUint32BE(ID_VARS);
|
||||
saveFile->writeUint16BE(52);
|
||||
saveFile->writeUint16BE(40);
|
||||
saveFile->writeUint16BE(84);
|
||||
saveFile->writeUint16BE(60);
|
||||
|
||||
saveFile->writeUint32BE(ID_VERS);
|
||||
saveFile->writeUint16BE(58);
|
||||
saveFile->writeUint16BE(42);
|
||||
saveFile->writeUint16BE(90);
|
||||
saveFile->writeUint16BE(62);
|
||||
|
||||
saveFile->writeUint32BE(ID_ZIPS);
|
||||
saveFile->writeUint16BE(96);
|
||||
saveFile->writeUint16BE(64);
|
||||
saveFile->writeUint16BE(44);
|
||||
|
||||
// Pseudo-String Table (2 bytes - total: 66)
|
||||
// Pseudo-String Table (2 bytes - total: 82)
|
||||
saveFile->writeUint16BE(0); // We don't need a name list
|
||||
|
||||
// Pseudo-Name Tables (8 bytes - total: 74)
|
||||
// Pseudo-Name Tables (12 bytes - total: 94)
|
||||
saveFile->writeUint16BE(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
|
||||
// NAME Section (Resource Table) (6 bytes - total: 80)
|
||||
// META Section (Resource Table) (6 bytes - total: 100)
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(1);
|
||||
|
||||
// VARS Section (Resource Table) (6 bytes - total: 86)
|
||||
// NAME Section (Resource Table) (6 bytes - total: 106)
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(2);
|
||||
|
||||
// VERS Section (Resource Table) (6 bytes - total: 92)
|
||||
// THMB Section (Resource Table) (6 bytes - total: 112)
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(3);
|
||||
|
||||
// ZIPS Section (Resource Table) (6 bytes - total: 98)
|
||||
// VARS Section (Resource Table) (6 bytes - total: 118)
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(4);
|
||||
|
||||
// File Table (4 bytes - total: 102)
|
||||
saveFile->writeUint32BE(4);
|
||||
// VERS Section (Resource Table) (6 bytes - total: 124)
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(5);
|
||||
|
||||
// NAME Section (File Table) (10 bytes - total: 112)
|
||||
saveFile->writeUint32BE(142);
|
||||
// ZIPS Section (Resource Table) (6 bytes - total: 130)
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(1);
|
||||
saveFile->writeUint16BE(6);
|
||||
|
||||
// File Table (4 bytes - total: 134)
|
||||
saveFile->writeUint32BE(6);
|
||||
|
||||
// META Section (File Table) (10 bytes - total: 144)
|
||||
saveFile->writeUint32BE(194);
|
||||
saveFile->writeUint16BE(metaSection->size() & 0xFFFF);
|
||||
saveFile->writeByte((metaSection->size() & 0xFF0000) >> 16);
|
||||
saveFile->writeByte(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
|
||||
// NAME Section (File Table) (10 bytes - total: 154)
|
||||
saveFile->writeUint32BE(194 + metaSection->size());
|
||||
saveFile->writeUint16BE(nameSection->size() & 0xFFFF);
|
||||
saveFile->writeByte((nameSection->size() & 0xFF0000) >> 16);
|
||||
saveFile->writeByte(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
|
||||
// VARS Section (File Table) (10 bytes - total: 122)
|
||||
saveFile->writeUint32BE(142 + nameSection->size());
|
||||
// THMB Section (File Table) (10 bytes - total: 164)
|
||||
saveFile->writeUint32BE(194 + metaSection->size() + nameSection->size());
|
||||
saveFile->writeUint16BE(thmbSection->size() & 0xFFFF);
|
||||
saveFile->writeByte((thmbSection->size() & 0xFF0000) >> 16);
|
||||
saveFile->writeByte(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
|
||||
// VARS Section (File Table) (10 bytes - total: 174)
|
||||
saveFile->writeUint32BE(194 + metaSection->size() + nameSection->size() + thmbSection->size());
|
||||
saveFile->writeUint16BE(varsSection->size() & 0xFFFF);
|
||||
saveFile->writeByte((varsSection->size() & 0xFF0000) >> 16);
|
||||
saveFile->writeByte(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
|
||||
// VERS Section (File Table) (10 bytes - total: 132)
|
||||
saveFile->writeUint32BE(142 + nameSection->size() + varsSection->size());
|
||||
// VERS Section (File Table) (10 bytes - total: 184)
|
||||
saveFile->writeUint32BE(194 + metaSection->size() + nameSection->size() + thmbSection->size() + varsSection->size());
|
||||
saveFile->writeUint16BE(versSection->size() & 0xFFFF);
|
||||
saveFile->writeByte((versSection->size() & 0xFF0000) >> 16);
|
||||
saveFile->writeByte(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
|
||||
// ZIPS Section (File Table) (10 bytes - total: 142)
|
||||
saveFile->writeUint32BE(142 + nameSection->size() + varsSection->size() + versSection->size());
|
||||
// ZIPS Section (File Table) (10 bytes - total: 194)
|
||||
saveFile->writeUint32BE(194 + metaSection->size() + nameSection->size() + thmbSection->size() + varsSection->size() + versSection->size());
|
||||
saveFile->writeUint16BE(zipsSection->size() & 0xFFFF);
|
||||
saveFile->writeByte((zipsSection->size() & 0xFF0000) >> 16);
|
||||
saveFile->writeByte(0);
|
||||
saveFile->writeUint16BE(0);
|
||||
|
||||
saveFile->write(metaSection->getData(), metaSection->size());
|
||||
saveFile->write(nameSection->getData(), nameSection->size());
|
||||
saveFile->write(thmbSection->getData(), thmbSection->size());
|
||||
saveFile->write(varsSection->getData(), varsSection->size());
|
||||
saveFile->write(versSection->getData(), versSection->size());
|
||||
saveFile->write(zipsSection->getData(), zipsSection->size());
|
||||
|
@ -371,9 +566,11 @@ Common::Error RivenSaveLoad::saveGame(Common::String filename) {
|
|||
saveFile->finalize();
|
||||
|
||||
delete saveFile;
|
||||
delete versSection;
|
||||
delete metaSection;
|
||||
delete nameSection;
|
||||
delete thmbSection;
|
||||
delete varsSection;
|
||||
delete versSection;
|
||||
delete zipsSection;
|
||||
|
||||
return Common::kNoError;
|
||||
|
|
|
@ -23,10 +23,13 @@
|
|||
#ifndef MOHAWK_SAVELOAD_H
|
||||
#define MOHAWK_SAVELOAD_H
|
||||
|
||||
#include "common/serializer.h"
|
||||
#include "common/savefile.h"
|
||||
#include "common/str.h"
|
||||
#include "common/memstream.h"
|
||||
|
||||
#include "engines/savestate.h"
|
||||
|
||||
namespace Mohawk {
|
||||
|
||||
class MohawkEngine_Riven;
|
||||
|
@ -36,6 +39,22 @@ enum {
|
|||
kDVDSaveGameVersion = 0x00010100
|
||||
};
|
||||
|
||||
struct RivenSaveMetadata {
|
||||
uint8 saveDay;
|
||||
uint8 saveMonth;
|
||||
uint16 saveYear;
|
||||
|
||||
uint8 saveHour;
|
||||
uint8 saveMinute;
|
||||
|
||||
uint32 totalPlayTime;
|
||||
|
||||
Common::String saveDescription;
|
||||
|
||||
RivenSaveMetadata();
|
||||
bool sync(Common::Serializer &s);
|
||||
};
|
||||
|
||||
class RivenSaveLoad {
|
||||
public:
|
||||
RivenSaveLoad(MohawkEngine_Riven*, Common::SaveFileManager*);
|
||||
|
@ -46,13 +65,18 @@ public:
|
|||
Common::Error saveGame(Common::String);
|
||||
void deleteSave(Common::String);
|
||||
|
||||
static SaveStateDescriptor querySaveMetaInfos(const Common::String &filename);
|
||||
static Common::String querySaveDescription(const Common::String &filename);
|
||||
|
||||
private:
|
||||
MohawkEngine_Riven *_vm;
|
||||
Common::SaveFileManager *_saveFileMan;
|
||||
|
||||
Common::MemoryWriteStreamDynamic *genVERSSection();
|
||||
Common::MemoryWriteStreamDynamic *genNAMESection();
|
||||
Common::MemoryWriteStreamDynamic *genMETASection(const Common::String &desc) const;
|
||||
Common::MemoryWriteStreamDynamic *genTHMBSection() const;
|
||||
Common::MemoryWriteStreamDynamic *genVARSSection();
|
||||
Common::MemoryWriteStreamDynamic *genVERSSection();
|
||||
Common::MemoryWriteStreamDynamic *genZIPSSection();
|
||||
};
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue