aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--engines/mohawk/detection.cpp41
-rw-r--r--engines/mohawk/myst.cpp10
-rw-r--r--engines/mohawk/myst_state.cpp205
-rw-r--r--engines/mohawk/myst_state.h37
4 files changed, 255 insertions, 38 deletions
diff --git a/engines/mohawk/detection.cpp b/engines/mohawk/detection.cpp
index 926c296257..986b35c85e 100644
--- a/engines/mohawk/detection.cpp
+++ b/engines/mohawk/detection.cpp
@@ -35,6 +35,7 @@
#ifdef ENABLE_MYST
#include "mohawk/myst.h"
+#include "mohawk/myst_state.h"
#endif
#ifdef ENABLE_RIVEN
@@ -184,13 +185,18 @@ public:
virtual SaveStateList listSaves(const char *target) const;
virtual int getMaximumSaveSlot() const { return 999; }
virtual void removeSaveState(const char *target, int slot) const;
+ virtual SaveStateDescriptor querySaveMetaInfos(const char *target, int slot) const;
};
bool MohawkMetaEngine::hasFeature(MetaEngineFeature f) const {
return
(f == kSupportsListSaves)
|| (f == kSupportsLoadingDuringStartup)
- || (f == kSupportsDeleteSave);
+ || (f == kSupportsDeleteSave)
+ || (f == kSavesSupportMetaInfo)
+ || (f == kSavesSupportThumbnail)
+ || (f == kSavesSupportCreationDate)
+ || (f == kSavesSupportPlayTime);
}
SaveStateList MohawkMetaEngine::listSaves(const char *target) const {
@@ -198,12 +204,15 @@ SaveStateList MohawkMetaEngine::listSaves(const char *target) const {
SaveStateList saveList;
// Loading games is only supported in Myst/Riven currently.
+#ifdef ENABLE_MYST
if (strstr(target, "myst")) {
- filenames = g_system->getSavefileManager()->listSavefiles("*.mys");
+ filenames = Mohawk::MystGameState::generateSaveGameList();
for (uint32 i = 0; i < filenames.size(); i++)
saveList.push_back(SaveStateDescriptor(i, filenames[i]));
- } else if (strstr(target, "riven")) {
+ } else
+#endif
+ if (strstr(target, "riven")) {
filenames = g_system->getSavefileManager()->listSavefiles("*.rvn");
for (uint32 i = 0; i < filenames.size(); i++)
@@ -215,15 +224,35 @@ SaveStateList MohawkMetaEngine::listSaves(const char *target) const {
void MohawkMetaEngine::removeSaveState(const char *target, int slot) const {
// Removing saved games is only supported in Myst/Riven currently.
+#ifdef ENABLE_MYST
if (strstr(target, "myst")) {
- Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("*.mys");
- g_system->getSavefileManager()->removeSavefile(filenames[slot].c_str());
- } else if (strstr(target, "riven")) {
+ Common::StringArray filenames = Mohawk::MystGameState::generateSaveGameList();
+ Mohawk::MystGameState::deleteSave(filenames[slot]);
+ } else
+#endif
+ if (strstr(target, "riven")) {
Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("*.rvn");
g_system->getSavefileManager()->removeSavefile(filenames[slot].c_str());
}
}
+SaveStateDescriptor MohawkMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+#ifdef ENABLE_MYST
+ if (strstr(target, "myst")) {
+ Common::StringArray filenames = Mohawk::MystGameState::generateSaveGameList();
+
+ if (slot >= (int) filenames.size()) {
+ return SaveStateDescriptor();
+ }
+
+ return Mohawk::MystGameState::querySaveMetaInfos(filenames[slot]);
+ } else
+#endif
+ {
+ return SaveStateDescriptor();
+ }
+}
+
bool MohawkMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const {
const Mohawk::MohawkGameDescription *gd = (const Mohawk::MohawkGameDescription *)desc;
diff --git a/engines/mohawk/myst.cpp b/engines/mohawk/myst.cpp
index 852196e6ac..5337a5e773 100644
--- a/engines/mohawk/myst.cpp
+++ b/engines/mohawk/myst.cpp
@@ -236,7 +236,7 @@ Common::Error MohawkEngine_Myst::run() {
// Load game from launcher/command line if requested
if (ConfMan.hasKey("save_slot") && canLoadGameStateCurrently()) {
uint32 gameToLoad = ConfMan.getInt("save_slot");
- Common::StringArray savedGamesList = _gameState->generateSaveGameList();
+ Common::StringArray savedGamesList = MystGameState::generateSaveGameList();
if (gameToLoad > savedGamesList.size())
error ("Could not find saved game");
_gameState->load(savedGamesList[gameToLoad]);
@@ -1066,19 +1066,19 @@ void MohawkEngine_Myst::loadResources() {
}
Common::Error MohawkEngine_Myst::loadGameState(int slot) {
- if (_gameState->load(_gameState->generateSaveGameList()[slot]))
+ if (_gameState->load(MystGameState::generateSaveGameList()[slot]))
return Common::kNoError;
return Common::kUnknownError;
}
Common::Error MohawkEngine_Myst::saveGameState(int slot, const Common::String &desc) {
- Common::StringArray saveList = _gameState->generateSaveGameList();
+ Common::StringArray saveList = MystGameState::generateSaveGameList();
if ((uint)slot < saveList.size())
- _gameState->deleteSave(saveList[slot]);
+ MystGameState::deleteSave(saveList[slot]);
- return _gameState->save(Common::String(desc)) ? Common::kNoError : Common::kUnknownError;
+ return _gameState->save(desc) ? Common::kNoError : Common::kUnknownError;
}
bool MohawkEngine_Myst::canLoadGameStateCurrently() {
diff --git a/engines/mohawk/myst_state.cpp b/engines/mohawk/myst_state.cpp
index 9bfbba67b6..06cd69b23c 100644
--- a/engines/mohawk/myst_state.cpp
+++ b/engines/mohawk/myst_state.cpp
@@ -26,11 +26,41 @@
#include "common/debug.h"
#include "common/serializer.h"
+#include "common/system.h"
#include "common/textconsole.h"
#include "common/util.h"
+#include "graphics/thumbnail.h"
+
namespace Mohawk {
+MystSaveMetadata::MystSaveMetadata() {
+ saveDay = 0;
+ saveMonth = 0;
+ saveYear = 0;
+ saveHour = 0;
+ saveMinute = 0;
+ totalPlayTime = 0;
+}
+
+bool MystSaveMetadata::sync(Common::Serializer &s) {
+ static const Common::Serializer::Version kCurrentVersion = 1;
+
+ if (!s.syncVersion(kCurrentVersion)) {
+ return false;
+ }
+
+ s.syncAsByte(saveDay);
+ s.syncAsByte(saveMonth);
+ s.syncAsUint16LE(saveYear);
+ s.syncAsByte(saveHour);
+ s.syncAsByte(saveMinute);
+ s.syncString(saveDescription);
+ s.syncAsUint32LE(totalPlayTime);
+
+ return true;
+}
+
MystGameState::MystGameState(MohawkEngine_Myst *vm, Common::SaveFileManager *saveFileMan) : _vm(vm), _saveFileMan(saveFileMan) {
// Most of the variables are zero at game start.
memset(&_globals, 0, sizeof(_globals));
@@ -77,28 +107,15 @@ MystGameState::~MystGameState() {
}
Common::StringArray MystGameState::generateSaveGameList() {
- return _saveFileMan->listSavefiles("*.mys");
+ return g_system->getSavefileManager()->listSavefiles("*.mys");
}
bool MystGameState::load(const Common::String &filename) {
- Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename);
- if (!loadFile)
- return false;
-
- debugC(kDebugSaveLoad, "Loading game from '%s'", filename.c_str());
-
- // First, let's make sure we're using a saved game file from this version of Myst
- // By checking length of file...
- int32 size = loadFile->size();
- if (size != 664 && size != 601) {
- warning("Incompatible saved game version");
- delete loadFile;
+ if (!loadState(filename)) {
return false;
}
- Common::Serializer s(loadFile, nullptr);
- syncGameState(s, size == 664);
- delete loadFile;
+ loadMetadata(filename);
// Set Channelwood elevator state to down, because we start on the lower level
_channelwood.elevatorState = 0;
@@ -119,15 +136,72 @@ bool MystGameState::load(const Common::String &filename) {
return true;
}
-bool MystGameState::save(const Common::String &fname) {
- Common::String filename(fname);
- // Make sure we have the right extension
- if (!filename.hasSuffix(".mys") && !filename.hasSuffix(".MYS"))
- filename += ".mys";
+bool MystGameState::loadState(const Common::String &filename) {
+ Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filename);
+ if (!loadFile) {
+ return false;
+ }
+
+ debugC(kDebugSaveLoad, "Loading game from '%s'", filename.c_str());
+
+ // First, let's make sure we're using a saved game file from this version of Myst
+ // By checking length of file...
+ int32 size = loadFile->size();
+ if (size != 664 && size != 601) {
+ warning("Incompatible saved game version");
+ delete loadFile;
+ return false;
+ }
+
+ Common::Serializer s(loadFile, nullptr);
+ syncGameState(s, size == 664);
+ delete loadFile;
+
+ return true;
+}
+void MystGameState::loadMetadata(const Common::String &filename) {
+ // Open the metadata file
+ Common::InSaveFile *metadataFile = openMetadataFile(filename);
+ if (!metadataFile) {
+ return;
+ }
+
+ debugC(kDebugSaveLoad, "Loading metadata from '%s'", filename.c_str());
+
+ Common::Serializer m(metadataFile, nullptr);
+
+ // Read the metadata file
+ if (_metadata.sync(m)) {
+ _vm->setTotalPlayTime(_metadata.totalPlayTime);
+ }
+
+ delete metadataFile;
+}
+
+bool MystGameState::save(const Common::String &filename) {
+ // Make sure the description does not have an extension
+ Common::String desc = filename;
+ if (filename.hasSuffix(".mys") || filename.hasSuffix(".MYS")) {
+ desc = removeExtension(filename);
+ }
+
+ if (!saveState(desc)) {
+ return false;
+ }
+
+ updateMetadateForSaving(desc);
+
+ return saveMetadata(desc);
+}
+
+bool MystGameState::saveState(const Common::String &desc) {
+ // Make sure we have the right extension
+ Common::String filename = desc + ".mys";
Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(filename);
- if (!saveFile)
+ if (!saveFile) {
return false;
+ }
debugC(kDebugSaveLoad, "Saving game to '%s'", filename.c_str());
@@ -139,6 +213,88 @@ bool MystGameState::save(const Common::String &fname) {
return true;
}
+void MystGameState::updateMetadateForSaving(const Common::String &desc) {
+ // Update save creation info
+ TimeDate t;
+ g_system->getTimeAndDate(t);
+ _metadata.saveYear = t.tm_year + 1900;
+ _metadata.saveMonth = t.tm_mon + 1;
+ _metadata.saveDay = t.tm_mday;
+ _metadata.saveHour = t.tm_hour;
+ _metadata.saveMinute = t.tm_min;
+ _metadata.saveDescription = desc;
+ _metadata.totalPlayTime = _vm->getTotalPlayTime();
+}
+
+bool MystGameState::saveMetadata(const Common::String &desc) {
+ // Write the metadata to a separate file so that the save files
+ // are still compatible with the original engine
+ Common::String metadataFilename = desc + ".mym";
+ Common::OutSaveFile *metadataFile = _saveFileMan->openForSaving(metadataFilename);
+ if (!metadataFile) {
+ return false;
+ }
+
+ // Save the metadata
+ Common::Serializer m(nullptr, metadataFile);
+ _metadata.sync(m);
+
+ // Append a thumbnail
+ Graphics::saveThumbnail(*metadataFile);
+
+ metadataFile->finalize();
+ delete metadataFile;
+
+ return true;
+}
+
+SaveStateDescriptor MystGameState::querySaveMetaInfos(const Common::String filename) {
+ SaveStateDescriptor desc;
+ desc.setDescription(filename);
+
+ // Open the metadata file
+ Common::InSaveFile *metadataFile = openMetadataFile(filename);
+ if (!metadataFile) {
+ return desc;
+ }
+
+ Common::Serializer m(metadataFile, nullptr);
+
+ // Read the metadata file
+ Mohawk::MystSaveMetadata metadata;
+ if (!metadata.sync(m)) {
+ delete metadataFile;
+ return desc;
+ }
+
+ // Set the save description
+ desc.setDescription(metadata.saveDescription);
+ desc.setSaveDate(metadata.saveYear, metadata.saveMonth, metadata.saveDay);
+ desc.setSaveTime(metadata.saveHour, metadata.saveMinute);
+ desc.setPlayTime(metadata.totalPlayTime);
+ desc.setThumbnail(Graphics::loadThumbnail(*metadataFile));
+
+ delete metadataFile;
+
+ return desc;
+}
+
+Common::InSaveFile *MystGameState::openMetadataFile(const Common::String &filename) {
+ // Remove the extension
+ Common::String baseName = removeExtension(filename);
+
+ // Open the metadata file
+ return g_system->getSavefileManager()->openForLoading(baseName + ".mym");
+}
+
+Common::String MystGameState::removeExtension(const Common::String &filename) {
+ Common::String baseName = filename;
+ for (uint i = 0; i < 4; i++) {
+ baseName.deleteLastChar();
+ }
+ return baseName;
+}
+
void MystGameState::syncGameState(Common::Serializer &s, bool isME) {
// Globals first
s.syncAsUint16LE(_globals.u0);
@@ -317,7 +473,10 @@ void MystGameState::syncGameState(Common::Serializer &s, bool isME) {
void MystGameState::deleteSave(const Common::String &saveName) {
debugC(kDebugSaveLoad, "Deleting save file \'%s\'", saveName.c_str());
- _saveFileMan->removeSavefile(saveName.c_str());
+ Common::String basename = removeExtension(saveName);
+
+ g_system->getSavefileManager()->removeSavefile(saveName);
+ g_system->getSavefileManager()->removeSavefile(basename + ".mym");
}
void MystGameState::addZipDest(uint16 stack, uint16 view) {
diff --git a/engines/mohawk/myst_state.h b/engines/mohawk/myst_state.h
index b07a0f2469..50359a5b52 100644
--- a/engines/mohawk/myst_state.h
+++ b/engines/mohawk/myst_state.h
@@ -27,6 +27,8 @@
#include "common/file.h"
#include "common/str.h"
+#include "engines/savestate.h"
+
namespace Common {
class Serializer;
}
@@ -35,15 +37,33 @@ namespace Mohawk {
class MohawkEngine_Myst;
+struct MystSaveMetadata {
+ uint8 saveDay;
+ uint8 saveMonth;
+ uint16 saveYear;
+
+ uint8 saveHour;
+ uint8 saveMinute;
+
+ uint32 totalPlayTime;
+
+ Common::String saveDescription;
+
+ MystSaveMetadata();
+ bool sync(Common::Serializer &s);
+};
+
class MystGameState {
public:
MystGameState(MohawkEngine_Myst*, Common::SaveFileManager*);
~MystGameState();
- Common::StringArray generateSaveGameList();
- bool load(const Common::String &);
- bool save(const Common::String &);
- void deleteSave(const Common::String &);
+ static Common::StringArray generateSaveGameList();
+ static SaveStateDescriptor querySaveMetaInfos(const Common::String filename);
+
+ bool load(const Common::String &filename);
+ bool save(const Common::String &filename);
+ static void deleteSave(const Common::String &saveName);
void addZipDest(uint16 stack, uint16 view);
bool isReachableZipDest(uint16 stack, uint16 view);
@@ -268,8 +288,17 @@ public:
uint32 generatorDepletionTime;
} _stoneship;
+ MystSaveMetadata _metadata;
+
private:
void syncGameState(Common::Serializer &s, bool isME);
+ static Common::InSaveFile *openMetadataFile(const Common::String &filename);
+ bool loadState(const Common::String &filename);
+ void loadMetadata(const Common::String &filename);
+ bool saveState(const Common::String &desc);
+ void updateMetadateForSaving(const Common::String &desc);
+ bool saveMetadata(const Common::String &desc);
+ static Common::String removeExtension(const Common::String &filename);
// The values in these regions are lists of VIEW resources
// which correspond to visited zip destinations