G_DirtyGameData: Dirty bit only applied in I_Error and signal handlers, nowhere else

- Unfortunately, the way this system previously worked, the unlock was given to you for free if you accidentially opened two copies of the game at once.
- Instead, open the file in r+ mode, shimmy along 5 bytes, and write a `true` to be read later.
- Far more memory safe than rewriting the entire gamedata out on crash.

ALSO:
- crashflags has been split into boolean evercrashed and UINT8 musicflags.
    - We don't need to track if the LAST session was a crash, at least not right now.
    - Opens the floor up to other music like Loser Club happening on the Challenges menu.
This commit is contained in:
toaster 2023-03-17 14:34:39 +00:00
parent c33ae4ae62
commit 3e900d7f57
18 changed files with 86 additions and 49 deletions

View file

@ -1051,7 +1051,7 @@ void D_ClearState(void)
cursongcredit.def = NULL;
if (gamedata && gamedata->deferredsave)
G_SaveGameData(true);
G_SaveGameData();
G_SetGamestate(GS_NULL);
wipegamestate = GS_NULL;

View file

@ -2931,7 +2931,7 @@ void readmaincfg(MYFILE *f, boolean mainfile)
if (!GoodDataFileName(word2))
I_Error("Maincfg: bad data file name '%s'\n", word2);
G_SaveGameData(false); // undirty your old gamedata
G_SaveGameData();
strlcpy(gamedatafilename, word2, sizeof (gamedatafilename));
strlwr(gamedatafilename);
savemoddata = true;

View file

@ -1084,7 +1084,7 @@ void F_GameEvaluationTicker(void)
++gamedata->timesBeaten;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(true);
G_SaveGameData();
}
else
{

View file

@ -4196,7 +4196,7 @@ void G_SaveDemo(void)
{
gamedata->eversavedreplay = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(true);
G_SaveGameData();
}
}
else

View file

