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.
This commit is contained in:
Michael Martin
2013-09-25 22:31:42 -07:00
parent 27087d0ea8
commit e4657ea3d1
3 changed files with 98 additions and 27 deletions
+82 -17
View File
@@ -160,6 +160,18 @@ read_a8 (void *fp, BYTE *ar, COUNT count)
return ReadResFile (ar, 1, count, fp) == 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 static inline size_t
read_str (void *fp, char *str, COUNT count) read_str (void *fp, char *str, COUNT count)
{ {
@@ -478,7 +490,7 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh)
} }
static BOOLEAN static BOOLEAN
LoadSisState (SIS_STATE *SSPtr, void *fp, SDWORD first) LoadSisState (SIS_STATE *SSPtr, void *fp, SDWORD first, BOOLEAN legacy)
{ {
SSPtr->log_x = first; SSPtr->log_x = first;
if ( 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->ShipName, SIS_NAME_SIZE) != 1 ||
read_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE) != 1 || read_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE) != 1 ||
read_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1 || read_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1
read_16 (fp, NULL) != 1 /* padding */
) )
return FALSE; return FALSE;
else if (legacy && (read_16 (fp, NULL) != 1))
return TRUE; return FALSE;
return TRUE;
} }
static BOOLEAN static BOOLEAN
LoadSummary (SUMMARY_DESC *SummPtr, void *fp) LoadSummary (SUMMARY_DESC *SummPtr, void *fp, BOOLEAN *legacy_ptr)
{ {
SDWORD magic; SDWORD magic;
DWORD nameSize = 0;
BOOLEAN legacy; BOOLEAN legacy;
SummPtr->SaveName[0] = 0;
if (!read_32s (fp, &magic)) if (!read_32s (fp, &magic))
return FALSE; return FALSE;
if (magic == SAVE_MAGIC) if (magic == SAVE_MAGIC)
{ {
legacy = FALSE; 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 // Read in the real first value for LoadSisState
if (read_32 (fp, &magic) != 1) if (read_32 (fp, &magic) != 1)
return FALSE; return FALSE;
@@ -525,9 +542,10 @@ LoadSummary (SUMMARY_DESC *SummPtr, void *fp)
// Otherwise, we're legacy and the "magic" number was // Otherwise, we're legacy and the "magic" number was
// really LoadSisState's first value // really LoadSisState's first value
legacy = TRUE; legacy = TRUE;
*legacy_ptr = TRUE;
} }
if (!LoadSisState (&SummPtr->SS, fp, magic)) if (!LoadSisState (&SummPtr->SS, fp, magic, legacy))
return FALSE; return FALSE;
if ( if (
@@ -544,14 +562,32 @@ LoadSummary (SUMMARY_DESC *SummPtr, void *fp)
read_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 read_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1
) )
return FALSE; return FALSE;
if (!legacy && (read_a8 (fp, SummPtr->SaveName, SAVE_NAME_SIZE) != 1))
return FALSE; if (!legacy)
// Don't trust the savefile to properly null-terminate! {
SummPtr->SaveName[SAVE_NAME_SIZE-1] = 0; if (nameSize < SAVE_NAME_SIZE)
if (read_16 (fp, NULL) != 1) /* padding */ {
return FALSE; 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 else
return TRUE; {
SummPtr->SaveName[0] = 0;
if (read_16 (fp, NULL) != 1) /* padding */
return FALSE;
}
return TRUE;
} }
static void static void
@@ -577,13 +613,15 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr)
COUNT num_links; COUNT num_links;
STAR_DESC SD; STAR_DESC SD;
ACTIVITY Activity; ACTIVITY Activity;
BOOLEAN legacy;
DWORD chunk, chunkSize;
sprintf (file, "starcon2.%02u", which_game); sprintf (file, "starcon2.%02u", which_game);
in_fp = res_OpenResFile (saveDir, file, "rb"); in_fp = res_OpenResFile (saveDir, file, "rb");
if (!in_fp) if (!in_fp)
return FALSE; return FALSE;
if (!LoadSummary (&loc_sd, in_fp)) if (!LoadSummary (&loc_sd, in_fp, &legacy))
{ {
log_add (log_Error, "Warning: Savegame is corrupt"); log_add (log_Error, "Warning: Savegame is corrupt");
res_CloseResFile (in_fp); res_CloseResFile (in_fp);
@@ -618,6 +656,33 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr)
GlobData.SIS_state = SummPtr->SS; 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) if ((fh = copen (in_fp, FILE_STREAM, STREAM_READ)) == 0)
{ {
res_CloseResFile (in_fp); res_CloseResFile (in_fp);
+12 -8
View File
@@ -431,9 +431,7 @@ SaveSisState (const SIS_STATE *SSPtr, void *fp)
write_str (fp, SSPtr->ShipName, SIS_NAME_SIZE) != 1 || write_str (fp, SSPtr->ShipName, SIS_NAME_SIZE) != 1 ||
write_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE) != 1 || write_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE) != 1 ||
write_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1 || write_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1
write_16 (fp, 0) != 1 /* padding */
) )
return FALSE; return FALSE;
else else
@@ -443,7 +441,11 @@ SaveSisState (const SIS_STATE *SSPtr, void *fp)
static BOOLEAN static BOOLEAN
SaveSummary (const SUMMARY_DESC *SummPtr, void *fp) 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; return FALSE;
if (!SaveSisState (&SummPtr->SS, fp)) if (!SaveSisState (&SummPtr->SS, fp))
return FALSE; return FALSE;
@@ -460,9 +462,7 @@ SaveSummary (const SUMMARY_DESC *SummPtr, void *fp)
write_8 (fp, SummPtr->NumDevices) != 1 || write_8 (fp, SummPtr->NumDevices) != 1 ||
write_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS) != 1 || write_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS) != 1 ||
write_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 || write_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 ||
write_a8 (fp, SummPtr->SaveName, SAVE_NAME_SIZE) != 1 || write_a8 (fp, SummPtr->SaveName, strlen(SummPtr->SaveName)+1) != 1
write_16 (fp, 0) != 1 /* padding */
) )
return FALSE; return FALSE;
else else
@@ -835,13 +835,17 @@ RetrySave:
// Write the memory file to the actual savegame file. // Write the memory file to the actual savegame file.
sprintf (file, "starcon2.%02u", which_game); sprintf (file, "starcon2.%02u", which_game);
log_add (log_Debug, "'%s' is %u bytes long", file, 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"))) if (flen && (out_fp = res_OpenResFile (saveDir, file, "wb")))
{ {
PrepareSummary (SummPtr, name); PrepareSummary (SummPtr, name);
success = SaveSummary (SummPtr, out_fp); success = SaveSummary (SummPtr, out_fp);
// Then write the rest of the data. // 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) if (success && WriteResFile (h, flen, 1, out_fp) != 1)
success = FALSE; success = FALSE;
+4 -2
View File
@@ -31,8 +31,10 @@ extern "C" {
// is only used for displaying savegame summaries. There is also // is only used for displaying savegame summaries. There is also
// room for only 16 devices on screen. // room for only 16 devices on screen.
#define MAX_EXCLUSIVE_DEVICES 16 #define MAX_EXCLUSIVE_DEVICES 16
#define SAVE_MAGIC 0x01534d55 #define SAVE_MAGIC 0x01534d55 // "UMS\x01": UQM Save version 1
#define SAVE_NAME_SIZE 24 #define SUMMARY_MAGIC 0x6d6d7553 // "Summ": Summary. Must be first!
#define OMNIZIP_MAGIC 0x5a6e6d4f // "OmnZ": All data compressed.
#define SAVE_NAME_SIZE 64
typedef struct typedef struct
{ {