/* 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 3 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, see . * */ #include "common/file.h" #include "asylum/system/savegame.h" #include "asylum/puzzles/puzzles.h" #include "asylum/resources/encounters.h" #include "asylum/resources/script.h" #include "asylum/resources/worldstats.h" #include "asylum/system/cursor.h" #include "asylum/system/screen.h" #include "asylum/system/text.h" #include "asylum/views/menu.h" #include "asylum/views/scene.h" #include "asylum/asylum.h" namespace Asylum { #define SAVEGAME_BUILD 851 #define SAVEGAME_VERSION_SIZE 11 #define SAVEGAME_NAME_SIZE 45 #define SAVEGAME_QUICKSLOT 24 static const char *savegame_version = "v1.01 FINAL"; Savegame::Savegame(AsylumEngine *engine) : _vm(engine), _index(0) { memset(&_moviesViewed, 0, sizeof(_moviesViewed)); memset(&_savegames, 0, sizeof(_savegames)); memset(&_savegameToScene, 0, sizeof(_savegameToScene)); resetVersion(); } void Savegame::resetVersion() { _version = savegame_version; _build = SAVEGAME_BUILD; } bool Savegame::hasSavegames() const { for (uint i = 0; i < SAVEGAME_COUNT; i++) if (isSavegamePresent(getFilename(i))) return true; return false; } void Savegame::loadList() { for (uint32 i = 0; i < SAVEGAME_COUNT; i++) { if (isSavegamePresent(getFilename(i))) { Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(getFilename(i)); if (!file) error("[Savegame::loadList] Cannot open savegame: %s", getFilename(i).c_str()); // Check file size (we handle empty files, but not invalid ones) if (file->size() == 0) { _names[i] = getText()->get(MAKE_RESOURCE(kResourcePackText, 1324)); _savegames[i] = false; } else { _savegameToScene[i] = read(file, "Level"); _names[i] = read(file, 45, "Game Name"); _savegames[i] = true; } delete file; } else { _names[i] = getText()->get(MAKE_RESOURCE(kResourcePackText, 1324)); _savegames[i] = false; } } } void Savegame::load() { getCursor()->hide(); // Original clears the graphic cache getScript()->resetQueue(); getSound()->playMusic(kResourceNone, 0); getScene()->load((ResourcePackId)(_savegameToScene[_index] + 4)); _vm->reset(); // Original loads encounter data loadData(getFilename(_index)); loadMoviesViewed(); getMenu()->setDword455C80(false); getScreen()->clear(); } bool Savegame::quickLoad() { if (!isSavegamePresent(getFilename(SAVEGAME_QUICKSLOT))) return false; _index = SAVEGAME_QUICKSLOT; (void)_vm->startGame(getScenePack(), AsylumEngine::kStartGameLoad); return true; } void Savegame::save() { // Original creates a folder to hold saved games and checks for disk space, we can skip that getCursor()->hide(); saveData(getFilename(_index), _names[_index], getWorld()->chapter); _savegames[_index] = true; getMenu()->setDword455C78(true); getMenu()->setDword455C80(false); getCursor()->show(); } bool Savegame::quickSave() { _index = 24; // Check if there is a quick save already if (!isSavegamePresent(getFilename(SAVEGAME_QUICKSLOT))) { _names[_index] = getText()->get(MAKE_RESOURCE(kResourcePackText, 1342)); save(); } else { Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(getFilename(SAVEGAME_QUICKSLOT)); if (!file) return false; // Read game name seek(file, 1, "Level"); _names[_index] = read(file, 45, "Game Name"); delete file; save(); } return true; } void Savegame::remove() { if (_index >= ARRAYSIZE(_savegames)) error("[Savegame::remove] Invalid savegame index"); getCursor()->hide(); g_system->getSavefileManager()->removeSavefile(getFilename(_index)); // Update status and name _savegames[_index] = false; _names[_index] = getText()->get(MAKE_RESOURCE(kResourcePackText, 1344)); getMenu()->setDword455C80(false); getCursor()->show(); } ////////////////////////////////////////////////////////////////////////// // Helpers ////////////////////////////////////////////////////////////////////////// bool Savegame::isCompatible() { Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(getFilename(_index)); assert(file); seek(file, 2, "Level and Name"); bool result = readHeader(file); delete file; return result; } Common::String Savegame::getFilename(uint32 index) const { if (index > SAVEGAME_COUNT - 1) error("[Savegame::getFilename] Invalid savegame index (was:%d, valid: [0-24])", index); return _vm->getSaveStateName(index); } bool Savegame::isSavegamePresent(const Common::String &filename) const { if (g_system->getSavefileManager()->listSavefiles(filename).size() == 0) return false; Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(filename); if (!file) return false; bool isSaveValid = (file->size() == 0) ? false : true; delete file; return isSaveValid; } ////////////////////////////////////////////////////////////////////////// // Reading & writing ////////////////////////////////////////////////////////////////////////// bool Savegame::readHeader(Common::InSaveFile *file) { uint32 versionLength = read(file, "Version Length"); _version = read(file, versionLength, "Version"); _build = read(file, "Build"); // Original does not do any version check return !strcmp(_version.c_str(), savegame_version) && _build == SAVEGAME_BUILD; } void Savegame::writeHeader(Common::OutSaveFile *file) const { // We write saved games with a 1.01 final version (build 851) write(file, SAVEGAME_VERSION_SIZE, "Version Length"); write(file, Common::String(savegame_version), SAVEGAME_VERSION_SIZE, "Version"); write(file, SAVEGAME_BUILD, "Build"); } void Savegame::loadData(const Common::String &filename) { Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(filename); assert(file); seek(file, 1, "Level"); seek(file, 1, "Game Name"); (void)readHeader(file); read(file, _vm, 1512, 1, "Game Stats"); read(file, getWorld(), 951928, 1, "World Stats"); read(file, getPuzzles(), 752, 1, "Blowup Puzzle Data"); read(file, getEncounter()->items(), 109, getEncounter()->items()->size(), "Encounter Data"); read(file, getEncounter()->variables(), 2, getEncounter()->variables()->size(), "Encounter Variables"); getScript()->reset(getWorld()->numScripts); if (getWorld()->numScripts) read(file, getScript(), 7096, (uint32)getWorld()->numScripts, "Action Lists"); uint32 tick = read(file, "Time"); _vm->setTick(tick); delete file; } void Savegame::saveData(const Common::String &filename, const Common::String &name, ChapterIndex chapter) { Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving(filename); assert(file); write(file, (unsigned) (int32)chapter, "Level"); write(file, name, SAVEGAME_NAME_SIZE, "Game Name"); writeHeader(file); write(file, _vm, 1512, 1, "Game Stats"); write(file, getWorld(), 951928, 1, "World Stats"); write(file, getPuzzles(), 752, 1, "Blowup Puzzle Data"); write(file, getEncounter()->items(), 109, getEncounter()->items()->size(), "Encounter Data"); write(file, getEncounter()->variables(), 2, getEncounter()->variables()->size(), "Encounter Variables"); if (getWorld()->numScripts) write(file, getScript(), 7096, (uint32)getWorld()->numScripts, "Action Lists"); write(file, _vm->getTick(), "Time"); _vm->getMetaEngine()->appendExtendedSaveToStream(file, _vm->getTotalPlayTime() / 1000, name, false); delete file; } void Savegame::seek(Common::InSaveFile *file, uint32 offset, const Common::String &description) { debugC(kDebugLevelSavegame, "[Savegame] Seeking to offset: %s", description.c_str()); if (offset == 0) return; uint32 size = 0; uint32 count = 0; for (uint i = 0; i < offset; i++) { size = file->readUint32LE(); count = file->readUint32LE(); file->seek(size * count, SEEK_CUR); } } uint32 Savegame::read(Common::InSaveFile *file, const Common::String &description) { debugC(kDebugLevelSavegame, "[Savegame] Reading %s", description.c_str()); uint32 size = file->readUint32LE(); uint32 count = file->readUint32LE(); if (size * count == 0) return 0; return file->readUint32LE(); } Common::String Savegame::read(Common::InSaveFile *file, uint32 strLength, const Common::String &description) { debugC(kDebugLevelSavegame, "[Savegame] Reading %s (of length %d)", description.c_str(), strLength); /*uint32 size =*/ file->readUint32LE(); uint32 count = file->readUint32LE(); if (strLength > count) error("[Savegame::read] Count too large (asked: %d, present: %d)", strLength, count); char *str = new char[strLength + 1](); file->read(str, strLength); Common::String ret(str); delete[] str; return ret; } void Savegame::read(Common::InSaveFile *file, Common::Serializable *data, uint32 size, uint32 count, const Common::String &description) { debugC(kDebugLevelSavegame, "[Savegame] Reading %s (%d block(s) of size %d)", description.c_str(), size, count); uint32 fileSize = file->readUint32LE(); if (size > fileSize) error("[Savegame::read] Size too large (asked: %d, present: %d)", size, fileSize); uint32 fileCount = file->readUint32LE(); if (count > fileCount) error("[Savegame::read] Count too large (asked: %d, present: %d)", count, fileCount); if (fileCount * fileSize == 0) return; Common::Serializer ser(file, nullptr); data->saveLoadWithSerializer(ser); } void Savegame::write(Common::OutSaveFile *file, uint32 val, const Common::String &description) { debugC(kDebugLevelSavegame, "[Savegame] Writing %s: %d", description.c_str(), val); file->writeUint32LE(4); file->writeUint32LE(1); file->writeUint32LE(val); } void Savegame::write(Common::OutSaveFile *file, const Common::String &val, uint32 strLength, const Common::String &description) { debugC(kDebugLevelSavegame, "[Savegame] Writing %s (of length %d): %s", description.c_str(), strLength, val.c_str()); if (val.size() > strLength) error("[Savegame::write] Trying to save a string that is longer than the specified size (string size: %d, size: %d)", val.size(), strLength); file->writeUint32LE(1); file->writeUint32LE(strLength); file->writeString(val); // Add padding if (val.size() < strLength) { for (uint32 i = 0; i < (strLength - val.size()); i++) file->writeByte(0); } } void Savegame::write(Common::OutSaveFile *file, Common::Serializable *data, uint32 size, uint32 count, const Common::String &description) { debugC(kDebugLevelSavegame, "[Savegame] Writing %s (%d block(s) of size %d)", description.c_str(), size, count); file->writeUint32LE(size); file->writeUint32LE(count); if (size * count == 0) return; Common::Serializer ser(nullptr, file); uint before = ser.bytesSynced(); // Save the data data->saveLoadWithSerializer(ser); // Check we wrote the correct amount of data uint after = ser.bytesSynced(); if ((after - before) != (size * count)) error("[Savegame::write] Invalid number of bytes written to file (was: %d, expected: %d)", after - before, size * count); } ////////////////////////////////////////////////////////////////////////// // Movies ////////////////////////////////////////////////////////////////////////// void Savegame::setMovieViewed(uint32 index) { if (index >= ARRAYSIZE(_moviesViewed)) error("[Savegame::setMovieViewed] Invalid movie index!"); if (!_moviesViewed[index]) { _moviesViewed[index] = 1; // Write data to disk Common::OutSaveFile *movies = g_system->getSavefileManager()->openForSaving(_vm->getMoviesFileName()); if (!movies) error("[Savegame::setMovieViewed] Could not open viewed movie list!"); movies->write((byte *)&_moviesViewed, sizeof(_moviesViewed)); delete movies; } } uint32 Savegame::getMoviesViewed(int32 *movieList) const { memset(movieList, -1, 196 * sizeof(int32)); uint32 count = 0; for (uint32 i = 0; i < ARRAYSIZE(_moviesViewed); i++) { if (_moviesViewed[i]) { movieList[count] = i; ++count; } } return count; } void Savegame::loadMoviesViewed() { if (!isSavegamePresent(_vm->getMoviesFileName())) return; // Load data from disk Common::InSaveFile *movies = g_system->getSavefileManager()->openForLoading(_vm->getMoviesFileName()); if (!movies) error("[Savegame::setMovieViewed] Could not open viewed movie list!"); movies->read((byte *)&_moviesViewed, sizeof(_moviesViewed)); delete movies; } ////////////////////////////////////////////////////////////////////////// // Accessors ////////////////////////////////////////////////////////////////////////// void Savegame::setName(uint32 index, const Common::String &name) { if (index >= ARRAYSIZE(_names)) error("[Savegame::setName] Invalid index (was: %d, max: %d)", index, ARRAYSIZE(_names) - 1); _names[index] = name; } Common::String Savegame::getName(uint32 index) const { if (index >= ARRAYSIZE(_names)) error("[Savegame::getName] Invalid index (was: %d, max: %d)", index, ARRAYSIZE(_names) - 1); return _names[index]; } bool Savegame::hasSavegame(uint32 index) const { if (index >= ARRAYSIZE(_savegames)) error("[Savegame::hasSavegame] Invalid index (was: %d, max: %d)", index, ARRAYSIZE(_savegames) - 1); return _savegames[index]; } } // End of namespace Asylum