mirror of
https://github.com/libretro/scummvm.git
synced 2025-02-16 01:08:56 +00:00
![Jamieson Christian](/assets/img/avatar_default.png)
Implemented _cmd_queue save/load. In addition to requiring _cmd_queue information, this bug arises from a rare assumption that sound resources are loaded in memory even though they aren't currently playing. Therefore, a list of sound resources loaded in memory is included in the savegame, so that all relevant sound resources are reloaded when the savegame is loaded. This also fixes an unreported music bug in S&M when saving a game while outside the Bumpusville mansion. As a result of savegame format modifications, we are now at savegame version 23. svn-id: r10254
960 lines
29 KiB
C++
960 lines
29 KiB
C++
/* ScummVM - Scumm Interpreter
|
|
* Copyright (C) 2001 Ludvig Strigeus
|
|
* Copyright (C) 2001-2003 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
|
|
*
|
|
* $Header$
|
|
*
|
|
*/
|
|
|
|
#include "stdafx.h"
|
|
|
|
#include "common/config-file.h"
|
|
|
|
#include "scumm/actor.h"
|
|
#include "scumm/charset.h"
|
|
#include "scumm/imuse_digi.h"
|
|
#include "scumm/imuse.h"
|
|
#include "scumm/object.h"
|
|
#include "scumm/resource.h"
|
|
#include "scumm/saveload.h"
|
|
#include "scumm/scumm.h"
|
|
#include "scumm/sound.h"
|
|
#include "scumm/verbs.h"
|
|
|
|
#include "sound/mixer.h"
|
|
|
|
|
|
struct SaveGameHeader {
|
|
uint32 type;
|
|
uint32 size;
|
|
uint32 ver;
|
|
char name[32];
|
|
};
|
|
|
|
|
|
void Scumm::requestSave(int slot, const char *name) {
|
|
_saveLoadSlot = slot;
|
|
_saveLoadCompatible = false;
|
|
_saveLoadFlag = 1; // 1 for save
|
|
strcpy(_saveLoadName, name);
|
|
}
|
|
|
|
void Scumm::requestLoad(int slot) {
|
|
_saveLoadSlot = slot;
|
|
_saveLoadCompatible = false;
|
|
_saveLoadFlag = 2; // 2 for load
|
|
}
|
|
|
|
bool Scumm::saveState(int slot, bool compat, SaveFileManager *mgr) {
|
|
char filename[256];
|
|
SaveFile *out;
|
|
SaveGameHeader hdr;
|
|
|
|
makeSavegameName(filename, slot, compat);
|
|
|
|
if (!(out = mgr->open_savefile(filename, getSavePath(), true)))
|
|
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));
|
|
|
|
Serializer ser(out, true, CURRENT_VER);
|
|
saveOrLoad(&ser, CURRENT_VER);
|
|
delete out;
|
|
debug(1, "State saved as '%s'", filename);
|
|
return true;
|
|
}
|
|
|
|
bool Scumm::loadState(int slot, bool compat, SaveFileManager *mgr) {
|
|
char filename[256];
|
|
SaveFile *out;
|
|
int i, j;
|
|
SaveGameHeader hdr;
|
|
int sb, sh;
|
|
byte *roomptr;
|
|
|
|
makeSavegameName(filename, slot, compat);
|
|
if (!(out = mgr->open_savefile(filename, getSavePath(), false)))
|
|
return false;
|
|
|
|
out->read(&hdr, sizeof(hdr));
|
|
if (hdr.type != MKID('SCVM')) {
|
|
warning("Invalid savegame '%s'", filename);
|
|
delete out;
|
|
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);
|
|
if (hdr.ver < VER(7) || hdr.ver > CURRENT_VER)
|
|
{
|
|
warning("Invalid version of '%s'", filename);
|
|
delete out;
|
|
return false;
|
|
}
|
|
|
|
// 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));
|
|
|
|
if (_imuseDigital) {
|
|
_imuseDigital->stopAllSounds();
|
|
}
|
|
|
|
_sound->stopBundleMusic();
|
|
_sound->stopCD();
|
|
_sound->pauseSounds(true);
|
|
|
|
CHECK_HEAP
|
|
closeRoom();
|
|
memset(_inventory, 0, sizeof(_inventory[0]) * _numInventory);
|
|
|
|
/* 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++) {
|
|
nukeResource(i, j);
|
|
res.flags[i][j] = 0;
|
|
}
|
|
|
|
initScummVars();
|
|
|
|
if (_features & GF_OLD_BUNDLE)
|
|
loadCharset(0); // FIXME - HACK ?
|
|
|
|
Serializer ser(out, false, hdr.ver);
|
|
saveOrLoad(&ser, hdr.ver);
|
|
delete out;
|
|
|
|
sb = _screenB;
|
|
sh = _screenH;
|
|
|
|
gdi._mask.top = gdi._mask.left = 32767;
|
|
gdi._mask.right = gdi._mask.bottom = 0;
|
|
_charset->_hasMask = false;
|
|
|
|
initScreens(0, 0, _screenWidth, _screenHeight);
|
|
|
|
// Force a fade to black
|
|
int old_screenEffectFlag = _screenEffectFlag;
|
|
_screenEffectFlag = true;
|
|
fadeOut(129);
|
|
_screenEffectFlag = old_screenEffectFlag ? true : false;
|
|
|
|
initScreens(0, sb, _screenWidth, sh);
|
|
|
|
_completeScreenRedraw = true;
|
|
|
|
// 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 (_gameId == GID_MANIAC)
|
|
setupV1ManiacPalette();
|
|
else
|
|
setupV1ZakPalette();
|
|
} else if (_features & GF_16COLOR) {
|
|
if ((_features & GF_AMIGA) || (_features & GF_ATARI_ST))
|
|
setupAmigaPalette();
|
|
else
|
|
setupEGAPalette();
|
|
} else
|
|
setDirtyColors(0, 255);
|
|
|
|
_lastCodePtr = NULL;
|
|
|
|
_drawObjectQueNr = 0;
|
|
_verbMouseOver = 0;
|
|
|
|
cameraMoved();
|
|
|
|
initBGBuffers(_roomHeight);
|
|
|
|
// Regenerate strip table when loading
|
|
if (_version == 1) {
|
|
roomptr = getResourceAddress(rtRoom, _roomResource);
|
|
_IM00_offs = 0;
|
|
for (i = 0; i < 4; i++){
|
|
gdi._C64Colors[i] = roomptr[6 + i];
|
|
}
|
|
gdi.decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 10), gdi._C64CharMap, 2048);
|
|
gdi.decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 12), gdi._C64PicMap, roomptr[4] * roomptr[5]);
|
|
gdi.decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 14), gdi._C64ColorMap, roomptr[4] * roomptr[5]);
|
|
gdi.decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 16), gdi._C64MaskMap, roomptr[4] * roomptr[5]);
|
|
gdi.decodeC64Gfx(roomptr + READ_LE_UINT16(roomptr + 18) + 2, gdi._C64MaskChar, READ_LE_UINT16(roomptr + READ_LE_UINT16(roomptr + 18)));
|
|
gdi._C64ObjectMode = true;
|
|
} else if (_version == 2) {
|
|
_roomStrips = gdi.generateStripTable(getResourceAddress(rtRoom, _roomResource) + _IM00_offs,
|
|
_roomWidth, _roomHeight, _roomStrips);
|
|
}
|
|
|
|
if ((_features & GF_AUDIOTRACKS) && VAR(VAR_MUSIC_TIMER) > 0)
|
|
_sound->startCDTimer();
|
|
|
|
CHECK_HEAP debug(1, "State loaded from '%s'", filename);
|
|
|
|
_sound->pauseSounds(false);
|
|
|
|
return true;
|
|
}
|
|
|
|
void Scumm::makeSavegameName(char *out, int slot, bool compatible) {
|
|
sprintf(out, "%s.%c%.2d", _game_name, compatible ? 'c' : 's', slot);
|
|
}
|
|
|
|
void Scumm::listSavegames(bool *marks, int num, SaveFileManager *mgr) {
|
|
char prefix[256];
|
|
makeSavegameName(prefix, 99, false);
|
|
prefix[strlen(prefix)-2] = 0;
|
|
mgr->list_savefiles(prefix, getSavePath(), marks, num);
|
|
}
|
|
|
|
bool Scumm::getSavegameName(int slot, char *desc, SaveFileManager *mgr) {
|
|
char filename[256];
|
|
SaveFile *out;
|
|
SaveGameHeader hdr;
|
|
int len;
|
|
|
|
makeSavegameName(filename, slot, false);
|
|
if (!(out = mgr->open_savefile(filename, getSavePath(), false))) {
|
|
strcpy(desc, "");
|
|
return false;
|
|
}
|
|
len = out->read(&hdr, sizeof(hdr));
|
|
delete out;
|
|
|
|
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;
|
|
}
|
|
|
|
memcpy(desc, hdr.name, sizeof(hdr.name));
|
|
desc[sizeof(hdr.name) - 1] = 0;
|
|
return true;
|
|
}
|
|
|
|
void Scumm::saveOrLoad(Serializer *s, uint32 savegameVersion) {
|
|
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)),
|
|
MKEND()
|
|
};
|
|
|
|
const SaveLoadEntry *actorEntries = Actor::getSaveLoadEntries();
|
|
|
|
const SaveLoadEntry verbEntries[] = {
|
|
MKLINE(VerbSlot, x, sleInt16, VER(8)),
|
|
MKLINE(VerbSlot, y, sleInt16, VER(8)),
|
|
MKLINE(VerbSlot, right, sleInt16, VER(8)),
|
|
MKLINE(VerbSlot, bottom, sleInt16, VER(8)),
|
|
MKLINE(VerbSlot, old.left, sleInt16, VER(8)),
|
|
MKLINE(VerbSlot, old.top, sleInt16, VER(8)),
|
|
MKLINE(VerbSlot, old.right, sleInt16, VER(8)),
|
|
MKLINE(VerbSlot, old.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[] = {
|
|
MKLINE(Scumm, _roomWidth, sleUint16, VER(8)),
|
|
MKLINE(Scumm, _roomHeight, sleUint16, VER(8)),
|
|
MKLINE(Scumm, _ENCD_offs, sleUint32, VER(8)),
|
|
MKLINE(Scumm, _EXCD_offs, sleUint32, VER(8)),
|
|
MKLINE(Scumm, _IM00_offs, sleUint32, VER(8)),
|
|
MKLINE(Scumm, _CLUT_offs, sleUint32, VER(8)),
|
|
MK_OBSOLETE(Scumm, _EPAL_offs, sleUint32, VER(8), VER(9)),
|
|
MKLINE(Scumm, _PALS_offs, sleUint32, VER(8)),
|
|
MKLINE(Scumm, _curPalIndex, sleByte, VER(8)),
|
|
MKLINE(Scumm, _currentRoom, sleByte, VER(8)),
|
|
MKLINE(Scumm, _roomResource, sleByte, VER(8)),
|
|
MKLINE(Scumm, _numObjectsInRoom, sleByte, VER(8)),
|
|
MKLINE(Scumm, _currentScript, sleByte, VER(8)),
|
|
MKARRAY(Scumm, _localScriptList[0], sleUint32, NUM_LOCALSCRIPT, VER(8)),
|
|
|
|
|
|
// 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(Scumm, vm.localvar[0][0], sleUint16, 17, 25, (byte*)vm.localvar[1] - (byte*)vm.localvar[0], VER(8), VER(8)),
|
|
MKARRAY2_OLD(Scumm, 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(Scumm, vm.localvar[0][0], sleUint16, 26, 38, (byte*)vm.localvar[1] - (byte*)vm.localvar[0], VER(15), VER(17)),
|
|
MK_OBSOLETE_ARRAY(Scumm, 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(Scumm, 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(Scumm, vm.localvar[0][0], sleUint32, 26, NUM_SCRIPT_SLOT, (byte*)vm.localvar[1] - (byte*)vm.localvar[0], VER(20)),
|
|
|
|
|
|
MKARRAY(Scumm, _resourceMapper[0], sleByte, 128, VER(8)),
|
|
MKARRAY(Scumm, _charsetColorMap[0], sleByte, 16, VER(8)),
|
|
|
|
// _charsetData grew from 10*16 to 15*16 bytes
|
|
MKARRAY_OLD(Scumm, _charsetData[0][0], sleByte, 10 * 16, VER(8), VER(9)),
|
|
MKARRAY(Scumm, _charsetData[0][0], sleByte, 15 * 16, VER(10)),
|
|
|
|
MKLINE(Scumm, _curExecScript, sleUint16, VER(8)),
|
|
|
|
MKLINE(Scumm, camera._dest.x, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._dest.y, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._cur.x, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._cur.y, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._last.x, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._last.y, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._accel.x, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._accel.y, sleInt16, VER(8)),
|
|
MKLINE(Scumm, _screenStartStrip, sleInt16, VER(8)),
|
|
MKLINE(Scumm, _screenEndStrip, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._mode, sleByte, VER(8)),
|
|
MKLINE(Scumm, camera._follows, sleByte, VER(8)),
|
|
MKLINE(Scumm, camera._leftTrigger, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._rightTrigger, sleInt16, VER(8)),
|
|
MKLINE(Scumm, camera._movingToActor, sleUint16, VER(8)),
|
|
|
|
MKLINE(Scumm, _actorToPrintStrFor, sleByte, VER(8)),
|
|
MKLINE(Scumm, _charsetColor, sleByte, VER(8)),
|
|
|
|
// _charsetBufPos was changed from byte to int
|
|
MKLINE_OLD(Scumm, _charsetBufPos, sleByte, VER(8), VER(9)),
|
|
MKLINE(Scumm, _charsetBufPos, sleInt16, VER(10)),
|
|
|
|
MKLINE(Scumm, _haveMsg, sleByte, VER(8)),
|
|
MKLINE(Scumm, _useTalkAnims, sleByte, VER(8)),
|
|
|
|
MKLINE(Scumm, _talkDelay, sleInt16, VER(8)),
|
|
MKLINE(Scumm, _defaultTalkDelay, sleInt16, VER(8)),
|
|
MKLINE(Scumm, _numInMsgStack, sleInt16, VER(8)),
|
|
MKLINE(Scumm, _sentenceNum, sleByte, VER(8)),
|
|
|
|
MKLINE(Scumm, vm.cutSceneStackPointer, sleByte, VER(8)),
|
|
MKARRAY(Scumm, vm.cutScenePtr[0], sleUint32, 5, VER(8)),
|
|
MKARRAY(Scumm, vm.cutSceneScript[0], sleByte, 5, VER(8)),
|
|
MKARRAY(Scumm, vm.cutSceneData[0], sleInt16, 5, VER(8)),
|
|
MKLINE(Scumm, vm.cutSceneScriptIndex, sleInt16, VER(8)),
|
|
|
|
MKLINE(Scumm, vm.numNestedScripts, sleByte, VER(8)),
|
|
MKLINE(Scumm, _userPut, sleByte, VER(8)),
|
|
MKLINE(Scumm, _userState, sleUint16, VER(17)),
|
|
MKLINE(Scumm, _cursor.state, sleByte, VER(8)),
|
|
MK_OBSOLETE(Scumm, gdi._cursorActive, sleByte, VER(8), VER(20)),
|
|
MKLINE(Scumm, _currentCursor, sleByte, VER(8)),
|
|
MKARRAY(Scumm, _grabbedCursor[0], sleByte, 8192, VER(20)),
|
|
MKLINE(Scumm, _cursor.width, sleInt16, VER(20)),
|
|
MKLINE(Scumm, _cursor.height, sleInt16, VER(20)),
|
|
MKLINE(Scumm, _cursor.hotspotX, sleInt16, VER(20)),
|
|
MKLINE(Scumm, _cursor.hotspotY, sleInt16, VER(20)),
|
|
MKLINE(Scumm, _cursor.animate, sleByte, VER(20)),
|
|
MKLINE(Scumm, _cursor.animateIndex, sleByte, VER(20)),
|
|
MKLINE(Scumm, _mouse.x, sleInt16, VER(20)),
|
|
MKLINE(Scumm, _mouse.y, sleInt16, VER(20)),
|
|
|
|
MKLINE(Scumm, _doEffect, sleByte, VER(8)),
|
|
MKLINE(Scumm, _switchRoomEffect, sleByte, VER(8)),
|
|
MKLINE(Scumm, _newEffect, sleByte, VER(8)),
|
|
MKLINE(Scumm, _switchRoomEffect2, sleByte, VER(8)),
|
|
MKLINE(Scumm, _BgNeedsRedraw, sleByte, VER(8)),
|
|
|
|
// The state of palManipulate is stored only since V10
|
|
MKLINE(Scumm, _palManipStart, sleByte, VER(10)),
|
|
MKLINE(Scumm, _palManipEnd, sleByte, VER(10)),
|
|
MKLINE(Scumm, _palManipCounter, sleUint16, VER(10)),
|
|
|
|
// gfxUsageBits grew from 200 to 410 entries. Then 3 * 410 entries:
|
|
MKARRAY_OLD(Scumm, gfxUsageBits[0], sleUint32, 200, VER(8), VER(9)),
|
|
MKARRAY_OLD(Scumm, gfxUsageBits[0], sleUint32, 410, VER(10), VER(13)),
|
|
MKARRAY(Scumm, gfxUsageBits[0], sleUint32, 3 * 410, VER(14)),
|
|
|
|
MKLINE(Scumm, gdi._transparentColor, sleByte, VER(8)),
|
|
MKARRAY(Scumm, _currentPalette[0], sleByte, 768, VER(8)),
|
|
|
|
MKARRAY(Scumm, _proc_special_palette[0], sleByte, 256, VER(8)),
|
|
|
|
MKARRAY(Scumm, _charsetBuffer[0], sleByte, 256, VER(8)),
|
|
|
|
MKLINE(Scumm, _egoPositioned, sleByte, VER(8)),
|
|
|
|
// gdi._imgBufOffs grew from 4 to 5 entries :
|
|
MKARRAY_OLD(Scumm, gdi._imgBufOffs[0], sleUint16, 4, VER(8), VER(9)),
|
|
MKARRAY(Scumm, gdi._imgBufOffs[0], sleUint16, 5, VER(10)),
|
|
|
|
MKLINE(Scumm, gdi._numZBuffer, sleByte, VER(8)),
|
|
|
|
MKLINE(Scumm, _screenEffectFlag, sleByte, VER(8)),
|
|
|
|
MK_OBSOLETE(Scumm, _randSeed1, sleUint32, VER(8), VER(9)),
|
|
MK_OBSOLETE(Scumm, _randSeed2, sleUint32, VER(8), VER(9)),
|
|
|
|
// Converted _shakeEnabled to boolean and added a _shakeFrame field.
|
|
MKLINE_OLD(Scumm, _shakeEnabled, sleInt16, VER(8), VER(9)),
|
|
MKLINE(Scumm, _shakeEnabled, sleByte, VER(10)),
|
|
MKLINE(Scumm, _shakeFrame, sleUint32, VER(10)),
|
|
|
|
MKLINE(Scumm, _keepText, sleByte, VER(8)),
|
|
|
|
MKLINE(Scumm, _screenB, sleUint16, VER(8)),
|
|
MKLINE(Scumm, _screenH, sleUint16, VER(8)),
|
|
|
|
MK_OBSOLETE(Scumm, _cd_track, sleInt16, VER(9), VER(9)),
|
|
MK_OBSOLETE(Scumm, _cd_loops, sleInt16, VER(9), VER(9)),
|
|
MK_OBSOLETE(Scumm, _cd_frame, sleInt16, VER(9), VER(9)),
|
|
MK_OBSOLETE(Scumm, _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)),
|
|
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[] = {
|
|
// TODO - It makes no sense to have all these t_* fields in StringTab
|
|
// Rather let's dump them all when the save game format changes, and
|
|
// keep two StringTab objects where we have one now: a "normal" one,
|
|
// and a temporar y"t_" one.
|
|
// Then backup/restore of a StringTab entry becomes a one liner.
|
|
MKLINE(StringTab, xpos, sleInt16, VER(8)),
|
|
MKLINE(StringTab, t_xpos, sleInt16, VER(8)),
|
|
MKLINE(StringTab, ypos, sleInt16, VER(8)),
|
|
MKLINE(StringTab, t_ypos, sleInt16, VER(8)),
|
|
MKLINE(StringTab, right, sleInt16, VER(8)),
|
|
MKLINE(StringTab, t_right, sleInt16, VER(8)),
|
|
MKLINE(StringTab, color, sleInt8, VER(8)),
|
|
MKLINE(StringTab, t_color, sleInt8, VER(8)),
|
|
MKLINE(StringTab, charset, sleInt8, VER(8)),
|
|
MKLINE(StringTab, t_charset, sleInt8, VER(8)),
|
|
MKLINE(StringTab, center, sleByte, VER(8)),
|
|
MKLINE(StringTab, t_center, sleByte, VER(8)),
|
|
MKLINE(StringTab, overhead, sleByte, VER(8)),
|
|
MKLINE(StringTab, t_overhead, sleByte, VER(8)),
|
|
MKLINE(StringTab, no_talk_anim, sleByte, VER(8)),
|
|
MKLINE(StringTab, t_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()
|
|
};
|
|
|
|
int i, j;
|
|
int var120Backup;
|
|
int var98Backup;
|
|
|
|
if (!s->isSaving() && (_saveSound || !_saveLoadCompatible)) {
|
|
_sound->stopAllSounds();
|
|
}
|
|
|
|
// Because old savegames won't fill the entire gfxUsageBits[] array,
|
|
// clear it here just to be sure it won't hold any unforseen garbage.
|
|
if (!s->isSaving())
|
|
memset(gfxUsageBits, 0, sizeof(gfxUsageBits));
|
|
|
|
s->saveLoadEntries(this, mainEntries);
|
|
|
|
if (!s->isSaving() && savegameVersion < VER(14))
|
|
upgradeGfxUsageBits();
|
|
|
|
if (!s->isSaving() && savegameVersion >= VER(20)) {
|
|
updateCursor();
|
|
_system->warp_mouse(_mouse.x, _mouse.y);
|
|
}
|
|
|
|
s->saveLoadArrayOf(_actors, _numActors, sizeof(_actors[0]), actorEntries);
|
|
|
|
if (savegameVersion < VER(9))
|
|
s->saveLoadArrayOf(vm.slot, 25, sizeof(vm.slot[0]), scriptSlotEntries);
|
|
else if (savegameVersion < 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);
|
|
|
|
s->saveLoadArrayOf(_objs, _numLocalObjects, sizeof(_objs[0]), objectEntries);
|
|
if (!s->isSaving() && savegameVersion < 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;
|
|
}
|
|
|
|
}
|
|
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 (savegameVersion >= VER(13))
|
|
s->saveLoadArrayOf(_scaleSlots, 20, sizeof(_scaleSlots[0]), scaleSlotsEntries);
|
|
|
|
// Save all resource. Fingolfin doesn't like this part of the save/load code a bit.
|
|
// It is very fragile: e.g. if we change the num limit for one resource type, this
|
|
// code will break down. Worse, there is no way such a problem could easily be detected.
|
|
// We should at least store for each save resource it's type and ID. Then at least
|
|
// we can perform some integrety checks when loading.
|
|
for (i = rtFirst; i <= rtLast; i++)
|
|
if (res.mode[i] != 1)
|
|
for (j = 1; j < res.num[i]; j++)
|
|
saveLoadResource(s, i, j);
|
|
|
|
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);
|
|
|
|
if (_shadowPaletteSize) {
|
|
s->saveLoadArrayOf(_shadowPalette, _shadowPaletteSize, 1, sleByte);
|
|
// _roomPalette didn't show up until V21 save games
|
|
if (savegameVersion >= VER(21) && _version < 5)
|
|
s->saveLoadArrayOf (_roomPalette, _shadowPaletteSize, 1, sleByte);
|
|
}
|
|
|
|
// PalManip data was not saved before V10 save games
|
|
if (savegameVersion < VER(10))
|
|
_palManipCounter = 0;
|
|
if (_palManipCounter) {
|
|
if (!_palManipPalette)
|
|
_palManipPalette = (byte *)calloc(0x300, 1);
|
|
if (!_palManipIntermediatePal)
|
|
_palManipPalette = (byte *)calloc(0x300, 1);
|
|
s->saveLoadArrayOf(_palManipPalette, 0x300, 1, sleByte);
|
|
s->saveLoadArrayOf(_palManipIntermediatePal, 0x600, 1, sleByte);
|
|
}
|
|
|
|
s->saveLoadArrayOf(_classData, _numGlobalObjects, sizeof(_classData[0]), sleUint32);
|
|
|
|
var120Backup = _scummVars[120];
|
|
var98Backup = _scummVars[98];
|
|
|
|
// The variables grew from 16 to 32 bit.
|
|
if (savegameVersion < 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 or 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.flags[i][j] & RF_LOCK) {
|
|
s->saveByte(i);
|
|
s->saveWord(j);
|
|
}
|
|
}
|
|
s->saveByte(0xFF);
|
|
} else {
|
|
int r;
|
|
while ((r = s->loadByte()) != 0xFF) {
|
|
res.flags[r][s->loadWord()] |= RF_LOCK;
|
|
}
|
|
}
|
|
|
|
// With version 22, we replace the scale items with scale slots
|
|
if (savegameVersion < VER(22) && !s->isSaving()) {
|
|
// Convert all rtScaleTable resources to matching scale items
|
|
for (i = 1; i < res.num[rtScaleTable]; i++) {
|
|
convertScaleTableToScaleSlot(i);
|
|
}
|
|
}
|
|
|
|
|
|
if (_imuse && (_saveSound || !_saveLoadCompatible)) {
|
|
_imuse->save_or_load(s, this);
|
|
_imuse->setMasterVolume(_sound->_sound_volume_master);
|
|
_imuse->set_music_volume(_sound->_sound_volume_music);
|
|
}
|
|
}
|
|
|
|
void Scumm::saveLoadResource(Serializer *ser, int type, int idx) {
|
|
byte *ptr;
|
|
uint32 size;
|
|
|
|
/* don't save/load these resource types */
|
|
if (type == rtTemp || type == rtBuffer)
|
|
return;
|
|
|
|
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->saveWord(_inventory[idx]);
|
|
}
|
|
} else {
|
|
size = ser->loadUint32();
|
|
if (size) {
|
|
createResource(type, idx, size);
|
|
ser->loadBytes(getResourceAddress(type, idx), size);
|
|
if (type == rtInventory) {
|
|
_inventory[idx] = ser->loadWord();
|
|
}
|
|
}
|
|
}
|
|
} 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->saveWord (res.address[type][idx] ? 1 : 0);
|
|
} else {
|
|
if (ser->loadWord())
|
|
ensureResourceLoaded (type, idx);
|
|
}
|
|
}
|
|
}
|
|
|
|
void Serializer::saveBytes(void *b, int len) {
|
|
_saveLoadStream->write(b, len);
|
|
}
|
|
|
|
void Serializer::loadBytes(void *b, int len) {
|
|
_saveLoadStream->read(b, len);
|
|
}
|
|
|
|
void Serializer::saveUint32(uint32 d) {
|
|
_saveLoadStream->writeUint32LE(d);
|
|
}
|
|
|
|
void Serializer::saveWord(uint16 d) {
|
|
_saveLoadStream->writeUint16LE(d);
|
|
}
|
|
|
|
void Serializer::saveByte(byte b) {
|
|
_saveLoadStream->writeByte(b);
|
|
}
|
|
|
|
uint32 Serializer::loadUint32() {
|
|
return _saveLoadStream->readUint32LE();
|
|
}
|
|
|
|
uint16 Serializer::loadWord() {
|
|
return _saveLoadStream->readUint16LE();
|
|
}
|
|
|
|
byte Serializer::loadByte() {
|
|
return _saveLoadStream->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) {
|
|
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:
|
|
saveWord((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 = loadWord();
|
|
break;
|
|
case sleInt16:
|
|
data = (int16)loadWord();
|
|
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 if (size == 0xFF) {
|
|
// save reference
|
|
void *ptr = *((void **)at);
|
|
saveWord(ptr ? ((*_save_ref) (_ref_me, type, ptr) + 1) : 0);
|
|
} 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 if (size == 0xFF) {
|
|
// load reference...
|
|
int num = loadWord();
|
|
// ...but only use it if it's still there in CURRENT_VER
|
|
if (sle->maxVersion == CURRENT_VER)
|
|
*((void **)at) = num ? (*_load_ref) (_ref_me, type, num - 1) : NULL;
|
|
} 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++;
|
|
}
|
|
}
|