mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-26 11:46:54 +00:00
304ac2a7c4
Whether or not SaveFailure() can delete the save file, we want to invalidate the save name because it probably only lives on the stack so the pointer will become invalid once DoSave() has ended.
690 lines
18 KiB
C++
690 lines
18 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.
|
|
*
|
|
* Save and restore scene and game.
|
|
*/
|
|
|
|
#include "tinsel/actors.h"
|
|
#include "tinsel/config.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 "common/textconsole.h"
|
|
#include "common/translation.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 2
|
|
|
|
/**
|
|
* 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 g_thingHeld = 0;
|
|
int g_restoreCD = 0;
|
|
SRSTATE g_SRstate = SR_IDLE;
|
|
|
|
//----------------- EXTERN FUNCTIONS --------------------
|
|
|
|
// in DOS_DW.C
|
|
extern void syncSCdata(Common::Serializer &s);
|
|
|
|
// in PCODE.C
|
|
extern void syncGlobInfo(Common::Serializer &s);
|
|
|
|
// in POLYGONS.C
|
|
extern void syncPolyInfo(Common::Serializer &s);
|
|
|
|
extern int g_sceneCtr;
|
|
|
|
extern bool g_ASceneIsSaved;
|
|
|
|
//----------------- LOCAL DEFINES --------------------
|
|
|
|
struct SaveGameHeader {
|
|
uint32 id;
|
|
uint32 size;
|
|
uint32 ver;
|
|
char desc[SG_DESC_LEN];
|
|
TimeDate dateTime;
|
|
bool scnFlag;
|
|
byte language;
|
|
uint16 numInterpreters; // Savegame version 2 or later only
|
|
};
|
|
|
|
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 + 1 + 1 + 2
|
|
};
|
|
|
|
#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 g_numSfiles = 0;
|
|
static SFILES g_savedFiles[MAX_SAVED_FILES];
|
|
|
|
static bool g_NeedLoad = true;
|
|
|
|
static SAVED_DATA *g_srsd = 0;
|
|
static int g_RestoreGameNumber = 0;
|
|
static char *g_SaveSceneName = 0;
|
|
static const char *g_SaveSceneDesc = 0;
|
|
static int *g_SaveSceneSsCount = 0;
|
|
static SAVED_DATA *g_SaveSceneSsData = 0; // points to 'SAVED_DATA ssdata[MAX_NEST]'
|
|
|
|
//------------- SAVE/LOAD SUPPORT METHODS ----------------
|
|
|
|
void setNeedLoad() {
|
|
g_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();
|
|
|
|
// NOTE: We can't use SAVEGAME_ID here when attempting to remove a saved game from the launcher,
|
|
// as there is no TinselEngine initialized then. This means that we can't check if this is a DW1
|
|
// or DW2 savegame in this case, but it doesn't really matter, as the saved game is about to be
|
|
// deleted anyway. Refer to bug #3387551.
|
|
bool correctID = _vm ? (hdr.id == SAVEGAME_ID) : (hdr.id == DW1_SAVEGAME_ID || hdr.id == DW2_SAVEGAME_ID);
|
|
|
|
// Perform sanity check
|
|
if (tmp < 0 || !correctID || hdr.ver > CURRENT_VER || hdr.size > 1024)
|
|
return false;
|
|
|
|
if (tmp > 0) {
|
|
// If there's header space left, handling syncing the Scn flag and game language
|
|
s.syncAsByte(hdr.scnFlag);
|
|
s.syncAsByte(hdr.language);
|
|
tmp -= 2;
|
|
|
|
if (_vm && s.isLoading()) {
|
|
// If the engine is loaded, ensure the Scn/Gra usage is correct, and it's the correct language
|
|
if ((hdr.scnFlag != ((_vm->getFeatures() & GF_SCNFILES) != 0)) ||
|
|
(hdr.language != _vm->_config->_language))
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Handle the number of interpreter contexts that will be saved in the savegame
|
|
if (tmp >= 2) {
|
|
tmp -= 2;
|
|
hdr.numInterpreters = NUM_INTERPRET;
|
|
s.syncAsUint16LE(hdr.numInterpreters);
|
|
} else {
|
|
hdr.numInterpreters = (TinselV2 ? 70 : 64) - 20;
|
|
}
|
|
|
|
// Skip over any extra bytes
|
|
s.skip(tmp);
|
|
return true;
|
|
}
|
|
|
|
static void syncSavedMover(Common::Serializer &s, SAVED_MOVER &sm) {
|
|
int i, j;
|
|
|
|
s.syncAsUint32LE(sm.bActive);
|
|
s.syncAsSint32LE(sm.actorID);
|
|
s.syncAsSint32LE(sm.objX);
|
|
s.syncAsSint32LE(sm.objY);
|
|
s.syncAsUint32LE(sm.hLastfilm);
|
|
|
|
// Sync walk reels
|
|
for (i = 0; i < TOTAL_SCALES; ++i)
|
|
for (j = 0; j < 4; ++j)
|
|
s.syncAsUint32LE(sm.walkReels[i][j]);
|
|
|
|
// Sync stand reels
|
|
for (i = 0; i < TOTAL_SCALES; ++i)
|
|
for (j = 0; j < 4; ++j)
|
|
s.syncAsUint32LE(sm.standReels[i][j]);
|
|
|
|
// Sync talk reels
|
|
for (i = 0; i < TOTAL_SCALES; ++i)
|
|
for (j = 0; j < 4; ++j)
|
|
s.syncAsUint32LE(sm.talkReels[i][j]);
|
|
|
|
|
|
if (TinselV2) {
|
|
s.syncAsByte(sm.bHidden);
|
|
|
|
s.syncAsSint32LE(sm.brightness);
|
|
s.syncAsSint32LE(sm.startColor);
|
|
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, int numInterp) {
|
|
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 < numInterp; ++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]);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* 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 (!g_NeedLoad)
|
|
return g_numSfiles;
|
|
|
|
int i;
|
|
|
|
const Common::String pattern = target + ".???";
|
|
Common::StringArray files = saveFileMan->listSavefiles(pattern);
|
|
|
|
g_numSfiles = 0;
|
|
|
|
for (Common::StringArray::const_iterator file = files.begin(); file != files.end(); ++file) {
|
|
if (g_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 = g_numSfiles;
|
|
#ifndef DISABLE_SAVEGAME_SORTING
|
|
for (i = 0; i < g_numSfiles; i++) {
|
|
if (cmpTimeDate(hdr.dateTime, g_savedFiles[i].dateTime) > 0) {
|
|
Common::copy_backward(&g_savedFiles[i], &g_savedFiles[g_numSfiles], &g_savedFiles[g_numSfiles + 1]);
|
|
break;
|
|
}
|
|
}
|
|
#endif
|
|
|
|
Common::strlcpy(g_savedFiles[i].name, fname.c_str(), FNAMELEN);
|
|
Common::strlcpy(g_savedFiles[i].desc, hdr.desc, SG_DESC_LEN);
|
|
g_savedFiles[i].dateTime = hdr.dateTime;
|
|
|
|
++g_numSfiles;
|
|
}
|
|
|
|
// Next getList() needn't do its stuff again
|
|
g_NeedLoad = false;
|
|
|
|
return g_numSfiles;
|
|
}
|
|
|
|
int getList() {
|
|
// No change since last call?
|
|
// TODO/FIXME: Just always reload this data? Be careful about slow downs!!!
|
|
if (!g_NeedLoad)
|
|
return g_numSfiles;
|
|
|
|
return getList(_vm->getSaveFileMan(), _vm->getTargetName());
|
|
}
|
|
|
|
char *ListEntry(int i, letype which) {
|
|
if (i == -1)
|
|
i = g_numSfiles;
|
|
|
|
assert(i >= 0);
|
|
|
|
if (i < g_numSfiles)
|
|
return which == LE_NAME ? g_savedFiles[i].name : g_savedFiles[i].desc;
|
|
else
|
|
return NULL;
|
|
}
|
|
|
|
static bool DoSync(Common::Serializer &s, int numInterp) {
|
|
int sg = 0;
|
|
|
|
if (TinselV2) {
|
|
if (s.isSaving())
|
|
g_restoreCD = GetCurrentCD();
|
|
s.syncAsSint16LE(g_restoreCD);
|
|
}
|
|
|
|
if (TinselV2 && s.isLoading())
|
|
HoldItem(INV_NOICON);
|
|
|
|
syncSavedData(s, *g_srsd, numInterp);
|
|
syncGlobInfo(s); // Glitter globals
|
|
syncInvInfo(s); // Inventory data
|
|
|
|
// Held object
|
|
if (s.isSaving())
|
|
sg = WhichItemHeld();
|
|
s.syncAsSint32LE(sg);
|
|
if (s.isLoading()) {
|
|
if (sg != -1 && !GetIsInvObject(sg))
|
|
// Not a valid inventory object, so return false
|
|
return false;
|
|
|
|
if (TinselV2)
|
|
g_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(*g_SaveSceneSsCount);
|
|
|
|
if (*g_SaveSceneSsCount != 0) {
|
|
SAVED_DATA *sdPtr = g_SaveSceneSsData;
|
|
for (int i = 0; i < *g_SaveSceneSsCount; ++i, ++sdPtr)
|
|
syncSavedData(s, *sdPtr, numInterp);
|
|
|
|
// Flag that there is a saved scene to return to. Note that in this context 'saved scene'
|
|
// is a stored scene to return to from another scene, such as from the Summoning Book close-up
|
|
// in Discworld 1 to whatever scene Rincewind was in prior to that
|
|
g_ASceneIsSaved = true;
|
|
}
|
|
|
|
if (!TinselV2)
|
|
syncAllActorsAlive(s);
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* DoRestore
|
|
*/
|
|
static bool DoRestore() {
|
|
Common::InSaveFile *f = _vm->getSaveFileMan()->openForLoading(g_savedFiles[g_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;
|
|
}
|
|
|
|
// Load in the data. For older savegame versions, we potentially need to load the data twice, once
|
|
// for pre 1.5 savegames, and if that fails, a second time for 1.5 savegames
|
|
int numInterpreters = hdr.numInterpreters;
|
|
int32 currentPos = f->pos();
|
|
for (int tryNumber = 0; tryNumber < ((hdr.ver >= 2) ? 1 : 2); ++tryNumber) {
|
|
// If it's the second loop iteration, try with the 1.5 savegame number of interpreter contexts
|
|
if (tryNumber == 1) {
|
|
f->seek(currentPos);
|
|
numInterpreters = 80;
|
|
}
|
|
|
|
// Load the savegame data
|
|
if (DoSync(s, numInterpreters))
|
|
// Data load was successful (or likely), so break out of loop
|
|
break;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
static void SaveFailure(Common::OutSaveFile *f) {
|
|
if (f) {
|
|
delete f;
|
|
_vm->getSaveFileMan()->removeSavefile(g_SaveSceneName);
|
|
}
|
|
g_SaveSceneName = NULL; // Invalidate save name
|
|
GUI::MessageDialog dialog(_("Failed to save game state to file."));
|
|
dialog.runModal();
|
|
}
|
|
|
|
/**
|
|
* DoSave
|
|
*/
|
|
static void DoSave() {
|
|
Common::OutSaveFile *f;
|
|
char tmpName[FNAMELEN];
|
|
|
|
// Next getList() must do its stuff again
|
|
g_NeedLoad = true;
|
|
|
|
if (g_SaveSceneName == NULL) {
|
|
// Generate a new unique save name
|
|
int i;
|
|
int ano = 1; // Allocated number
|
|
|
|
while (1) {
|
|
Common::String fname = _vm->getSavegameFilename(ano);
|
|
strcpy(tmpName, fname.c_str());
|
|
|
|
for (i = 0; i < g_numSfiles; i++)
|
|
if (!strcmp(g_savedFiles[i].name, tmpName))
|
|
break;
|
|
|
|
if (i == g_numSfiles)
|
|
break;
|
|
ano++;
|
|
}
|
|
|
|
g_SaveSceneName = tmpName;
|
|
}
|
|
|
|
|
|
if (g_SaveSceneDesc[0] == 0)
|
|
g_SaveSceneDesc = "unnamed";
|
|
|
|
f = _vm->getSaveFileMan()->openForSaving(g_SaveSceneName);
|
|
Common::Serializer s(0, f);
|
|
|
|
if (f == NULL) {
|
|
SaveFailure(f);
|
|
return;
|
|
}
|
|
|
|
// Write out a savegame header
|
|
SaveGameHeader hdr;
|
|
hdr.id = SAVEGAME_ID;
|
|
hdr.size = SAVEGAME_HEADER_SIZE;
|
|
hdr.ver = CURRENT_VER;
|
|
memcpy(hdr.desc, g_SaveSceneDesc, SG_DESC_LEN);
|
|
hdr.desc[SG_DESC_LEN - 1] = 0;
|
|
g_system->getTimeAndDate(hdr.dateTime);
|
|
hdr.scnFlag = _vm->getFeatures() & GF_SCNFILES;
|
|
hdr.language = _vm->_config->_language;
|
|
|
|
if (!syncSaveGameHeader(s, hdr) || f->err()) {
|
|
SaveFailure(f);
|
|
return;
|
|
}
|
|
|
|
DoSync(s, hdr.numInterpreters);
|
|
|
|
// Write out the special Id for Discworld savegames
|
|
f->writeUint32LE(0xFEEDFACE);
|
|
if (f->err()) {
|
|
SaveFailure(f);
|
|
return;
|
|
}
|
|
|
|
f->finalize();
|
|
delete f;
|
|
g_SaveSceneName = NULL; // Invalidate save name
|
|
}
|
|
|
|
/**
|
|
* ProcessSRQueue
|
|
*/
|
|
void ProcessSRQueue() {
|
|
switch (g_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 (g_sceneCtr < 10)
|
|
g_sceneCtr = 10;
|
|
|
|
if (DoRestore()) {
|
|
DoRestoreScene(g_srsd, false);
|
|
}
|
|
g_SRstate = SR_IDLE;
|
|
break;
|
|
|
|
case SR_DOSAVE:
|
|
DoSave();
|
|
g_SRstate = SR_IDLE;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
void RequestSaveGame(char *name, char *desc, SAVED_DATA *sd, int *pSsCount, SAVED_DATA *pSsData) {
|
|
assert(g_SRstate == SR_IDLE);
|
|
|
|
g_SaveSceneName = name;
|
|
g_SaveSceneDesc = desc;
|
|
g_SaveSceneSsCount = pSsCount;
|
|
g_SaveSceneSsData = pSsData;
|
|
g_srsd = sd;
|
|
g_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 = g_RestoreGameNumber;
|
|
}
|
|
}
|
|
|
|
assert(num >= 0);
|
|
|
|
g_RestoreGameNumber = num;
|
|
g_SaveSceneSsCount = pSsCount;
|
|
g_SaveSceneSsData = pSsData;
|
|
g_srsd = sd;
|
|
g_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
|