Rewrite Profile saving and loading

- Previous implementation used fopen, fwrite, fread, etc.
- Instead, use the byteptr.h macros to/from a buffer, performing IO all at once before/after.
- This way, if we do something unrecoverable mid-write, we won't corrupt the user's profile.
- Also cross-endian compatible AND now capable of supporting changes in the struct.
- Sadly not back-compatible. This should be the last time we destroy the team's existing profiles...

Also, modify a typo in a gamedata error this system used as reference.
This commit is contained in:
toaster 2022-09-02 19:38:54 +01:00
parent 86b1e57f32
commit 5c4ff2ead0
3 changed files with 159 additions and 67 deletions

View file

@ -4013,7 +4013,7 @@ void G_LoadGameData(void)
Z_Free(savebuffer); Z_Free(savebuffer);
save_p = NULL; save_p = NULL;
I_Error("Game data is from another version of SRB2.\nDelete %s(maybe in %s) and try again.", gamedatafilename, gdfolder); I_Error("Game data is from another version of SRB2.\nDelete %s (maybe in %s) and try again.", gamedatafilename, gdfolder);
} }
totalplaytime = READUINT32(save_p); totalplaytime = READUINT32(save_p);

View file

@ -11,6 +11,9 @@
/// \brief implements methods for profiles etc. /// \brief implements methods for profiles etc.
#include "d_main.h" // pandf #include "d_main.h" // pandf
#include "byteptr.h" // READ/WRITE macros
#include "p_saveg.h" // save_p
#include "m_misc.h" //FIL_WriteFile()
#include "k_profiles.h" #include "k_profiles.h"
#include "z_zone.h" #include "z_zone.h"
#include "r_skins.h" #include "r_skins.h"
@ -198,31 +201,75 @@ void PR_InitNewProfile(void)
PR_AddProfile(dprofile); PR_AddProfile(dprofile);
} }
static UINT8 *savebuffer;
void PR_SaveProfiles(void) void PR_SaveProfiles(void)
{ {
FILE *f = NULL; size_t length = 0;
const size_t headerlen = strlen(PROFILEHEADER);
UINT8 i, j, k;
f = fopen(va(pandf, srb2home, PROFILESFILE), "w"); save_p = savebuffer = (UINT8 *)malloc(sizeof(UINT32) + (numprofiles * sizeof(profile_t)));
if (f != NULL) if (!save_p)
{ {
UINT8 i; I_Error("No more free memory for saving profiles\n");
return;
fwrite(&numprofiles, sizeof numprofiles, 1, f);
for (i = 1; i < numprofiles; ++i)
{
fwrite(profilesList[i], sizeof(profile_t), 1, f);
} }
fclose(f); // Add header.
WRITESTRINGN(save_p, PROFILEHEADER, headerlen);
WRITEUINT8(save_p, PROFILEVER);
WRITEUINT8(save_p, numprofiles);
for (i = 1; i < numprofiles; i++)
{
// Names.
WRITESTRINGN(save_p, profilesList[i]->profilename, PROFILENAMELEN);
WRITESTRINGN(save_p, profilesList[i]->playername, MAXPLAYERNAME);
// Character and colour.
WRITESTRINGN(save_p, profilesList[i]->skinname, SKINNAMESIZE);
WRITEUINT16(save_p, profilesList[i]->color);
// Follower and colour.
WRITESTRINGN(save_p, profilesList[i]->follower, SKINNAMESIZE);
WRITEUINT16(save_p, profilesList[i]->followercolor);
// PWR.
for (j = 0; j < PWRLV_NUMTYPES; j++)
{
WRITEUINT16(save_p, profilesList[i]->powerlevels[j]);
} }
else
// Consvars.
WRITEUINT8(save_p, profilesList[i]->kickstartaccel);
// Controls.
for (j = 0; j < num_gamecontrols; j++)
{
for (k = 0; k < MAXINPUTMAPPING; k++)
{
WRITEINT32(save_p, profilesList[i]->controls[j][k]);
}
}
}
length = save_p - savebuffer;
if (!FIL_WriteFile(va(pandf, srb2home, PROFILESFILE), savebuffer, length))
{
free(savebuffer);
I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?"); I_Error("Couldn't save profiles. Are you out of Disk space / playing in a protected folder?");
}
free(savebuffer);
save_p = savebuffer = NULL;
} }
void PR_LoadProfiles(void) void PR_LoadProfiles(void)
{ {
FILE *f = NULL; size_t length = 0;
const size_t headerlen = strlen(PROFILEHEADER);
UINT8 i, j, k, version;
profile_t *dprofile = PR_MakeProfile( profile_t *dprofile = PR_MakeProfile(
PROFILEDEFAULTNAME, PROFILEDEFAULTNAME,
PROFILEDEFAULTPNAME, PROFILEDEFAULTPNAME,
@ -232,23 +279,55 @@ void PR_LoadProfiles(void)
true true
); );
f = fopen(va(pandf, srb2home, PROFILESFILE), "r"); length = FIL_ReadFile(va(pandf, srb2home, PROFILESFILE), &savebuffer);
if (!length)
if (f != NULL)
{ {
INT32 i, j; // No profiles. Add the default one.
PR_AddProfile(dprofile);
return;
}
fread(&numprofiles, sizeof numprofiles, 1, f); save_p = savebuffer;
if (strncmp(PROFILEHEADER, (const char *)savebuffer, headerlen))
{
const char *gdfolder = "the Ring Racers folder";
if (strcmp(srb2home,"."))
gdfolder = srb2home;
Z_Free(savebuffer);
save_p = NULL;
I_Error("Not a valid Profile file.\nDelete %s (maybe in %s) and try again.", PROFILESFILE, gdfolder);
}
save_p += headerlen;
version = READUINT8(save_p);
if (version > PROFILEVER)
{
Z_Free(savebuffer);
save_p = NULL;
I_Error("Existing %s is from the future! (expected %d, got %d)", PROFILESFILE, PROFILEVER, version);
}
numprofiles = READUINT8(save_p);
if (numprofiles > MAXPROFILES) if (numprofiles > MAXPROFILES)
numprofiles = MAXPROFILES; numprofiles = MAXPROFILES;
for (i = PROFILE_GUEST+1; i < numprofiles; ++i) for (i = 1; i < numprofiles; i++)
{ {
profilesList[i] = Z_Malloc(sizeof(profile_t), PU_STATIC, NULL); profilesList[i] = Z_Malloc(sizeof(profile_t), PU_STATIC, NULL);
fread(profilesList[i], sizeof(profile_t), 1, f);
// Attempt to correct numerical footguns // Version.
profilesList[i]->version = version;
// Names.
READSTRINGN(save_p, profilesList[i]->profilename, PROFILENAMELEN);
READSTRINGN(save_p, profilesList[i]->playername, MAXPLAYERNAME);
// Character and colour.
READSTRINGN(save_p, profilesList[i]->skinname, SKINNAMESIZE);
profilesList[i]->color = READUINT16(save_p);
if (profilesList[i]->color == SKINCOLOR_NONE) if (profilesList[i]->color == SKINCOLOR_NONE)
{ {
; // Valid, even outside the bounds ; // Valid, even outside the bounds
@ -259,6 +338,10 @@ void PR_LoadProfiles(void)
profilesList[i]->color = PROFILEDEFAULTCOLOR; profilesList[i]->color = PROFILEDEFAULTCOLOR;
} }
// Follower and colour.
READSTRINGN(save_p, profilesList[i]->follower, SKINNAMESIZE);
profilesList[i]->followercolor = READUINT16(save_p);
if (profilesList[i]->followercolor == FOLLOWERCOLOR_MATCH if (profilesList[i]->followercolor == FOLLOWERCOLOR_MATCH
|| profilesList[i]->followercolor == FOLLOWERCOLOR_OPPOSITE) || profilesList[i]->followercolor == FOLLOWERCOLOR_OPPOSITE)
{ {
@ -271,8 +354,10 @@ void PR_LoadProfiles(void)
profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR; profilesList[i]->followercolor = PROFILEDEFAULTFOLLOWERCOLOR;
} }
// PWR.
for (j = 0; j < PWRLV_NUMTYPES; j++) for (j = 0; j < PWRLV_NUMTYPES; j++)
{ {
profilesList[i]->powerlevels[j] = READUINT16(save_p);
if (profilesList[i]->powerlevels[j] < PWRLVRECORD_MIN if (profilesList[i]->powerlevels[j] < PWRLVRECORD_MIN
|| profilesList[i]->powerlevels[j] > PWRLVRECORD_MAX) || profilesList[i]->powerlevels[j] > PWRLVRECORD_MAX)
{ {
@ -280,18 +365,22 @@ void PR_LoadProfiles(void)
profilesList[i]->powerlevels[j] = PWRLVRECORD_START; profilesList[i]->powerlevels[j] = PWRLVRECORD_START;
} }
} }
}
fclose(f); // Consvars.
profilesList[i]->kickstartaccel = (boolean)READUINT8(save_p);
// Overwrite the first profile for the default profile to avoid letting anyone tamper with it. // Controls.
profilesList[PROFILE_GUEST] = dprofile; for (j = 0; j < num_gamecontrols; j++)
}
else
{ {
// No profiles. Add the default one. for (k = 0; k < MAXINPUTMAPPING; k++)
PR_AddProfile(dprofile); {
profilesList[i]->controls[j][k] = READINT32(save_p);
} }
}
}
// Add the the default profile directly to avoid letting anyone tamper with it.
profilesList[PROFILE_GUEST] = dprofile;
} }
skincolornum_t PR_GetProfileColor(profile_t *p) skincolornum_t PR_GetProfileColor(profile_t *p)

View file

@ -39,9 +39,12 @@
#define PROFILEDEFAULTFOLLOWER "none" #define PROFILEDEFAULTFOLLOWER "none"
#define PROFILEDEFAULTFOLLOWERCOLOR FOLLOWERCOLOR_MATCH #define PROFILEDEFAULTFOLLOWERCOLOR FOLLOWERCOLOR_MATCH
#define PROFILEHEADER "Doctor Robotnik's Ring Racers Profiles"
// Man I wish I had more than 16 friends!! // Man I wish I had more than 16 friends!!
// profile_t definition (WIP) // profile_t definition (WIP)
// If you edit, see PR_SaveProfiles and PR_LoadProfiles
typedef struct profile_s typedef struct profile_s
{ {