diff --git a/src/k_stats.c b/src/k_stats.c index 4c126f700..e2da4cbee 100644 --- a/src/k_stats.c +++ b/src/k_stats.c @@ -2,6 +2,7 @@ #include "doomstat.h" #include "g_game.h" #include "byteptr.h" +#include "z_zone.h" kartstats_t kartstats = {0}; @@ -65,6 +66,8 @@ void K_StatRound(void) if (demo.playback) return; + kartstats.somenewfield = 42; + int numplayers = 0; for (int i = 0; i < MAXPLAYERS; ++i) @@ -87,23 +90,18 @@ void K_EraseStats(void) // The only field we want to remember boolean vanilla = kartstats.vanilla; + if (kartstats.copy) + Z_Free(kartstats.copy); + memset(&kartstats, 0, sizeof(kartstats_t)); kartstats.vanilla = vanilla; } -void K_ReadStats(savebuffer_t *save, boolean vanilla) +static void K_ReadStatsCustom(savebuffer_t *save) { - memset(&kartstats, 0, sizeof(kartstats_t)); - - kartstats.vanilla = vanilla; - - kartstats.totalplaytime = READUINT32(save->p); - kartstats.matchesplayed = READUINT32(save->p); - - // Vanilla only has those 2 - if (vanilla) - return; + kartstats.size = READUINT32(save->p); + kartstats.version = READUINT32(save->p); // So many similar-looking read's... scary... kartstats.raplaytime = READUINT32(save->p); @@ -119,17 +117,51 @@ void K_ReadStats(savebuffer_t *save, boolean vanilla) kartstats.sinks = READUINT32(save->p); kartstats.sinked = READUINT32(save->p); kartstats.respawns = READUINT32(save->p); + + // If kartstats gets updated, uncomment this and read next fields after this early return. Do same on next updates, + // this way even data is read on different versions, it doesn't get corrupted (as long as fields aren't removed, which shouldn't happen) + //if (kartstats.version < 2) + // return; + // + //kartstats.somenewfield = READUINT32(save->p); } -void K_WriteStats(savebuffer_t *save, boolean vanilla) +void K_ReadStats(savebuffer_t *save, boolean vanilla) { - WRITEUINT32(save->p, kartstats.totalplaytime); - WRITEUINT32(save->p, kartstats.matchesplayed); + // Basically, free old kartstats.copy if needed and memset kartstats with zeros + K_EraseStats(); + + kartstats.vanilla = vanilla; + + kartstats.totalplaytime = READUINT32(save->p); + kartstats.matchesplayed = READUINT32(save->p); // Vanilla only has those 2 if (vanilla) return; + // Save where block of custom stats starts + UINT8 *customblockstart = save->p; + + K_ReadStatsCustom(save); + + // Make a copy of entire custom stats block, so unread values will still be written back + kartstats.copy = Z_Malloc(kartstats.size, PU_STATIC, NULL); + memcpy(kartstats.copy, customblockstart, kartstats.size); + + // If there were extra fields we couldn't read, skip them + save->p = customblockstart + kartstats.size; +} + +static void K_WriteStatsCustom(savebuffer_t *save) +{ + // Update, if needed + kartstats.version = max(kartstats.version, KARTSTATSVERSION); + + WRITEUINT32(save->p, kartstats.size); // Note: if we will end up writing more that stored in there, this field in save file would be updated + WRITEUINT32(save->p, kartstats.version); + + // Version 1 WRITEUINT32(save->p, kartstats.raplaytime); WRITEUINT32(save->p, kartstats.onlineplaytime); WRITEUINT32(save->p, kartstats.raceplaytime); @@ -143,4 +175,42 @@ void K_WriteStats(savebuffer_t *save, boolean vanilla) WRITEUINT32(save->p, kartstats.sinks); WRITEUINT32(save->p, kartstats.sinked); WRITEUINT32(save->p, kartstats.respawns); + + // No need for early returns, but please mark each new block of write's with version it corresponds to :3 + + // Version 2 + //WRITEUINT32(save->p, kartstats.somenewfield); +} + +void K_WriteStats(savebuffer_t *save, boolean vanilla) +{ + WRITEUINT32(save->p, kartstats.totalplaytime); + WRITEUINT32(save->p, kartstats.matchesplayed); + + // Vanilla only has those 2 + if (vanilla) + return; + + // Now need to be careful... We save start of custom stats block + UINT8 *customblockstart = save->p; + + // Write everything we know how to write + K_WriteStatsCustom(save); + + // Calculate size of data that we wrote + UINT32 size = (UINT32)(save->p - customblockstart); + + // Now, if we wrote less data than save originally had, that means our copy that we kept in K_ReadStats + // has stats from newer version that we need to append + if (size < kartstats.size) + { + I_Assert(kartstats.copy != NULL); // If kartstats.copy is null, kartstats.size **always** should be 0, this can only happen if we create new save + WRITEMEM(save->p, kartstats.copy + size, kartstats.size - size); + } + else if (size > kartstats.size) // No need to do anything if we wrote exactly same amount... + { + // ...but, if we wrote more, we need to update size field in header. **It's always located at start of block** + // So we can just: + WRITEUINT32(customblockstart, size); + } } diff --git a/src/k_stats.h b/src/k_stats.h index 87a173653..bd145de56 100644 --- a/src/k_stats.h +++ b/src/k_stats.h @@ -5,6 +5,8 @@ #include "d_player.h" #include "p_saveg.h" +#define KARTSTATSVERSION 1 + typedef struct kartstats_s { // Remember if stats we loaded are vanilla ones or not boolean vanilla; @@ -13,6 +15,12 @@ typedef struct kartstats_s { tic_t totalplaytime; UINT32 matchesplayed; + // Custom data, fields go in same order as they are written into game save + + // IMPORTANT: size **must be first**, so K_WriteStats can overwrite it (not that we're gonna change order, but just in case) + UINT32 size; // Size required for our stats. If older version reads save from newer one, it will just skip unread fields, avoiding reading corrupted values + UINT32 version; // Version, so we know which fields we actually can read + tic_t raplaytime; tic_t onlineplaytime; tic_t raceplaytime; @@ -26,6 +34,8 @@ typedef struct kartstats_s { UINT32 sinks; // Times hitting *others* by kitchen sink UINT32 sinked; // Times *being hit* by kitchen sink UINT32 respawns; + + UINT8 *copy; // Copy of stats block that has been read from file. In case we're older version that reads newer save, we will keep values that we couldn't read } kartstats_t; extern kartstats_t kartstats;