scummvm/engines/tinsel/saveload.cpp
Max Horn 1dbf8d73d5 TINSEL: Mark all (?) global vars with a FIXME comment
Use of global vars is what prevents RTL from working in Tinsel (and
probably in other engines). More specifically, the fact that many
global vars are not explicitly inited when the engine is (re)launched.

svn-id: r54262
2010-11-16 09:53:55 +00:00

618 lines
15 KiB
C++

/* ScummVM - Graphic Adventure Engine
*
* ScummVM is the legal property of its developers, whose names
* are too numerous to list here. Please refer to the COPYRIGHT
* file distributed with this source distribution.
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or (at your option) any later version.
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*
* $URL$
* $Id$
*
* Save and restore scene and game.
*/
#include "tinsel/actors.h"
#include "tinsel/dialogs.h"
#include "tinsel/drives.h"
#include "tinsel/dw.h"
#include "tinsel/rince.h"
#include "tinsel/savescn.h"
#include "tinsel/timers.h"
#include "tinsel/tinlib.h"
#include "tinsel/tinsel.h"
#include "common/serializer.h"
#include "common/savefile.h"
#include "gui/message.h"
namespace Tinsel {
/**
* The current savegame format version.
* Our save/load system uses an elaborate scheme to allow us to modify the
* savegame while keeping full backward compatibility, in the sense that newer
* ScummVM versions always are able to load old savegames.
* In order to achieve that, we store a version in the savegame files, and whenever
* the savegame layout is modified, the version is incremented.
*
* This roughly works by marking each savegame entry with a range of versions
* for which it is valid; the save/load code iterates over all entries, but
* only saves/loads those which are valid for the version of the savegame
* which is being loaded/saved currently.
*/
#define CURRENT_VER 1
// TODO: Not yet used
/**
* An auxillary macro, used to specify savegame versions. We use this instead
* of just writing the raw version, because this way they stand out more to
* the reading eye, making it a bit easier to navigate through the code.
*/
#define VER(x) x
//----------------- GLOBAL GLOBAL DATA --------------------
int thingHeld = 0;
int restoreCD = 0;
SRSTATE SRstate = SR_IDLE;
//----------------- EXTERN FUNCTIONS --------------------
// in DOS_DW.C
extern void syncSCdata(Common::Serializer &s);
// in DOS_MAIN.C
//char HardDriveLetter();
// in PCODE.C
extern void syncGlobInfo(Common::Serializer &s);
// in POLYGONS.C
extern void syncPolyInfo(Common::Serializer &s);
extern int sceneCtr;
//----------------- LOCAL DEFINES --------------------
struct SaveGameHeader {
uint32 id;
uint32 size;
uint32 ver;
char desc[SG_DESC_LEN];
TimeDate dateTime;
};
enum {
DW1_SAVEGAME_ID = 0x44575399, // = 'DWSc' = "DiscWorld 1 ScummVM"
DW2_SAVEGAME_ID = 0x44573253, // = 'DW2S' = "DiscWorld 2 ScummVM"
SAVEGAME_HEADER_SIZE = 4 + 4 + 4 + SG_DESC_LEN + 7
};
#define SAVEGAME_ID (TinselV2 ? (uint32)DW2_SAVEGAME_ID : (uint32)DW1_SAVEGAME_ID)
enum {
// FIXME: Save file names in ScummVM can be longer than 8.3, overflowing the
// name field in savedFiles. Raising it to 256 as a preliminary fix.
FNAMELEN = 256 // 8.3
};
struct SFILES {
char name[FNAMELEN];
char desc[SG_DESC_LEN + 2];
TimeDate dateTime;
};
//----------------- LOCAL GLOBAL DATA --------------------
// FIXME: Avoid non-const global vars
static int numSfiles = 0;
static SFILES savedFiles[MAX_SAVED_FILES];
static bool NeedLoad = true;
static SAVED_DATA *srsd = 0;
static int RestoreGameNumber = 0;
static char *SaveSceneName = 0;
static const char *SaveSceneDesc = 0;
static int *SaveSceneSsCount = 0;
static SAVED_DATA *SaveSceneSsData = 0; // points to 'SAVED_DATA ssdata[MAX_NEST]'
//------------- SAVE/LOAD SUPPORT METHODS ----------------
void setNeedLoad() { NeedLoad = true; }
static void syncTime(Common::Serializer &s, TimeDate &t) {
s.syncAsUint16LE(t.tm_year);
s.syncAsByte(t.tm_mon);
s.syncAsByte(t.tm_mday);
s.syncAsByte(t.tm_hour);
s.syncAsByte(t.tm_min);
s.syncAsByte(t.tm_sec);
}
static bool syncSaveGameHeader(Common::Serializer &s, SaveGameHeader &hdr) {
s.syncAsUint32LE(hdr.id);
s.syncAsUint32LE(hdr.size);
s.syncAsUint32LE(hdr.ver);
s.syncBytes((byte *)hdr.desc, SG_DESC_LEN);
hdr.desc[SG_DESC_LEN - 1] = 0;
syncTime(s, hdr.dateTime);
int tmp = hdr.size - s.bytesSynced();
// Perform sanity check
if (tmp < 0 || hdr.id != SAVEGAME_ID || hdr.ver > CURRENT_VER || hdr.size > 1024)
return false;
// Skip over any extra bytes
s.skip(tmp);
return true;
}
static void syncSavedMover(Common::Serializer &s, SAVED_MOVER &sm) {
SCNHANDLE *pList[3] = { (SCNHANDLE *)&sm.walkReels,
(SCNHANDLE *)&sm.standReels, (SCNHANDLE *)&sm.talkReels };
s.syncAsUint32LE(sm.bActive);
s.syncAsSint32LE(sm.actorID);
s.syncAsSint32LE(sm.objX);
s.syncAsSint32LE(sm.objY);
s.syncAsUint32LE(sm.hLastfilm);
for (int pIndex = 0; pIndex < 3; ++pIndex) {
SCNHANDLE *p = pList[pIndex];
for (int i = 0; i < TOTAL_SCALES * 4; ++i)
s.syncAsUint32LE(*p++);
}
if (TinselV2) {
s.syncAsByte(sm.bHidden);
s.syncAsSint32LE(sm.brightness);
s.syncAsSint32LE(sm.startColour);
s.syncAsSint32LE(sm.paletteLength);
}
}
static void syncSavedActor(Common::Serializer &s, SAVED_ACTOR &sa) {
s.syncAsUint16LE(sa.actorID);
s.syncAsUint16LE(sa.zFactor);
s.syncAsUint16LE(sa.bAlive);
s.syncAsUint16LE(sa.bHidden);
s.syncAsUint32LE(sa.presFilm);
s.syncAsUint16LE(sa.presRnum);
s.syncAsUint16LE(sa.presPlayX);
s.syncAsUint16LE(sa.presPlayY);
}
extern void syncAllActorsAlive(Common::Serializer &s);
static void syncNoScrollB(Common::Serializer &s, NOSCROLLB &ns) {
s.syncAsSint32LE(ns.ln);
s.syncAsSint32LE(ns.c1);
s.syncAsSint32LE(ns.c2);
}
static void syncZPosition(Common::Serializer &s, Z_POSITIONS &zp) {
s.syncAsSint16LE(zp.actor);
s.syncAsSint16LE(zp.column);
s.syncAsSint32LE(zp.z);
}
static void syncPolyVolatile(Common::Serializer &s, POLY_VOLATILE &p) {
s.syncAsByte(p.bDead);
s.syncAsSint16LE(p.xoff);
s.syncAsSint16LE(p.yoff);
}
static void syncSoundReel(Common::Serializer &s, SOUNDREELS &sr) {
s.syncAsUint32LE(sr.hFilm);
s.syncAsSint32LE(sr.column);
s.syncAsSint32LE(sr.actorCol);
}
static void syncSavedData(Common::Serializer &s, SAVED_DATA &sd) {
s.syncAsUint32LE(sd.SavedSceneHandle);
s.syncAsUint32LE(sd.SavedBgroundHandle);
for (int i = 0; i < MAX_MOVERS; ++i)
syncSavedMover(s, sd.SavedMoverInfo[i]);
for (int i = 0; i < MAX_SAVED_ACTORS; ++i)
syncSavedActor(s, sd.SavedActorInfo[i]);
s.syncAsSint32LE(sd.NumSavedActors);
s.syncAsSint32LE(sd.SavedLoffset);
s.syncAsSint32LE(sd.SavedToffset);
for (int i = 0; i < NUM_INTERPRET; ++i)
sd.SavedICInfo[i].syncWithSerializer(s);
for (int i = 0; i < MAX_POLY; ++i)
s.syncAsUint32LE(sd.SavedDeadPolys[i]);
s.syncAsUint32LE(sd.SavedControl);
s.syncAsUint32LE(sd.SavedMidi);
s.syncAsUint32LE(sd.SavedLoop);
s.syncAsUint32LE(sd.SavedNoBlocking);
// SavedNoScrollData
for (int i = 0; i < MAX_VNOSCROLL; ++i)
syncNoScrollB(s, sd.SavedNoScrollData.NoVScroll[i]);
for (int i = 0; i < MAX_HNOSCROLL; ++i)
syncNoScrollB(s, sd.SavedNoScrollData.NoHScroll[i]);
s.syncAsUint32LE(sd.SavedNoScrollData.NumNoV);
s.syncAsUint32LE(sd.SavedNoScrollData.NumNoH);
// Tinsel 2 fields
if (TinselV2) {
// SavedNoScrollData
s.syncAsUint32LE(sd.SavedNoScrollData.xTrigger);
s.syncAsUint32LE(sd.SavedNoScrollData.xDistance);
s.syncAsUint32LE(sd.SavedNoScrollData.xSpeed);
s.syncAsUint32LE(sd.SavedNoScrollData.yTriggerTop);
s.syncAsUint32LE(sd.SavedNoScrollData.yTriggerBottom);
s.syncAsUint32LE(sd.SavedNoScrollData.yDistance);
s.syncAsUint32LE(sd.SavedNoScrollData.ySpeed);
for (int i = 0; i < NUM_ZPOSITIONS; ++i)
syncZPosition(s, sd.zPositions[i]);
s.syncBytes(sd.savedActorZ, MAX_SAVED_ACTOR_Z);
for (int i = 0; i < MAX_POLY; ++i)
syncPolyVolatile(s, sd.SavedPolygonStuff[i]);
for (int i = 0; i < 3; ++i)
s.syncAsUint32LE(sd.SavedTune[i]);
s.syncAsByte(sd.bTinselDim);
s.syncAsSint32LE(sd.SavedScrollFocus);
for (int i = 0; i < SV_TOPVALID; ++i)
s.syncAsSint32LE(sd.SavedSystemVars[i]);
for (int i = 0; i < MAX_SOUNDREELS; ++i)
syncSoundReel(s, sd.SavedSoundReels[i]);
}
}
/**
* Called when saving a game to a new file.
* Generates a new, unique, filename.
*/
static char *NewName() {
static char result[FNAMELEN]; // FIXME: Avoid non-const global vars
int i;
int ano = 1; // Allocated number
while (1) {
Common::String fname = _vm->getSavegameFilename(ano);
strcpy(result, fname.c_str());
for (i = 0; i < numSfiles; i++)
if (!strcmp(savedFiles[i].name, result))
break;
if (i == numSfiles)
break;
ano++;
}
return result;
}
/**
* Compare two TimeDate structs to see which one was earlier.
* Returns 0 if they are equal, a negative value if a is lower / first, and
* a positive value if b is lower / first.
*/
static int cmpTimeDate(const TimeDate &a, const TimeDate &b) {
int tmp;
#define CMP_ENTRY(x) tmp = a.x - b.x; if (tmp != 0) return tmp
CMP_ENTRY(tm_year);
CMP_ENTRY(tm_mon);
CMP_ENTRY(tm_mday);
CMP_ENTRY(tm_hour);
CMP_ENTRY(tm_min);
CMP_ENTRY(tm_sec);
#undef CMP_ENTRY
return 0;
}
/**
* Compute a list of all available saved game files.
* Store the file details, ordered by time, in savedFiles[] and return
* the number of files found.
*/
int getList(Common::SaveFileManager *saveFileMan, const Common::String &target) {
// No change since last call?
// TODO/FIXME: Just always reload this data? Be careful about slow downs!!!
if (!NeedLoad)
return numSfiles;
int i;
const Common::String pattern = target + ".???";
Common::StringArray files = saveFileMan->listSavefiles(pattern);
numSfiles = 0;
for (Common::StringArray::const_iterator file = files.begin(); file != files.end(); ++file) {
if (numSfiles >= MAX_SAVED_FILES)
break;
const Common::String &fname = *file;
Common::InSaveFile *f = saveFileMan->openForLoading(fname);
if (f == NULL) {
continue;
}
// Try to load save game header
Common::Serializer s(f, 0);
SaveGameHeader hdr;
bool validHeader = syncSaveGameHeader(s, hdr);
delete f;
if (!validHeader) {
continue; // Invalid header, or savegame too new -> skip it
// TODO: In SCUMM, we still show an entry for the save, but with description
// "incompatible version".
}
i = numSfiles;
#ifndef DISABLE_SAVEGAME_SORTING
for (i = 0; i < numSfiles; i++) {
if (cmpTimeDate(hdr.dateTime, savedFiles[i].dateTime) > 0) {
Common::copy_backward(&savedFiles[i], &savedFiles[numSfiles], &savedFiles[numSfiles + 1]);
break;
}
}
#endif
Common::strlcpy(savedFiles[i].name, fname.c_str(), FNAMELEN);
Common::strlcpy(savedFiles[i].desc, hdr.desc, SG_DESC_LEN);
savedFiles[i].dateTime = hdr.dateTime;
++numSfiles;
}
// Next getList() needn't do its stuff again
NeedLoad = false;
return numSfiles;
}
int getList() {
// No change since last call?
// TODO/FIXME: Just always reload this data? Be careful about slow downs!!!
if (!NeedLoad)
return numSfiles;
return getList(_vm->getSaveFileMan(), _vm->getTargetName());
}
char *ListEntry(int i, letype which) {
if (i == -1)
i = numSfiles;
assert(i >= 0);
if (i < numSfiles)
return which == LE_NAME ? savedFiles[i].name : savedFiles[i].desc;
else
return NULL;
}
static void DoSync(Common::Serializer &s) {
int sg = 0;
if (TinselV2) {
if (s.isSaving())
restoreCD = GetCurrentCD();
s.syncAsSint16LE(restoreCD);
}
if (TinselV2 && s.isLoading())
HoldItem(INV_NOICON);
syncSavedData(s, *srsd);
syncGlobInfo(s); // Glitter globals
syncInvInfo(s); // Inventory data
// Held object
if (s.isSaving())
sg = WhichItemHeld();
s.syncAsSint32LE(sg);
if (s.isLoading()) {
if (TinselV2)
thingHeld = sg;
else
HoldItem(sg);
}
syncTimerInfo(s); // Timer data
if (!TinselV2)
syncPolyInfo(s); // Dead polygon data
syncSCdata(s); // Hook Scene and delayed scene
s.syncAsSint32LE(*SaveSceneSsCount);
if (*SaveSceneSsCount != 0) {
SAVED_DATA *sdPtr = SaveSceneSsData;
for (int i = 0; i < *SaveSceneSsCount; ++i, ++sdPtr)
syncSavedData(s, *sdPtr);
}
if (!TinselV2)
syncAllActorsAlive(s);
}
/**
* DoRestore
*/
static bool DoRestore() {
Common::InSaveFile *f = _vm->getSaveFileMan()->openForLoading(savedFiles[RestoreGameNumber].name);
if (f == NULL) {
return false;
}
Common::Serializer s(f, 0);
SaveGameHeader hdr;
if (!syncSaveGameHeader(s, hdr)) {
delete f; // Invalid header, or savegame too new -> skip it
return false;
}
DoSync(s);
uint32 id = f->readSint32LE();
if (id != (uint32)0xFEEDFACE)
error("Incompatible saved game");
bool failed = (f->eos() || f->err());
delete f;
if (failed) {
GUI::MessageDialog dialog("Failed to load game state from file.");
dialog.runModal();
}
return !failed;
}
/**
* DoSave
*/
static void DoSave() {
Common::OutSaveFile *f;
const char *fname;
// Next getList() must do its stuff again
NeedLoad = true;
if (SaveSceneName == NULL)
SaveSceneName = NewName();
if (SaveSceneDesc[0] == 0)
SaveSceneDesc = "unnamed";
fname = SaveSceneName;
f = _vm->getSaveFileMan()->openForSaving(fname);
Common::Serializer s(0, f);
if (f == NULL)
goto save_failure;
// Write out a savegame header
SaveGameHeader hdr;
hdr.id = SAVEGAME_ID;
hdr.size = SAVEGAME_HEADER_SIZE;
hdr.ver = CURRENT_VER;
memcpy(hdr.desc, SaveSceneDesc, SG_DESC_LEN);
hdr.desc[SG_DESC_LEN - 1] = 0;
g_system->getTimeAndDate(hdr.dateTime);
if (!syncSaveGameHeader(s, hdr) || f->err()) {
goto save_failure;
}
DoSync(s);
// Write out the special Id for Discworld savegames
f->writeUint32LE(0xFEEDFACE);
if (f->err())
goto save_failure;
f->finalize();
delete f;
return;
save_failure:
if (f) {
delete f;
_vm->getSaveFileMan()->removeSavefile(fname);
}
GUI::MessageDialog dialog("Failed to save game state to file.");
dialog.runModal();
}
/**
* ProcessSRQueue
*/
void ProcessSRQueue() {
switch (SRstate) {
case SR_DORESTORE:
// If a load has been done directly from title screens, set a larger value for scene ctr so the
// code used to skip the title screens in Discworld 1 gets properly disabled
if (sceneCtr < 10) sceneCtr = 10;
if (DoRestore()) {
DoRestoreScene(srsd, false);
}
SRstate = SR_IDLE;
break;
case SR_DOSAVE:
DoSave();
SRstate = SR_IDLE;
break;
default:
break;
}
}
void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
assert(SRstate == SR_IDLE);
SaveSceneName = name;
SaveSceneDesc = desc;
SaveSceneSsCount = pSsCount;
SaveSceneSsData = pSsData;
srsd = sd;
SRstate = SR_DOSAVE;
}
void RequestRestoreGame(int num, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
if (TinselV2) {
if (num == -1)
return;
else if (num == -2) {
// From CD change for restore
num = RestoreGameNumber;
}
}
assert(num >= 0);
RestoreGameNumber = num;
SaveSceneSsCount = pSsCount;
SaveSceneSsData = pSsData;
srsd = sd;
SRstate = SR_DORESTORE;
}
/**
* Returns the index of the most recently saved savegame. This will always be
* the file at the first index, since the list is sorted by date/time
*/
int NewestSavedGame() {
int numFiles = getList();
return (numFiles == 0) ? -1 : 0;
}
} // End of namespace Tinsel