mirror of
https://github.com/libretro/scummvm.git
synced 2024-12-18 15:48:48 +00:00
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
|