scummvm/engines/agi/saveload.cpp
2016-05-25 05:37:07 +02:00

1093 lines
33 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.
*
*/
//
// Savegame support by Vasyl Tsvirkunov <vasyl@pacbell.net>
// Multi-slots by Claudio Matsuoka <claudio@helllabs.org>
//
#include "common/file.h"
#include "common/config-manager.h"
#include "common/savefile.h"
#include "common/textconsole.h"
#include "common/translation.h"
#include "gui/saveload.h"
#include "graphics/thumbnail.h"
#include "graphics/surface.h"
#include "agi/agi.h"
#include "agi/graphics.h"
#include "agi/text.h"
#include "agi/sprite.h"
#include "agi/keyboard.h"
#include "agi/menu.h"
#include "agi/systemui.h"
#include "agi/words.h"
#define SAVEGAME_CURRENT_VERSION 11
//
// Version 0 (Sarien): view table has 64 entries
// Version 1 (Sarien): view table has 256 entries (needed in KQ3)
// Version 2 (ScummVM): first ScummVM version
// Version 3 (ScummVM): added AGIPAL save/load support
// Version 4 (ScummVM): added thumbnails and save creation date/time
// Version 5 (ScummVM): Added game md5
// Version 6 (ScummVM): Added game played time
// Version 7 (ScummVM): Added controller key mappings
// required for some games for quick-loading from ScummVM main menu
// for games, that do not set all key mappings right at the start
// Added automatic save data (for command SetSimple)
// Version 8 (ScummVM): Added Hold-Key-Mode boolean
// required for at least Mixed Up Mother Goose
// gets set at the start of the game only
// Version 9 (ScummVM): Added seconds to saved game time stamp
// Version 10 (ScummVM): Added priorityTableSet boolean
namespace Agi {
static const uint32 AGIflag = MKTAG('A', 'G', 'I', ':');
int AgiEngine::saveGame(const Common::String &fileName, const Common::String &descriptionString) {
char gameIDstring[8] = "gameIDX";
int i;
Common::OutSaveFile *out;
int result = errOK;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::saveGame(%s, %s)", fileName.c_str(), descriptionString.c_str());
if (!(out = _saveFileMan->openForSaving(fileName))) {
warning("Can't create file '%s', game not saved", fileName.c_str());
return errBadFileOpen;
} else {
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for writing", fileName.c_str());
}
out->writeUint32BE(AGIflag);
// Write description of saved game, limited to SAVEDGAME_DESCRIPTION_LEN characters + terminating NUL
char description[SAVEDGAME_DESCRIPTION_LEN + 1];
memset(description, 0, sizeof(description));
strncpy(description, descriptionString.c_str(), SAVEDGAME_DESCRIPTION_LEN);
assert(SAVEDGAME_DESCRIPTION_LEN + 1 == 31); // safety
out->write(description, 31);
out->writeByte(SAVEGAME_CURRENT_VERSION);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save game version (%d)", SAVEGAME_CURRENT_VERSION);
// Thumbnail
Graphics::saveThumbnail(*out);
// Creation date/time
TimeDate curTime;
_system->getTimeAndDate(curTime);
uint32 saveDate = ((curTime.tm_mday & 0xFF) << 24) | (((curTime.tm_mon + 1) & 0xFF) << 16) | ((curTime.tm_year + 1900) & 0xFFFF);
uint16 saveTime = ((curTime.tm_hour & 0xFF) << 8) | ((curTime.tm_min) & 0xFF);
uint32 playTime = g_engine->getTotalPlayTime() / 1000;
out->writeUint32BE(saveDate);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save date (%d)", saveDate);
out->writeUint16BE(saveTime);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing save time (%d)", saveTime);
// Version 9+: save seconds of current time as well
out->writeByte(curTime.tm_sec & 0xFF);
out->writeUint32BE(playTime);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing play time (%d)", playTime);
out->writeByte(2); // was _game.state, 2 = STATE_RUNNING
Common::strlcpy(gameIDstring, _game.id, 8);
out->write(gameIDstring, 8);
debugC(5, kDebugLevelMain | kDebugLevelSavegame, "Writing game id (%s, %s)", gameIDstring, _game.id);
const char *tmp = getGameMD5();
// As reported in bug report #2849084 "AGI: Crash when saving fallback-matched game"
// getGameMD5 will return NULL for fallback matched games. Since there is also no
// filename available we can not compute any MD5 here either. Thus we will just set
// the MD5 sum in the savegame to all zero, when getGameMD5 returns NULL.
if (!tmp) {
for (i = 0; i < 32; ++i)
out->writeByte(0);
} else {
for (i = 0; i < 32; ++i)
out->writeByte(tmp[i]);
}
// Version 7+: Save automatic saving state (set.simple opcode)
out->writeByte(_game.automaticSave);
out->write(_game.automaticSaveDescription, 31);
// touch VM_VAR_SECONDS, so that it gets updated
getVar(VM_VAR_SECONDS);
for (i = 0; i < MAX_FLAGS; i++)
out->writeByte(_game.flags[i]);
for (i = 0; i < MAX_VARS; i++)
out->writeByte(_game.vars[i]);
out->writeSint16BE((int8)_game.horizon);
out->writeSint16BE((int16)_text->statusRow_Get());
out->writeSint16BE((int16)_text->promptRow_Get());
out->writeSint16BE((int16)_text->getWindowRowMin());
out->writeSint16BE(1); // was _game.inputMode, we set it to 1, which was INPUTMODE_NORMAL
out->writeSint16BE((int16)_game.curLogicNr);
out->writeSint16BE((int16)_game.playerControl);
out->writeSint16BE((int16)shouldQuit());
if (_text->statusEnabled()) {
out->writeSint16BE(0x7FFF);
} else {
out->writeSint16BE(0);
}
out->writeSint16BE(1); // was clock enabled
// (previous in-game-timer, in-game-timer is always enabled during the regular game, so need to save/load it)
out->writeSint16BE((int16)_game.exitAllLogics);
out->writeSint16BE((int16)_game.pictureShown);
out->writeSint16BE((int16)_text->promptIsEnabled()); // was "_game.hasPrompt", no longer needed
out->writeSint16BE((int16)_game.gameFlags);
if (_text->promptIsEnabled()) {
out->writeSint16BE(0x7FFF);
} else {
out->writeSint16BE(0);
}
for (i = 0; i < SCRIPT_HEIGHT; i++)
out->writeByte(_gfx->saveLoadGetPriority(i));
// Version 10+: Save, if priority table got modified (set.pri.base opcode)
out->writeSint16BE((int16)_gfx->saveLoadWasPriorityTableModified());
out->writeSint16BE((int16)_game.gfxMode);
out->writeByte(_text->inputGetCursorChar());
out->writeSint16BE((int16)_text->charAttrib_GetForeground());
out->writeSint16BE((int16)_text->charAttrib_GetBackground());
// game.hires
// game.sbuf
// game.ego_words
// game.num_ego_words
out->writeSint16BE((int16)_game.numObjects);
for (i = 0; i < (int16)_game.numObjects; i++)
out->writeSint16BE((int16)objectGetLocation(i));
// Version 7+: save controller key mappings
// required for games, that do not set all key mappings right at the start
// when quick restoring is used from ScummVM menu, only 1 cycle is executed
for (i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) {
out->writeUint16BE(_game.controllerKeyMapping[i].keycode);
out->writeByte(_game.controllerKeyMapping[i].controllerSlot);
}
// Version 8+: hold-key-mode
// required for at least Mixed Up Mother Goose
out->writeByte(_keyHoldMode);
// game.ev_keyp
for (i = 0; i < MAX_STRINGS; i++)
out->write(_game.strings[i], MAX_STRINGLEN);
// record info about loaded resources
for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
out->writeByte(_game.dirLogic[i].flags);
out->writeSint16BE((int16)_game.logics[i].sIP);
out->writeSint16BE((int16)_game.logics[i].cIP);
}
for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++)
out->writeByte(_game.dirPic[i].flags);
for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++)
out->writeByte(_game.dirView[i].flags);
for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++)
out->writeByte(_game.dirSound[i].flags);
// game.pictures
// game.logics
// game.views
// game.sounds
for (i = 0; i < SCREENOBJECTS_MAX; i++) {
ScreenObjEntry *screenObj = &_game.screenObjTable[i];
out->writeByte(screenObj->stepTime);
out->writeByte(screenObj->stepTimeCount);
out->writeByte(screenObj->objectNr);
out->writeSint16BE(screenObj->xPos);
out->writeSint16BE(screenObj->yPos);
out->writeByte(screenObj->currentViewNr);
// v->view_data
out->writeByte(screenObj->currentLoopNr);
out->writeByte(screenObj->loopCount);
// v->loop_data
out->writeByte(screenObj->currentCelNr);
out->writeByte(screenObj->celCount);
// v->cel_data
// v->cel_data_2
out->writeSint16BE(screenObj->xPos_prev);
out->writeSint16BE(screenObj->yPos_prev);
// v->s
out->writeSint16BE(screenObj->xSize);
out->writeSint16BE(screenObj->ySize);
out->writeByte(screenObj->stepSize);
out->writeByte(screenObj->cycleTime);
out->writeByte(screenObj->cycleTimeCount);
out->writeByte(screenObj->direction);
out->writeByte(screenObj->motionType);
out->writeByte(screenObj->cycle);
// Version 11+: loop_flag, was saved previously under vt.parm1
out->writeByte(screenObj->loop_flag);
out->writeByte(screenObj->priority);
out->writeUint16BE(screenObj->flags);
// this was done so that saved games compatibility isn't broken
switch (screenObj->motionType) {
case kMotionNormal:
out->writeByte(0);
out->writeByte(0);
out->writeByte(0);
out->writeByte(0);
break;
case kMotionWander:
out->writeByte(screenObj->wander_count);
out->writeByte(0);
out->writeByte(0);
out->writeByte(0);
break;
case kMotionFollowEgo:
out->writeByte(screenObj->follow_stepSize);
out->writeByte(screenObj->follow_flag);
out->writeByte(screenObj->follow_count);
out->writeByte(0);
break;
case kMotionEgo:
case kMotionMoveObj:
out->writeByte((byte)screenObj->move_x); // problematic! int16 -> byte
out->writeByte((byte)screenObj->move_y);
out->writeByte(screenObj->move_stepSize);
out->writeByte(screenObj->move_flag);
break;
default:
error("unknown motion-type");
}
}
// Save image stack
for (Common::Stack<ImageStackElement>::size_type j = 0; j < _imageStack.size(); ++j) {
const ImageStackElement &ise = _imageStack[j];
out->writeByte(ise.type);
out->writeSint16BE(ise.parm1);
out->writeSint16BE(ise.parm2);
out->writeSint16BE(ise.parm3);
out->writeSint16BE(ise.parm4);
out->writeSint16BE(ise.parm5);
out->writeSint16BE(ise.parm6);
out->writeSint16BE(ise.parm7);
}
out->writeByte(0);
//Write which file number AGIPAL is using (0 if not being used)
out->writeSint16BE(_gfx->getAGIPalFileNum());
out->finalize();
if (out->err()) {
warning("Can't write file '%s'. (Disk full?)", fileName.c_str());
result = errIOError;
} else
debugC(1, kDebugLevelMain | kDebugLevelSavegame, "Saved game %s in file %s", descriptionString.c_str(), fileName.c_str());
delete out;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName.c_str());
_lastSaveTime = _system->getMillis();
return result;
}
int AgiEngine::loadGame(const Common::String &fileName, bool checkId) {
char description[SAVEDGAME_DESCRIPTION_LEN + 1];
byte saveVersion = 0;
char loadId[8];
int i, vtEntries = SCREENOBJECTS_MAX;
uint8 t;
int16 parm[7];
Common::InSaveFile *in;
bool totalPlayTimeWasSet = false;
byte oldLoopFlag = 0;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "AgiEngine::loadGame(%s)", fileName.c_str());
if (!(in = _saveFileMan->openForLoading(fileName))) {
warning("Can't open file '%s', game not loaded", fileName.c_str());
return errBadFileOpen;
} else {
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName.c_str());
}
uint32 typea = in->readUint32BE();
if (typea == AGIflag) {
debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start");
} else {
warning("This doesn't appear to be an AGI savegame, game not restored");
delete in;
return errOK;
}
assert(SAVEDGAME_DESCRIPTION_LEN + 1 == 31); // safety
in->read(description, 31); // skip description
// check, if there is a terminating NUL inside description
uint16 descriptionPos = 0;
while (description[descriptionPos]) {
descriptionPos++;
if (descriptionPos >= sizeof(description))
error("saved game description is corrupt");
}
debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Description is: %s", description);
saveVersion = in->readByte();
if (saveVersion < 2) // is the save game pre-ScummVM?
warning("Old save game version (%d, current version is %d). Will try and read anyway, but don't be surprised if bad things happen", saveVersion, SAVEGAME_CURRENT_VERSION);
if (saveVersion < 3)
warning("This save game contains no AGIPAL data, if the game is using the AGIPAL hack, it won't work correctly");
if (saveVersion > SAVEGAME_CURRENT_VERSION)
error("Saved game was created with a newer version of ScummVM. Unable to load.");
if (saveVersion >= 4) {
// We don't need the thumbnail here, so just read it and discard it
Graphics::skipThumbnail(*in);
in->readUint32BE(); // save date
in->readUint16BE(); // save time (hour + minute)
if (saveVersion >= 9) {
in->readByte(); // save time seconds
}
if (saveVersion >= 6) {
uint32 playTime = in->readUint32BE();
inGameTimerReset(playTime * 1000);
totalPlayTimeWasSet = true;
}
}
in->readByte(); // was _game.state, not needed anymore
in->read(loadId, 8);
if (strcmp(loadId, _game.id) != 0 && checkId) {
delete in;
warning("This save seems to be from a different AGI game (save from %s, running %s), not loaded", loadId, _game.id);
return errBadFileOpen;
}
Common::strlcpy(_game.id, loadId, 8);
if (saveVersion >= 5) {
char md5[32 + 1];
for (i = 0; i < 32; i++) {
md5[i] = in->readByte();
}
md5[i] = 0; // terminate
// As noted above in AgiEngine::saveGame the MD5 sum field may be all zero
// when the save was made via a fallback matched game. In this case we will
// replace the MD5 sum with a nicer string, so that the user can easily see
// this fact in the debug output. The string saved in "md5" will never match
// any valid MD5 sum, thus it is safe to do that here.
if (md5[0] == 0)
strcpy(md5, "fallback matched");
debug(0, "Saved game MD5: \"%s\"", md5);
if (!getGameMD5()) {
warning("Since your game was only detected via the fallback detector, there is no possibility to assure the save is compatible with your game version");
debug(0, "The game used for saving is \"%s\".", md5);
} else if (strcmp(md5, getGameMD5()) != 0) {
warning("Game was saved with different gamedata - you may encounter problems");
debug(0, "Your game is \"%s\" and save is \"%s\".", getGameMD5(), md5);
}
}
if (saveVersion >= 7) {
// Restore automatic saving state (set.simple opcode)
_game.automaticSave = in->readByte();
in->read(_game.automaticSaveDescription, 31);
} else {
_game.automaticSave = false;
_game.automaticSaveDescription[0] = 0;
}
for (i = 0; i < MAX_FLAGS; i++)
_game.flags[i] = in->readByte();
for (i = 0; i < MAX_VARS; i++)
_game.vars[i] = in->readByte();
if (!totalPlayTimeWasSet) {
// If we haven't gotten total play time by now, try to calculate it by using VM Variables
// This will happen for at least saves before version 6
// Direct access because otherwise we would trigger an update to these variables according to ScummVM total play time
byte playTimeSeconds = _game.vars[VM_VAR_SECONDS];
byte playTimeMinutes = _game.vars[VM_VAR_MINUTES];
byte playTimeHours = _game.vars[VM_VAR_HOURS];
byte playTimeDays = _game.vars[VM_VAR_DAYS];
uint32 playTime = (playTimeSeconds + (playTimeMinutes * 60) + (playTimeHours * 3600) + (playTimeDays * 86400)) * 1000;
inGameTimerReset(playTime);
}
setVar(VM_VAR_FREE_PAGES, 180); // Set amount of free memory to realistic value (Overwriting the just loaded value)
_game.horizon = in->readSint16BE();
_text->statusRow_Set(in->readSint16BE());
_text->promptRow_Set(in->readSint16BE());
_text->configureScreen(in->readSint16BE());
// These are never saved
_text->promptReset();
in->readSint16BE(); // was _game.inputMode, not needed anymore
_game.curLogicNr = in->readSint16BE();
_game.playerControl = in->readSint16BE();
if (in->readSint16BE())
quitGame();
if (in->readSint16BE()) {
_text->statusEnable();
} else {
_text->statusDisable();
}
in->readSint16BE(); // was clock enabled, no longer needed
_game.exitAllLogics = in->readSint16BE();
in->readSint16BE(); // was _game.pictureShown
in->readSint16BE(); // was _game.hasPrompt, no longer needed
_game.gameFlags = in->readSint16BE();
if (in->readSint16BE()) {
_text->promptEnable();
} else {
_text->promptDisable();
}
for (i = 0; i < SCRIPT_HEIGHT; i++)
_gfx->saveLoadSetPriority(i, in->readByte());
if (saveVersion >= 10) {
// Version 10+: priority table was modified by scripts
int16 priorityTableWasModified = in->readSint16BE();
if (priorityTableWasModified) {
_gfx->saveLoadSetPriorityTableModifiedBool(true);
} else {
_gfx->saveLoadSetPriorityTableModifiedBool(false);
}
} else {
// Try to figure it out by ourselves
_gfx->saveLoadFigureOutPriorityTableModifiedBool();
}
_text->closeWindow();
_game.block.active = false;
_game.gfxMode = in->readSint16BE();
_text->inputSetCursorChar(in->readByte());
int16 textForeground = in->readSint16BE();
int16 textBackground = in->readSint16BE();
_text->charAttrib_Set(textForeground, textBackground);
// game.ego_words - fixed by clean_input
// game.num_ego_words - fixed by clean_input
_game.numObjects = in->readSint16BE();
for (i = 0; i < (int16)_game.numObjects; i++)
objectSetLocation(i, in->readSint16BE());
// Those are not serialized
for (i = 0; i < MAX_CONTROLLERS; i++) {
_game.controllerOccured[i] = false;
}
if (saveVersion >= 7) {
// For old saves, we just keep the current controllers
for (i = 0; i < MAX_CONTROLLER_KEYMAPPINGS; i++) {
_game.controllerKeyMapping[i].keycode = in->readUint16BE();
_game.controllerKeyMapping[i].controllerSlot = in->readByte();
}
}
if (saveVersion >= 8) {
// Version 8+: hold-key-mode
if (in->readByte()) {
_keyHoldMode = true;
} else {
_keyHoldMode = false;
}
}
for (i = 0; i < MAX_STRINGS; i++)
in->read(_game.strings[i], MAX_STRINGLEN);
for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
if (in->readByte() & RES_LOADED)
agiLoadResource(RESOURCETYPE_LOGIC, i);
else
agiUnloadResource(RESOURCETYPE_LOGIC, i);
_game.logics[i].sIP = in->readSint16BE();
_game.logics[i].cIP = in->readSint16BE();
}
for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
if (in->readByte() & RES_LOADED)
agiLoadResource(RESOURCETYPE_PICTURE, i);
else
agiUnloadResource(RESOURCETYPE_PICTURE, i);
}
for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
if (in->readByte() & RES_LOADED)
agiLoadResource(RESOURCETYPE_VIEW, i);
else
agiUnloadResource(RESOURCETYPE_VIEW, i);
}
for (i = 0; i < MAX_DIRECTORY_ENTRIES; i++) {
if (in->readByte() & RES_LOADED)
agiLoadResource(RESOURCETYPE_SOUND, i);
else
agiUnloadResource(RESOURCETYPE_SOUND, i);
}
// game.pictures - loaded above
// game.logics - loaded above
// game.views - loaded above
// game.sounds - loaded above
for (i = 0; i < vtEntries; i++) {
ScreenObjEntry *screenObj = &_game.screenObjTable[i];
screenObj->stepTime = in->readByte();
screenObj->stepTimeCount = in->readByte();
screenObj->objectNr = in->readByte();
screenObj->xPos = in->readSint16BE();
screenObj->yPos = in->readSint16BE();
screenObj->currentViewNr = in->readByte();
// screenObj->view_data - fixed below
screenObj->currentLoopNr = in->readByte();
screenObj->loopCount = in->readByte();
// screenObj->loop_data - fixed below
screenObj->currentCelNr = in->readByte();
screenObj->celCount = in->readByte();
// screenObj->cel_data - fixed below
// screenObj->cel_data_2 - fixed below
screenObj->xPos_prev = in->readSint16BE();
screenObj->yPos_prev = in->readSint16BE();
// screenObj->s - fixed below
screenObj->xSize = in->readSint16BE();
screenObj->ySize = in->readSint16BE();
screenObj->stepSize = in->readByte();
screenObj->cycleTime = in->readByte();
screenObj->cycleTimeCount = in->readByte();
screenObj->direction = in->readByte();
screenObj->motionType = (MotionType)in->readByte();
screenObj->cycle = (CycleType)in->readByte();
if (saveVersion >= 11) {
// Version 11+: loop_flag, was previously vt.parm1
screenObj->loop_flag = in->readByte();
}
screenObj->priority = in->readByte();
screenObj->flags = in->readUint16BE();
// this was done so that saved games compatibility isn't broken
switch (screenObj->motionType) {
case kMotionNormal:
oldLoopFlag = in->readByte();
in->readByte();
in->readByte();
in->readByte();
break;
case kMotionWander:
screenObj->wander_count = in->readByte();
in->readByte();
in->readByte();
in->readByte();
oldLoopFlag = screenObj->wander_count;
break;
case kMotionFollowEgo:
screenObj->follow_stepSize = in->readByte();
screenObj->follow_flag = in->readByte();
screenObj->follow_count = in->readByte();
in->readByte();
oldLoopFlag = screenObj->follow_stepSize;
break;
case kMotionEgo:
case kMotionMoveObj:
screenObj->move_x = in->readByte(); // problematic! int16 -> byte
screenObj->move_y = in->readByte();
screenObj->move_stepSize = in->readByte();
screenObj->move_flag = in->readByte();
oldLoopFlag = screenObj->move_x;
break;
default:
error("unknown motion-type");
}
if (saveVersion < 11) {
if (saveVersion < 7) {
// Recreate loop_flag from motion-type (was previously vt.parm1)
// vt.parm1 was shared for multiple uses
screenObj->loop_flag = oldLoopFlag;
} else {
// for Version 7-10 we can't really do anything, it was not saved
screenObj->loop_flag = 0; // set it to 0
}
}
}
// Fix some pointers in screenObjTable
for (i = 0; i < SCREENOBJECTS_MAX; i++) {
ScreenObjEntry *screenObj = &_game.screenObjTable[i];
if (_game.dirView[screenObj->currentViewNr].offset == _EMPTY)
continue;
if (!(_game.dirView[screenObj->currentViewNr].flags & RES_LOADED))
agiLoadResource(RESOURCETYPE_VIEW, screenObj->currentViewNr);
setView(screenObj, screenObj->currentViewNr); // Fix v->view_data
setLoop(screenObj, screenObj->currentLoopNr); // Fix v->loop_data
setCel(screenObj, screenObj->currentCelNr); // Fix v->cel_data
}
_sprites->eraseSprites();
_game.pictureShown = false;
_gfx->clearDisplay(0, false); // clear display screen, but not copy it to actual screen for now b/c transition
// Recreate background from saved image stack
clearImageStack();
while ((t = in->readByte()) != 0) {
for (i = 0; i < 7; i++)
parm[i] = in->readSint16BE();
replayImageStackCall(t, parm[0], parm[1], parm[2],
parm[3], parm[4], parm[5], parm[6]);
}
// Load AGIPAL Data
if (saveVersion >= 3)
_gfx->setAGIPal(in->readSint16BE());
delete in;
debugC(3, kDebugLevelMain | kDebugLevelSavegame, "Closed %s", fileName.c_str());
setFlag(VM_FLAG_RESTORE_JUST_RAN, true);
_words->clearEgoWords();
// don't delay anything right after restoring a game
artificialDelay_Reset();
_sprites->eraseSprites();
_sprites->buildAllSpriteLists();
_sprites->drawAllSpriteLists();
_picture->showPicWithTransition();
_game.pictureShown = true;
_text->statusDraw();
_text->promptRedraw();
// copy everything over (we should probably only copy over the remaining parts of the screen w/o play screen
_gfx->copyDisplayToScreen();
// Sync volume settings from ScummVM system settings, so that VM volume variable is overwritten
setVolumeViaSystemSetting();
return errOK;
}
int AgiEngine::scummVMSaveLoadDialog(bool isSave) {
GUI::SaveLoadChooser *dialog;
Common::String desc;
int slot;
if (isSave) {
dialog = new GUI::SaveLoadChooser(_("Save game:"), _("Save"), true);
slot = dialog->runModalWithCurrentTarget();
desc = dialog->getResultString();
if (desc.empty()) {
// create our own description for the saved game, the user didnt enter it
desc = dialog->createDefaultSaveDescription(slot);
}
if (desc.size() > 28)
desc = Common::String(desc.c_str(), 28);
} else {
dialog = new GUI::SaveLoadChooser(_("Restore game:"), _("Restore"), false);
slot = dialog->runModalWithCurrentTarget();
}
delete dialog;
if (slot < 0)
return true;
if (isSave)
return doSave(slot, desc);
else
return doLoad(slot, false);
}
int AgiEngine::doSave(int slot, const Common::String &desc) {
Common::String fileName = getSavegameFilename(slot);
debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", fileName.c_str());
// Make sure all graphics was blitted to screen. This fixes bug
// #2960567: "AGI: Ego partly erased in Load/Save thumbnails"
_gfx->updateScreen();
// _gfx->doUpdate();
return saveGame(fileName, desc);
}
int AgiEngine::doLoad(int slot, bool showMessages) {
Common::String fileName = getSavegameFilename(slot);
debugC(8, kDebugLevelMain | kDebugLevelResources, "file is [%s]", fileName.c_str());
_sprites->eraseSprites();
_sound->stopSound();
_text->closeWindow();
int result = loadGame(fileName);
if (result == errOK) {
_game.exitAllLogics = true;
_menu->itemEnableAll();
} else {
if (showMessages)
_text->messageBox("Error restoring game.");
}
return result;
}
SavedGameSlotIdArray AgiEngine::getSavegameSlotIds() {
Common::StringArray filenames;
int16 numberPos = _targetName.size() + 1;
int16 slotId = 0;
SavedGameSlotIdArray slotIdArray;
// search for saved game filenames...
filenames = _saveFileMan->listSavefiles(_targetName + ".###");
Common::StringArray::iterator it;
Common::StringArray::iterator end = filenames.end();
// convert to lower-case, just to be sure
for (it = filenames.begin(); it != end; it++) {
it->toLowercase();
}
// sort
Common::sort(filenames.begin(), filenames.end());
// now extract slot-Ids
for (it = filenames.begin(); it != end; it++) {
slotId = atoi(it->c_str() + numberPos);
slotIdArray.push_back(slotId);
}
return slotIdArray;
}
Common::String AgiEngine::getSavegameFilename(int16 slotId) const {
Common::String saveLoadSlot = _targetName;
saveLoadSlot += Common::String::format(".%.3d", slotId);
return saveLoadSlot;
}
bool AgiEngine::getSavegameInformation(int16 slotId, Common::String &saveDescription, uint32 &saveDate, uint32 &saveTime, bool &saveIsValid) {
Common::InSaveFile *in;
Common::String fileName = getSavegameFilename(slotId);
char saveGameDescription[31];
int16 curPos = 0;
byte saveVersion = 0;
saveDescription.clear();
saveDate = 0;
saveTime = 0;
saveIsValid = false;
debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Current game id is %s", _targetName.c_str());
if (!(in = _saveFileMan->openForLoading(fileName))) {
debugC(4, kDebugLevelMain | kDebugLevelSavegame, "File %s does not exist", fileName.c_str());
return false;
} else {
debugC(4, kDebugLevelMain | kDebugLevelSavegame, "Successfully opened %s for reading", fileName.c_str());
uint32 type = in->readUint32BE();
if (type != AGIflag) {
warning("This doesn't appear to be an AGI savegame");
saveDescription += "[ScummVM: not an AGI save]";
delete in;
return true;
}
debugC(6, kDebugLevelMain | kDebugLevelSavegame, "Has AGI flag, good start");
if (in->read(saveGameDescription, 31) != 31) {
warning("unexpected EOF");
delete in;
saveDescription += "[ScummVM: invalid save]";
return true;
}
for (curPos = 0; curPos < 31; curPos++) {
if (!saveGameDescription[curPos])
break;
}
if (curPos >= 31) {
warning("corrupted description");
delete in;
saveDescription += "[ScummVM: invalid save]";
return true;
}
saveVersion = in->readByte();
if (saveVersion > SAVEGAME_CURRENT_VERSION) {
warning("save from a future ScummVM, not supported");
delete in;
saveDescription += "[ScummVM: not supported]";
return true;
}
if (saveVersion >= 4) {
// We don't need the thumbnail here, so just read it and discard it
Graphics::skipThumbnail(*in);
saveDate = in->readUint32BE();
saveTime = in->readUint16BE() << 8;
if (saveVersion >= 9) {
saveTime |= in->readByte(); // add seconds (only available since saved game version 9+)
}
// save date is DDMMYYYY, we need a proper format
byte saveDateDay = saveDate >> 24;
byte saveDateMonth = (saveDate >> 16) & 0xFF;
uint16 saveDateYear = saveDate & 0xFFFF;
saveDate = (saveDateYear << 16) | (saveDateMonth << 8) | saveDateDay;
} else {
saveDate = 0;
saveTime = 0;
}
saveDescription += saveGameDescription;
saveIsValid = true;
delete in;
return true;
}
}
bool AgiEngine::loadGameAutomatic() {
int16 automaticRestoreGameSlotId = 0;
automaticRestoreGameSlotId = _systemUI->figureOutAutomaticRestoreGameSlot(_game.automaticSaveDescription);
if (automaticRestoreGameSlotId >= 0) {
if (doLoad(automaticRestoreGameSlotId, true) == errOK) {
return true;
}
}
return false;
}
bool AgiEngine::loadGameDialog() {
int16 restoreGameSlotId = 0;
if (!ConfMan.getBool("originalsaveload"))
return scummVMSaveLoadDialog(false);
restoreGameSlotId = _systemUI->askForRestoreGameSlot();
if (restoreGameSlotId >= 0) {
if (doLoad(restoreGameSlotId, true) == errOK) {
return true;
}
}
return errOK;
}
// Try to figure out either the slot, that is currently using the automatic saved game description
// or get a new slot.
// If we fail, return false, so that the regular saved game dialog is called
// Original AGI was limited to 12 saves, we are effectively limited to 100 saves at the moment.
//
// btw. this also means that entering an existant name in Mixed Up Mother Goose will effectively overwrite
// that saved game. This is also what original AGI did.
bool AgiEngine::saveGameAutomatic() {
int16 automaticSaveGameSlotId = 0;
automaticSaveGameSlotId = _systemUI->figureOutAutomaticSaveGameSlot(_game.automaticSaveDescription);
if (automaticSaveGameSlotId >= 0) {
Common::String slotDescription(_game.automaticSaveDescription);
// WORKAROUND: Remove window in case one is currently shown, otherwise it would get saved in the thumbnail
// Happens for Mixed Up Mother Goose. The scripts close the window after saving.
// Original interpreter obviously did not do this, but original interpreter also did not save thumbnails.
_text->closeWindow();
if (doSave(automaticSaveGameSlotId, slotDescription) == errOK) {
return true;
}
}
return false;
}
bool AgiEngine::saveGameDialog() {
int16 saveGameSlotId = 0;
Common::String slotDescription;
if (!ConfMan.getBool("originalsaveload"))
return scummVMSaveLoadDialog(true);
saveGameSlotId = _systemUI->askForSaveGameSlot();
if (saveGameSlotId >= 0) {
if (_systemUI->askForSaveGameDescription(saveGameSlotId, slotDescription)) {
if (doSave(saveGameSlotId, slotDescription) == errOK) {
return true;
}
}
}
return false;
}
void AgiEngine::recordImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
int16 p4, int16 p5, int16 p6, int16 p7) {
ImageStackElement pnew;
pnew.type = type;
pnew.pad = 0;
pnew.parm1 = p1;
pnew.parm2 = p2;
pnew.parm3 = p3;
pnew.parm4 = p4;
pnew.parm5 = p5;
pnew.parm6 = p6;
pnew.parm7 = p7;
_imageStack.push(pnew);
}
void AgiEngine::replayImageStackCall(uint8 type, int16 p1, int16 p2, int16 p3,
int16 p4, int16 p5, int16 p6, int16 p7) {
switch (type) {
case ADD_PIC:
debugC(8, kDebugLevelMain, "--- decoding picture %d ---", p1);
agiLoadResource(RESOURCETYPE_PICTURE, p1);
_picture->decodePicture(p1, p2, p3 != 0);
break;
case ADD_VIEW:
agiLoadResource(RESOURCETYPE_VIEW, p1);
_sprites->addToPic(p1, p2, p3, p4, p5, p6, p7);
break;
}
}
void AgiEngine::clearImageStack() {
_imageStack.clear();
}
void AgiEngine::releaseImageStack() {
_imageStack.clear();
}
void AgiEngine::checkQuickLoad() {
if (ConfMan.hasKey("save_slot")) {
Common::String saveNameBuffer = getSavegameFilename(ConfMan.getInt("save_slot"));
_sprites->eraseSprites();
_sound->stopSound();
if (loadGame(saveNameBuffer, false) == errOK) { // Do not check game id
_game.exitAllLogics = true;
_menu->itemEnableAll();
}
}
}
Common::Error AgiEngine::loadGameState(int slot) {
Common::String saveLoadSlot = getSavegameFilename(slot);
_sprites->eraseSprites();
_sound->stopSound();
if (loadGame(saveLoadSlot) == errOK) {
_game.exitAllLogics = true;
_menu->itemEnableAll();
return Common::kNoError;
} else {
return Common::kUnknownError;
}
}
Common::Error AgiEngine::saveGameState(int slot, const Common::String &description) {
Common::String saveLoadSlot = getSavegameFilename(slot);
if (saveGame(saveLoadSlot, description) == errOK)
return Common::kNoError;
else
return Common::kUnknownError;
}
} // End of namespace Agi