From f78bb08b1850b349f28b3cb5f1357fdebd5b8e00 Mon Sep 17 00:00:00 2001 From: Bastien Bouclet Date: Sun, 10 Jul 2016 20:43:34 +0200 Subject: MOHAWK: Save ScummVM specific metadata in the Riven saves - Thumbnail - Save date - Save description - Total play time --- engines/mohawk/detection.cpp | 17 ++- engines/mohawk/resource.h | 2 + engines/mohawk/riven_saveload.cpp | 265 +++++++++++++++++++++++++++++++++----- engines/mohawk/riven_saveload.h | 26 +++- 4 files changed, 272 insertions(+), 38 deletions(-) (limited to 'engines') diff --git a/engines/mohawk/detection.cpp b/engines/mohawk/detection.cpp index 7c202998eb..e1aac64098 100644 --- a/engines/mohawk/detection.cpp +++ b/engines/mohawk/detection.cpp @@ -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 { diff --git a/engines/mohawk/resource.h b/engines/mohawk/resource.h index d9074a5b73..12c5a139e4 100644 --- a/engines/mohawk/resource.h +++ b/engines/mohawk/resource.h @@ -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 diff --git a/engines/mohawk/riven_saveload.cpp b/engines/mohawk/riven_saveload.cpp index 7bb8582edc..4d52179372 100644 --- a/engines/mohawk/riven_saveload.cpp +++ b/engines/mohawk/riven_saveload.cpp @@ -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,11 +66,96 @@ 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; - Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename); + Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename); if (!loadFile) return Common::kReadingFailed; @@ -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; diff --git a/engines/mohawk/riven_saveload.h b/engines/mohawk/riven_saveload.h index a6ddce5713..2ef4326439 100644 --- a/engines/mohawk/riven_saveload.h +++ b/engines/mohawk/riven_saveload.h @@ -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(); }; -- cgit v1.2.3