aboutsummaryrefslogtreecommitdiff
path: root/engines/prince/saveload.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'engines/prince/saveload.cpp')
-rw-r--r--engines/prince/saveload.cpp508
1 files changed, 508 insertions, 0 deletions
diff --git a/engines/prince/saveload.cpp b/engines/prince/saveload.cpp
new file mode 100644
index 0000000000..83d45a4bd7
--- /dev/null
+++ b/engines/prince/saveload.cpp
@@ -0,0 +1,508 @@
+/* ScummVM - Graphic Adventure Engine
+ *
+ * ScummVM is the legal property of its developers, whose names
+ * are too numerous to list here. Please refer to the COPYRIGHT
+ * file distributed with this source distribution.
+ *
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License
+ * as published by the Free Software Foundation; either version 2
+ * of the License, or (at your option) any later version.
+
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ *
+ */
+
+#include "prince/prince.h"
+#include "prince/graphics.h"
+#include "prince/detection.h"
+#include "prince/flags.h"
+#include "prince/script.h"
+#include "prince/hero.h"
+#include "common/savefile.h"
+#include "common/system.h"
+#include "common/config-manager.h"
+#include "common/memstream.h"
+#include "graphics/thumbnail.h"
+#include "graphics/surface.h"
+#include "graphics/palette.h"
+#include "graphics/scaler.h"
+
+namespace Prince {
+
+#define kBadSVG 99
+#define kSavegameVersion 1
+#define kSavegameStrSize 14
+#define kSavegameStr "SCUMMVM_PRINCE"
+
+class InterpreterFlags;
+class Interpreter;
+
+struct SavegameHeader {
+ uint8 version;
+ Common::String saveName;
+ Graphics::Surface *thumbnail;
+ int saveYear, saveMonth, saveDay;
+ int saveHour, saveMinutes;
+};
+
+int PrinceMetaEngine::getMaximumSaveSlot() const {
+ return 99;
+}
+
+SaveStateList PrinceMetaEngine::listSaves(const char *target) const {
+ Common::SaveFileManager *saveFileMan = g_system->getSavefileManager();
+ Common::StringArray filenames;
+ Common::String pattern = target;
+ pattern += ".???";
+
+ filenames = saveFileMan->listSavefiles(pattern);
+ sort(filenames.begin(), filenames.end()); // Sort (hopefully ensuring we are sorted numerically..)
+
+ SaveStateList saveList;
+ for (Common::StringArray::const_iterator filename = filenames.begin(); filename != filenames.end(); filename++) {
+ // Obtain the last 3 digits of the filename, since they correspond to the save slot
+ int slotNum = atoi(filename->c_str() + filename->size() - 3);
+
+ if (slotNum >= 0 && slotNum <= 99) {
+
+ Common::InSaveFile *file = saveFileMan->openForLoading(*filename);
+ if (file) {
+ Prince::SavegameHeader header;
+
+ // Check to see if it's a ScummVM savegame or not
+ char buffer[kSavegameStrSize + 1];
+ file->read(buffer, kSavegameStrSize + 1);
+
+ if (!strncmp(buffer, kSavegameStr, kSavegameStrSize + 1)) {
+ // Valid savegame
+ if (Prince::PrinceEngine::readSavegameHeader(file, header)) {
+ saveList.push_back(SaveStateDescriptor(slotNum, header.saveName));
+ if (header.thumbnail) {
+ header.thumbnail->free();
+ delete header.thumbnail;
+ }
+ }
+ } else {
+ // Must be an original format savegame
+ saveList.push_back(SaveStateDescriptor(slotNum, "Unknown"));
+ }
+
+ delete file;
+ }
+ }
+ }
+
+ return saveList;
+}
+
+SaveStateDescriptor PrinceMetaEngine::querySaveMetaInfos(const char *target, int slot) const {
+ Common::String fileName = Common::String::format("%s.%03d", target, slot);
+ Common::InSaveFile *f = g_system->getSavefileManager()->openForLoading(fileName);
+
+ if (f) {
+ Prince::SavegameHeader header;
+
+ // Check to see if it's a ScummVM savegame or not
+ char buffer[kSavegameStrSize + 1];
+ f->read(buffer, kSavegameStrSize + 1);
+
+ bool hasHeader = !strncmp(buffer, kSavegameStr, kSavegameStrSize + 1) &&
+ Prince::PrinceEngine::readSavegameHeader(f, header);
+ delete f;
+
+ if (!hasHeader) {
+ // Original savegame perhaps?
+ SaveStateDescriptor desc(slot, "Unknown");
+ return desc;
+ } else {
+ // Create the return descriptor
+ SaveStateDescriptor desc(slot, header.saveName);
+ desc.setThumbnail(header.thumbnail);
+ desc.setSaveDate(header.saveYear, header.saveMonth, header.saveDay);
+ desc.setSaveTime(header.saveHour, header.saveMinutes);
+
+ return desc;
+ }
+ }
+
+ return SaveStateDescriptor();
+}
+
+bool PrinceEngine::readSavegameHeader(Common::InSaveFile *in, SavegameHeader &header) {
+ header.thumbnail = nullptr;
+
+ // Get the savegame version
+ header.version = in->readByte();
+ if (header.version > kSavegameVersion)
+ return false;
+
+ // Read in the string
+ header.saveName.clear();
+ char ch;
+ while ((ch = (char)in->readByte()) != '\0')
+ header.saveName += ch;
+
+ // Get the thumbnail
+ header.thumbnail = Graphics::loadThumbnail(*in);
+ if (!header.thumbnail)
+ return false;
+
+ // Read in save date/time
+ header.saveYear = in->readSint16LE();
+ header.saveMonth = in->readSint16LE();
+ header.saveDay = in->readSint16LE();
+ header.saveHour = in->readSint16LE();
+ header.saveMinutes = in->readSint16LE();
+
+ return true;
+}
+
+void PrinceMetaEngine::removeSaveState(const char *target, int slot) const {
+ Common::String fileName = Common::String::format("%s.%03d", target, slot);
+ g_system->getSavefileManager()->removeSavefile(fileName);
+}
+
+// TODO
+bool PrinceEngine::canSaveGameStateCurrently() {
+ return true;
+}
+
+// TODO
+bool PrinceEngine::canLoadGameStateCurrently() {
+ return true;
+}
+
+Common::Error PrinceEngine::saveGameState(int slot, const Common::String &desc) {
+ // Set up the serializer
+ Common::String slotName = generateSaveName(slot);
+ Common::OutSaveFile *saveFile = g_system->getSavefileManager()->openForSaving(slotName);
+
+ // Write out the ScummVM savegame header
+ SavegameHeader header;
+ header.saveName = desc;
+ header.version = kSavegameVersion;
+ writeSavegameHeader(saveFile, header);
+
+ // Write out the data of the savegame
+ syncGame(nullptr, saveFile);
+
+ // Finish writing out game data
+ saveFile->finalize();
+ delete saveFile;
+
+ return Common::kNoError;
+}
+
+Common::String PrinceEngine::generateSaveName(int slot) {
+ return Common::String::format("%s.%03d", _targetName.c_str(), slot);
+}
+
+void PrinceEngine::writeSavegameHeader(Common::OutSaveFile *out, SavegameHeader &header) {
+ // Write out a savegame header
+ out->write(kSavegameStr, kSavegameStrSize + 1);
+
+ out->writeByte(kSavegameVersion);
+
+ // Write savegame name
+ out->write(header.saveName.c_str(), header.saveName.size() + 1);
+
+ // Get the active palette
+ uint8 thumbPalette[256 * 3];
+ _system->getPaletteManager()->grabPalette(thumbPalette, 0, 256);
+
+ // Create a thumbnail and save it
+ Graphics::Surface *thumb = new Graphics::Surface();
+ Graphics::Surface *s = _graph->_frontScreen; // check inventory / map etc..
+ ::createThumbnail(thumb, (const byte *)s->getPixels(), s->w, s->h, thumbPalette);
+ Graphics::saveThumbnail(*out, *thumb);
+ thumb->free();
+ delete thumb;
+
+ // Write out the save date/time
+ TimeDate td;
+ g_system->getTimeAndDate(td);
+ out->writeSint16LE(td.tm_year + 1900);
+ out->writeSint16LE(td.tm_mon + 1);
+ out->writeSint16LE(td.tm_mday);
+ out->writeSint16LE(td.tm_hour);
+ out->writeSint16LE(td.tm_min);
+}
+
+void PrinceEngine::syncGame(Common::SeekableReadStream *readStream, Common::WriteStream *writeStream) {
+ int emptyRoom = 0x00;
+ int normRoom = 0xFF;
+ byte endInv = 0xFF;
+
+ Common::Serializer s(readStream, writeStream);
+
+ if (s.isSaving()) {
+ // Flag values
+ for (int i = 0; i < _flags->kMaxFlags; i++) {
+ uint32 value = _flags->getFlagValue((Flags::Id)(_flags->kFlagMask + i));
+ s.syncAsUint32LE(value);
+ }
+
+ // Dialog data
+ for (uint32 i = 0; i < _dialogDatSize; i++) {
+ byte value = _dialogDat[i];
+ s.syncAsByte(value);
+ }
+
+ // Location number
+ s.syncAsUint16LE(_locationNr);
+
+ // Rooms
+ for (int roomId = 0; roomId < _script->kMaxRooms; roomId++) {
+ Room *room = new Room();
+ room->loadRoom(_script->getRoomOffset(roomId));
+
+ if (room->_mobs) {
+ s.syncAsByte(normRoom);
+ } else {
+ s.syncAsByte(emptyRoom);
+ delete room;
+ continue;
+ }
+
+ // Mobs
+ for (int mobId = 0; mobId < kMaxMobs; mobId++) {
+ byte value = _script->getMobVisible(room->_mobs, mobId);
+ s.syncAsByte(value);
+ }
+
+ // Background animations
+ for (int backAnimSlot = 0; backAnimSlot < kMaxBackAnims; backAnimSlot++) {
+ uint32 value = _script->getBackAnimId(_room->_backAnim, backAnimSlot);
+ s.syncAsUint32LE(value);
+ }
+
+ // Objects
+ for (int objectSlot = 0; objectSlot < kMaxObjects; objectSlot++) {
+ byte value = _script->getObjId(room->_obj, objectSlot);
+ s.syncAsByte(value);
+ }
+
+ delete room;
+ }
+
+ // Main hero
+ s.syncAsUint16LE(_mainHero->_visible);
+ s.syncAsUint16LE(_mainHero->_middleX);
+ s.syncAsUint16LE(_mainHero->_middleY);
+ s.syncAsUint16LE(_mainHero->_lastDirection);
+ s.syncAsUint32LE(_mainHero->_color);
+ s.syncAsUint16LE(_mainHero->_maxBoredom);
+ s.syncAsUint32LE(_mainHero->_animSetNr);
+
+ for (uint inv1Slot = 0; inv1Slot < _mainHero->_inventory.size(); inv1Slot++) {
+ s.syncAsByte(_mainHero->_inventory[inv1Slot]);
+ }
+ s.syncAsByte(endInv);
+
+ for (uint inv2Slot = 0; inv2Slot < _mainHero->_inventory2.size(); inv2Slot++) {
+ s.syncAsByte(_mainHero->_inventory2[inv2Slot]);
+ }
+ s.syncAsByte(endInv);
+
+ // Second hero
+ s.syncAsUint16LE(_secondHero->_visible);
+ s.syncAsUint16LE(_secondHero->_middleX);
+ s.syncAsUint16LE(_secondHero->_middleY);
+ s.syncAsUint16LE(_secondHero->_lastDirection);
+ s.syncAsUint32LE(_secondHero->_color);
+ s.syncAsUint16LE(_secondHero->_maxBoredom);
+ s.syncAsUint32LE(_secondHero->_animSetNr);
+
+ for (uint inv1Slot = 0; inv1Slot < _secondHero->_inventory.size(); inv1Slot++) {
+ s.syncAsByte(_secondHero->_inventory[inv1Slot]);
+ }
+ s.syncAsByte(endInv);
+
+ for (uint inv2Slot = 0; inv2Slot < _secondHero->_inventory2.size(); inv2Slot++) {
+ s.syncAsByte(_secondHero->_inventory2[inv2Slot]);
+ }
+ s.syncAsByte(endInv);
+
+ } else {
+ // Flag values
+ for (int i = 0; i < _flags->kMaxFlags; i++) {
+ uint32 value = 0;
+ s.syncAsUint32LE(value);
+ _flags->setFlagValue((Flags::Id)(_flags->kFlagMask + i), value);
+ }
+
+ // Dialog data
+ for (uint32 i = 0; i < _dialogDatSize; i++) {
+ byte value = 0;
+ s.syncAsByte(value);
+ _dialogDat[i] = value;
+ }
+
+ // Location number
+ int restoreRoom = 0;
+ s.syncAsUint16LE(restoreRoom);
+ _flags->setFlagValue(Flags::RESTOREROOM, restoreRoom);
+
+ // Rooms
+ for (int roomId = 0; roomId < _script->kMaxRooms; roomId++) {
+ Room *room = new Room();
+ room->loadRoom(_script->getRoomOffset(roomId));
+
+ byte roomType = emptyRoom;
+ s.syncAsByte(roomType);
+ if (roomType == emptyRoom) {
+ delete room;
+ continue;
+ }
+
+ // Mobs
+ for (int mobId = 0; mobId < kMaxMobs; mobId++) {
+ byte value = 0;
+ s.syncAsByte(value);
+ _script->setMobVisible(room->_mobs, mobId, value);
+ }
+
+ // Background animations
+ for (int backAnimSlot = 0; backAnimSlot < kMaxBackAnims; backAnimSlot++) {
+ uint32 value = 0;
+ s.syncAsUint32LE(value);
+ _script->setBackAnimId(_room->_backAnim, backAnimSlot, value);
+ }
+
+ // Objects
+ for (int objectSlot = 0; objectSlot < kMaxObjects; objectSlot++) {
+ byte value = 0;
+ s.syncAsByte(value);
+ _script->setObjId(room->_obj, objectSlot, value);
+ }
+
+ delete room;
+ }
+
+ // Main hero
+ s.syncAsUint16LE(_mainHero->_visible);
+ s.syncAsUint16LE(_mainHero->_middleX);
+ s.syncAsUint16LE(_mainHero->_middleY);
+ s.syncAsUint16LE(_mainHero->_lastDirection);
+ s.syncAsUint32LE(_mainHero->_color);
+ s.syncAsUint16LE(_mainHero->_maxBoredom);
+ s.syncAsUint32LE(_mainHero->_animSetNr);
+ _mainHero->loadAnimSet(_mainHero->_animSetNr);
+
+ _mainHero->_inventory.clear();
+ byte invId = endInv;
+ while (1) {
+ s.syncAsByte(invId);
+ if (invId == endInv) {
+ break;
+ }
+ _mainHero->_inventory.push_back(invId);
+ }
+
+ _mainHero->_inventory2.clear();
+ invId = endInv;
+ while (1) {
+ s.syncAsByte(invId);
+ if (invId == endInv) {
+ break;
+ }
+ _mainHero->_inventory.push_back(invId);
+ }
+
+ // Second hero
+ s.syncAsUint16LE(_secondHero->_visible);
+ s.syncAsUint16LE(_secondHero->_middleX);
+ s.syncAsUint16LE(_secondHero->_middleY);
+ s.syncAsUint16LE(_secondHero->_lastDirection);
+ s.syncAsUint32LE(_secondHero->_color);
+ s.syncAsUint16LE(_secondHero->_maxBoredom);
+ s.syncAsUint32LE(_secondHero->_animSetNr);
+ _secondHero->loadAnimSet(_secondHero->_animSetNr);
+
+ _secondHero->_inventory.clear();
+ invId = endInv;
+ while (1) {
+ s.syncAsByte(invId);
+ if (invId == endInv) {
+ break;
+ }
+ _secondHero->_inventory.push_back(invId);
+ }
+
+ _secondHero->_inventory2.clear();
+ invId = endInv;
+ while (1) {
+ s.syncAsByte(invId);
+ if (invId == endInv) {
+ break;
+ }
+ _secondHero->_inventory.push_back(invId);
+ }
+
+ // Script
+ _interpreter->setBgOpcodePC(0);
+ _interpreter->setFgOpcodePC(_script->_scriptInfo.restoreGame);
+
+ }
+}
+
+Common::Error PrinceEngine::loadGameState(int slot) {
+ if (!loadGame(slot)) {
+ return Common::kReadingFailed;
+ }
+ return Common::kNoError;
+}
+
+bool PrinceEngine::loadGame(int slotNumber) {
+ Common::MemoryReadStream *readStream;
+
+ // Open up the savegame file
+ Common::String slotName = generateSaveName(slotNumber);
+ Common::InSaveFile *saveFile = g_system->getSavefileManager()->openForLoading(slotName);
+
+ // Read the data into a data buffer
+ int size = saveFile->size();
+ byte *dataBuffer = (byte *)malloc(size);
+ saveFile->read(dataBuffer, size);
+ readStream = new Common::MemoryReadStream(dataBuffer, size, DisposeAfterUse::YES);
+ delete saveFile;
+
+ // Check to see if it's a ScummVM savegame or not
+ char buffer[kSavegameStrSize + 1];
+ readStream->read(buffer, kSavegameStrSize + 1);
+
+ if (strncmp(buffer, kSavegameStr, kSavegameStrSize + 1) != 0) {
+ delete readStream;
+ return false;
+ } else {
+ SavegameHeader saveHeader;
+
+ if (!readSavegameHeader(readStream, saveHeader)) {
+ delete readStream;
+ return false;
+ }
+
+ // Delete the thumbnail
+ saveHeader.thumbnail->free();
+ delete saveHeader.thumbnail;
+ }
+
+ // Get in the savegame
+ syncGame(readStream, nullptr);
+ delete readStream;
+
+ // TODO
+ //syncSpeechSettings();
+
+ return true;
+}
+
+} // End of namespace Prince