@ -3834,7 +3834,7 @@ static void G_UpdateVisited(void)
CONS_Printf(M_GetText("\x82" "Earned %hu emblem%s for level completion.\n"), (UINT16)earnedEmblems, earnedEmblems > 1 ? "s" : "");
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(true);
G_SaveGameData();
}
static boolean CanSaveLevel(INT32 mapnum)
@ -3926,7 +3926,7 @@ static void G_GetNextMap(void)
{
gamedata->everseenspecial = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(true);
G_SaveGameData();
}
}
}
@ -4161,7 +4161,7 @@ static void G_DoCompleted(void)
}
if (gamedata->deferredsave)
G_SaveGameData(true);
G_SaveGameData();
legitimateexit = false;
@ -4567,6 +4567,11 @@ void G_LoadGameData(void)
gridunusable = true;
}
if (versionMinor > 1)
{
gamedata->evercrashed = (boolean)READUINT8(save.p);
}
gamedata->totalplaytime = READUINT32(save.p);
if (versionMinor > 1)
@ -4583,10 +4588,6 @@ void G_LoadGameData(void)
gamedata->keyspending = READUINT8(save.p);
gamedata->chaokeys = READUINT16(save.p);
gamedata->crashflags = READUINT8(save.p);
if (gamedata->crashflags & GDCRASH_LAST)
gamedata->crashflags |= GDCRASH_ANY;
gamedata->everloadedaddon = (boolean)READUINT8(save.p);
gamedata->eversavedreplay = (boolean)READUINT8(save.p);
gamedata->everseenspecial = (boolean)READUINT8(save.p);
@ -4782,9 +4783,34 @@ void G_LoadGameData(void)
}
}
// G_DirtyGameData
// Modifies the gamedata as little as possible to maintain safety in a crash event, while still recording it.
void G_DirtyGameData(void)
{
FILE *handle = NULL;
const UINT8 writebytesource = true;
if (gamedata)
gamedata->evercrashed = true;
//if (FIL_WriteFileOK(name))
handle = fopen(va(pandf, srb2home, gamedatafilename), "r+");
if (!handle)
return;
// Write a dirty byte immediately after the gamedata check + minor version.
if (fseek(handle, 5, SEEK_SET) != -1)
fwrite(&writebytesource, 1, 1, handle);
fclose(handle);
return;
}
// G_SaveGameData
// Saves the main data file, which stores information such as emblems found, etc.
void G_SaveGameData(boolean dirty)
void G_SaveGameData(void)
{
size_t length;
INT32 i, j, numcups;
@ -4805,10 +4831,11 @@ void G_SaveGameData(boolean dirty)
return;
}
length = (4+1+4+4+
length = (4+1+1+
4+4+
(4*GDGT_MAX)+
4+1+1+2+
1+1+1+1+
1+1+1+
4+
(MAXEMBLEMS+(MAXUNLOCKABLES*2)+MAXCONDITIONSETS)+
4+2);
@ -4836,6 +4863,13 @@ void G_SaveGameData(boolean dirty)
WRITEUINT32(save.p, GD_VERSIONCHECK); // 4
WRITEUINT8(save.p, GD_VERSIONMINOR); // 1
// Crash dirtiness
// cannot move, see G_DirtyGameData
WRITEUINT8(save.p, gamedata->evercrashed); // 1
// Statistics
WRITEUINT32(save.p, gamedata->totalplaytime); // 4
WRITEUINT32(save.p, gamedata->totalrings); // 4
@ -4849,13 +4883,6 @@ void G_SaveGameData(boolean dirty)
WRITEUINT8(save.p, gamedata->keyspending); // 1
WRITEUINT16(save.p, gamedata->chaokeys); // 2
{
UINT8 crashflags = (gamedata->crashflags & GDCRASH_ANY);
if (dirty)
crashflags |= GDCRASH_LAST;
WRITEUINT8(save.p, crashflags); // 1
}
WRITEUINT8(save.p, gamedata->everloadedaddon); // 1
WRITEUINT8(save.p, gamedata->eversavedreplay); // 1
WRITEUINT8(save.p, gamedata->everseenspecial); // 1

View file

@ -175,7 +175,8 @@ boolean G_IsTitleCardAvailable(void);
// Can be called by the startup code or M_Responder, calls P_SetupLevel.
void G_LoadGame(UINT32 slot, INT16 mapoverride);
void G_SaveGameData(boolean dirty);
void G_SaveGameData(void);
void G_DirtyGameData(void);
void G_SaveGame(UINT32 slot, INT16 mapnum);

View file

@ -373,7 +373,7 @@ void M_PlayMenuJam(void)
{
menu_t *refMenu = (menuactive ? currentMenu : restoreMenu);
static boolean loserclubpermitted = false;
boolean loserclub = (loserclubpermitted && (gamedata->crashflags & GDCRASH_LOSERCLUB));
boolean loserclub = (loserclubpermitted && (gamedata->musicflags & GDMUSIC_LOSERCLUB));
if (challengesmenu.pending)
{

View file

@ -291,7 +291,7 @@ void K_FinishCeremony(void)
// Play the noise now
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(true);
G_SaveGameData();
}
/*--------------------------------------------------
@ -339,7 +339,7 @@ void K_ResetCeremony(void)
grandprixinfo.cup->windata[i].got_emerald = true;
// Save before playing the noise
G_SaveGameData(true);
G_SaveGameData();
}
/*--------------------------------------------------

View file

@ -428,7 +428,7 @@ void K_CashInPowerLevels(void)
if (gamedataupdate)
{
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(true);
G_SaveGameData();
}
//CONS_Printf("========\n");
@ -644,6 +644,6 @@ void K_PlayerForfeit(UINT8 playerNum, boolean pointLoss)
pr->powerlevels[powerType] = yourPower + inc;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(true);
G_SaveGameData();
}
}

View file

@ -529,7 +529,8 @@ void M_ClearStats(void)
gamedata->everloadedaddon = false;
gamedata->eversavedreplay = false;
gamedata->everseenspecial = false;
gamedata->crashflags = 0;
gamedata->evercrashed = false;
gamedata->musicflags = 0;
}
void M_ClearSecrets(void)
@ -764,9 +765,9 @@ boolean M_CheckCondition(condition_t *cn, player_t *player)
case UC_REPLAY:
return (gamedata->eversavedreplay == true);
case UC_CRASH:
if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY))
if (gamedata->evercrashed)
{
gamedata->crashflags |= GDCRASH_LOSERCLUB;
gamedata->musicflags |= GDMUSIC_LOSERCLUB;
return true;
}
return false;
@ -1224,7 +1225,7 @@ static const char *M_GetConditionString(condition_t *cn)
case UC_REPLAY:
return "save a replay after finishing a round";
case UC_CRASH:
if (gamedata->crashflags & (GDCRASH_LAST|GDCRASH_ANY))
if (gamedata->evercrashed)
return "launch \"Dr. Robotnik's Ring Racers\" again after a game crash";
return NULL;

View file

@ -217,9 +217,7 @@ typedef enum
#endif
#define challengegridloops (gamedata->challengegridwidth >= CHALLENGEGRIDLOOPWIDTH)
#define GDCRASH_LAST 0x01
#define GDCRASH_ANY 0x02
#define GDCRASH_LOSERCLUB 0x04
#define GDMUSIC_LOSERCLUB 0x01
// This is the largest number of 9s that will fit in UINT32 and UINT16 respectively.
#define GDMAX_RINGS 999999999
@ -281,7 +279,8 @@ struct gamedata_t
boolean everloadedaddon;
boolean eversavedreplay;
boolean everseenspecial;
UINT8 crashflags;
boolean evercrashed;
UINT8 musicflags;
};
extern gamedata_t *gamedata;

View file

@ -399,7 +399,7 @@ void M_ChallengesTick(void)
{
// All done! Let's save the unlocks we've busted open.
challengesmenu.pending = challengesmenu.chaokeyadd = false;
G_SaveGameData(true);
G_SaveGameData();
}
}
else if (challengesmenu.pending)

View file

@ -7501,7 +7501,7 @@ static void P_InitGametype(void)
// Started a game? Move on to the next jam when you go back to the title screen
CV_SetValue(&cv_menujam_update, 1);
gamedata->crashflags &= ~GDCRASH_LOSERCLUB;
gamedata->musicflags = 0;
}
struct minimapinfo minimapinfo;
@ -7995,7 +7995,7 @@ boolean P_LoadLevel(boolean fromnetsave, boolean reloadinggamestate)
mapheaderinfo[gamemap-1]->mapvisited |= MV_VISITED;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(true);
G_SaveGameData();
}
G_AddMapToBuffer(gamemap-1);

View file

@ -620,7 +620,7 @@ void P_Ticker(boolean run)
// TODO would this be laggy with more conditions in play...
if (((!demo.playback && leveltime > introtime && M_UpdateUnlockablesAndExtraEmblems(true, false))
|| (gamedata && gamedata->deferredsave)))
G_SaveGameData(true);
G_SaveGameData();
}
// Keep track of how long they've been playing!

View file

@ -328,6 +328,7 @@ FUNCNORETURN static ATTRNORETURN void signal_handler(INT32 num)
{
D_QuitNetGame(); // Fix server freezes
CL_AbortDownloadResume();
G_DirtyGameData();
#ifdef UNIXBACKTRACE
write_backtrace(num);
#endif
@ -1448,7 +1449,7 @@ void I_Quit(void)
if (Playing())
K_PlayerForfeit(consoleplayer, true);
G_SaveGameData(false); // Tails 12-08-2002 -- undirty your save
G_SaveGameData(); // Tails 12-08-2002
//added:16-02-98: when recording a demo, should exit using 'q' key,
// but sometimes we forget and use 'F10'.. so save here too.
@ -1532,7 +1533,8 @@ void I_Error(const char *error, ...)
if (errorcount == 8)
{
M_SaveConfig(NULL);
G_SaveGameData(true);
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData();
}
if (errorcount > 20)
{
@ -1563,7 +1565,8 @@ void I_Error(const char *error, ...)
M_SaveConfig(NULL); // save game config, cvars..
D_SaveBan(); // save the ban list
G_SaveGameData(true); // Tails 12-08-2002
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData(); // Tails 12-08-2002
// Shutdown. Here might be other errors.

View file

@ -357,6 +357,7 @@ static void signal_handler(INT32 num)
sigmsg = sigdef;
}
G_DirtyGameData();
I_OutputMsg("signal_handler() error: %s\n", sigmsg);
signal(num, SIG_DFL); //default signal action
raise(num);
@ -2983,7 +2984,7 @@ void I_Quit(void)
if (Playing())
K_PlayerForfeit(consoleplayer, true);
G_SaveGameData(false); // Tails 12-08-2002 -- undirty your save
G_SaveGameData(); // Tails 12-08-2002
//added:16-02-98: when recording a demo, should exit using 'q' key,
// but sometimes we forget and use 'F10'.. so save here too.
@ -3078,7 +3079,8 @@ void I_Error(const char *error, ...)
if (errorcount == 9)
{
M_SaveConfig(NULL);
G_SaveGameData(true);
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData();
}
if (errorcount > 20)
{
@ -3142,7 +3144,8 @@ void I_Error(const char *error, ...)
#ifndef NONET
D_SaveBan(); // save the ban list
#endif
G_SaveGameData(true); // Tails 12-08-2002
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData(); // Tails 12-08-2002
// Shutdown. Here might be other errors.
if (demorecording)

View file

@ -819,7 +819,7 @@ UINT16 W_InitFile(const char *filename, boolean mainfile, boolean startup)
{
gamedata->everloadedaddon = true;
M_UpdateUnlockablesAndExtraEmblems(true, true);
G_SaveGameData(true);
G_SaveGameData();
}
switch(type = ResourceFileDetect(filename))

View file

@ -469,6 +469,7 @@ static void signal_handler(int num)
char sigdef[64];
D_QuitNetGame(); // Fix server freezes
G_DirtyGameData();
I_ShutdownSystem();
switch (num)
@ -607,7 +608,8 @@ void I_Error(const char *error, ...)
if (errorcount == 7)
{
M_SaveConfig(NULL);
G_SaveGameData(true);
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData();
}
if (errorcount > 20)
{
@ -636,7 +638,8 @@ void I_Error(const char *error, ...)
if (!errorcount)
{
M_SaveConfig(NULL); // save game config, cvars..
G_SaveGameData(true);
G_DirtyGameData(); // done first in case an error is in G_SaveGameData
G_SaveGameData();
}
// save demo, could be useful for debug
@ -726,7 +729,7 @@ void I_Quit(void)
G_CheckDemoStatus();
M_SaveConfig(NULL); // save game config, cvars..
G_SaveGameData(false); // undirty your save
G_SaveGameData(); // undirty your save
// maybe it needs that the ticcount continues,
// or something else that will be finished by I_ShutdownSystem(),