From e4657ea3d1b1724ab434c2ebb53fdd76494f710b Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Wed, 25 Sep 2013 22:31:42 -0700 Subject: [PATCH] Improved save file format inspired by IFF. Savefile is built of chunks that include their length. The only two right now are Summ (the summary, must be first) and OmnZ (the omnibus compressed data). Padding that was originally used for alignment purposes has been removed from the format, unless is it is part of the compressed data. --- sc2/src/uqm/load.c | 99 ++++++++++++++++++++++++++++++++++++++-------- sc2/src/uqm/save.c | 20 ++++++---- sc2/src/uqm/save.h | 6 ++- 3 files changed, 98 insertions(+), 27 deletions(-) diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index e115273b6..b4da899ec 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -160,6 +160,18 @@ read_a8 (void *fp, BYTE *ar, COUNT count) return ReadResFile (ar, 1, count, fp) == count; } +static inline size_t +skip_8 (void *fp, COUNT count) +{ + int i; + for (i = 0; i < count; ++i) + { + if (read_8(fp, NULL) != 1) + return 0; + } + return 1; +} + static inline size_t read_str (void *fp, char *str, COUNT count) { @@ -478,7 +490,7 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) } static BOOLEAN -LoadSisState (SIS_STATE *SSPtr, void *fp, SDWORD first) +LoadSisState (SIS_STATE *SSPtr, void *fp, SDWORD first, BOOLEAN legacy) { SSPtr->log_x = first; if ( @@ -496,26 +508,31 @@ LoadSisState (SIS_STATE *SSPtr, void *fp, SDWORD first) read_str (fp, SSPtr->ShipName, SIS_NAME_SIZE) != 1 || read_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE) != 1 || - read_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1 || - - read_16 (fp, NULL) != 1 /* padding */ + read_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1 ) return FALSE; - else - return TRUE; + if (legacy && (read_16 (fp, NULL) != 1)) + return FALSE; + return TRUE; } static BOOLEAN -LoadSummary (SUMMARY_DESC *SummPtr, void *fp) +LoadSummary (SUMMARY_DESC *SummPtr, void *fp, BOOLEAN *legacy_ptr) { SDWORD magic; + DWORD nameSize = 0; BOOLEAN legacy; - SummPtr->SaveName[0] = 0; if (!read_32s (fp, &magic)) return FALSE; if (magic == SAVE_MAGIC) { legacy = FALSE; + *legacy_ptr = FALSE; + if (read_32 (fp, &magic) != 1 || magic != SUMMARY_MAGIC) + return FALSE; + if (read_32 (fp, &magic) != 1 || magic < 161) + return FALSE; + nameSize = magic - 160; // Read in the real first value for LoadSisState if (read_32 (fp, &magic) != 1) return FALSE; @@ -525,9 +542,10 @@ LoadSummary (SUMMARY_DESC *SummPtr, void *fp) // Otherwise, we're legacy and the "magic" number was // really LoadSisState's first value legacy = TRUE; + *legacy_ptr = TRUE; } - if (!LoadSisState (&SummPtr->SS, fp, magic)) + if (!LoadSisState (&SummPtr->SS, fp, magic, legacy)) return FALSE; if ( @@ -544,14 +562,32 @@ LoadSummary (SUMMARY_DESC *SummPtr, void *fp) read_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 ) return FALSE; - if (!legacy && (read_a8 (fp, SummPtr->SaveName, SAVE_NAME_SIZE) != 1)) - return FALSE; - // Don't trust the savefile to properly null-terminate! - SummPtr->SaveName[SAVE_NAME_SIZE-1] = 0; - if (read_16 (fp, NULL) != 1) /* padding */ - return FALSE; + + if (!legacy) + { + if (nameSize < SAVE_NAME_SIZE) + { + if (read_a8 (fp, SummPtr->SaveName, nameSize) != 1) + return FALSE; + SummPtr->SaveName[nameSize] = 0; + } + else + { + DWORD remaining = nameSize - SAVE_NAME_SIZE + 1; + if (read_a8 (fp, SummPtr->SaveName, SAVE_NAME_SIZE-1) != 1) + return FALSE; + SummPtr->SaveName[SAVE_NAME_SIZE-1] = 0; + if (skip_8 (fp, remaining) != 1) + return FALSE; + } + } else - return TRUE; + { + SummPtr->SaveName[0] = 0; + if (read_16 (fp, NULL) != 1) /* padding */ + return FALSE; + } + return TRUE; } static void @@ -577,13 +613,15 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) COUNT num_links; STAR_DESC SD; ACTIVITY Activity; + BOOLEAN legacy; + DWORD chunk, chunkSize; sprintf (file, "starcon2.%02u", which_game); in_fp = res_OpenResFile (saveDir, file, "rb"); if (!in_fp) return FALSE; - if (!LoadSummary (&loc_sd, in_fp)) + if (!LoadSummary (&loc_sd, in_fp, &legacy)) { log_add (log_Error, "Warning: Savegame is corrupt"); res_CloseResFile (in_fp); @@ -618,6 +656,33 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) GlobData.SIS_state = SummPtr->SS; + if (!legacy) + { + chunk = 0; + while (chunk != OMNIZIP_MAGIC) + { + if (read_32(in_fp, &chunk) != 1) + { + res_CloseResFile (in_fp); + return FALSE; + } + if (read_32(in_fp, &chunkSize) != 1) + { + res_CloseResFile (in_fp); + return FALSE; + } + if (chunk == OMNIZIP_MAGIC) + break; + + log_add (log_Debug, "Skipping chunk of tag %08X (size %u)", chunk, chunkSize); + if (skip_8(in_fp, chunkSize) != 1) + return FALSE; + } + /* Fortunately for us, the OmnZ chunk is internally + * self-sizing, so we can ignore the chunk size as long as we + * aren't skipping it. */ + } + if ((fh = copen (in_fp, FILE_STREAM, STREAM_READ)) == 0) { res_CloseResFile (in_fp); diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index 9c3e389d8..509515c15 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -431,9 +431,7 @@ SaveSisState (const SIS_STATE *SSPtr, void *fp) write_str (fp, SSPtr->ShipName, SIS_NAME_SIZE) != 1 || write_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE) != 1 || - write_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1 || - - write_16 (fp, 0) != 1 /* padding */ + write_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1 ) return FALSE; else @@ -443,7 +441,11 @@ SaveSisState (const SIS_STATE *SSPtr, void *fp) static BOOLEAN SaveSummary (const SUMMARY_DESC *SummPtr, void *fp) { - if (write_32 (fp, SAVE_MAGIC) != 1) + if ( + write_32 (fp, SAVE_MAGIC) != 1 || + write_32 (fp, SUMMARY_MAGIC) != 1 || + write_32 (fp, 161 + strlen(SummPtr->SaveName)) != 1 + ) return FALSE; if (!SaveSisState (&SummPtr->SS, fp)) return FALSE; @@ -460,9 +462,7 @@ SaveSummary (const SUMMARY_DESC *SummPtr, void *fp) write_8 (fp, SummPtr->NumDevices) != 1 || write_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS) != 1 || write_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 || - write_a8 (fp, SummPtr->SaveName, SAVE_NAME_SIZE) != 1 || - - write_16 (fp, 0) != 1 /* padding */ + write_a8 (fp, SummPtr->SaveName, strlen(SummPtr->SaveName)+1) != 1 ) return FALSE; else @@ -835,13 +835,17 @@ RetrySave: // Write the memory file to the actual savegame file. sprintf (file, "starcon2.%02u", which_game); log_add (log_Debug, "'%s' is %u bytes long", file, - flen + sizeof (*SummPtr)); + flen + 181 + strlen(SummPtr->SaveName)); if (flen && (out_fp = res_OpenResFile (saveDir, file, "wb"))) { PrepareSummary (SummPtr, name); success = SaveSummary (SummPtr, out_fp); // Then write the rest of the data. + if (success && write_32 (out_fp, OMNIZIP_MAGIC) != 1) + success = FALSE; + if (success && write_32 (out_fp, flen) != 1) + success = FALSE; if (success && WriteResFile (h, flen, 1, out_fp) != 1) success = FALSE; diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index 6f37f01ff..f06c4a051 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -31,8 +31,10 @@ extern "C" { // is only used for displaying savegame summaries. There is also // room for only 16 devices on screen. #define MAX_EXCLUSIVE_DEVICES 16 -#define SAVE_MAGIC 0x01534d55 -#define SAVE_NAME_SIZE 24 +#define SAVE_MAGIC 0x01534d55 // "UMS\x01": UQM Save version 1 +#define SUMMARY_MAGIC 0x6d6d7553 // "Summ": Summary. Must be first! +#define OMNIZIP_MAGIC 0x5a6e6d4f // "OmnZ": All data compressed. +#define SAVE_NAME_SIZE 64 typedef struct {