Files
2026-02-02 04:50:13 +01:00

471 lines
14 KiB
C++

/* 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 <http://www.gnu.org/licenses/>.
*
*/
#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