diff --git a/engines/pegasus/constants.h b/engines/pegasus/constants.h index dd12727baf58..b9b854066334 100755 --- a/engines/pegasus/constants.h +++ b/engines/pegasus/constants.h @@ -26,6 +26,8 @@ #ifndef PEGASUS_CONSTANTS_H #define PEGASUS_CONSTANTS_H +#include "common/endian.h" + #include "pegasus/types.h" namespace Pegasus { @@ -247,6 +249,21 @@ const tNotificationFlags kNeighborhoodFlags = kNeighborhoodMovieCompletedFlag | kActionRequestCompletedFlag | kDeathExtraCompletedFlag; +const uint32 kPegasusPrimeCreator = MKTAG('J', 'P', 'P', 'P'); +const uint32 kPegasusPrimeContinueType = MKTAG('P', 'P', 'C', 'T'); + +const uint32 kPegasusPrimeDisk1GameType = MKTAG('P', 'P', 'G', '1'); +const uint32 kPegasusPrimeDisk2GameType = MKTAG('P', 'P', 'G', '2'); +const uint32 kPegasusPrimeDisk3GameType = MKTAG('P', 'P', 'G', '3'); +const uint32 kPegasusPrimeDisk4GameType = MKTAG('P', 'P', 'G', '4'); + +// We only support one of the save versions; the rest are from betas +// and we are not supporting them. +const uint32 kPegasusPrimeVersion = 0x00009019; + +const char kNormalSave = 0; +const char kContinueSave = 1; + } // End of namespace Pegasus #endif diff --git a/engines/pegasus/detection.cpp b/engines/pegasus/detection.cpp index 9c486f7548a5..af651c8f22aa 100644 --- a/engines/pegasus/detection.cpp +++ b/engines/pegasus/detection.cpp @@ -25,6 +25,7 @@ #include "engines/advancedDetector.h" #include "common/config-manager.h" #include "common/file.h" +#include "common/savefile.h" #include "pegasus/pegasus.h" @@ -36,7 +37,9 @@ struct PegasusGameDescription { bool PegasusEngine::hasFeature(EngineFeature f) const { return - (f == kSupportsRTL); + (f == kSupportsRTL) + || (f == kSupportsLoadingDuringRuntime) + || (f == kSupportsSavingDuringRuntime); } bool PegasusEngine::isDemo() const { @@ -98,9 +101,44 @@ class PegasusMetaEngine : public AdvancedMetaEngine { return "The Journeyman Project: Pegasus Prime (C) Presto Studios"; } + virtual bool hasFeature(MetaEngineFeature f) const; virtual bool createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const; + virtual SaveStateList listSaves(const char *target) const; + virtual int getMaximumSaveSlot() const { return 999; } + virtual void removeSaveState(const char *target, int slot) const; }; +bool PegasusMetaEngine::hasFeature(MetaEngineFeature f) const { + return + (f == kSupportsListSaves) + || (f == kSupportsDeleteSave); +} + +SaveStateList PegasusMetaEngine::listSaves(const char *target) const { + // The original had no pattern, so the user must rename theirs + // Note that we ignore the target because saves are compatible between + // all versions + Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("pegasus-*.sav"); + + SaveStateList saveList; + for (uint32 i = 0; i < filenames.size(); i++) { + // Isolate the description from the file name + Common::String desc = filenames[i].c_str() + 8; + for (int j = 0; j < 4; j++) + desc.deleteLastChar(); + + saveList.push_back(SaveStateDescriptor(i, desc)); + } + + return saveList; +} + +void PegasusMetaEngine::removeSaveState(const char *target, int slot) const { + // See listSaves() for info on the pattern + Common::StringArray filenames = g_system->getSavefileManager()->listSavefiles("pegasus-*.sav"); + g_system->getSavefileManager()->removeSavefile(filenames[slot].c_str()); +} + bool PegasusMetaEngine::createInstance(OSystem *syst, Engine **engine, const ADGameDescription *desc) const { const Pegasus::PegasusGameDescription *gd = (const Pegasus::PegasusGameDescription *)desc; diff --git a/engines/pegasus/pegasus.cpp b/engines/pegasus/pegasus.cpp index dacc2a0a30ad..3a9c7d1a5f72 100644 --- a/engines/pegasus/pegasus.cpp +++ b/engines/pegasus/pegasus.cpp @@ -25,6 +25,8 @@ #include "common/events.h" #include "common/file.h" #include "common/fs.h" +#include "common/memstream.h" +#include "common/savefile.h" #include "common/textconsole.h" #include "common/translation.h" #include "base/plugins.h" @@ -36,6 +38,7 @@ #include "pegasus/gamestate.h" #include "pegasus/pegasus.h" #include "pegasus/timers.h" +#include "pegasus/items/itemlist.h" #include "pegasus/items/biochips/biochipitem.h" #include "pegasus/items/inventory/inventoryitem.h" @@ -49,6 +52,8 @@ namespace Pegasus { PegasusEngine::PegasusEngine(OSystem *syst, const PegasusGameDescription *gamedesc) : Engine(syst), InputHandler(0), _gameDescription(gamedesc) { + _continuePoint = 0; + _saveAllowed = _loadAllowed = true; } PegasusEngine::~PegasusEngine() { @@ -59,6 +64,7 @@ PegasusEngine::~PegasusEngine() { delete _biochipLid; delete _console; delete _cursor; + delete _continuePoint; } Common::Error PegasusEngine::run() { @@ -340,4 +346,146 @@ void PegasusEngine::removeTimeBase(TimeBase *timeBase) { _timeBases.remove(timeBase); } +bool PegasusEngine::loadFromStream(Common::ReadStream *stream) { + // TODO: Dispose currently running stuff (neighborhood, etc.) + + // Signature + uint32 creator = stream->readUint32BE(); + if (creator != kPegasusPrimeCreator) { + warning("Bad save creator '%s'", tag2str(creator)); + return false; + } + + uint32 gameType = stream->readUint32BE(); + int saveType; + + switch (gameType) { + case kPegasusPrimeDisk1GameType: + case kPegasusPrimeDisk2GameType: + case kPegasusPrimeDisk3GameType: + case kPegasusPrimeDisk4GameType: + saveType = kNormalSave; + break; + case kPegasusPrimeContinueType: + saveType = kContinueSave; + break; + default: + // There are five other possible game types on the Pippin + // version, but hopefully we don't see any of those here + warning("Unhandled pegasus game type '%s'", tag2str(gameType)); + return false; + } + + uint32 version = stream->readUint32BE(); + if (version != kPegasusPrimeVersion) { + warning("Where did you get this save? It's a beta (v%04x)!", version & 0x7fff); + return false; + } + + // Game State + GameState.readGameState(stream); + + // TODO: Energy + stream->readUint32BE(); + + // TODO: Death reason + stream->readByte(); + + // TODO: This is as far as we can go right now + return true; + + // Items + g_allItems.readFromStream(stream); + + // TODO: Player Inventory + // TODO: Player BioChips + // TODO: Disc check + // TODO: Jump to environment + // TODO: AI rules + + // Make a new continue point if this isn't already one + if (saveType == kNormalSave) + makeContinuePoint(); + + return true; +} + +bool PegasusEngine::writeToStream(Common::WriteStream *stream, int saveType) { + // Not ready yet! :P + return false; + + // Signature + stream->writeUint32BE(kPegasusPrimeCreator); + + if (saveType == kNormalSave) { + // TODO: Disc check + stream->writeUint32BE(kPegasusPrimeDisk1GameType); + } else { // Continue + stream->writeUint32BE(kPegasusPrimeContinueType); + } + + stream->writeUint32BE(kPegasusPrimeVersion); + + // Game State + GameState.writeGameState(stream); + + // TODO: Energy + stream->writeUint32BE(0); + + // TODO: Death reason + stream->writeByte(0); + + // Items + g_allItems.writeToStream(stream); + + // TODO: Player Inventory + // TODO: Player BioChips + // TODO: Jump to environment + // TODO: AI rules + return true; +} + +void PegasusEngine::makeContinuePoint() { + delete _continuePoint; + + Common::MemoryWriteStreamDynamic newPoint(DisposeAfterUse::NO); + writeToStream(&newPoint, kContinueSave); + _continuePoint = new Common::MemoryReadStream(newPoint.getData(), newPoint.size(), DisposeAfterUse::YES); +} + +void PegasusEngine::loadFromContinuePoint() { + // Failure to load a continue point is fatal + + if (!_continuePoint) + error("Attempting to load from non-existant continue point"); + + if (!loadFromStream(_continuePoint)) + error("Failed loading continue point"); +} + +Common::Error PegasusEngine::loadGameState(int slot) { + Common::StringArray filenames = _saveFileMan->listSavefiles("pegasus-*.sav"); + Common::InSaveFile *loadFile = _saveFileMan->openForLoading(filenames[slot]); + if (!loadFile) + return Common::kUnknownError; + + bool valid = loadFromStream(loadFile); + warning("pos = %d", loadFile->pos()); + delete loadFile; + + return valid ? Common::kNoError : Common::kUnknownError; +} + +Common::Error PegasusEngine::saveGameState(int slot, const Common::String &desc) { + Common::String output = Common::String::format("pegasus-%s.sav", desc.c_str()); + Common::OutSaveFile *saveFile = _saveFileMan->openForSaving(output); + if (!saveFile) + return Common::kUnknownError; + + bool valid = writeToStream(saveFile, kNormalSave); + delete saveFile; + + return valid ? Common::kNoError : Common::kUnknownError; +} + } // End of namespace Pegasus diff --git a/engines/pegasus/pegasus.h b/engines/pegasus/pegasus.h index 5074aaba0934..8d49050ab671 100644 --- a/engines/pegasus/pegasus.h +++ b/engines/pegasus/pegasus.h @@ -72,22 +72,29 @@ friend class InputHandler; public: PegasusEngine(OSystem *syst, const PegasusGameDescription *gamedesc); virtual ~PegasusEngine(); - + + // Engine stuff const PegasusGameDescription *_gameDescription; bool hasFeature(EngineFeature f) const; GUI::Debugger *getDebugger(); - + bool canLoadGameStateCurrently() { return _loadAllowed; } + bool canSaveGameStateCurrently() { return _saveAllowed; } + Common::Error loadGameState(int slot); + Common::Error saveGameState(int slot, const Common::String &desc); + + // Base classes VideoManager *_video; GraphicsManager *_gfx; Common::MacResManager *_resFork, *_inventoryLid, *_biochipLid; + // Misc. bool isDemo() const; - void addIdler(Idler *idler); void removeIdler(Idler *idler); - void addTimeBase(TimeBase *timeBase); void removeTimeBase(TimeBase *timeBase); + void swapSaveAllowed(bool allow) { _saveAllowed = allow; } + void swapLoadAllowed(bool allow) { _loadAllowed = allow; } protected: Common::Error run(); @@ -148,6 +155,14 @@ friend class InputHandler; // TimeBases Common::List _timeBases; + + // Save/Load + bool loadFromStream(Common::ReadStream *stream); + bool writeToStream(Common::WriteStream *stream, int saveType); + void makeContinuePoint(); + void loadFromContinuePoint(); + Common::ReadStream *_continuePoint; + bool _saveAllowed, _loadAllowed; // It's so nice that this was in the original code already :P }; } // End of namespace Pegasus