diff options
Diffstat (limited to 'engines/sword2/save_rest.cpp')
-rw-r--r-- | engines/sword2/save_rest.cpp | 414 |
1 files changed, 414 insertions, 0 deletions
diff --git a/engines/sword2/save_rest.cpp b/engines/sword2/save_rest.cpp new file mode 100644 index 0000000000..df03c21be3 --- /dev/null +++ b/engines/sword2/save_rest.cpp @@ -0,0 +1,414 @@ +/* Copyright (C) 1994-1998 Revolution Software Ltd. + * Copyright (C) 2003-2006 The ScummVM project + * + * 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. + * + * $URL$ + * $Id$ + */ + +// --------------------------------------------------------------------------- +// SAVE_REST.CPP save, restore & restart functions +// +// James 05feb97 +// +// "Jesus Saves", but could he Restore or Restart? He can now... +// +// --------------------------------------------------------------------------- + +#include "common/stdafx.h" +#include "common/savefile.h" +#include "sword2/sword2.h" +#include "sword2/defs.h" +#include "sword2/logic.h" +#include "sword2/mouse.h" +#include "sword2/resman.h" +#include "sword2/sound.h" + +namespace Sword2 { + +// A savegame consists of a header and the global variables + +// Max length of a savegame filename +#define MAX_FILENAME_LEN 128 + +/** + * Calculate size of required savegame buffer + */ + +uint32 Sword2Engine::findBufferSize() { + // Size of savegame header + size of global variables + return 212 + _resman->fetchLen(1); +} + +/** + * Save the game. + */ + +uint32 Sword2Engine::saveGame(uint16 slotNo, byte *desc) { + char description[SAVE_DESCRIPTION_LEN]; + uint32 bufferSize = findBufferSize(); + byte *saveBuffer = (byte *)malloc(bufferSize); + ScreenInfo *screenInfo = _screen->getScreenInfo(); + + memset(description, 0, sizeof(description)); + strncpy(description, (char *)desc, SAVE_DESCRIPTION_LEN - 1); + + Common::MemoryWriteStream writeS(saveBuffer, bufferSize); + + byte *globalVars = _resman->openResource(1); + byte *objectHub = _resman->openResource(CUR_PLAYER_ID) + ResHeader::size(); + + // Script no. 7 - 'george_savedata_request' calls fnPassPlayerSaveData + _logic->runResScript(CUR_PLAYER_ID, 7); + + writeS.writeUint32LE(0); // Checksum + writeS.write(description, SAVE_DESCRIPTION_LEN); + writeS.writeUint32LE(_resman->fetchLen(1)); + writeS.writeUint32LE(screenInfo->background_layer_id); + writeS.writeUint32LE(_logic->getRunList()); + writeS.writeUint32LE(screenInfo->feet_x); + writeS.writeUint32LE(screenInfo->feet_y); + writeS.writeUint32LE(_sound->getLoopingMusicId()); + writeS.write(objectHub, ObjectHub::size()); + writeS.write(_logic->_saveLogic, ObjectLogic::size()); + writeS.write(_logic->_saveGraphic, ObjectGraphic::size()); + writeS.write(_logic->_saveMega, ObjectMega::size()); + writeS.write(globalVars, _resman->fetchLen(1)); + + WRITE_LE_UINT32(saveBuffer, calcChecksum(saveBuffer + 4, bufferSize - 4)); + + _resman->closeResource(CUR_PLAYER_ID); + _resman->closeResource(1); + + uint32 errorCode = saveData(slotNo, saveBuffer, bufferSize); + + free(saveBuffer); + + if (errorCode != SR_OK) { + uint32 textId; + + switch (errorCode) { + case SR_ERR_FILEOPEN: + textId = TEXT_SAVE_CANT_OPEN; + break; + default: + textId = TEXT_SAVE_FAILED; + break; + } + + _screen->displayMsg(fetchTextLine(_resman->openResource(textId / SIZE), textId & 0xffff) + 2, 0); + } + + return errorCode; +} + +uint32 Sword2Engine::saveData(uint16 slotNo, byte *buffer, uint32 bufferSize) { + char saveFileName[MAX_FILENAME_LEN]; + + sprintf(saveFileName, "%s.%.3d", _targetName.c_str(), slotNo); + + Common::OutSaveFile *out; + + if (!(out = _saveFileMan->openForSaving(saveFileName))) { + return SR_ERR_FILEOPEN; + } + + out->write(buffer, bufferSize); + out->flush(); + + if (!out->ioFailed()) { + delete out; + return SR_OK; + } + + delete out; + return SR_ERR_WRITEFAIL; +} + +/** + * Restore the game. + */ + +uint32 Sword2Engine::restoreGame(uint16 slotNo) { + uint32 bufferSize = findBufferSize(); + byte *saveBufferMem = (byte *)malloc(bufferSize); + + uint32 errorCode = restoreData(slotNo, saveBufferMem, bufferSize); + + // If it was read in successfully, then restore the game from the + // buffer & free the buffer. Note that restoreFromBuffer() frees the + // buffer in order to clear it from memory before loading in the new + // screen and runlist, so we only need to free it in case of failure. + + if (errorCode == SR_OK) + errorCode = restoreFromBuffer(saveBufferMem, bufferSize); + else + free(saveBufferMem); + + if (errorCode != SR_OK) { + uint32 textId; + + switch (errorCode) { + case SR_ERR_FILEOPEN: + textId = TEXT_RESTORE_CANT_OPEN; + break; + case SR_ERR_INCOMPATIBLE: + textId = TEXT_RESTORE_INCOMPATIBLE; + break; + default: + textId = TEXT_RESTORE_FAILED; + break; + } + + _screen->displayMsg(fetchTextLine(_resman->openResource(textId / SIZE), textId & 0xffff) + 2, 0); + } else { + // Prime system with a game cycle + + // Reset the graphic 'BuildUnit' list before a new logic list + // (see fnRegisterFrame) + _screen->resetRenderLists(); + + // Reset the mouse hot-spot list. See fnRegisterMouse() + // and fnRegisterFrame() + _mouse->resetMouseList(); + + if (_logic->processSession()) + error("restore 1st cycle failed??"); + } + + // Force the game engine to pick a cursor. This appears to be needed + // when using the -x command-line option to restore a game. + _mouse->setMouseTouching(1); + return errorCode; +} + +uint32 Sword2Engine::restoreData(uint16 slotNo, byte *buffer, uint32 bufferSize) { + char saveFileName[MAX_FILENAME_LEN]; + + sprintf(saveFileName, "%s.%.3d", _targetName.c_str(), slotNo); + + Common::InSaveFile *in; + + if (!(in = _saveFileMan->openForLoading(saveFileName))) { + // error: couldn't open file + return SR_ERR_FILEOPEN; + } + + // Read savegame into the buffer + uint32 itemsRead = in->read(buffer, bufferSize); + + delete in; + + if (itemsRead != bufferSize) { + // We didn't get all of it. At the moment we have no way of + // knowing why, so assume that it's an incompatible savegame. + + return SR_ERR_INCOMPATIBLE; + } + + return SR_OK; +} + +uint32 Sword2Engine::restoreFromBuffer(byte *buffer, uint32 size) { + Common::MemoryReadStream readS(buffer, size); + + // Calc checksum & check that aginst the value stored in the header + + if (readS.readUint32LE() != calcChecksum(buffer + 4, size - 4)) { + free(buffer); + return SR_ERR_INCOMPATIBLE; + } + + readS.seek(SAVE_DESCRIPTION_LEN, SEEK_CUR); + + // Check savegame against length of current global variables resource + // This would most probably be trapped by the checksum test anyway, + // but it doesn't do any harm to check this as well. + + // Historical note: During development, earlier savegames would often + // be shorter than the current expected length. + + if (readS.readUint32LE() != _resman->fetchLen(1)) { + free(buffer); + return SR_ERR_INCOMPATIBLE; + } + + byte *globalVars = _resman->openResource(1); + byte *objectHub = _resman->openResource(CUR_PLAYER_ID) + ResHeader::size(); + + uint32 screenId = readS.readUint32LE(); + uint32 runListId = readS.readUint32LE(); + uint32 feetX = readS.readUint32LE(); + uint32 feetY = readS.readUint32LE(); + uint32 musicId = readS.readUint32LE(); + + // Trash all resources from memory except player object & global vars + _resman->killAll(false); + _logic->resetKillList(); + + readS.read(objectHub, ObjectHub::size()); + readS.read(_logic->_saveLogic, ObjectLogic::size()); + readS.read(_logic->_saveGraphic, ObjectGraphic::size()); + readS.read(_logic->_saveMega, ObjectMega::size()); + + // Fill out the player object structures from the savegame structures. + // Also run the appropriate scripts to set up George's anim tables and + // walkdata, and Nico's anim tables. + + // Script no. 8 - 'george_savedata_return' calls fnGetPlayerSaveData + _logic->runResScript(CUR_PLAYER_ID, 8); + + // Script no. 14 - 'set_up_nico_anim_tables' + _logic->runResScript(CUR_PLAYER_ID, 14); + + // Which megaset was the player at the time of saving? + ObjectMega obMega(_logic->_saveMega); + + uint32 scriptNo = 0; + + switch (obMega.getMegasetRes()) { + case 36: // GeoMega: + scriptNo = 9; // script no.9 - 'player_is_george' + break; + case 2003: // GeoMegaB: + scriptNo = 13; // script no.13 - 'player_is_georgeB' + break; + case 1366: // NicMegaA: + scriptNo = 11; // script no.11 - 'player_is_nicoA' + break; + case 1437: // NicMegaB: + scriptNo = 12; // script no.12 - 'player_is_nicoB' + break; + case 1575: // NicMegaC: + scriptNo = 10; // script no.10 - 'player_is_nicoC' + break; + } + + _logic->runResScript(CUR_PLAYER_ID, scriptNo); + + // Copy variables from savegame buffer to memory + readS.read(globalVars, _resman->fetchLen(1)); + + _resman->closeResource(CUR_PLAYER_ID); + _resman->closeResource(1); + + free(buffer); + + int32 pars[2]; + + pars[0] = screenId; + pars[1] = 1; + _logic->fnInitBackground(pars); + + ScreenInfo *screenInfo = _screen->getScreenInfo(); + + // So palette not restored immediately after control panel - we want to + // fade up instead! + screenInfo->new_palette = 99; + + // These need setting after the defaults get set in fnInitBackground. + // Remember that these can change through the game, so need saving & + // restoring too. + + screenInfo->feet_x = feetX; + screenInfo->feet_y = feetY; + + // Start the new run list + _logic->expressChangeSession(runListId); + + // Force in the new scroll position, so unsightly scroll-catch-up does + // not occur when screen first draws after returning from restore panel + + // Set the screen record of player position - ready for setScrolling() + + screenInfo->player_feet_x = obMega.getFeetX(); + screenInfo->player_feet_y = obMega.getFeetY(); + + // if this screen is wide, recompute the scroll offsets now + if (screenInfo->scroll_flag) + _screen->setScrolling(); + + // Any music required will be started after we've returned from + // restoreControl() - see systemMenuMouse() in mouse.cpp! + + // Restart any looping music. Originally this was - and still is - done + // in systemMenuMouse(), but with ScummVM we have other ways of + // restoring savegames so it's easier to put it here as well. + + if (musicId) { + pars[0] = musicId; + pars[1] = FX_LOOP; + _logic->fnPlayMusic(pars); + } else + _logic->fnStopMusic(NULL); + + return SR_OK; +} + +/** + * Get the description of a savegame + */ + +uint32 Sword2Engine::getSaveDescription(uint16 slotNo, byte *description) { + char saveFileName[MAX_FILENAME_LEN]; + + sprintf(saveFileName, "%s.%.3d", _targetName.c_str(), slotNo); + + Common::InSaveFile *in; + + if (!(in = _saveFileMan->openForLoading(saveFileName))) { + return SR_ERR_FILEOPEN; + } + + in->readUint32LE(); + in->read(description, SAVE_DESCRIPTION_LEN); + + delete in; + return SR_OK; +} + +bool Sword2Engine::saveExists() { + for (int i = 0; i <= 99; i++) + if (saveExists(i)) + return true; + return false; +} + +bool Sword2Engine::saveExists(uint16 slotNo) { + char saveFileName[MAX_FILENAME_LEN]; + + sprintf(saveFileName, "%s.%.3d", _targetName.c_str(), slotNo); + + Common::InSaveFile *in; + + if (!(in = _saveFileMan->openForLoading(saveFileName))) { + return false; + } + + delete in; + return true; +} + +uint32 Sword2Engine::calcChecksum(byte *buffer, uint32 size) { + uint32 total = 0; + + for (uint32 pos = 0; pos < size; pos++) + total += buffer[pos]; + + return total; +} + +} // End of namespace Sword2 |