516 lines
15 KiB
C++
516 lines
15 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 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 "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
|
|
|
|
#define SAVEGAME_MOVIES "asylum.movies"
|
|
|
|
static const char *savegame_version = "v1.01 FINAL";
|
|
|
|
Savegame::Savegame(AsylumEngine *engine) : _vm(engine), _index(0), _valid(false) {
|
|
memset(&_moviesViewed, 0, sizeof(_moviesViewed));
|
|
memset(&_savegames, 0, sizeof(_savegames));
|
|
memset(&_savegameToScene, 0, sizeof(_savegameToScene));
|
|
}
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
bool Savegame::load() {
|
|
if (!check()) {
|
|
getMenu()->setDword455C78(true);
|
|
getMenu()->setDword455C80(false);
|
|
|
|
return false;
|
|
}
|
|
|
|
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
|
|
|
|
if (!loadData(getFilename(_index))) {
|
|
// FIXME convert to GUI dialog
|
|
if (_valid)
|
|
error("[Savegame::load] Could not load game!");
|
|
else
|
|
error("[Savegame::load] Trying to load a game for a different version of Sanitarium!");
|
|
}
|
|
|
|
loadMoviesViewed();
|
|
|
|
getMenu()->setDword455C80(false);
|
|
getScreen()->clear();
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Savegame::quickLoad() {
|
|
if (!isSavegamePresent(getFilename(SAVEGAME_QUICKSLOT)))
|
|
return false;
|
|
|
|
_index = SAVEGAME_QUICKSLOT;
|
|
_vm->startGame(getScenePack(), AsylumEngine::kStartGameLoad);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Savegame::save(bool appendExtended) {
|
|
// Original creates a folder to hold saved games and checks for disk space, we can skip that
|
|
getCursor()->hide();
|
|
|
|
if (saveData(getFilename(_index), _names[_index], getWorld()->chapter, appendExtended)) {
|
|
_savegames[_index] = true;
|
|
|
|
getMenu()->setDword455C78(true);
|
|
} else {
|
|
warning("[Savegame::save] Could not save game: %s", getFilename(_index).c_str());
|
|
|
|
_savegames[_index] = false;
|
|
_names[_index] = getText()->get(MAKE_RESOURCE(kResourcePackText, 1344));
|
|
}
|
|
|
|
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::check() {
|
|
Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(getFilename(_index));
|
|
if (!file)
|
|
return false;
|
|
|
|
seek(file, 2, "Level and Name");
|
|
|
|
bool valid = false;
|
|
if (readHeader(file))
|
|
valid = true;
|
|
|
|
delete file;
|
|
|
|
return valid;
|
|
}
|
|
|
|
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(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");
|
|
Common::String version = read(file, versionLength, "Version");
|
|
uint32 build = read(file, "Build");
|
|
|
|
// Original does not do any version check
|
|
// Until we can verify all versions have compatible savegames, we only handle the final patched version savegames
|
|
if (version != Common::String(savegame_version) || build != SAVEGAME_BUILD) {
|
|
_valid = false;
|
|
return false;
|
|
}
|
|
|
|
_valid = true;
|
|
return true;
|
|
}
|
|
|
|
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");
|
|
}
|
|
|
|
bool Savegame::loadData(Common::String filename) {
|
|
Common::InSaveFile *file = g_system->getSavefileManager()->openForLoading(filename);
|
|
if (!file) {
|
|
getWorld()->chapter = kChapterInvalid;
|
|
return false;
|
|
}
|
|
|
|
seek(file, 1, "Level");
|
|
seek(file, 1, "Game Name");
|
|
|
|
if (!readHeader(file)) {
|
|
getWorld()->chapter = kChapterInvalid;
|
|
return false;
|
|
}
|
|
|
|
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;
|
|
|
|
return true;
|
|
}
|
|
|
|
bool Savegame::saveData(Common::String filename, Common::String name, ChapterIndex chapter, bool appendExtended) {
|
|
Common::OutSaveFile *file = g_system->getSavefileManager()->openForSaving(filename);
|
|
if (!file)
|
|
return false;
|
|
|
|
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");
|
|
|
|
if (appendExtended)
|
|
_vm->getMetaEngine()->appendExtendedSaveToStream(file, _vm->getTotalPlayTime() / 1000, name, false);
|
|
else
|
|
file->writeUint32LE(0);
|
|
|
|
delete file;
|
|
|
|
return true;
|
|
}
|
|
|
|
void Savegame::seek(Common::InSaveFile *file, uint32 offset, 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, 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, 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];
|
|
memset(str, 0, 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, 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, NULL);
|
|
data->saveLoadWithSerializer(ser);
|
|
}
|
|
|
|
void Savegame::write(Common::OutSaveFile *file, uint32 val, 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, Common::String val, uint32 strLength, 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, 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(NULL, 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(SAVEGAME_MOVIES);
|
|
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(SAVEGAME_MOVIES))
|
|
return;
|
|
|
|
// Load data from disk
|
|
Common::InSaveFile *movies = g_system->getSavefileManager()->openForLoading(SAVEGAME_MOVIES);
|
|
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, 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
|