diff options
author | Max Horn | 2006-02-11 22:45:04 +0000 |
---|---|---|
committer | Max Horn | 2006-02-11 22:45:04 +0000 |
commit | 26ee630756ebdd7c96bccede0881a8c8b98e8f2b (patch) | |
tree | 26e378d5cf990a2b81c2c96e9e683a7f333b62e8 /engines/scumm/saveload.cpp | |
parent | 2a9a0d4211b1ea5723f1409d91cb95de8984429e (diff) | |
download | scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.gz scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.tar.bz2 scummvm-rg350-26ee630756ebdd7c96bccede0881a8c8b98e8f2b.zip |
Moved engines to the new engines/ directory
svn-id: r20582
Diffstat (limited to 'engines/scumm/saveload.cpp')
-rw-r--r-- | engines/scumm/saveload.cpp | 1648 |
1 files changed, 1648 insertions, 0 deletions
diff --git a/engines/scumm/saveload.cpp b/engines/scumm/saveload.cpp new file mode 100644 index 0000000000..9dca7abd40 --- /dev/null +++ b/engines/scumm/saveload.cpp @@ -0,0 +1,1648 @@ +/* ScummVM - Scumm Interpreter + * Copyright (C) 2001 Ludvig Strigeus + * Copyright (C) 2001-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$ + * + */ + +#include "common/stdafx.h" + +#include "common/config-manager.h" +#include "common/savefile.h" +#include "common/system.h" + +#include "scumm/actor.h" +#include "scumm/charset.h" +#include "scumm/imuse_digi/dimuse.h" +#include "scumm/imuse.h" +#include "scumm/intern.h" +#include "scumm/intern_he.h" +#include "scumm/object.h" +#include "scumm/resource.h" +#include "scumm/saveload.h" +#include "scumm/scumm.h" +#include "scumm/sound.h" +#include "scumm/sprite_he.h" +#include "scumm/verbs.h" + +#include "sound/audiocd.h" +#include "sound/mixer.h" + +namespace Scumm { + +struct SaveGameHeader { + uint32 type; + uint32 size; + uint32 ver; + char name[32]; +}; + +#if !defined(__GNUC__) + #pragma START_PACK_STRUCTS +#endif + +struct SaveInfoSection { + uint32 type; + uint32 version; + uint32 size; + + uint32 timeTValue; // Obsolete since version 2, but kept for compatibility + uint32 playtime; + + uint32 date; + uint16 time; +} GCC_PACK; + +#if !defined(__GNUC__) + #pragma END_PACK_STRUCTS +#endif + +#define INFOSECTION_VERSION 2 + +void ScummEngine::requestSave(int slot, const char *name, bool temporary) { + _saveLoadSlot = slot; + _saveTemporaryState = temporary; + _saveLoadFlag = 1; // 1 for save + assert(name); + strncpy(_saveLoadName, name, sizeof(_saveLoadName)); + _saveLoadName[sizeof(_saveLoadName) - 1] = 0; +} + +void ScummEngine::requestLoad(int slot) { + _saveLoadSlot = slot; + _saveTemporaryState = false; + _saveLoadFlag = 2; // 2 for load +} + +bool ScummEngine::saveState(int slot, bool compat) { + char filename[256]; + Common::OutSaveFile *out; + SaveGameHeader hdr; + + makeSavegameName(filename, slot, compat); + + if (!(out = _saveFileMan->openForSaving(filename))) + return false; + + memcpy(hdr.name, _saveLoadName, sizeof(hdr.name)); + + hdr.type = MKID('SCVM'); + hdr.size = 0; + hdr.ver = TO_LE_32(CURRENT_VER); + + out->write(&hdr, sizeof(hdr)); + saveThumbnail(out); + saveInfos(out); + + Serializer ser(0, out, CURRENT_VER); + saveOrLoad(&ser); + out->flush(); + if(out->ioFailed()) { + delete out; + debug(1, "State save as '%s' FAILED", filename); + return false; + } + delete out; + debug(1, "State saved as '%s'", filename); + return true; +} + +bool ScummEngine::loadState(int slot, bool compat) { + char filename[256]; + Common::InSaveFile *in; + int i, j; + SaveGameHeader hdr; + int sb, sh; + + makeSavegameName(filename, slot, compat); + if (!(in = _saveFileMan->openForLoading(filename))) + return false; + + in->read(&hdr, sizeof(hdr)); + if (hdr.type != MKID('SCVM')) { + warning("Invalid savegame '%s'", filename); + delete in; + return false; + } + + // In older versions of ScummVM, the header version was not endian safe. + // We account for that by retrying once with swapped byte order. + if (hdr.ver > CURRENT_VER) + hdr.ver = SWAP_BYTES_32(hdr.ver); + + // Reject save games which are too old or too new. Note that + // We do not really support V7 games, but still accept them here + // to work around a bug from the stone age (see below for more + // information). + if (hdr.ver < VER(7) || hdr.ver > CURRENT_VER) { + warning("Invalid version of '%s'", filename); + delete in; + return false; + } + + // We (deliberately) broke HE savegame compatibility at some point. + if (hdr.ver < VER(50) && _heversion >= 71) { + warning("Unsupported version of '%s'", filename); + delete in; + return false; + } + + // Since version 52 a thumbnail is saved directly after the header. + if (hdr.ver >= VER(52)) { + uint32 type; + in->read(&type, 4); + + // Check for the THMB header. Also, work around a bug which caused + // the chunk type (incorrectly) to be written in LE on LE machines. + if (! (type == MKID('THMB') || (hdr.ver < VER(55) && type == MKID('BMHT')))){ + warning("Can not load thumbnail"); + delete in; + return false; + } + uint32 size = in->readUint32BE(); + in->skip(size - 8); + } + + // Since version 56 we save additional information about the creation of + // the save game and the save time. + if (hdr.ver >= VER(56)) { + InfoStuff infos; + if (!loadInfos(in, &infos)) { + warning("Info section could not be found"); + delete in; + return false; + } + + _engineStartTime = _system->getMillis() / 1000 - infos.playtime; + } else { + // start time counting + _engineStartTime = _system->getMillis() / 1000; + } + + _dialogStartTime = _system->getMillis() / 1000; + + // Due to a bug in scummvm up to and including 0.3.0, save games could be saved + // in the V8/V9 format but were tagged with a V7 mark. Ouch. So we just pretend V7 == V8 here + if (hdr.ver == VER(7)) + hdr.ver = VER(8); + + memcpy(_saveLoadName, hdr.name, sizeof(hdr.name)); + + // Unless specifically requested with _saveSound, we do not save the iMUSE + // state for temporary state saves - such as certain cutscenes in DOTT, + // FOA, Sam and Max, etc. + // + // Thusly, we should probably not stop music when restoring from one of + // these saves. This change stops the Mole Man theme from going quiet in + // Sam & Max when Doug tells you about the Ball of Twine, as mentioned in + // patch #886058. + // + // If we don't have iMUSE at all we may as well stop the sounds. The previous + // default behavior here was to stopAllSounds on all state restores. + + if (!_imuse || _saveSound || !_saveTemporaryState) + _sound->stopAllSounds(); + +#ifndef DISABLE_SCUMM_7_8 + if (_imuseDigital) { + _imuseDigital->stopAllSounds(); + _imuseDigital->resetState(); + } +#endif + + _sound->stopCD(); + + _sound->pauseSounds(true); + + CHECK_HEAP + + closeRoom(); + + memset(_inventory, 0, sizeof(_inventory[0]) * _numInventory); + memset(_newNames, 0, sizeof(_newNames[0]) * _numNewNames); + + // Because old savegames won't fill the entire gfxUsageBits[] array, + // clear it here just to be sure it won't hold any unforseen garbage. + memset(gfxUsageBits, 0, sizeof(gfxUsageBits)); + + // Nuke all resources + for (i = rtFirst; i <= rtLast; i++) + if (i != rtTemp && i != rtBuffer && (i != rtSound || _saveSound || !compat)) + for (j = 0; j < res.num[i]; j++) { + res.nukeResource(i, j); + } + + initScummVars(); + + if (_features & GF_OLD_BUNDLE) + loadCharset(0); // FIXME - HACK ? + + // + // Now do the actual loading + // + Serializer ser(in, 0, hdr.ver); + saveOrLoad(&ser); + delete in; + + // Update volume settings + setupVolumes(); + + // Init NES costume data + if (_platform == Common::kPlatformNES) { + if (hdr.ver < VER(47)) + _NESCostumeSet = 0; + NES_loadCostumeSet(_NESCostumeSet); + } + + // Normally, _vm->_screenTop should always be >= 0, but for some old save games + // it is not, hence we check & correct it here. + if (_screenTop < 0) + _screenTop = 0; + + if (hdr.ver < VER(33) && _version >= 7) { + // For a long time, we didn't set these vars to default values. + VAR(VAR_DEFAULT_TALK_DELAY) = 60; + if (_version == 7) + VAR(VAR_NUM_GLOBAL_OBJS) = _numGlobalObjects - 1; + } + + if (hdr.ver < VER(30)) { + // For a long time, we used incorrect location, causing it to default to zero. + if (_version == 8) + _scummVars[VAR_CHARINC] = (_features & GF_DEMO) ? 3 : 1; + // Needed due to subtitle speed changes + _defaultTalkDelay /= 20; + } + + // For a long time, we used incorrect locations for some camera related + // scumm vars. We now know the proper locations. To be able to properly use + // old save games, we update the old (bad) variables to the new (correct) + // ones. + if (hdr.ver < VER(28) && _version == 8) { + _scummVars[VAR_CAMERA_MIN_X] = _scummVars[101]; + _scummVars[VAR_CAMERA_MAX_X] = _scummVars[102]; + _scummVars[VAR_CAMERA_MIN_Y] = _scummVars[103]; + _scummVars[VAR_CAMERA_MAX_Y] = _scummVars[104]; + _scummVars[VAR_CAMERA_THRESHOLD_X] = _scummVars[105]; + _scummVars[VAR_CAMERA_THRESHOLD_Y] = _scummVars[106]; + _scummVars[VAR_CAMERA_SPEED_X] = _scummVars[107]; + _scummVars[VAR_CAMERA_SPEED_Y] = _scummVars[108]; + _scummVars[VAR_CAMERA_ACCEL_X] = _scummVars[109]; + _scummVars[VAR_CAMERA_ACCEL_Y] = _scummVars[110]; + } + + // With version 22, we replaced the scale items with scale slots. So when + // loading such an old save game, try to upgrade the old to new format. + if (hdr.ver < VER(22)) { + // Convert all rtScaleTable resources to matching scale items + for (i = 1; i < res.num[rtScaleTable]; i++) { + convertScaleTableToScaleSlot(i); + } + } + + // We could simply dirty colours 0-15 for 16-colour games -- nowadays + // they handle their palette pretty much like the more recent games + // anyway. There was a time, though, when re-initializing was necessary + // for backwards compatibility, and it may still prove useful if we + // ever add options for using different 16-colour palettes. + if (_version == 1) { + if (_platform == Common::kPlatformC64) { + setupC64Palette(); + } else if (_platform == Common::kPlatformNES) { + setupNESPalette(); + } else { + setupV1Palette(); + } + } else if (_features & GF_16COLOR) { + switch (_renderMode) { + case Common::kRenderEGA: + setupEGAPalette(); + break; + + case Common::kRenderAmiga: + setupAmigaPalette(); + break; + + case Common::kRenderCGA: + setupCGAPalette(); + break; + + case Common::kRenderHercA: + case Common::kRenderHercG: + setupHercPalette(); + break; + + default: + if ((_platform == Common::kPlatformAmiga) || (_platform == Common::kPlatformAtariST)) + setupAmigaPalette(); + else + setupEGAPalette(); + } + } else + setDirtyColors(0, 255); + + + if (hdr.ver < VER(35) && _gameId == GID_MANIAC && _version == 1) + setupV1ActorTalkColor(); + + // Load the static room data + loadRoomSubBlocks(); + + if (!(_features & GF_NEW_CAMERA)) { + camera._last.x = camera._cur.x; + } + + sb = _screenB; + sh = _screenH; + + // Restore the virtual screens and force a fade to black. + initScreens(0, _screenHeight); + + VirtScreen *vs = &virtscr[kMainVirtScreen]; + memset(vs->getPixels(0, 0), 0, vs->pitch * vs->h); + vs->setDirtyRange(0, vs->h); + updateDirtyScreen(kMainVirtScreen); + updatePalette(); + initScreens(sb, sh); + + _completeScreenRedraw = true; + + // Reset charset mask + _charset->_hasMask = false; + _charset->clearTextSurface(); + + _lastCodePtr = NULL; + _drawObjectQueNr = 0; + _verbMouseOver = 0; + + cameraMoved(); + + initBGBuffers(_roomHeight); + + if (VAR_ROOM_FLAG != 0xFF) + VAR(VAR_ROOM_FLAG) = 1; + + // Sync with current config setting + if (VAR_VOICE_MODE != 0xFF) + VAR(VAR_VOICE_MODE) = ConfMan.getBool("subtitles"); + + CHECK_HEAP + debug(1, "State loaded from '%s'", filename); + + _sound->pauseSounds(false); + + _engineStartTime += _system->getMillis() / 1000 - _dialogStartTime; + _dialogStartTime = 0; + + return true; +} + +void ScummEngine::makeSavegameName(char *out, int slot, bool temporary) { + sprintf(out, "%s.%c%.2d", _targetName.c_str(), temporary ? 'c' : 's', slot); +} + +void ScummEngine::listSavegames(bool *marks, int num) { + char prefix[256]; + makeSavegameName(prefix, 99, false); + prefix[strlen(prefix)-2] = 0; + _saveFileMan->listSavefiles(prefix, marks, num); +} + +bool ScummEngine::getSavegameName(int slot, char *desc) { + char filename[256]; + Common::InSaveFile *in; + SaveGameHeader hdr; + int len; + + makeSavegameName(filename, slot, false); + if (!(in = _saveFileMan->openForLoading(filename))) { + strcpy(desc, ""); + return false; + } + len = in->read(&hdr, sizeof(hdr)); + delete in; + + if (len != sizeof(hdr) || hdr.type != MKID('SCVM')) { + strcpy(desc, "Invalid savegame"); + return false; + } + + if (hdr.ver > CURRENT_VER) + hdr.ver = TO_LE_32(hdr.ver); + if (hdr.ver < VER(7) || hdr.ver > CURRENT_VER) { + strcpy(desc, "Invalid version"); + return false; + } + + // We (deliberately) broke HE savegame compatibility at some point. + if (hdr.ver < VER(57) && _heversion >= 60) { + strcpy(desc, "Unsupported version"); + return false; + } + + memcpy(desc, hdr.name, sizeof(hdr.name)); + desc[sizeof(hdr.name) - 1] = 0; + return true; +} + +Graphics::Surface *ScummEngine::loadThumbnailFromSlot(int slot) { + char filename[256]; + Common::InSaveFile *in; + SaveGameHeader hdr; + int len; + + makeSavegameName(filename, slot, false); + if (!(in = _saveFileMan->openForLoading(filename))) { + return 0; + } + len = in->read(&hdr, sizeof(hdr)); + + if (len != sizeof(hdr) || hdr.type != MKID('SCVM')) { + delete in; + return 0; + } + + if (hdr.ver > CURRENT_VER) + hdr.ver = TO_LE_32(hdr.ver); + if (hdr.ver < VER(52)) { + delete in; + return 0; + } + + Graphics::Surface *thumb = loadThumbnail(in); + + delete in; + return thumb; +} + +bool ScummEngine::loadInfosFromSlot(int slot, InfoStuff *stuff) { + char filename[256]; + Common::InSaveFile *in; + SaveGameHeader hdr; + int len; + + makeSavegameName(filename, slot, false); + if (!(in = _saveFileMan->openForLoading(filename))) { + return false; + } + len = in->read(&hdr, sizeof(hdr)); + + if (len != sizeof(hdr) || hdr.type != MKID('SCVM')) { + delete in; + return false; + } + + if (hdr.ver > CURRENT_VER) + hdr.ver = TO_LE_32(hdr.ver); + if (hdr.ver < VER(56)) { + delete in; + return false; + } + + uint32 type; + in->read(&type, 4); + + // Check for the THMB header. Also, work around a bug which caused + // the chunk type (incorrectly) to be written in LE on LE machines. + if (! (type == MKID('THMB') || (hdr.ver < VER(55) && type == MKID('BMHT')))){ + delete in; + return false; + } + uint32 size = in->readUint32BE(); + in->skip(size - 8); + + if (!loadInfos(in, stuff)) { + delete in; + return false; + } + + delete in; + return true; +} + +bool ScummEngine::loadInfos(Common::InSaveFile *file, InfoStuff *stuff) { + memset(stuff, 0, sizeof(InfoStuff)); + + SaveInfoSection section; + file->read(§ion.type, 4); + if (section.type != MKID('INFO')) { + return false; + } + + section.version = file->readUint32BE(); + section.size = file->readUint32BE(); + + // if we extend this we should add a table for the special sizes at the versions to check it + if (section.version == INFOSECTION_VERSION && section.size != sizeof(SaveInfoSection)) { + warning("Info section is corrupt"); + file->skip(section.size); + return false; + } + + section.timeTValue = file->readUint32BE(); + section.playtime = file->readUint32BE(); + + // for compatibility for older version we + // to load in with our old method + if (section.version == 1) { + time_t curTime_ = section.timeTValue; + tm *curTime = localtime(&curTime_); + stuff->date = (curTime->tm_mday & 0xFF) << 24 | ((curTime->tm_mon + 1) & 0xFF) << 16 | (curTime->tm_year + 1900) & 0xFFFF; + stuff->time = (curTime->tm_hour & 0xFF) << 8 | (curTime->tm_min) & 0xFF; + } + + if (section.version >= 2) { + section.date = file->readUint32BE(); + section.time = file->readUint16BE(); + + stuff->date = section.date; + stuff->time = section.time; + } + + stuff->playtime = section.playtime; + + // skip all newer features, this could make problems if some older version uses more space for + // saving informations, but this should NOT happen + if (section.size > sizeof(SaveInfoSection)) { + file->skip(section.size - sizeof(SaveInfoSection)); + } + + return true; +} + +void ScummEngine::saveInfos(Common::OutSaveFile* file) { + SaveInfoSection section; + section.type = MKID('INFO'); + section.version = INFOSECTION_VERSION; + section.size = sizeof(SaveInfoSection); + + // still save old format for older versions + section.timeTValue = time(0); + section.playtime = _system->getMillis() / 1000 - _engineStartTime; + + time_t curTime_ = time(0); + tm *curTime = localtime(&curTime_); + section.date = (curTime->tm_mday & 0xFF) << 24 | ((curTime->tm_mon + 1) & 0xFF) << 16 | (curTime->tm_year + 1900) & 0xFFFF; + section.time = (curTime->tm_hour & 0xFF) << 8 | (curTime->tm_min) & 0xFF; + + file->write(§ion.type, 4); + file->writeUint32BE(section.version); + file->writeUint32BE(section.size); + file->writeUint32BE(section.timeTValue); + file->writeUint32BE(section.playtime); + file->writeUint32BE(section.date); + file->writeUint16BE(section.time); +} + +void ScummEngine::saveOrLoad(Serializer *s) { + const SaveLoadEntry objectEntries[] = { + MKLINE(ObjectData, OBIMoffset, sleUint32, VER(8)), + MKLINE(ObjectData, OBCDoffset, sleUint32, VER(8)), + MKLINE(ObjectData, walk_x, sleUint16, VER(8)), + MKLINE(ObjectData, walk_y, sleUint16, VER(8)), + MKLINE(ObjectData, obj_nr, sleUint16, VER(8)), + MKLINE(ObjectData, x_pos, sleInt16, VER(8)), + MKLINE(ObjectData, y_pos, sleInt16, VER(8)), + MKLINE(ObjectData, width, sleUint16, VER(8)), + MKLINE(ObjectData, height, sleUint16, VER(8)), + MKLINE(ObjectData, actordir, sleByte, VER(8)), + MKLINE(ObjectData, parentstate, sleByte, VER(8)), + MKLINE(ObjectData, parent, sleByte, VER(8)), + MKLINE(ObjectData, state, sleByte, VER(8)), + MKLINE(ObjectData, fl_object_index, sleByte, VER(8)), + MKLINE(ObjectData, flags, sleByte, VER(46)), + MKEND() + }; + + const SaveLoadEntry verbEntries[] = { + MKLINE(VerbSlot, curRect.left, sleInt16, VER(8)), + MKLINE(VerbSlot, curRect.top, sleInt16, VER(8)), + MKLINE(VerbSlot, curRect.right, sleInt16, VER(8)), + MKLINE(VerbSlot, curRect.bottom, sleInt16, VER(8)), + MKLINE(VerbSlot, oldRect.left, sleInt16, VER(8)), + MKLINE(VerbSlot, oldRect.top, sleInt16, VER(8)), + MKLINE(VerbSlot, oldRect.right, sleInt16, VER(8)), + MKLINE(VerbSlot, oldRect.bottom, sleInt16, VER(8)), + + MKLINE_OLD(VerbSlot, verbid, sleByte, VER(8), VER(11)), + MKLINE(VerbSlot, verbid, sleInt16, VER(12)), + + MKLINE(VerbSlot, color, sleByte, VER(8)), + MKLINE(VerbSlot, hicolor, sleByte, VER(8)), + MKLINE(VerbSlot, dimcolor, sleByte, VER(8)), + MKLINE(VerbSlot, bkcolor, sleByte, VER(8)), + MKLINE(VerbSlot, type, sleByte, VER(8)), + MKLINE(VerbSlot, charset_nr, sleByte, VER(8)), + MKLINE(VerbSlot, curmode, sleByte, VER(8)), + MKLINE(VerbSlot, saveid, sleByte, VER(8)), + MKLINE(VerbSlot, key, sleByte, VER(8)), + MKLINE(VerbSlot, center, sleByte, VER(8)), + MKLINE(VerbSlot, prep, sleByte, VER(8)), + MKLINE(VerbSlot, imgindex, sleUint16, VER(8)), + MKEND() + }; + + const SaveLoadEntry mainEntries[] = { + MKARRAY(ScummEngine, _gameMD5[0], sleUint8, 16, VER(39)), + MK_OBSOLETE(ScummEngine, _roomWidth, sleUint16, VER(8), VER(50)), + MK_OBSOLETE(ScummEngine, _roomHeight, sleUint16, VER(8), VER(50)), + MK_OBSOLETE(ScummEngine, _ENCD_offs, sleUint32, VER(8), VER(50)), + MK_OBSOLETE(ScummEngine, _EXCD_offs, sleUint32, VER(8), VER(50)), + MK_OBSOLETE(ScummEngine, _IM00_offs, sleUint32, VER(8), VER(50)), + MK_OBSOLETE(ScummEngine, _CLUT_offs, sleUint32, VER(8), VER(50)), + MK_OBSOLETE(ScummEngine, _EPAL_offs, sleUint32, VER(8), VER(9)), + MK_OBSOLETE(ScummEngine, _PALS_offs, sleUint32, VER(8), VER(50)), + MKLINE(ScummEngine, _curPalIndex, sleByte, VER(8)), + MKLINE(ScummEngine, _currentRoom, sleByte, VER(8)), + MKLINE(ScummEngine, _roomResource, sleByte, VER(8)), + MKLINE(ScummEngine, _numObjectsInRoom, sleByte, VER(8)), + MKLINE(ScummEngine, _currentScript, sleByte, VER(8)), + MK_OBSOLETE_ARRAY(ScummEngine, _localScriptOffsets[0], sleUint32, _numLocalScripts, VER(8), VER(50)), + + + // vm.localvar grew from 25 to 40 script entries and then from + // 16 to 32 bit variables (but that wasn't reflect here)... and + // THEN from 16 to 25 variables. + MKARRAY2_OLD(ScummEngine, vm.localvar[0][0], sleUint16, 17, 25, (byte*)vm.localvar[1] - (byte*)vm.localvar[0], VER(8), VER(8)), + MKARRAY2_OLD(ScummEngine, vm.localvar[0][0], sleUint16, 17, 40, (byte*)vm.localvar[1] - (byte*)vm.localvar[0], VER(9), VER(14)), + + // We used to save 25 * 40 = 1000 blocks; but actually, each 'row consisted of 26 entry, + // i.e. 26 * 40 = 1040. Thus the last 40 blocks of localvar where not saved at all. To be + // able to load this screwed format, we use a trick: We load 26 * 38 = 988 blocks. + // Then, we mark the followin 12 blocks (24 bytes) as obsolete. + MKARRAY2_OLD(ScummEngine, vm.localvar[0][0], sleUint16, 26, 38, (byte*)vm.localvar[1] - (byte*)vm.localvar[0], VER(15), VER(17)), + MK_OBSOLETE_ARRAY(ScummEngine, vm.localvar[39][0], sleUint16, 12, VER(15), VER(17)), + + // This was the first proper multi dimensional version of the localvars, with 32 bit values + MKARRAY2_OLD(ScummEngine, vm.localvar[0][0], sleUint32, 26, 40, (byte*)vm.localvar[1] - (byte*)vm.localvar[0], VER(18), VER(19)), + + // Then we doubled the script slots again, from 40 to 80 + MKARRAY2(ScummEngine, vm.localvar[0][0], sleUint32, 26, NUM_SCRIPT_SLOT, (byte*)vm.localvar[1] - (byte*)vm.localvar[0], VER(20)), + + + MKARRAY(ScummEngine, _resourceMapper[0], sleByte, 128, VER(8)), + MKARRAY(ScummEngine, _charsetColorMap[0], sleByte, 16, VER(8)), + + // _charsetData grew from 10*16 to 15*16 bytes + MKARRAY_OLD(ScummEngine, _charsetData[0][0], sleByte, 10 * 16, VER(8), VER(9)), + MKARRAY(ScummEngine, _charsetData[0][0], sleByte, 15 * 16, VER(10)), + + MK_OBSOLETE(ScummEngine, _curExecScript, sleUint16, VER(8), VER(62)), + + MKLINE(ScummEngine, camera._dest.x, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._dest.y, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._cur.x, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._cur.y, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._last.x, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._last.y, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._accel.x, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._accel.y, sleInt16, VER(8)), + MKLINE(ScummEngine, _screenStartStrip, sleInt16, VER(8)), + MKLINE(ScummEngine, _screenEndStrip, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._mode, sleByte, VER(8)), + MKLINE(ScummEngine, camera._follows, sleByte, VER(8)), + MKLINE(ScummEngine, camera._leftTrigger, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._rightTrigger, sleInt16, VER(8)), + MKLINE(ScummEngine, camera._movingToActor, sleUint16, VER(8)), + + MKLINE(ScummEngine, _actorToPrintStrFor, sleByte, VER(8)), + MKLINE(ScummEngine, _charsetColor, sleByte, VER(8)), + + // _charsetBufPos was changed from byte to int + MKLINE_OLD(ScummEngine, _charsetBufPos, sleByte, VER(8), VER(9)), + MKLINE(ScummEngine, _charsetBufPos, sleInt16, VER(10)), + + MKLINE(ScummEngine, _haveMsg, sleByte, VER(8)), + MKLINE(ScummEngine, _haveActorSpeechMsg, sleByte, VER(61)), + MKLINE(ScummEngine, _useTalkAnims, sleByte, VER(8)), + + MKLINE(ScummEngine, _talkDelay, sleInt16, VER(8)), + MKLINE(ScummEngine, _defaultTalkDelay, sleInt16, VER(8)), + MK_OBSOLETE(ScummEngine, _numInMsgStack, sleInt16, VER(8), VER(27)), + MKLINE(ScummEngine, _sentenceNum, sleByte, VER(8)), + + MKLINE(ScummEngine, vm.cutSceneStackPointer, sleByte, VER(8)), + MKARRAY(ScummEngine, vm.cutScenePtr[0], sleUint32, 5, VER(8)), + MKARRAY(ScummEngine, vm.cutSceneScript[0], sleByte, 5, VER(8)), + MKARRAY(ScummEngine, vm.cutSceneData[0], sleInt16, 5, VER(8)), + MKLINE(ScummEngine, vm.cutSceneScriptIndex, sleInt16, VER(8)), + + MKLINE(ScummEngine, vm.numNestedScripts, sleByte, VER(8)), + MKLINE(ScummEngine, _userPut, sleByte, VER(8)), + MKLINE(ScummEngine, _userState, sleUint16, VER(17)), + MKLINE(ScummEngine, _cursor.state, sleByte, VER(8)), + MK_OBSOLETE(ScummEngine, gdi._cursorActive, sleByte, VER(8), VER(20)), + MKLINE(ScummEngine, _currentCursor, sleByte, VER(8)), + MKARRAY(ScummEngine, _grabbedCursor[0], sleByte, 8192, VER(20)), + MKLINE(ScummEngine, _cursor.width, sleInt16, VER(20)), + MKLINE(ScummEngine, _cursor.height, sleInt16, VER(20)), + MKLINE(ScummEngine, _cursor.hotspotX, sleInt16, VER(20)), + MKLINE(ScummEngine, _cursor.hotspotY, sleInt16, VER(20)), + MKLINE(ScummEngine, _cursor.animate, sleByte, VER(20)), + MKLINE(ScummEngine, _cursor.animateIndex, sleByte, VER(20)), + MKLINE(ScummEngine, _mouse.x, sleInt16, VER(20)), + MKLINE(ScummEngine, _mouse.y, sleInt16, VER(20)), + + MKARRAY(ScummEngine, _colorUsedByCycle[0], sleByte, 256, VER(60)), + MKLINE(ScummEngine, _doEffect, sleByte, VER(8)), + MKLINE(ScummEngine, _switchRoomEffect, sleByte, VER(8)), + MKLINE(ScummEngine, _newEffect, sleByte, VER(8)), + MKLINE(ScummEngine, _switchRoomEffect2, sleByte, VER(8)), + MKLINE(ScummEngine, _bgNeedsRedraw, sleByte, VER(8)), + + // The state of palManipulate is stored only since V10 + MKLINE(ScummEngine, _palManipStart, sleByte, VER(10)), + MKLINE(ScummEngine, _palManipEnd, sleByte, VER(10)), + MKLINE(ScummEngine, _palManipCounter, sleUint16, VER(10)), + + // gfxUsageBits grew from 200 to 410 entries. Then 3 * 410 entries: + MKARRAY_OLD(ScummEngine, gfxUsageBits[0], sleUint32, 200, VER(8), VER(9)), + MKARRAY_OLD(ScummEngine, gfxUsageBits[0], sleUint32, 410, VER(10), VER(13)), + MKARRAY(ScummEngine, gfxUsageBits[0], sleUint32, 3 * 410, VER(14)), + + MK_OBSOLETE(ScummEngine, gdi._transparentColor, sleByte, VER(8), VER(50)), + MKARRAY(ScummEngine, _currentPalette[0], sleByte, 768, VER(8)), + MKARRAY(ScummEngine, _darkenPalette[0], sleByte, 768, VER(53)), + + // Sam & Max specific palette replaced by _shadowPalette now. + MK_OBSOLETE_ARRAY(ScummEngine, _proc_special_palette[0], sleByte, 256, VER(8), VER(33)), + + MKARRAY(ScummEngine, _charsetBuffer[0], sleByte, 256, VER(8)), + + MKLINE(ScummEngine, _egoPositioned, sleByte, VER(8)), + + // gdi._imgBufOffs grew from 4 to 5 entries. Then one day we realized + // that we don't have to store it since initBGBuffers() recomputes it. + MK_OBSOLETE_ARRAY(ScummEngine, gdi._imgBufOffs[0], sleUint16, 4, VER(8), VER(9)), + MK_OBSOLETE_ARRAY(ScummEngine, gdi._imgBufOffs[0], sleUint16, 5, VER(10), VER(26)), + + // See _imgBufOffs: _numZBuffer is recomputed by initBGBuffers(). + MK_OBSOLETE(ScummEngine, gdi._numZBuffer, sleByte, VER(8), VER(26)), + + MKLINE(ScummEngine, _screenEffectFlag, sleByte, VER(8)), + + MK_OBSOLETE(ScummEngine, _randSeed1, sleUint32, VER(8), VER(9)), + MK_OBSOLETE(ScummEngine, _randSeed2, sleUint32, VER(8), VER(9)), + + // Converted _shakeEnabled to boolean and added a _shakeFrame field. + MKLINE_OLD(ScummEngine, _shakeEnabled, sleInt16, VER(8), VER(9)), + MKLINE(ScummEngine, _shakeEnabled, sleByte, VER(10)), + MKLINE(ScummEngine, _shakeFrame, sleUint32, VER(10)), + + MKLINE(ScummEngine, _keepText, sleByte, VER(8)), + + MKLINE(ScummEngine, _screenB, sleUint16, VER(8)), + MKLINE(ScummEngine, _screenH, sleUint16, VER(8)), + + MKLINE(ScummEngine, _NESCostumeSet, sleUint16, VER(47)), + + MK_OBSOLETE(ScummEngine, _cd_track, sleInt16, VER(9), VER(9)), + MK_OBSOLETE(ScummEngine, _cd_loops, sleInt16, VER(9), VER(9)), + MK_OBSOLETE(ScummEngine, _cd_frame, sleInt16, VER(9), VER(9)), + MK_OBSOLETE(ScummEngine, _cd_end, sleInt16, VER(9), VER(9)), + + MKEND() + }; + + const SaveLoadEntry scriptSlotEntries[] = { + MKLINE(ScriptSlot, offs, sleUint32, VER(8)), + MKLINE(ScriptSlot, delay, sleInt32, VER(8)), + MKLINE(ScriptSlot, number, sleUint16, VER(8)), + MKLINE(ScriptSlot, delayFrameCount, sleUint16, VER(8)), + MKLINE(ScriptSlot, status, sleByte, VER(8)), + MKLINE(ScriptSlot, where, sleByte, VER(8)), + MKLINE(ScriptSlot, freezeResistant, sleByte, VER(8)), + MKLINE(ScriptSlot, recursive, sleByte, VER(8)), + MKLINE(ScriptSlot, freezeCount, sleByte, VER(8)), + MKLINE(ScriptSlot, didexec, sleByte, VER(8)), + MKLINE(ScriptSlot, cutsceneOverride, sleByte, VER(8)), + MKLINE(ScriptSlot, cycle, sleByte, VER(46)), + MK_OBSOLETE(ScriptSlot, unk5, sleByte, VER(8), VER(10)), + MKEND() + }; + + const SaveLoadEntry nestedScriptEntries[] = { + MKLINE(NestedScript, number, sleUint16, VER(8)), + MKLINE(NestedScript, where, sleByte, VER(8)), + MKLINE(NestedScript, slot, sleByte, VER(8)), + MKEND() + }; + + const SaveLoadEntry sentenceTabEntries[] = { + MKLINE(SentenceTab, verb, sleUint8, VER(8)), + MKLINE(SentenceTab, preposition, sleUint8, VER(8)), + MKLINE(SentenceTab, objectA, sleUint16, VER(8)), + MKLINE(SentenceTab, objectB, sleUint16, VER(8)), + MKLINE(SentenceTab, freezeCount, sleUint8, VER(8)), + MKEND() + }; + + const SaveLoadEntry stringTabEntries[] = { + // Then _default/restore of a StringTab entry becomes a one liner. + MKLINE(StringTab, xpos, sleInt16, VER(8)), + MKLINE(StringTab, _default.xpos, sleInt16, VER(8)), + MKLINE(StringTab, ypos, sleInt16, VER(8)), + MKLINE(StringTab, _default.ypos, sleInt16, VER(8)), + MKLINE(StringTab, right, sleInt16, VER(8)), + MKLINE(StringTab, _default.right, sleInt16, VER(8)), + MKLINE(StringTab, color, sleInt8, VER(8)), + MKLINE(StringTab, _default.color, sleInt8, VER(8)), + MKLINE(StringTab, charset, sleInt8, VER(8)), + MKLINE(StringTab, _default.charset, sleInt8, VER(8)), + MKLINE(StringTab, center, sleByte, VER(8)), + MKLINE(StringTab, _default.center, sleByte, VER(8)), + MKLINE(StringTab, overhead, sleByte, VER(8)), + MKLINE(StringTab, _default.overhead, sleByte, VER(8)), + MKLINE(StringTab, no_talk_anim, sleByte, VER(8)), + MKLINE(StringTab, _default.no_talk_anim, sleByte, VER(8)), + MKEND() + }; + + const SaveLoadEntry colorCycleEntries[] = { + MKLINE(ColorCycle, delay, sleUint16, VER(8)), + MKLINE(ColorCycle, counter, sleUint16, VER(8)), + MKLINE(ColorCycle, flags, sleUint16, VER(8)), + MKLINE(ColorCycle, start, sleByte, VER(8)), + MKLINE(ColorCycle, end, sleByte, VER(8)), + MKEND() + }; + + const SaveLoadEntry scaleSlotsEntries[] = { + MKLINE(ScaleSlot, x1, sleUint16, VER(13)), + MKLINE(ScaleSlot, y1, sleUint16, VER(13)), + MKLINE(ScaleSlot, scale1, sleUint16, VER(13)), + MKLINE(ScaleSlot, x2, sleUint16, VER(13)), + MKLINE(ScaleSlot, y2, sleUint16, VER(13)), + MKLINE(ScaleSlot, scale2, sleUint16, VER(13)), + MKEND() + }; + + // MSVC6 FIX (Jamieson630): + // MSVC6 has a problem with any notation that involves + // more than one set of double colons :: + // The following MKLINE macros expand to such things + // as AudioCDManager::Status::playing, and MSVC6 has + // a fit with that. This typedef simplifies the notation + // to something MSVC6 can grasp. + typedef AudioCDManager::Status AudioCDManager_Status; + const SaveLoadEntry audioCDEntries[] = { + MKLINE(AudioCDManager_Status, playing, sleUint32, VER(24)), + MKLINE(AudioCDManager_Status, track, sleInt32, VER(24)), + MKLINE(AudioCDManager_Status, start, sleUint32, VER(24)), + MKLINE(AudioCDManager_Status, duration, sleUint32, VER(24)), + MKLINE(AudioCDManager_Status, numLoops, sleInt32, VER(24)), + MKEND() + }; + + int i, j; + int var120Backup; + int var98Backup; + uint8 md5Backup[16]; + + // MD5 Operations: Backup on load, compare, and reset. + if (s->isLoading()) + memcpy(md5Backup, _gameMD5, 16); + + + // + // Save/load main state (many members of class ScummEngine get saved here) + // + s->saveLoadEntries(this, mainEntries); + + // MD5 Operations: Backup on load, compare, and reset. + if (s->isLoading()) { + char md5str1[32+1], md5str2[32+1]; + for (j = 0; j < 16; j++) { + sprintf(md5str1 + j*2, "%02x", (int)_gameMD5[j]); + sprintf(md5str2 + j*2, "%02x", (int)md5Backup[j]); + } + + debug(2, "Save version: %d", s->getVersion()); + debug(2, "Saved game MD5: %s", (s->getVersion() >= 39) ? md5str1 : "unknown"); + + if (memcmp(md5Backup, _gameMD5, 16) != 0) { + warning("Game was saved with different gamedata - you may encounter problems."); + debug(1, "You have %s and save is %s.", md5str2, md5str1); + memcpy(_gameMD5, md5Backup, 16); + } + } + + + // Starting V14, we extended the usage bits, to be able to cope with games + // that have more than 30 actors (up to 94 are supported now, in theory). + // Since the format of the usage bits was changed by this, we have to + // convert them when loading an older savegame. + if (s->isLoading() && s->getVersion() < VER(14)) + upgradeGfxUsageBits(); + + // When loading, move the mouse to the saved mouse position. + if (s->isLoading() && s->getVersion() >= VER(20)) { + updateCursor(); + _system->warpMouse(_mouse.x, _mouse.y); + } + + // Before V61, we re-used the _haveMsg flag to handle "alternative" speech + // sound files (see charset code 10). + if (s->isLoading() && s->getVersion() < VER(61)) { + if (_haveMsg == 0xFE) { + _haveActorSpeechMsg = false; + _haveMsg = 0xFF; + } else { + _haveActorSpeechMsg = true; + } + } + + // + // Save/load actors + // + for (i = 0; i < _numActors; i++) + _actors[i].saveLoadWithSerializer(s); + + + // + // Save/load sound data + // + _sound->saveLoadWithSerializer(s); + + + // + // Save/load script data + // + if (s->getVersion() < VER(9)) + s->saveLoadArrayOf(vm.slot, 25, sizeof(vm.slot[0]), scriptSlotEntries); + else if (s->getVersion() < VER(20)) + s->saveLoadArrayOf(vm.slot, 40, sizeof(vm.slot[0]), scriptSlotEntries); + else + s->saveLoadArrayOf(vm.slot, NUM_SCRIPT_SLOT, sizeof(vm.slot[0]), scriptSlotEntries); + + if (s->getVersion() < VER(46)) { + // When loading an old savegame, make sure that the 'cycle' + // field is set to something sensible, otherwise the scripts + // that were running probably won't be. + + for (i = 0; i < NUM_SCRIPT_SLOT; i++) { + vm.slot[i].cycle = 1; + } + } + + + // + // Save/load local objects + // + s->saveLoadArrayOf(_objs, _numLocalObjects, sizeof(_objs[0]), objectEntries); + if (s->isLoading() && s->getVersion() < VER(13)) { + // Since roughly v13 of the save games, the objs storage has changed a bit + for (i = _numObjectsInRoom; i < _numLocalObjects; i++) { + _objs[i].obj_nr = 0; + } + + } + + + // + // Save/load misc stuff + // + s->saveLoadArrayOf(_verbs, _numVerbs, sizeof(_verbs[0]), verbEntries); + s->saveLoadArrayOf(vm.nest, 16, sizeof(vm.nest[0]), nestedScriptEntries); + s->saveLoadArrayOf(_sentence, 6, sizeof(_sentence[0]), sentenceTabEntries); + s->saveLoadArrayOf(_string, 6, sizeof(_string[0]), stringTabEntries); + s->saveLoadArrayOf(_colorCycle, 16, sizeof(_colorCycle[0]), colorCycleEntries); + if (s->getVersion() >= VER(13)) + s->saveLoadArrayOf(_scaleSlots, 20, sizeof(_scaleSlots[0]), scaleSlotsEntries); + + + // + // Save/load resources + // + int type, idx; + if (s->getVersion() >= VER(26)) { + // New, more robust resource save/load system. This stores the type + // and index of each resource. Thus if we increase e.g. the maximum + // number of script resources, savegames won't break. + if (s->isSaving()) { + for (type = rtFirst; type <= rtLast; type++) { + if (res.mode[type] != 1 && type != rtTemp && type != rtBuffer) { + s->saveUint16(type); // Save the res type... + for (idx = 0; idx < res.num[type]; idx++) { + // Only save resources which actually exist... + if (res.address[type][idx]) { + s->saveUint16(idx); // Save the index of the resource + saveResource(s, type, idx); + } + } + s->saveUint16(0xFFFF); // End marker + } + } + s->saveUint16(0xFFFF); // End marker + } else { + while ((type = s->loadUint16()) != 0xFFFF) { + while ((idx = s->loadUint16()) != 0xFFFF) { + assert(0 <= idx && idx < res.num[type]); + loadResource(s, type, idx); + } + } + } + } else { + // Old, fragile resource save/load system. Doesn't save resources + // with index 0, and breaks whenever we change the limit on a given + // resource type. + for (type = rtFirst; type <= rtLast; type++) + if (res.mode[type] != 1 && type != rtTemp && type != rtBuffer) { + // For V1-V5 games, there used to be no object name resources. + // At some point this changed. But since old savegames rely on + // unchanged resource counts, we have to hard code the following check + if (_version < 6 && type == rtObjectName) + continue; + for (idx = 1; idx < res.num[type]; idx++) + saveLoadResource(s, type, idx); + } + } + + + // + // Save/load global object state + // + s->saveLoadArrayOf(_objectOwnerTable, _numGlobalObjects, sizeof(_objectOwnerTable[0]), sleByte); + s->saveLoadArrayOf(_objectStateTable, _numGlobalObjects, sizeof(_objectStateTable[0]), sleByte); + if (_objectRoomTable) + s->saveLoadArrayOf(_objectRoomTable, _numGlobalObjects, sizeof(_objectRoomTable[0]), sleByte); + + + // + // Save/load palette data + // + if (_shadowPaletteSize) { + s->saveLoadArrayOf(_shadowPalette, _shadowPaletteSize, 1, sleByte); + // _roomPalette didn't show up until V21 save games + if (s->getVersion() >= VER(21) && _version < 5) + s->saveLoadArrayOf(_roomPalette, sizeof(_roomPalette), 1, sleByte); + } + + // PalManip data was not saved before V10 save games + if (s->getVersion() < VER(10)) + _palManipCounter = 0; + if (_palManipCounter) { + if (!_palManipPalette) + _palManipPalette = (byte *)calloc(0x300, 1); + if (!_palManipIntermediatePal) + _palManipIntermediatePal = (byte *)calloc(0x600, 1); + s->saveLoadArrayOf(_palManipPalette, 0x300, 1, sleByte); + s->saveLoadArrayOf(_palManipIntermediatePal, 0x600, 1, sleByte); + } + + // darkenPalette was not saved before V53 + if (s->isLoading() && s->getVersion() < VER(53)) { + memcpy(_darkenPalette, _currentPalette, 768); + } + + // _colorUsedByCycle was not saved before V60 + if (s->isLoading() && s->getVersion() < VER(60)) { + memset(_colorUsedByCycle, 0, sizeof(_colorUsedByCycle)); + } + + // + // Save/load more global object state + // + s->saveLoadArrayOf(_classData, _numGlobalObjects, sizeof(_classData[0]), sleUint32); + + + // + // Save/load script variables + // + var120Backup = _scummVars[120]; + var98Backup = _scummVars[98]; + + if (s->getVersion() > VER(37)) + s->saveLoadArrayOf(_roomVars, _numRoomVariables, sizeof(_roomVars[0]), sleInt32); + + // The variables grew from 16 to 32 bit. + if (s->getVersion() < VER(15)) + s->saveLoadArrayOf(_scummVars, _numVariables, sizeof(_scummVars[0]), sleInt16); + else + s->saveLoadArrayOf(_scummVars, _numVariables, sizeof(_scummVars[0]), sleInt32); + + if (_gameId == GID_TENTACLE) // Maybe misplaced, but that's the main idea + _scummVars[120] = var120Backup; + if (_gameId == GID_INDY4) + _scummVars[98] = var98Backup; + + s->saveLoadArrayOf(_bitVars, _numBitVariables >> 3, 1, sleByte); + + + // + // Save/load a list of the locked objects + // + if (s->isSaving()) { + for (i = rtFirst; i <= rtLast; i++) + for (j = 1; j < res.num[i]; j++) { + if (res.isLocked(i, j)) { + s->saveByte(i); + s->saveUint16(j); + } + } + s->saveByte(0xFF); + } else { + while ((i = s->loadByte()) != 0xFF) { + j = s->loadUint16(); + res.lock(i, j); + } + } + + + // + // Save/load the Audio CD status + // + if (s->getVersion() >= VER(24)) { + AudioCDManager::Status info; + if (s->isSaving()) + info = AudioCD.getStatus(); + s->saveLoadArrayOf(&info, 1, sizeof(info), audioCDEntries); + // If we are loading, and the music being loaded was supposed to loop + // forever, then resume playing it. This helps a lot when the audio CD + // is used to provide ambient music (see bug #788195). + if (s->isLoading() && info.playing && info.numLoops < 0) + AudioCD.play(info.track, info.numLoops, info.start, info.duration); + } + + + // + // Save/load the iMuse status + // + if (_imuse && (_saveSound || !_saveTemporaryState)) { + _imuse->save_or_load(s, this); + } +} + +void ScummEngine_v5::saveOrLoad(Serializer *s) { + ScummEngine::saveOrLoad(s); + + const SaveLoadEntry cursorEntries[] = { + MKARRAY2(ScummEngine_v5, _cursorImages[0][0], sleUint16, 16, 4, (byte*)_cursorImages[1] - (byte*)_cursorImages[0], VER(44)), + MKARRAY(ScummEngine_v5, _cursorHotspots[0], sleByte, 8, VER(44)), + MKEND() + }; + + // This is probably only needed for Loom. + s->saveLoadEntries(this, cursorEntries); +} + +#ifndef DISABLE_SCUMM_7_8 +void ScummEngine_v7::saveOrLoad(Serializer *s) { + ScummEngine::saveOrLoad(s); + + const SaveLoadEntry subtitleQueueEntries[] = { + MKARRAY(SubtitleText, text[0], sleByte, 256, VER(61)), + MKLINE(SubtitleText, charset, sleByte, VER(61)), + MKLINE(SubtitleText, color, sleByte, VER(61)), + MKLINE(SubtitleText, xpos, sleInt16, VER(61)), + MKLINE(SubtitleText, ypos, sleInt16, VER(61)), + MKLINE(SubtitleText, actorSpeechMsg, sleByte, VER(61)), + MKEND() + }; + + const SaveLoadEntry V7Entries[] = { + MKLINE(ScummEngine_v7, _subtitleQueuePos, sleInt32, VER(61)), + MKEND() + }; + + _imuseDigital->saveOrLoad(s); + + s->saveLoadArrayOf(_subtitleQueue, ARRAYSIZE(_subtitleQueue), sizeof(_subtitleQueue[0]), subtitleQueueEntries); + s->saveLoadEntries(this, V7Entries); +} +#endif + +void ScummEngine_v60he::saveOrLoad(Serializer *s) { + ScummEngine::saveOrLoad(s); + + s->saveLoadArrayOf(_arraySlot, _numArray, sizeof(_arraySlot[0]), sleByte); +} + +#ifndef DISABLE_HE +void ScummEngine_v70he::saveOrLoad(Serializer *s) { + ScummEngine_v60he::saveOrLoad(s); + + const SaveLoadEntry HE70Entries[] = { + MKLINE(ScummEngine_v70he, _heSndSoundId, sleInt32, VER(51)), + MKLINE(ScummEngine_v70he, _heSndOffset, sleInt32, VER(51)), + MKLINE(ScummEngine_v70he, _heSndChannel, sleInt32, VER(51)), + MKLINE(ScummEngine_v70he, _heSndFlags, sleInt32, VER(51)), + MKEND() + }; + + s->saveLoadEntries(this, HE70Entries); +} + +void ScummEngine_v71he::saveOrLoad(Serializer *s) { + ScummEngine_v70he::saveOrLoad(s); + + const SaveLoadEntry polygonEntries[] = { + MKLINE(WizPolygon, vert[0].x, sleInt16, VER(40)), + MKLINE(WizPolygon, vert[0].y, sleInt16, VER(40)), + MKLINE(WizPolygon, vert[1].x, sleInt16, VER(40)), + MKLINE(WizPolygon, vert[1].y, sleInt16, VER(40)), + MKLINE(WizPolygon, vert[2].x, sleInt16, VER(40)), + MKLINE(WizPolygon, vert[2].y, sleInt16, VER(40)), + MKLINE(WizPolygon, vert[3].x, sleInt16, VER(40)), + MKLINE(WizPolygon, vert[3].y, sleInt16, VER(40)), + MKLINE(WizPolygon, vert[4].x, sleInt16, VER(40)), + MKLINE(WizPolygon, vert[4].y, sleInt16, VER(40)), + MKLINE(WizPolygon, bound.left, sleInt16, VER(40)), + MKLINE(WizPolygon, bound.top, sleInt16, VER(40)), + MKLINE(WizPolygon, bound.right, sleInt16, VER(40)), + MKLINE(WizPolygon, bound.bottom, sleInt16, VER(40)), + MKLINE(WizPolygon, id, sleInt16, VER(40)), + MKLINE(WizPolygon, numVerts, sleInt16, VER(40)), + MKLINE(WizPolygon, flag, sleByte, VER(40)), + MKEND() + }; + + s->saveLoadArrayOf(_wiz->_polygons, ARRAYSIZE(_wiz->_polygons), sizeof(_wiz->_polygons[0]), polygonEntries); +} + +void ScummEngine_v90he::saveOrLoad(Serializer *s) { + ScummEngine_v71he::saveOrLoad(s); + + const SaveLoadEntry floodFillEntries[] = { + MKLINE(FloodFillParameters, box.left, sleInt32, VER(51)), + MKLINE(FloodFillParameters, box.top, sleInt32, VER(51)), + MKLINE(FloodFillParameters, box.right, sleInt32, VER(51)), + MKLINE(FloodFillParameters, box.bottom, sleInt32, VER(51)), + MKLINE(FloodFillParameters, x, sleInt32, VER(51)), + MKLINE(FloodFillParameters, y, sleInt32, VER(51)), + MKLINE(FloodFillParameters, flags, sleInt32, VER(51)), + MK_OBSOLETE(FloodFillParameters, unk1C, sleInt32, VER(51), VER(62)), + MKEND() + }; + + const SaveLoadEntry HE90Entries[] = { + MKLINE(ScummEngine_v90he, _curMaxSpriteId, sleInt32, VER(51)), + MKLINE(ScummEngine_v90he, _curSpriteId, sleInt32, VER(51)), + MKLINE(ScummEngine_v90he, _curSpriteGroupId, sleInt32, VER(51)), + MK_OBSOLETE(ScummEngine_v90he, _numSpritesToProcess, sleInt32, VER(51), VER(63)), + MKLINE(ScummEngine_v90he, _heObject, sleInt32, VER(51)), + MKLINE(ScummEngine_v90he, _heObjectNum, sleInt32, VER(51)), + MKLINE(ScummEngine_v90he, _hePaletteNum, sleInt32, VER(51)), + MKEND() + }; + + _sprite->saveOrLoadSpriteData(s); + + s->saveLoadArrayOf(&_floodFillParams, 1, sizeof(_floodFillParams), floodFillEntries); + + s->saveLoadEntries(this, HE90Entries); +} + +void ScummEngine_v99he::saveOrLoad(Serializer *s) { + ScummEngine_v90he::saveOrLoad(s); + + s->saveLoadArrayOf(_hePalettes, (_numPalettes + 1) * 1024, sizeof(_hePalettes[0]), sleUint8); +} + +void ScummEngine_v100he::saveOrLoad(Serializer *s) { + ScummEngine_v99he::saveOrLoad(s); + + const SaveLoadEntry HE100Entries[] = { + MKLINE(ScummEngine_v100he, _heResId, sleInt32, VER(51)), + MKLINE(ScummEngine_v100he, _heResType, sleInt32, VER(51)), + MKEND() + }; + + s->saveLoadEntries(this, HE100Entries); +} +#endif + +void ScummEngine::saveLoadResource(Serializer *ser, int type, int idx) { + byte *ptr; + uint32 size; + + if (!res.mode[type]) { + if (ser->isSaving()) { + ptr = res.address[type][idx]; + if (ptr == NULL) { + ser->saveUint32(0); + return; + } + + size = ((MemBlkHeader *)ptr)->size; + + ser->saveUint32(size); + ser->saveBytes(ptr + sizeof(MemBlkHeader), size); + + if (type == rtInventory) { + ser->saveUint16(_inventory[idx]); + } + if (type == rtObjectName && ser->getVersion() >= VER(25)) { + ser->saveUint16(_newNames[idx]); + } + } else { + size = ser->loadUint32(); + if (size) { + res.createResource(type, idx, size); + ser->loadBytes(getResourceAddress(type, idx), size); + if (type == rtInventory) { + _inventory[idx] = ser->loadUint16(); + } + if (type == rtObjectName && ser->getVersion() >= VER(25)) { + // Paranoia: We increased the possible number of new names + // to fix bugs #933610 and #936323. The savegame format + // didn't change, but at least during the transition + // period there is a slight chance that we try to load + // more names than we have allocated space for. If so, + // discard them. + if (idx < _numNewNames) + _newNames[idx] = ser->loadUint16(); + } + } + } + } else if (res.mode[type] == 2 && ser->getVersion() >= VER(23)) { + // Save/load only a list of resource numbers that need reloaded. + if (ser->isSaving()) { + ser->saveUint16(res.address[type][idx] ? 1 : 0); + } else { + if (ser->loadUint16()) + ensureResourceLoaded(type, idx); + } + } +} + +void ScummEngine::saveResource(Serializer *ser, int type, int idx) { + assert(res.address[type][idx]); + + if ((res.mode[type] == 0) || (_heversion >= 60 && res.mode[type] == 2 && idx == 1)) { + byte *ptr = res.address[type][idx]; + uint32 size = ((MemBlkHeader *)ptr)->size; + + ser->saveUint32(size); + ser->saveBytes(ptr + sizeof(MemBlkHeader), size); + + if (type == rtInventory) { + ser->saveUint16(_inventory[idx]); + } + if (type == rtObjectName) { + ser->saveUint16(_newNames[idx]); + } + } +} + +void ScummEngine::loadResource(Serializer *ser, int type, int idx) { + if ((res.mode[type] == 0) || (_heversion >= 60 && res.mode[type] == 2 && idx == 1)) { + uint32 size = ser->loadUint32(); + assert(size); + res.createResource(type, idx, size); + ser->loadBytes(getResourceAddress(type, idx), size); + + if (type == rtInventory) { + _inventory[idx] = ser->loadUint16(); + } + if (type == rtObjectName) { + _newNames[idx] = ser->loadUint16(); + } + } else if (res.mode[type] == 2) { + ensureResourceLoaded(type, idx); + } +} + +void Serializer::saveBytes(void *b, int len) { + _saveStream->write(b, len); +} + +void Serializer::loadBytes(void *b, int len) { + _loadStream->read(b, len); +} + +void Serializer::saveUint32(uint32 d) { + _saveStream->writeUint32LE(d); +} + +void Serializer::saveUint16(uint16 d) { + _saveStream->writeUint16LE(d); +} + +void Serializer::saveByte(byte b) { + _saveStream->writeByte(b); +} + +uint32 Serializer::loadUint32() { + return _loadStream->readUint32LE(); +} + +uint16 Serializer::loadUint16() { + return _loadStream->readUint16LE(); +} + +byte Serializer::loadByte() { + return _loadStream->readByte(); +} + +void Serializer::saveArrayOf(void *b, int len, int datasize, byte filetype) { + byte *at = (byte *)b; + uint32 data; + + // speed up byte arrays + if (datasize == 1 && filetype == sleByte) { + if (len > 0) { + saveBytes(b, len); + } + return; + } + + while (--len >= 0) { + if (datasize == 0) { + // Do nothing for obsolete data + data = 0; + } else if (datasize == 1) { + data = *(byte *)at; + at += 1; + } else if (datasize == 2) { + data = *(uint16 *)at; + at += 2; + } else if (datasize == 4) { + data = *(uint32 *)at; + at += 4; + } else { + error("saveLoadArrayOf: invalid size %d", datasize); + } + switch (filetype) { + case sleByte: + saveByte((byte)data); + break; + case sleUint16: + case sleInt16: + saveUint16((int16)data); + break; + case sleInt32: + case sleUint32: + saveUint32(data); + break; + default: + error("saveLoadArrayOf: invalid filetype %d", filetype); + } + } +} + +void Serializer::loadArrayOf(void *b, int len, int datasize, byte filetype) { + byte *at = (byte *)b; + uint32 data; + + // speed up byte arrays + if (datasize == 1 && filetype == sleByte) { + loadBytes(b, len); + return; + } + + while (--len >= 0) { + switch (filetype) { + case sleByte: + data = loadByte(); + break; + case sleUint16: + data = loadUint16(); + break; + case sleInt16: + data = (int16)loadUint16(); + break; + case sleUint32: + data = loadUint32(); + break; + case sleInt32: + data = (int32)loadUint32(); + break; + default: + error("saveLoadArrayOf: invalid filetype %d", filetype); + } + if (datasize == 0) { + // Do nothing for obsolete data + } else if (datasize == 1) { + *(byte *)at = (byte)data; + at += 1; + } else if (datasize == 2) { + *(uint16 *)at = (uint16)data; + at += 2; + } else if (datasize == 4) { + *(uint32 *)at = data; + at += 4; + } else { + error("saveLoadArrayOf: invalid size %d", datasize); + } + } +} + +void Serializer::saveLoadArrayOf(void *b, int num, int datasize, const SaveLoadEntry *sle) { + byte *data = (byte *)b; + + if (isSaving()) { + while (--num >= 0) { + saveEntries(data, sle); + data += datasize; + } + } else { + while (--num >= 0) { + loadEntries(data, sle); + data += datasize; + } + } +} + +void Serializer::saveLoadArrayOf(void *b, int len, int datasize, byte filetype) { + if (isSaving()) + saveArrayOf(b, len, datasize, filetype); + else + loadArrayOf(b, len, datasize, filetype); +} + +void Serializer::saveLoadEntries(void *d, const SaveLoadEntry *sle) { + if (isSaving()) + saveEntries(d, sle); + else + loadEntries(d, sle); +} + +void Serializer::saveEntries(void *d, const SaveLoadEntry *sle) { + byte type; + byte *at; + int size; + + while (sle->offs != 0xFFFF) { + at = (byte *)d + sle->offs; + size = sle->size; + type = (byte) sle->type; + + if (sle->maxVersion != CURRENT_VER) { + // Skip obsolete entries + if (type & 128) + sle++; + } else { + // save entry + int columns = 1; + int rows = 1; + int rowlen = 0; + if (type & 128) { + sle++; + columns = sle->offs; + rows = sle->type; + rowlen = sle->size; + type &= ~128; + } + while (rows--) { + saveArrayOf(at, columns, size, type); + at += rowlen; + } + } + sle++; + } +} + +void Serializer::loadEntries(void *d, const SaveLoadEntry *sle) { + byte type; + byte *at; + int size; + + while (sle->offs != 0xFFFF) { + at = (byte *)d + sle->offs; + size = sle->size; + type = (byte) sle->type; + + if (_savegameVersion < sle->minVersion || _savegameVersion > sle->maxVersion) { + // Skip entries which are not present in this save game version + if (type & 128) + sle++; + } else { + // load entry + int columns = 1; + int rows = 1; + int rowlen = 0; + + if (type & 128) { + sle++; + columns = sle->offs; + rows = sle->type; + rowlen = sle->size; + type &= ~128; + } + while (rows--) { + loadArrayOf(at, columns, size, type); + at += rowlen; + } + } + sle++; + } +} + +} // End of namespace Scumm |