TINSEL: Handle DW2 saves that had Noir-sized SysVars

With this change we now once again can read pre 2.6 savegames.
Similarly savegames created after this fix will be readable by
ScummVM < 2.6.0. We will also be able to load savegames created
by ScummVM 2.6.x.

The only limitation is that since we now create the same kind
of savegames as older versions again, ScummVM 2.6.x will be
unable to load savegames created after this.

This fixes bug #13897

(cherry picked from commit 366c1e505d)
This commit is contained in:
Einar Johan Trøan Sømåen 2023-01-26 00:03:59 +01:00 committed by Donovan Watteau
parent a8bbb5f07f
commit 21b72c5bc3
4 changed files with 50 additions and 19 deletions

View file

@ -287,7 +287,7 @@ static void syncSoundReel(Common::Serializer &s, SOUNDREELS &sr) {
s.syncAsSint32LE(sr.actorCol);
}
static void syncSavedData(Common::Serializer &s, SAVED_DATA &sd, int numInterp) {
static void syncSavedData(Common::Serializer &s, SAVED_DATA &sd, int numInterp, int numSystemVars) {
s.syncAsUint32LE(sd.SavedSceneHandle);
s.syncAsUint32LE(sd.SavedBgroundHandle);
for (int i = 0; i < MAX_MOVERS; ++i)
@ -335,7 +335,7 @@ static void syncSavedData(Common::Serializer &s, SAVED_DATA &sd, int numInterp)
s.syncAsUint32LE(sd.SavedTune[i]);
s.syncAsByte(sd.bTinselDim);
s.syncAsSint32LE(sd.SavedScrollFocus);
for (int i = 0; i < SV_TOPVALID; ++i)
for (int i = 0; i < numSystemVars; ++i)
s.syncAsSint32LE(sd.SavedSystemVars[i]);
for (int i = 0; i < MAX_SOUNDREELS; ++i)
syncSoundReel(s, sd.SavedSoundReels[i]);
@ -447,7 +447,7 @@ char *ListEntry(int i, letype which) {
return NULL;
}
static bool DoSync(Common::Serializer &s, int numInterp) {
static bool DoSync(Common::Serializer &s, int numInterp, int numSystemVars) {
int sg = 0;
if (TinselVersion >= 2) {
@ -459,8 +459,7 @@ static bool DoSync(Common::Serializer &s, int numInterp) {
_vm->_dialogs->holdItem(INV_NOICON);
}
syncSavedData(s, *g_srsd, numInterp);
syncSavedData(s, *g_srsd, numInterp, numSystemVars);
syncGlobInfo(s); // Glitter globals
_vm->_dialogs->syncInvInfo(s); // Inventory data
@ -489,7 +488,7 @@ static bool DoSync(Common::Serializer &s, int numInterp) {
if (*g_SaveSceneSsCount != 0) {
SAVED_DATA *sdPtr = g_SaveSceneSsData;
for (int i = 0; i < *g_SaveSceneSsCount; ++i, ++sdPtr)
syncSavedData(s, *sdPtr, numInterp);
syncSavedData(s, *sdPtr, numInterp, numSystemVars);
// 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
@ -529,23 +528,52 @@ static bool DoRestore() {
// 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
int numberOfTries = ((hdr.ver >= 2) ? 1 : 2);
int numSystemVars = SV_TOPVALID; // Appropriate for both Noir and DW2
for (int tryNumber = 0; tryNumber < numberOfTries; ++tryNumber) {
if (tryNumber == 1) {
f->seek(currentPos);
numInterpreters = 80;
// If it's the second loop iteration, try with the 1.5 savegame number of interpreter contexts
if (hdr.ver < 2) {
numInterpreters = 80;
}
}
// Load the savegame data
if (DoSync(s, numInterpreters))
bool successfullSync = DoSync(s, numInterpreters, numSystemVars);
uint32 id = f->readSint32LE();
int remaining = f->size() - f->pos();
// BUG #13897: Older savegames won't run on ScummVM 2.6.x
// The reason being that the system vars for Noir were added increasing the SV_TOPVALID value,
// which also affected DW2 unintentionally, creating some v3 savegames that had additional data
// stored there. To properly load these savegames, we'll have to try again, with the knowledge
// that we have to skip this data.
if (hdr.id == DW2_SAVEGAME_ID && hdr.ver == 3 && remaining != 0) {
if (tryNumber == 0) {
numberOfTries++;
// Make the second attempt read the Noir amount of sys-vars, so that the problematic
// savegames can be loaded.
numSystemVars = SV_TOPVALID_T3;
}
continue;
}
if (successfullSync) {
if (id != (uint32)0xFEEDFACE) {
error("Incompatible saved game");
}
// Data load was successful (or likely), so break out of loop
break;
}
}
uint32 id = f->readSint32LE();
if (id != (uint32)0xFEEDFACE)
error("Incompatible saved game");
int remainingBytes = f->size() - f->pos();
if (remainingBytes != 0) {
error("%d bytes of savegame not read", remainingBytes);
}
bool failed = (f->eos() || f->err());
delete f;
@ -628,7 +656,7 @@ static void DoSave() {
return;
}
DoSync(s, hdr.numInterpreters);
DoSync(s, hdr.numInterpreters, SV_TOPVALID);
// Write out the special Id for Discworld savegames
f->writeUint32LE(0xFEEDFACE);

View file

@ -59,7 +59,7 @@ struct SAVED_DATA {
uint32 SavedTune[3]; // Music
bool bTinselDim;
int SavedScrollFocus;
int SavedSystemVars[SV_TOPVALID];
int SavedSystemVars[SV_TOPVALID_T3];
SOUNDREELS SavedSoundReels[MAX_SOUNDREELS];
};

View file

@ -42,7 +42,7 @@ extern int NewestSavedGame();
// These vars are reset upon engine destruction
static int g_systemVars[SV_TOPVALID];
static int g_systemVars[SV_TOPVALID_T3];
static SCNHANDLE g_systemStrings[SS_MAX_VALID];
//----------------- FUNCTIONS --------------------------------
@ -57,7 +57,7 @@ void ResetVarsSysVar() {
*/
void InitSysVars() {
int initialSystemVars[SV_TOPVALID] = {
int initialSystemVars[SV_TOPVALID_T3] = {
INV_1, // Default inventory
10, // Y-offset of Conversation(TOP)

View file

@ -83,6 +83,8 @@ typedef enum { SV_DEFAULT_INV,
ISV_GHOST_BASE_T2 = 0x2B,
ISV_GHOST_COLOR_T2 = 0x2C,
SV_TOPVALID_T2 = 0x2D,
SV_SPRITER_SCENE_ID = 0x2F, // Noir, loaded scene
ISV_DIVERT_ACTOR_T3 = 0x32,
ISV_NO_BLOCKING_T3 = 0x33,
@ -92,13 +94,14 @@ typedef enum { SV_DEFAULT_INV,
SV_SPRITER_SCALE = 0x37, // Noir, scale used for 3D rendering
SV_SPRITER_OVERLAY = 0x38, // Noir, if additional model is loaded
SV_TOPVALID } SYSVARS;
SV_TOPVALID_T3 } SYSVARS;
#define ISV_DIVERT_ACTOR ((TinselVersion == 3) ? ISV_DIVERT_ACTOR_T3 : ISV_DIVERT_ACTOR_T2)
#define ISV_NO_BLOCKING ((TinselVersion == 3) ? ISV_NO_BLOCKING_T3 : ISV_NO_BLOCKING_T2)
#define ISV_GHOST_ACTOR ((TinselVersion == 3) ? ISV_GHOST_ACTOR_T3 : ISV_GHOST_ACTOR_T2)
#define ISV_GHOST_BASE ((TinselVersion == 3) ? ISV_GHOST_BASE_T3 : ISV_GHOST_BASE_T2)
#define ISV_GHOST_COLOR ((TinselVersion == 3) ? ISV_GHOST_COLOR_T3 : ISV_GHOST_COLOR_T2)
#define SV_TOPVALID ((TinselVersion == 3) ? SV_TOPVALID_T3 : SV_TOPVALID_T2)
typedef enum {