From 28fb037258129fad1cd202d00488c284ca6d1cb6 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Wed, 25 Sep 2013 01:15:30 -0700 Subject: [PATCH 01/18] First draft of namable savegames. This code is loosely adapted from JMS's code in UQM-HD, but very little remains beyond the text input loop internals. Fully backwards compatible, but this format shouldn't survive to the final merge. --- sc2/src/uqm/gameopt.c | 242 +++++++++++++++++++++++++++++++++++++----- sc2/src/uqm/load.c | 45 +++++--- sc2/src/uqm/save.c | 29 ++--- sc2/src/uqm/save.h | 2 +- sc2/src/uqm/sis.h | 5 +- 5 files changed, 268 insertions(+), 55 deletions(-) diff --git a/sc2/src/uqm/gameopt.c b/sc2/src/uqm/gameopt.c index 726f0b2ce..80070e7bf 100644 --- a/sc2/src/uqm/gameopt.c +++ b/sc2/src/uqm/gameopt.c @@ -34,6 +34,7 @@ #include "sounds.h" #include "util.h" #include "libs/graphics/gfx_common.h" +#include "libs/log.h" #include @@ -253,7 +254,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, // disallow the change return (FALSE); } - + PreUpdateFlashRect (); SetContextForeGroundColor (BackGround); @@ -264,7 +265,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, text_r.corner.x += *pchar_deltas++; if (CursorPos < lf.CharCount) /* end of line */ --text_r.corner.x; - + if (state & DDSHS_BLOCKCUR) { // Use block cursor for keyboardless systems if (CursorPos == lf.CharCount) @@ -284,7 +285,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, { // Insertion point cursor text_r.extent.width = 1; } - + text_r.corner.y = r.corner.y; text_r.extent.height = r.extent.height; SetContextForeGroundColor (BLACK_COLOR); @@ -292,7 +293,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, SetContextForeGroundColor (ForeGround); font_DrawText (&lf); - + PostUpdateFlashRect (); } @@ -347,15 +348,183 @@ NameCaptainOrShip (bool nameCaptain) utf8StringCopy (Setting, tes.MaxSize, buf); else utf8StringCopy (buf, sizeof (buf), Setting); - + SetFlashRect (SFR_MENU_3DO); - + DrawNameString (nameCaptain, buf, 0, DDSHS_NORMAL); + if (namingCB) + namingCB (); +} + +static BOOLEAN +DrawSaveNameString (UNICODE *Str, COUNT CursorPos, COUNT state, COUNT gameIndex) +{ + RECT r; + TEXT lf; + Color BackGround, ForeGround; + FONT Font; + + SetContextForeGroundColor (BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33)); + r.extent.width = 15; + if (MAX_SAVED_GAMES > 99) + r.extent.width += 5; + r.extent.height = 11; + r.corner.x = 8; + r.corner.y = (160 + ((gameIndex % SAVES_PER_PAGE) * 13)); + DrawRectangle (&r); + + r.extent.width = (204 - SAFE_X); + r.corner.x = (30 + SAFE_X); + DrawRectangle (&r); + + Font = TinyFont; + lf.baseline.x = r.corner.x + 3; + lf.baseline.y = r.corner.y + 8; + + BackGround = BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33); + ForeGround = BUILD_COLOR (MAKE_RGB15 (0x00, 0x00, 0x14), 0x01); + + lf.align = ALIGN_LEFT; + + SetContextFont (Font); + lf.pStr = Str; + lf.CharCount = (COUNT)~0; + + if (!(state & DDSHS_EDIT)) + { + //RECT r; + TEXT t; + + SetContextForeGroundColor (BLACK_COLOR); + DrawFilledRectangle (&r); + + t.baseline.x = r.corner.x + 3; + t.baseline.y = r.corner.y + 8; + t.align = ALIGN_LEFT; + t.pStr = Str; + t.CharCount = (COUNT)~0; + SetContextForeGroundColor (CAPTAIN_NAME_TEXT_COLOR); + font_DrawText (&t); + } + else + { // editing state + COUNT i; + RECT text_r; + BYTE char_deltas[SAVE_NAME_SIZE]; + BYTE *pchar_deltas; + + TextRect (&lf, &text_r, char_deltas); + if ((text_r.extent.width + 2) >= r.extent.width) + { // the text does not fit the input box size and so + // will not fit when displayed later + // disallow the change + return (FALSE); + } + + PreUpdateFlashRect (); + + SetContextForeGroundColor (BackGround); + DrawFilledRectangle (&r); + + pchar_deltas = char_deltas; + + for (i = CursorPos; i > 0; --i) + text_r.corner.x += *pchar_deltas++; + if (CursorPos < lf.CharCount) /* end of line */ + --text_r.corner.x; + + if (state & DDSHS_BLOCKCUR) + { // Use block cursor for keyboardless systems + if (CursorPos == lf.CharCount) + { // cursor at end-line -- use insertion point + text_r.extent.width = 1; + } + else if (CursorPos + 1 == lf.CharCount) + { // extra pixel for last char margin + text_r.extent.width = (SIZE)*pchar_deltas + 2; + } + else + { // normal mid-line char + text_r.extent.width = (SIZE)*pchar_deltas + 1; + } + } + else + { // Insertion point cursor + text_r.extent.width = 1; + } + + text_r.corner.y = r.corner.y; + text_r.extent.height = r.extent.height; + SetContextForeGroundColor (BLACK_COLOR); + DrawFilledRectangle (&text_r); + + SetContextForeGroundColor (ForeGround); + font_DrawText (&lf); + PostUpdateFlashRect (); + } + + return (TRUE); +} + +static BOOLEAN +OnSaveNameChange (TEXTENTRY_STATE *pTES) +{ + COUNT hl = DDSHS_EDIT; + COUNT *gameIndex = pTES->CbParam; + + if (pTES->JoystickMode) + hl |= DDSHS_BLOCKCUR; + + return DrawSaveNameString (pTES->BaseStr, pTES->CursorPos, hl, *gameIndex); +} + +static BOOLEAN +NameSaveGame (COUNT gameIndex, UNICODE *buf) +{ + TEXTENTRY_STATE tes; + COUNT CursPos = 0; + COUNT *gIndex = HMalloc (sizeof (COUNT)); + RECT r; + *gIndex = gameIndex; + + DrawSaveNameString (buf, CursPos, DDSHS_EDIT, gameIndex); + + tes.MaxSize = SAVE_NAME_SIZE; + + // text entry setup + tes.Initialized = FALSE; + tes.BaseStr = buf; + tes.CursorPos = CursPos; + tes.CbParam = gIndex; + tes.ChangeCallback = OnSaveNameChange; + tes.FrameCallback = 0; + r.extent.width = (204 - SAFE_X); + r.extent.height = 11; + r.corner.x = (30 + SAFE_X); + r.corner.y = (160 + ((gameIndex % SAVES_PER_PAGE) * 13)); + SetFlashRect (&r); + + if (!DoTextEntry (&tes)) + buf[0] = 0; + + SetFlashRect(NULL); + + DrawSaveNameString (buf, CursPos, DDSHS_NORMAL, gameIndex); + if (namingCB) namingCB (); SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); + + HFree (gIndex); + + SetFlashRect (NULL); + + if (tes.Success) + return (TRUE); + else + return (FALSE); } void @@ -611,7 +780,7 @@ DrawSavegameSummary (PICK_GAME_STATE *pickState, COUNT gameIndex) UninitQueue (&GLOBAL (built_ship_q)); SetContextClipRect (&OldRect); - + SetContext (SpaceContext); // draw devices s.origin.y = 13; @@ -680,7 +849,7 @@ DrawSavegameSummary (PICK_GAME_STATE *pickState, COUNT gameIndex) SetContextForeGroundColor ( BUILD_COLOR (MAKE_RGB15 (0x10, 0x00, 0x10), 0x01)); font_DrawText (&t); - + // print the location t.baseline.x = 6; t.baseline.y = 139 + 6; @@ -697,7 +866,7 @@ DrawSavegameSummary (PICK_GAME_STATE *pickState, COUNT gameIndex) { BYTE QuasiState; STAR_DESC *SDPtr; - + QuasiState = GET_GAME_STATE (ARILOU_SPACE_SIDE); SET_GAME_STATE (ARILOU_SPACE_SIDE, 0); SDPtr = FindStar (NULL, &starPt, 1, 1); @@ -770,11 +939,11 @@ DrawGameSelection (PICK_GAME_STATE *pickState, COUNT selSlot) COUNT curSlot; UNICODE buf[256]; UNICODE buf2[80]; - + BatchGraphics (); SetContextFont (TinyFont); - + // Erase the selection menu r.extent.width = 240; r.extent.height = 65; @@ -825,9 +994,10 @@ DrawGameSelection (PICK_GAME_STATE *pickState, COUNT selSlot) { DateToString (buf2, sizeof buf2, desc->month_index, desc->day_index, desc->year_index); - snprintf (buf, sizeof buf, "%s %s", - GAME_STRING (SAVEGAME_STRING_BASE + 4), buf2); - // "Saved Game - Date:" + if (desc->SaveName[0]) + snprintf (buf, sizeof buf, "%s: %s", buf2, desc->SaveName); + else + snprintf (buf, sizeof buf, "%s", buf2); } font_DrawText (&t); } @@ -928,7 +1098,7 @@ DoPickGame (MENU_STATE *pMS) SetContext (SpaceContext); RedrawPickDisplay (pickState, pMS->CurState); } - + SleepThreadUntil (TimeIn + ONE_SECOND / 30); } @@ -936,22 +1106,35 @@ DoPickGame (MENU_STATE *pMS) } static BOOLEAN -SaveLoadGame (PICK_GAME_STATE *pickState, COUNT gameIndex) +SaveLoadGame (PICK_GAME_STATE *pickState, COUNT gameIndex, BOOLEAN *canceled_by_user) { SUMMARY_DESC *desc = pickState->summary + gameIndex; + UNICODE nameBuf[SAVE_NAME_SIZE]; STAMP saveStamp; BOOLEAN success; saveStamp.frame = NULL; - // TODO: fix ConfirmSaveLoad() interface so it does not rely on - // MsgStamp != NULL parameter. - ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL); - if (pickState->saving) - success = SaveGame (gameIndex, desc); + { + nameBuf[0] = 0; + if (NameSaveGame (gameIndex, nameBuf)) + { + PlayMenuSound (MENU_SOUND_SUCCESS); + ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL); + success = SaveGame (gameIndex, desc, nameBuf); + } + else + { + success = FALSE; + *canceled_by_user = TRUE; + } + } else + { + ConfirmSaveLoad (pickState->saving ? &saveStamp : NULL); success = LoadGame (gameIndex, NULL); + } // TODO: the same should be done for both save and load if we also // display a load problem message @@ -1005,12 +1188,12 @@ PickGame (BOOLEAN saving, BOOLEAN fromMainMenu) // draw the current savegame and fade in SetTransitionSource (NULL); BatchGraphics (); - + SetContextBackGroundColor (BLACK_COLOR); ClearDrawable (); RedrawPickDisplay (&pickState, MenuState.CurState); DrawSaveLoad (&pickState); - + if (fromMainMenu) { UnbatchGraphics (); @@ -1028,10 +1211,12 @@ PickGame (BOOLEAN saving, BOOLEAN fromMainMenu) SetMenuSounds (MENU_SOUND_ARROWS | MENU_SOUND_PAGEUP | MENU_SOUND_PAGEDOWN, 0); MenuState.InputFunc = DoPickGame; - + // Save/load retry loop while (1) { + BOOLEAN canceled_by_user = FALSE; + pickState.success = FALSE; DoInput (&MenuState, TRUE); if (!pickState.success) @@ -1039,11 +1224,11 @@ PickGame (BOOLEAN saving, BOOLEAN fromMainMenu) lastUsedSlot = MenuState.CurState; - if (SaveLoadGame (&pickState, MenuState.CurState)) + if (SaveLoadGame (&pickState, MenuState.CurState, &canceled_by_user)) break; // all good // something broke - if (saving) + if (saving && !canceled_by_user) SaveProblem (); // TODO: Shouldn't we have a Problem() equivalent for Load too? @@ -1058,7 +1243,7 @@ PickGame (BOOLEAN saving, BOOLEAN fromMainMenu) { // Load succeeded, signal up the chain GLOBAL (CurrentActivity) |= CHECK_LOAD; } - + if (!(GLOBAL (CurrentActivity) & CHECK_ABORT) && (saving || (!pickState.success && !fromMainMenu))) { // Restore previous screen @@ -1145,7 +1330,7 @@ GameOptions (void) DrawMenuStateStrings (PM_SAVE_GAME, MenuState.CurState); SetFlashRect (SFR_MENU_3DO); - + SetMenuSounds (MENU_SOUND_ARROWS, MENU_SOUND_SELECT); MenuState.InputFunc = DoGameOptions; DoInput (&MenuState, TRUE); @@ -1154,4 +1339,3 @@ GameOptions (void) return !(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)); } - diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index 98924f173..c7a58c6d0 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -368,7 +368,7 @@ LoadEncounter (ENCOUNTER *EncounterPtr, DECODE_REF fh) cread_ptr (fh); /* useless ptr; FRAME icons */ cread_ptr (fh); /* useless ptr; FRAME melee_icon */ } - + // Load the stuff after the BRIEF_SHIP_INFO array cread_32s (fh, &EncounterPtr->log_x); cread_32s (fh, &EncounterPtr->log_y); @@ -405,7 +405,7 @@ DummyLoadQueue (QUEUE *QueuePtr, DECODE_REF fh) cread_16 (fh, NULL); /* MEM_HANDLE hq_tab */ cread_16 (fh, NULL); /* COUNT object_size */ cread_8 (fh, NULL); /* BYTE num_objects */ - + cread_8 (fh, NULL); /* padding */ cread_16 (fh, NULL); /* padding */ } @@ -438,7 +438,7 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) cread_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); cread_ptr (fh); /* not loading ptr; PRIMITIVE *DisplayArray */ cread_16 (fh, &GSPtr->CurrentActivity); - + cread_16 (fh, NULL); /* CLOCK_STATE alignment padding */ LoadClockState (&GSPtr->GameClock, fh); @@ -466,7 +466,7 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) cread_16 (fh, NULL); /* VELOCITY_DESC padding */ cread_32 (fh, &GSPtr->BattleGroupRef); - + DummyLoadQueue (&GSPtr->avail_race_q, fh); DummyLoadQueue (&GSPtr->npc_built_ship_q, fh); // Not loading ip_group_q, was not there originally @@ -480,10 +480,10 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) } static BOOLEAN -LoadSisState (SIS_STATE *SSPtr, void *fp) +LoadSisState (SIS_STATE *SSPtr, void *fp, SDWORD first) { + SSPtr->log_x = first; if ( - read_32s (fp, &SSPtr->log_x) != 1 || read_32s (fp, &SSPtr->log_y) != 1 || read_32 (fp, &SSPtr->ResUnits) != 1 || read_32 (fp, &SSPtr->FuelOnBoard) != 1 || @@ -510,7 +510,26 @@ LoadSisState (SIS_STATE *SSPtr, void *fp) static BOOLEAN LoadSummary (SUMMARY_DESC *SummPtr, void *fp) { - if (!LoadSisState (&SummPtr->SS, fp)) + SDWORD magic; + BOOLEAN legacy; + SummPtr->SaveName[0] = 0; + if (!read_32s (fp, &magic)) + return FALSE; + if (magic == SAVE_MAGIC) + { + legacy = FALSE; + // Read in the real first value for LoadSisState + if (read_32 (fp, &magic) != 1) + return FALSE; + } + else + { + // Otherwise, we're legacy and the "magic" number was + // really LoadSisState's first value + legacy = TRUE; + } + + if (!LoadSisState (&SummPtr->SS, fp, magic)) return FALSE; if ( @@ -524,11 +543,15 @@ LoadSummary (SUMMARY_DESC *SummPtr, void *fp) read_8 (fp, &SummPtr->NumShips) != 1 || read_8 (fp, &SummPtr->NumDevices) != 1 || read_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS) != 1 || - read_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 || - - read_16 (fp, NULL) != 1 /* padding */ + 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; else return TRUE; } @@ -760,5 +783,3 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) return TRUE; } - - diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index c49fe505e..95be06eda 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -305,7 +305,7 @@ SaveEncounter (const ENCOUNTER *EncounterPtr, DECODE_REF fh) cwrite_ptr (fh); /* useless ptr; FRAME icons */ cwrite_ptr (fh); /* useless ptr; FRAME melee_icon */ } - + // Save the stuff after the BRIEF_SHIP_INFO array cwrite_32 (fh, EncounterPtr->log_x); cwrite_32 (fh, EncounterPtr->log_y); @@ -340,7 +340,7 @@ DummySaveQueue (const QUEUE *QueuePtr, DECODE_REF fh) cwrite_16 (fh, 0); /* MEM_HANDLE hq_tab */ cwrite_16 (fh, 0); /* COUNT object_size */ cwrite_8 (fh, 0); /* BYTE num_objects */ - + cwrite_8 (fh, 0); /* padding */ cwrite_16 (fh, 0); /* padding */ } @@ -371,7 +371,7 @@ SaveGameState (const GAME_STATE *GSPtr, DECODE_REF fh) cwrite_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); cwrite_ptr (fh); /* useless ptr; PRIMITIVE *DisplayArray */ cwrite_16 (fh, GSPtr->CurrentActivity); - + cwrite_16 (fh, 0); /* CLOCK_STATE alignment padding */ SaveClockState (&GSPtr->GameClock, fh); @@ -399,7 +399,7 @@ SaveGameState (const GAME_STATE *GSPtr, DECODE_REF fh) cwrite_16 (fh, 0); /* VELOCITY_DESC padding */ cwrite_32 (fh, GSPtr->BattleGroupRef); - + DummySaveQueue (&GSPtr->avail_race_q, fh); DummySaveQueue (&GSPtr->npc_built_ship_q, fh); // Not saving ip_group_q, was not there originally @@ -443,6 +443,8 @@ SaveSisState (const SIS_STATE *SSPtr, void *fp) static BOOLEAN SaveSummary (const SUMMARY_DESC *SummPtr, void *fp) { + if (write_32 (fp, SAVE_MAGIC) != 1) + return FALSE; if (!SaveSisState (&SummPtr->SS, fp)) return FALSE; @@ -458,6 +460,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 */ ) @@ -478,8 +481,9 @@ SaveStarDesc (const STAR_DESC *SDPtr, DECODE_REF fh) } static void -PrepareSummary (SUMMARY_DESC *SummPtr) +PrepareSummary (SUMMARY_DESC *SummPtr, const char *name) { + int i; SummPtr->SS = GlobData.SIS_state; SummPtr->Activity = LOBYTE (GLOBAL (CurrentActivity)); @@ -535,6 +539,8 @@ PrepareSummary (SUMMARY_DESC *SummPtr) SummPtr->day_index = GLOBAL (GameClock.day_index); SummPtr->month_index = GLOBAL (GameClock.month_index); SummPtr->year_index = GLOBAL (GameClock.year_index); + SummPtr->SaveName[SAVE_NAME_SIZE-1] = 0; + strncpy (SummPtr->SaveName, name, SAVE_NAME_SIZE-1); } static void @@ -549,7 +555,7 @@ SaveProblemMessage (STAMP *MsgStamp) // TODO: This should probably just use DoPopupWindow() ppStr[0] = GAME_STRING (SAVEGAME_STRING_BASE + 2); - + SetContextFont (StarConFont); t.baseline.x = t.baseline.y = 0; @@ -579,7 +585,7 @@ SaveProblemMessage (STAMP *MsgStamp) r.extent.height += 8; *MsgStamp = SaveContextFrame (&r); - + BatchGraphics (); DrawStarConBox (&r, 2, BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19), @@ -605,7 +611,7 @@ SaveProblem (void) { STAMP s; CONTEXT OldContext; - + OldContext = SetContext (SpaceContext); SaveProblemMessage (&s); FlushGraphics (); @@ -635,7 +641,7 @@ SaveFlagshipState (void) // This function first writes to a memory file, and then writes the whole // lot to the actual save file at once. BOOLEAN -SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr) +SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name) { BOOLEAN success, made_room; void *out_fp, *h; @@ -833,7 +839,7 @@ RetrySave: flen + sizeof (*SummPtr)); if (flen && (out_fp = res_OpenResFile (saveDir, file, "wb"))) { - PrepareSummary (SummPtr); + PrepareSummary (SummPtr, name); success = SaveSummary (SummPtr, out_fp); // Then write the rest of the data. @@ -846,7 +852,7 @@ RetrySave: } else success = FALSE; - + if (!success) DeleteResFile (saveDir, file); } @@ -858,4 +864,3 @@ RetrySave: return (success); } - diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index bb7084b18..7a3c3c2ac 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -26,7 +26,7 @@ extern "C" { #endif extern void SaveProblem (void); -extern BOOLEAN SaveGame (COUNT which_game, SUMMARY_DESC *summary_desc); +extern BOOLEAN SaveGame (COUNT which_game, SUMMARY_DESC *summary_desc, const char *name); #if defined(__cplusplus) } diff --git a/sc2/src/uqm/sis.h b/sc2/src/uqm/sis.h index 06dea4265..723081887 100644 --- a/sc2/src/uqm/sis.h +++ b/sc2/src/uqm/sis.h @@ -76,7 +76,7 @@ enum CANNON_WEAPON, TRACKING_SYSTEM, ANTIMISSILE_DEFENSE, - + NUM_PURCHASE_MODULES, BOMB_MODULE_0 = NUM_PURCHASE_MODULES, @@ -160,6 +160,8 @@ typedef struct // 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 typedef struct { @@ -172,6 +174,7 @@ typedef struct BYTE NumShips, NumDevices; BYTE ShipList[MAX_BUILT_SHIPS]; BYTE DeviceList[MAX_EXCLUSIVE_DEVICES]; + UNICODE SaveName[SAVE_NAME_SIZE]; } SUMMARY_DESC; #define OVERRIDE_LANDER_FLAGS (1 << 7) From 2fc4f01ec4f1550ac974ee7472bc6cfc1d46aa29 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Wed, 25 Sep 2013 17:41:43 -0700 Subject: [PATCH 02/18] Normalize legacy/modern save display. Also clean up some detritus from the initial implementation. --- sc2/content/base/gamestrings.txt | 2 +- sc2/src/uqm/gameopt.c | 6 +----- sc2/src/uqm/save.c | 1 - 3 files changed, 2 insertions(+), 7 deletions(-) diff --git a/sc2/content/base/gamestrings.txt b/sc2/content/base/gamestrings.txt index adc10e55a..fe0a504e3 100644 --- a/sc2/content/base/gamestrings.txt +++ b/sc2/content/base/gamestrings.txt @@ -1831,7 +1831,7 @@ Empty Slot #(Saved Game - Date:) -Saved Game - Date: +Saved Game #(Sound) -- OPTION_STRING_BASE diff --git a/sc2/src/uqm/gameopt.c b/sc2/src/uqm/gameopt.c index 80070e7bf..296d9d3b9 100644 --- a/sc2/src/uqm/gameopt.c +++ b/sc2/src/uqm/gameopt.c @@ -34,7 +34,6 @@ #include "sounds.h" #include "util.h" #include "libs/graphics/gfx_common.h" -#include "libs/log.h" #include @@ -994,10 +993,7 @@ DrawGameSelection (PICK_GAME_STATE *pickState, COUNT selSlot) { DateToString (buf2, sizeof buf2, desc->month_index, desc->day_index, desc->year_index); - if (desc->SaveName[0]) - snprintf (buf, sizeof buf, "%s: %s", buf2, desc->SaveName); - else - snprintf (buf, sizeof buf, "%s", buf2); + snprintf (buf, sizeof buf, "%s: %s", buf2, desc->SaveName[0] ? desc->SaveName : GAME_STRING (SAVEGAME_STRING_BASE + 4)); } font_DrawText (&t); } diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index 95be06eda..9c3e389d8 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -483,7 +483,6 @@ SaveStarDesc (const STAR_DESC *SDPtr, DECODE_REF fh) static void PrepareSummary (SUMMARY_DESC *SummPtr, const char *name) { - int i; SummPtr->SS = GlobData.SIS_state; SummPtr->Activity = LOBYTE (GLOBAL (CurrentActivity)); From 27087d0ea83134865f09733a626973191f5c1750 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Wed, 25 Sep 2013 20:32:15 -0700 Subject: [PATCH 03/18] Refactor savegame information Coalesce it out of three header files into one (save.h). This also includes references to NextActivity, which may be the wrong place to do it. Time will tell. --- sc2/src/uqm/comm.c | 2 +- sc2/src/uqm/gameopt.c | 1 - sc2/src/uqm/load.c | 4 +--- sc2/src/uqm/load.h | 39 ---------------------------------- sc2/src/uqm/planets/devices.c | 2 +- sc2/src/uqm/planets/scan.c | 2 +- sc2/src/uqm/planets/solarsys.c | 2 +- sc2/src/uqm/restart.c | 2 +- sc2/src/uqm/save.h | 31 +++++++++++++++++++++++++-- sc2/src/uqm/sis.h | 23 -------------------- sc2/src/uqm/starbase.c | 2 +- sc2/src/uqm/starcon.c | 2 +- 12 files changed, 37 insertions(+), 75 deletions(-) delete mode 100644 sc2/src/uqm/load.h diff --git a/sc2/src/uqm/comm.c b/sc2/src/uqm/comm.c index 55c07df11..6336de8a9 100644 --- a/sc2/src/uqm/comm.c +++ b/sc2/src/uqm/comm.c @@ -31,8 +31,8 @@ #include "endian_uqm.h" #include "gamestr.h" #include "options.h" -#include "load.h" #include "oscill.h" +#include "save.h" #include "settings.h" #include "setup.h" #include "sounds.h" diff --git a/sc2/src/uqm/gameopt.c b/sc2/src/uqm/gameopt.c index 296d9d3b9..18bc0ab8b 100644 --- a/sc2/src/uqm/gameopt.c +++ b/sc2/src/uqm/gameopt.c @@ -26,7 +26,6 @@ #include "sis.h" #include "units.h" #include "gamestr.h" -#include "load.h" #include "options.h" #include "save.h" #include "settings.h" diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index c7a58c6d0..e115273b6 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -18,16 +18,14 @@ #include -#include "load.h" - #include "build.h" #include "libs/declib.h" #include "encount.h" #include "starmap.h" #include "libs/file.h" #include "globdata.h" -#include "load.h" #include "options.h" +#include "save.h" #include "setup.h" #include "state.h" #include "grpinfo.h" diff --git a/sc2/src/uqm/load.h b/sc2/src/uqm/load.h deleted file mode 100644 index 175e38c80..000000000 --- a/sc2/src/uqm/load.h +++ /dev/null @@ -1,39 +0,0 @@ -/* - * This program is free software; you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation; either version 2 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program; if not, write to the Free Software - * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA - */ - -#ifndef _LOAD_H -#define _LOAD_H - -#include "sis.h" - // for SUMMARY_DESC -#include "libs/compiler.h" -#include "globdata.h" - -#if defined(__cplusplus) -extern "C" { -#endif - -extern ACTIVITY NextActivity; - -extern BOOLEAN LoadGame (COUNT which_game, SUMMARY_DESC *summary_desc); - - -#if defined(__cplusplus) -} -#endif - -#endif /* _LOAD_H */ - diff --git a/sc2/src/uqm/planets/devices.c b/sc2/src/uqm/planets/devices.c index 99b0ac379..e781d5b1c 100644 --- a/sc2/src/uqm/planets/devices.c +++ b/sc2/src/uqm/planets/devices.c @@ -23,9 +23,9 @@ #include "../encount.h" #include "../gamestr.h" #include "../controls.h" +#include "../save.h" #include "../settings.h" #include "../shipcont.h" -#include "../load.h" #include "../setup.h" #include "../state.h" #include "../sis.h" diff --git a/sc2/src/uqm/planets/scan.c b/sc2/src/uqm/planets/scan.c index ed063337b..3d5d9fd7b 100644 --- a/sc2/src/uqm/planets/scan.c +++ b/sc2/src/uqm/planets/scan.c @@ -30,13 +30,13 @@ #include "../nameref.h" #include "../resinst.h" #include "../settings.h" -#include "../load.h" #include "../util.h" #include "../process.h" #include "../setup.h" #include "../sounds.h" #include "../state.h" #include "../sis.h" +#include "../save.h" #include "options.h" #include "libs/graphics/gfx_common.h" #include "libs/graphics/drawable.h" diff --git a/sc2/src/uqm/planets/solarsys.c b/sc2/src/uqm/planets/solarsys.c index 677b9dfdb..11bd4c0bf 100644 --- a/sc2/src/uqm/planets/solarsys.c +++ b/sc2/src/uqm/planets/solarsys.c @@ -37,11 +37,11 @@ #include "../ipdisp.h" #include "../grpinfo.h" #include "../process.h" -#include "../load.h" #include "../setup.h" #include "../sounds.h" #include "../state.h" #include "../uqmdebug.h" +#include "../save.h" #include "options.h" #include "libs/graphics/gfx_common.h" #include "libs/mathlib.h" diff --git a/sc2/src/uqm/restart.c b/sc2/src/uqm/restart.c index b877a2edb..f52e753b4 100644 --- a/sc2/src/uqm/restart.c +++ b/sc2/src/uqm/restart.c @@ -30,8 +30,8 @@ #include "supermelee/melee.h" #include "resinst.h" #include "nameref.h" +#include "save.h" #include "settings.h" -#include "load.h" #include "setup.h" #include "sounds.h" #include "setupmenu.h" diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index 7a3c3c2ac..6f37f01ff 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -17,14 +17,41 @@ #ifndef _SAVE_H #define _SAVE_H -#include "sis.h" - // for SUMMARY_DESC +#include "sis.h" // SUMMARY_DESC includes SIS_STATE in it +#include "globdata.h" #include "libs/compiler.h" #if defined(__cplusplus) extern "C" { #endif +// XXX: Theoretically, a player can have 17 devices on board without +// cheating. We only provide +// room for 16 below, which is not really a problem since this +// 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 + +typedef struct +{ + SIS_STATE SS; + BYTE Activity; + BYTE Flags; + BYTE day_index, month_index; + COUNT year_index; + BYTE MCreditLo, MCreditHi; + BYTE NumShips, NumDevices; + BYTE ShipList[MAX_BUILT_SHIPS]; + BYTE DeviceList[MAX_EXCLUSIVE_DEVICES]; + UNICODE SaveName[SAVE_NAME_SIZE]; +} SUMMARY_DESC; + +extern ACTIVITY NextActivity; + +extern BOOLEAN LoadGame (COUNT which_game, SUMMARY_DESC *summary_desc); + extern void SaveProblem (void); extern BOOLEAN SaveGame (COUNT which_game, SUMMARY_DESC *summary_desc, const char *name); diff --git a/sc2/src/uqm/sis.h b/sc2/src/uqm/sis.h index 723081887..ee07a81db 100644 --- a/sc2/src/uqm/sis.h +++ b/sc2/src/uqm/sis.h @@ -154,29 +154,6 @@ typedef struct UNICODE PlanetName[SIS_NAME_SIZE]; } SIS_STATE; -// XXX: Theoretically, a player can have 17 devices on board without -// cheating. We only provide -// room for 16 below, which is not really a problem since this -// 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 - -typedef struct -{ - SIS_STATE SS; - BYTE Activity; - BYTE Flags; - BYTE day_index, month_index; - COUNT year_index; - BYTE MCreditLo, MCreditHi; - BYTE NumShips, NumDevices; - BYTE ShipList[MAX_BUILT_SHIPS]; - BYTE DeviceList[MAX_EXCLUSIVE_DEVICES]; - UNICODE SaveName[SAVE_NAME_SIZE]; -} SUMMARY_DESC; - #define OVERRIDE_LANDER_FLAGS (1 << 7) #define AFTER_BOMB_INSTALLED (1 << 7) diff --git a/sc2/src/uqm/starbase.c b/sc2/src/uqm/starbase.c index be12f06c7..f1192085c 100644 --- a/sc2/src/uqm/starbase.c +++ b/sc2/src/uqm/starbase.c @@ -22,7 +22,7 @@ #include "starmap.h" #include "comm.h" #include "gamestr.h" -#include "load.h" +#include "save.h" #include "starbase.h" #include "sis.h" #include "resinst.h" diff --git a/sc2/src/uqm/starcon.c b/sc2/src/uqm/starcon.c index cf9d6724f..5903e6860 100644 --- a/sc2/src/uqm/starcon.c +++ b/sc2/src/uqm/starcon.c @@ -24,10 +24,10 @@ #include "gameev.h" #include "types.h" #include "globdata.h" -#include "load.h" #include "resinst.h" #include "restart.h" #include "starbase.h" +#include "save.h" #include "setup.h" #include "master.h" #include "controls.h" From e4657ea3d1b1724ab434c2ebb53fdd76494f710b Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Wed, 25 Sep 2013 22:31:42 -0700 Subject: [PATCH 04/18] 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 { From 78d6312a1e9cd91fee1aa1fb961d4c050821f6ad Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sun, 6 Oct 2013 17:24:20 -0700 Subject: [PATCH 05/18] Make the UI for named saves acceptable. --- sc2/src/uqm/gameopt.c | 34 ++++++++++++++++++++++------------ 1 file changed, 22 insertions(+), 12 deletions(-) diff --git a/sc2/src/uqm/gameopt.c b/sc2/src/uqm/gameopt.c index 18bc0ab8b..b604dd538 100644 --- a/sc2/src/uqm/gameopt.c +++ b/sc2/src/uqm/gameopt.c @@ -362,6 +362,12 @@ DrawSaveNameString (UNICODE *Str, COUNT CursorPos, COUNT state, COUNT gameIndex) TEXT lf; Color BackGround, ForeGround; FONT Font; + UNICODE fullStr[256], dateStr[80]; + + DateToString (dateStr, sizeof dateStr, GLOBAL(GameClock.month_index), + GLOBAL(GameClock.day_index), GLOBAL(GameClock.year_index)); + strncat (dateStr, ": ", sizeof(dateStr) - strlen(dateStr) -1); + snprintf (fullStr, sizeof fullStr, "%s%s", dateStr, Str); SetContextForeGroundColor (BUILD_COLOR (MAKE_RGB15 (0x1B, 0x00, 0x1B), 0x33)); r.extent.width = 15; @@ -386,12 +392,11 @@ DrawSaveNameString (UNICODE *Str, COUNT CursorPos, COUNT state, COUNT gameIndex) lf.align = ALIGN_LEFT; SetContextFont (Font); - lf.pStr = Str; + lf.pStr = fullStr; lf.CharCount = (COUNT)~0; if (!(state & DDSHS_EDIT)) { - //RECT r; TEXT t; SetContextForeGroundColor (BLACK_COLOR); @@ -403,13 +408,13 @@ DrawSaveNameString (UNICODE *Str, COUNT CursorPos, COUNT state, COUNT gameIndex) t.pStr = Str; t.CharCount = (COUNT)~0; SetContextForeGroundColor (CAPTAIN_NAME_TEXT_COLOR); - font_DrawText (&t); + font_DrawText (&lf); } else { // editing state - COUNT i; + COUNT i, FullCursorPos; RECT text_r; - BYTE char_deltas[SAVE_NAME_SIZE]; + BYTE char_deltas[256]; BYTE *pchar_deltas; TextRect (&lf, &text_r, char_deltas); @@ -427,18 +432,20 @@ DrawSaveNameString (UNICODE *Str, COUNT CursorPos, COUNT state, COUNT gameIndex) pchar_deltas = char_deltas; - for (i = CursorPos; i > 0; --i) + FullCursorPos = CursorPos + strlen(dateStr) - 1; + for (i = FullCursorPos; i > 0; --i) text_r.corner.x += *pchar_deltas++; - if (CursorPos < lf.CharCount) /* end of line */ + + if (FullCursorPos < lf.CharCount) /* end of line */ --text_r.corner.x; if (state & DDSHS_BLOCKCUR) { // Use block cursor for keyboardless systems - if (CursorPos == lf.CharCount) + if (FullCursorPos == lf.CharCount) { // cursor at end-line -- use insertion point text_r.extent.width = 1; } - else if (CursorPos + 1 == lf.CharCount) + else if (FullCursorPos + 1 == lf.CharCount) { // extra pixel for last char margin text_r.extent.width = (SIZE)*pchar_deltas + 2; } @@ -481,7 +488,7 @@ static BOOLEAN NameSaveGame (COUNT gameIndex, UNICODE *buf) { TEXTENTRY_STATE tes; - COUNT CursPos = 0; + COUNT CursPos = strlen(buf); COUNT *gIndex = HMalloc (sizeof (COUNT)); RECT r; *gIndex = gameIndex; @@ -1104,7 +1111,7 @@ static BOOLEAN SaveLoadGame (PICK_GAME_STATE *pickState, COUNT gameIndex, BOOLEAN *canceled_by_user) { SUMMARY_DESC *desc = pickState->summary + gameIndex; - UNICODE nameBuf[SAVE_NAME_SIZE]; + UNICODE nameBuf[256]; STAMP saveStamp; BOOLEAN success; @@ -1112,7 +1119,10 @@ SaveLoadGame (PICK_GAME_STATE *pickState, COUNT gameIndex, BOOLEAN *canceled_by_ if (pickState->saving) { - nameBuf[0] = 0; + // Initialize the save name with whatever name is there already + // SAVE_NAME_SIZE is less than 256, so this is safe. + strncpy(nameBuf, desc->SaveName, SAVE_NAME_SIZE); + nameBuf[SAVE_NAME_SIZE] = 0; if (NameSaveGame (gameIndex, nameBuf)) { PlayMenuSound (MENU_SOUND_SUCCESS); From 51fa5a7ae50f8b1f51cb403004f8167f0f034247 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sun, 6 Oct 2013 23:28:31 -0700 Subject: [PATCH 06/18] Save format refactoring: Uncompressed save format Legacy files are handled by load_legacy.c, but this now should be the only part of the code that still uses the custom decompression library. These save formats are very much interim and held for testing purposes! Do not use these commits for actual play, or your saves will be lost. --- sc2/src/uqm/Makeinfo | 3 +- sc2/src/uqm/load.c | 526 +++++++++++--------------- sc2/src/uqm/load_legacy.c | 761 ++++++++++++++++++++++++++++++++++++++ sc2/src/uqm/save.c | 92 +++-- sc2/src/uqm/save.h | 3 +- 5 files changed, 1034 insertions(+), 351 deletions(-) create mode 100644 sc2/src/uqm/load_legacy.c diff --git a/sc2/src/uqm/Makeinfo b/sc2/src/uqm/Makeinfo index 5f0145617..4b224fac0 100644 --- a/sc2/src/uqm/Makeinfo +++ b/sc2/src/uqm/Makeinfo @@ -4,6 +4,7 @@ uqm_CFILES="battle.c battlecontrols.c border.c build.c cleanup.c clock.c cyborg.c demo.c displist.c dummy.c encount.c flash.c fmv.c galaxy.c gameev.c gameinp.c gameopt.c gendef.c getchar.c globdata.c gravity.c cons_res.c grpinfo.c hyper.c init.c intel.c intro.c ipdisp.c load.c + load_legacy.c loadship.c master.c menu.c misc.c oscill.c outfit.c pickship.c plandata.c process.c restart.c save.c settings.c setup.c setupmenu.c ship.c shipstat.c shipyard.c sis.c sounds.c starbase.c starcon.c @@ -14,7 +15,7 @@ uqm_HFILES="battlecontrols.h battle.h build.h clock.h cnctdlg.h coderes.h corecode.h credits.h demo.h displist.h dummy.h element.h encount.h flash.h fmv.h gameev.h gameopt.h gamestr.h gendef.h globdata.h grpinfo.h hyper.h ifontres.h igfxres.h ikey_con.h imusicre.h init.h - intel.h ipdisp.h isndres.h istrtab.h load.h master.h menustat.h + intel.h ipdisp.h isndres.h istrtab.h master.h menustat.h nameref.h oscill.h pickship.h process.h races.h resinst.h respkg.h restart.h save.h settings.h setup.h setupmenu.h shipcont.h ship.h sis.h sounds.h starbase.h starcon.h state.h status.h tactrans.h diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index b4da899ec..081ce9526 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -19,7 +19,6 @@ #include #include "build.h" -#include "libs/declib.h" #include "encount.h" #include "starmap.h" #include "libs/file.h" @@ -38,74 +37,6 @@ ACTIVITY NextActivity; -// XXX: these should handle endian conversions later -static inline COUNT -cread_8 (DECODE_REF fh, BYTE *v) -{ - BYTE t; - if (!v) /* read value ignored */ - v = &t; - return cread (v, 1, 1, fh); -} - -static inline COUNT -cread_16 (DECODE_REF fh, UWORD *v) -{ - UWORD t; - if (!v) /* read value ignored */ - v = &t; - return cread (v, 2, 1, fh); -} - -static inline COUNT -cread_16s (DECODE_REF fh, SWORD *v) -{ - UWORD t; - COUNT ret; - // value was converted to unsigned when saved - ret = cread_16 (fh, &t); - // unsigned to signed conversion - if (v) - *v = t; - return ret; -} - -static inline COUNT -cread_32 (DECODE_REF fh, DWORD *v) -{ - DWORD t; - if (!v) /* read value ignored */ - v = &t; - return cread (v, 4, 1, fh); -} - -static inline COUNT -cread_32s (DECODE_REF fh, SDWORD *v) -{ - DWORD t; - COUNT ret; - // value was converted to unsigned when saved - ret = cread_32 (fh, &t); - // unsigned to signed conversion - if (v) - *v = t; - return ret; -} - -static inline COUNT -cread_ptr (DECODE_REF fh) -{ - DWORD t; - return cread_32 (fh, &t); /* ptrs are useless in saves */ -} - -static inline COUNT -cread_a8 (DECODE_REF fh, BYTE *ar, COUNT count) -{ - assert (ar != NULL); - return cread (ar, 1, count, fh) == count; -} - static inline size_t read_8 (void *fp, BYTE *v) { @@ -124,6 +55,15 @@ read_16 (void *fp, UWORD *v) return ReadResFile (v, 2, 1, fp); } +static inline size_t +read_16s (void *fp, SWORD *v) +{ + SWORD t; + if (!v) /* read value ignored */ + v = &t; + return ReadResFile (v, 2, 1, fp); +} + static inline size_t read_32 (void *fp, DWORD *v) { @@ -193,11 +133,11 @@ read_a16 (void *fp, UWORD *ar, COUNT count) } static void -LoadEmptyQueue (DECODE_REF fh) +LoadEmptyQueue (void *fh) { COUNT num_links; - cread_16 (fh, &num_links); + read_16 (fh, &num_links); if (num_links) { log_add (log_Error, "LoadEmptyQueue(): BUG: the queue is not empty!"); @@ -208,11 +148,11 @@ LoadEmptyQueue (DECODE_REF fh) } static void -LoadShipQueue (DECODE_REF fh, QUEUE *pQueue) +LoadShipQueue (void *fh, QUEUE *pQueue) { COUNT num_links; - cread_16 (fh, &num_links); + read_16 (fh, &num_links); while (num_links--) { @@ -221,38 +161,38 @@ LoadShipQueue (DECODE_REF fh, QUEUE *pQueue) COUNT Index; BYTE tmpb; - cread_16 (fh, &Index); + read_16 (fh, &Index); hStarShip = CloneShipFragment (Index, pQueue, 0); FragPtr = LockShipFrag (pQueue, hStarShip); // Read SHIP_FRAGMENT elements - cread_16 (fh, NULL); /* unused: was which_side */ - cread_8 (fh, &FragPtr->captains_name_index); - cread_8 (fh, NULL); /* padding */ - cread_16 (fh, NULL); /* unused: was ship_flags */ - cread_8 (fh, &FragPtr->race_id); - cread_8 (fh, &FragPtr->index); + read_16 (fh, NULL); /* unused: was which_side */ + read_8 (fh, &FragPtr->captains_name_index); + read_8 (fh, NULL); /* padding */ + read_16 (fh, NULL); /* unused: was ship_flags */ + read_8 (fh, &FragPtr->race_id); + read_8 (fh, &FragPtr->index); // XXX: reading crew as BYTE to maintain savegame compatibility - cread_8 (fh, &tmpb); + read_8 (fh, &tmpb); FragPtr->crew_level = tmpb; - cread_8 (fh, &tmpb); + read_8 (fh, &tmpb); FragPtr->max_crew = tmpb; - cread_8 (fh, &FragPtr->energy_level); - cread_8 (fh, &FragPtr->max_energy); - cread_16 (fh, NULL); /* unused; was loc.x */ - cread_16 (fh, NULL); /* unused; was loc.y */ + read_8 (fh, &FragPtr->energy_level); + read_8 (fh, &FragPtr->max_energy); + read_16 (fh, NULL); /* unused; was loc.x */ + read_16 (fh, NULL); /* unused; was loc.y */ UnlockShipFrag (pQueue, hStarShip); } } static void -LoadRaceQueue (DECODE_REF fh, QUEUE *pQueue) +LoadRaceQueue (void *fh, QUEUE *pQueue) { COUNT num_links; - cread_16 (fh, &num_links); + read_16 (fh, &num_links); while (num_links--) { @@ -261,44 +201,44 @@ LoadRaceQueue (DECODE_REF fh, QUEUE *pQueue) COUNT Index; BYTE tmpb; - cread_16 (fh, &Index); + read_16 (fh, &Index); hStarShip = GetStarShipFromIndex (pQueue, Index); FleetPtr = LockFleetInfo (pQueue, hStarShip); // Read FLEET_INFO elements - cread_16 (fh, &FleetPtr->allied_state); - cread_8 (fh, &FleetPtr->days_left); - cread_8 (fh, &FleetPtr->growth_fract); - cread_8 (fh, &tmpb); + read_16 (fh, &FleetPtr->allied_state); + read_8 (fh, &FleetPtr->days_left); + read_8 (fh, &FleetPtr->growth_fract); + read_8 (fh, &tmpb); FleetPtr->crew_level = tmpb; - cread_8 (fh, &tmpb); + read_8 (fh, &tmpb); FleetPtr->max_crew = tmpb; - cread_8 (fh, &FleetPtr->growth); - cread_8 (fh, &FleetPtr->max_energy); - cread_16s(fh, &FleetPtr->loc.x); - cread_16s(fh, &FleetPtr->loc.y); + read_8 (fh, &FleetPtr->growth); + read_8 (fh, &FleetPtr->max_energy); + read_16s(fh, &FleetPtr->loc.x); + read_16s(fh, &FleetPtr->loc.y); - cread_16 (fh, &FleetPtr->actual_strength); - cread_16 (fh, &FleetPtr->known_strength); - cread_16s(fh, &FleetPtr->known_loc.x); - cread_16s(fh, &FleetPtr->known_loc.y); - cread_8 (fh, &FleetPtr->growth_err_term); - cread_8 (fh, &FleetPtr->func_index); - cread_16s(fh, &FleetPtr->dest_loc.x); - cread_16s(fh, &FleetPtr->dest_loc.y); - cread_16 (fh, NULL); /* alignment padding */ + read_16 (fh, &FleetPtr->actual_strength); + read_16 (fh, &FleetPtr->known_strength); + read_16s(fh, &FleetPtr->known_loc.x); + read_16s(fh, &FleetPtr->known_loc.y); + read_8 (fh, &FleetPtr->growth_err_term); + read_8 (fh, &FleetPtr->func_index); + read_16s(fh, &FleetPtr->dest_loc.x); + read_16s(fh, &FleetPtr->dest_loc.y); + read_16 (fh, NULL); /* alignment padding */ UnlockFleetInfo (pQueue, hStarShip); } } static void -LoadGroupQueue (DECODE_REF fh, QUEUE *pQueue) +LoadGroupQueue (void *fh, QUEUE *pQueue) { COUNT num_links; - cread_16 (fh, &num_links); + read_16 (fh, &num_links); while (num_links--) { @@ -306,101 +246,101 @@ LoadGroupQueue (DECODE_REF fh, QUEUE *pQueue) IP_GROUP *GroupPtr; BYTE tmpb; - cread_16 (fh, NULL); /* unused; was race_id */ + read_16 (fh, NULL); /* unused; was race_id */ hGroup = BuildGroup (pQueue, 0); GroupPtr = LockIpGroup (pQueue, hGroup); - cread_16 (fh, NULL); /* unused; was which_side */ - cread_8 (fh, NULL); /* unused; was captains_name_index */ - cread_8 (fh, NULL); /* padding; for savegame compat */ - cread_16 (fh, &GroupPtr->group_counter); - cread_8 (fh, &GroupPtr->race_id); - cread_8 (fh, &tmpb); /* was var2 */ + read_16 (fh, NULL); /* unused; was which_side */ + read_8 (fh, NULL); /* unused; was captains_name_index */ + read_8 (fh, NULL); /* padding; for savegame compat */ + read_16 (fh, &GroupPtr->group_counter); + read_8 (fh, &GroupPtr->race_id); + read_8 (fh, &tmpb); /* was var2 */ GroupPtr->sys_loc = LONIBBLE (tmpb); GroupPtr->task = HINIBBLE (tmpb); - cread_8 (fh, &GroupPtr->in_system); /* was crew_level */ - cread_8 (fh, NULL); /* unused; was max_crew */ - cread_8 (fh, &tmpb); /* was energy_level */ + read_8 (fh, &GroupPtr->in_system); /* was crew_level */ + read_8 (fh, NULL); /* unused; was max_crew */ + read_8 (fh, &tmpb); /* was energy_level */ GroupPtr->dest_loc = LONIBBLE (tmpb); GroupPtr->orbit_pos = HINIBBLE (tmpb); - cread_8 (fh, &GroupPtr->group_id); /* was max_energy */ - cread_16s(fh, &GroupPtr->loc.x); - cread_16s(fh, &GroupPtr->loc.y); + read_8 (fh, &GroupPtr->group_id); /* was max_energy */ + read_16s(fh, &GroupPtr->loc.x); + read_16s(fh, &GroupPtr->loc.y); UnlockIpGroup (pQueue, hGroup); } } static void -LoadEncounter (ENCOUNTER *EncounterPtr, DECODE_REF fh) +LoadEncounter (ENCOUNTER *EncounterPtr, void *fh) { COUNT i; BYTE tmpb; - cread_ptr (fh); /* useless ptr; HENCOUNTER pred */ + read_ptr (fh); /* useless ptr; HENCOUNTER pred */ EncounterPtr->pred = 0; - cread_ptr (fh); /* useless ptr; HENCOUNTER succ */ + read_ptr (fh); /* useless ptr; HENCOUNTER succ */ EncounterPtr->succ = 0; - cread_ptr (fh); /* useless ptr; HELEMENT hElement */ + read_ptr (fh); /* useless ptr; HELEMENT hElement */ EncounterPtr->hElement = 0; - cread_16s (fh, &EncounterPtr->transition_state); - cread_16s (fh, &EncounterPtr->origin.x); - cread_16s (fh, &EncounterPtr->origin.y); - cread_16 (fh, &EncounterPtr->radius); + read_16s (fh, &EncounterPtr->transition_state); + read_16s (fh, &EncounterPtr->origin.x); + read_16s (fh, &EncounterPtr->origin.y); + read_16 (fh, &EncounterPtr->radius); // former STAR_DESC fields - cread_16s (fh, &EncounterPtr->loc_pt.x); - cread_16s (fh, &EncounterPtr->loc_pt.y); - cread_8 (fh, &EncounterPtr->race_id); - cread_8 (fh, &tmpb); + read_16s (fh, &EncounterPtr->loc_pt.x); + read_16s (fh, &EncounterPtr->loc_pt.y); + read_8 (fh, &EncounterPtr->race_id); + read_8 (fh, &tmpb); EncounterPtr->num_ships = tmpb & ENCOUNTER_SHIPS_MASK; EncounterPtr->flags = tmpb & ENCOUNTER_FLAGS_MASK; - cread_16 (fh, NULL); /* alignment padding */ + read_16 (fh, NULL); /* alignment padding */ // Load each entry in the BRIEF_SHIP_INFO array for (i = 0; i < MAX_HYPER_SHIPS; i++) { BRIEF_SHIP_INFO *ShipInfo = &EncounterPtr->ShipList[i]; - cread_16 (fh, NULL); /* useless; was SHIP_INFO.ship_flags */ - cread_8 (fh, &ShipInfo->race_id); - cread_8 (fh, NULL); /* useless; was SHIP_INFO.var2 */ + read_16 (fh, NULL); /* useless; was SHIP_INFO.ship_flags */ + read_8 (fh, &ShipInfo->race_id); + read_8 (fh, NULL); /* useless; was SHIP_INFO.var2 */ // XXX: reading crew as BYTE to maintain savegame compatibility - cread_8 (fh, &tmpb); + read_8 (fh, &tmpb); ShipInfo->crew_level = tmpb; - cread_8 (fh, &tmpb); + read_8 (fh, &tmpb); ShipInfo->max_crew = tmpb; - cread_8 (fh, NULL); /* useless; was SHIP_INFO.energy_level */ - cread_8 (fh, &ShipInfo->max_energy); - cread_16 (fh, NULL); /* useless; was SHIP_INFO.loc.x */ - cread_16 (fh, NULL); /* useless; was SHIP_INFO.loc.y */ - cread_32 (fh, NULL); /* useless val; STRING race_strings */ - cread_ptr (fh); /* useless ptr; FRAME icons */ - cread_ptr (fh); /* useless ptr; FRAME melee_icon */ + read_8 (fh, NULL); /* useless; was SHIP_INFO.energy_level */ + read_8 (fh, &ShipInfo->max_energy); + read_16 (fh, NULL); /* useless; was SHIP_INFO.loc.x */ + read_16 (fh, NULL); /* useless; was SHIP_INFO.loc.y */ + read_32 (fh, NULL); /* useless val; STRING race_strings */ + read_ptr (fh); /* useless ptr; FRAME icons */ + read_ptr (fh); /* useless ptr; FRAME melee_icon */ } // Load the stuff after the BRIEF_SHIP_INFO array - cread_32s (fh, &EncounterPtr->log_x); - cread_32s (fh, &EncounterPtr->log_y); + read_32s (fh, &EncounterPtr->log_x); + read_32s (fh, &EncounterPtr->log_y); } static void -LoadEvent (EVENT *EventPtr, DECODE_REF fh) +LoadEvent (EVENT *EventPtr, void *fh) { - cread_ptr (fh); /* useless ptr; HEVENT pred */ + read_ptr (fh); /* useless ptr; HEVENT pred */ EventPtr->pred = 0; - cread_ptr (fh); /* useless ptr; HEVENT succ */ + read_ptr (fh); /* useless ptr; HEVENT succ */ EventPtr->succ = 0; - cread_8 (fh, &EventPtr->day_index); - cread_8 (fh, &EventPtr->month_index); - cread_16 (fh, &EventPtr->year_index); - cread_8 (fh, &EventPtr->func_index); - cread_8 (fh, NULL); /* padding */ - cread_16 (fh, NULL); /* padding */ + read_8 (fh, &EventPtr->day_index); + read_8 (fh, &EventPtr->month_index); + read_16 (fh, &EventPtr->year_index); + read_8 (fh, &EventPtr->func_index); + read_8 (fh, NULL); /* padding */ + read_16 (fh, NULL); /* padding */ } static void -DummyLoadQueue (QUEUE *QueuePtr, DECODE_REF fh) +DummyLoadQueue (QUEUE *QueuePtr, void *fh) { /* QUEUE should never actually be loaded since it contains * purely internal representation and the lists @@ -408,74 +348,74 @@ DummyLoadQueue (QUEUE *QueuePtr, DECODE_REF fh) (void)QueuePtr; /* silence compiler */ /* QUEUE format with QUEUE_TABLE defined -- UQM default */ - cread_ptr (fh); /* HLINK head */ - cread_ptr (fh); /* HLINK tail */ - cread_ptr (fh); /* BYTE* pq_tab */ - cread_ptr (fh); /* HLINK free_list */ - cread_16 (fh, NULL); /* MEM_HANDLE hq_tab */ - cread_16 (fh, NULL); /* COUNT object_size */ - cread_8 (fh, NULL); /* BYTE num_objects */ + read_ptr (fh); /* HLINK head */ + read_ptr (fh); /* HLINK tail */ + read_ptr (fh); /* BYTE* pq_tab */ + read_ptr (fh); /* HLINK free_list */ + read_16 (fh, NULL); /* MEM_HANDLE hq_tab */ + read_16 (fh, NULL); /* COUNT object_size */ + read_8 (fh, NULL); /* BYTE num_objects */ - cread_8 (fh, NULL); /* padding */ - cread_16 (fh, NULL); /* padding */ + read_8 (fh, NULL); /* padding */ + read_16 (fh, NULL); /* padding */ } static void -LoadClockState (CLOCK_STATE *ClockPtr, DECODE_REF fh) +LoadClockState (CLOCK_STATE *ClockPtr, void *fh) { - cread_8 (fh, &ClockPtr->day_index); - cread_8 (fh, &ClockPtr->month_index); - cread_16 (fh, &ClockPtr->year_index); - cread_16s (fh, &ClockPtr->tick_count); - cread_16s (fh, &ClockPtr->day_in_ticks); - cread_ptr (fh); /* not loading ptr; Semaphore clock_sem */ - cread_ptr (fh); /* not loading ptr; Task clock_task */ - cread_32 (fh, NULL); /* not loading; DWORD TimeCounter */ + read_8 (fh, &ClockPtr->day_index); + read_8 (fh, &ClockPtr->month_index); + read_16 (fh, &ClockPtr->year_index); + read_16s (fh, &ClockPtr->tick_count); + read_16s (fh, &ClockPtr->day_in_ticks); + read_ptr (fh); /* not loading ptr; Semaphore clock_sem */ + read_ptr (fh); /* not loading ptr; Task clock_task */ + read_32 (fh, NULL); /* not loading; DWORD TimeCounter */ DummyLoadQueue (&ClockPtr->event_q, fh); } static void -LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) +LoadGameState (GAME_STATE *GSPtr, void *fh) { BYTE dummy8; - cread_8 (fh, &dummy8); /* obsolete */ - cread_8 (fh, &GSPtr->glob_flags); - cread_8 (fh, &GSPtr->CrewCost); - cread_8 (fh, &GSPtr->FuelCost); - cread_a8 (fh, GSPtr->ModuleCost, NUM_MODULES); - cread_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); - cread_ptr (fh); /* not loading ptr; PRIMITIVE *DisplayArray */ - cread_16 (fh, &GSPtr->CurrentActivity); + read_8 (fh, &dummy8); /* obsolete */ + read_8 (fh, &GSPtr->glob_flags); + read_8 (fh, &GSPtr->CrewCost); + read_8 (fh, &GSPtr->FuelCost); + read_a8 (fh, GSPtr->ModuleCost, NUM_MODULES); + read_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); + read_ptr (fh); /* not loading ptr; PRIMITIVE *DisplayArray */ + read_16 (fh, &GSPtr->CurrentActivity); - cread_16 (fh, NULL); /* CLOCK_STATE alignment padding */ + read_16 (fh, NULL); /* CLOCK_STATE alignment padding */ LoadClockState (&GSPtr->GameClock, fh); - cread_16s (fh, &GSPtr->autopilot.x); - cread_16s (fh, &GSPtr->autopilot.y); - cread_16s (fh, &GSPtr->ip_location.x); - cread_16s (fh, &GSPtr->ip_location.y); + read_16s (fh, &GSPtr->autopilot.x); + read_16s (fh, &GSPtr->autopilot.y); + read_16s (fh, &GSPtr->ip_location.x); + read_16s (fh, &GSPtr->ip_location.y); /* STAMP ShipStamp */ - cread_16s (fh, &GSPtr->ShipStamp.origin.x); - cread_16s (fh, &GSPtr->ShipStamp.origin.y); - cread_16 (fh, &GSPtr->ShipFacing); - cread_8 (fh, &GSPtr->ip_planet); - cread_8 (fh, &GSPtr->in_orbit); + read_16s (fh, &GSPtr->ShipStamp.origin.x); + read_16s (fh, &GSPtr->ShipStamp.origin.y); + read_16 (fh, &GSPtr->ShipFacing); + read_8 (fh, &GSPtr->ip_planet); + read_8 (fh, &GSPtr->in_orbit); /* VELOCITY_DESC velocity */ - cread_16 (fh, &GSPtr->velocity.TravelAngle); - cread_16s (fh, &GSPtr->velocity.vector.width); - cread_16s (fh, &GSPtr->velocity.vector.height); - cread_16s (fh, &GSPtr->velocity.fract.width); - cread_16s (fh, &GSPtr->velocity.fract.height); - cread_16s (fh, &GSPtr->velocity.error.width); - cread_16s (fh, &GSPtr->velocity.error.height); - cread_16s (fh, &GSPtr->velocity.incr.width); - cread_16s (fh, &GSPtr->velocity.incr.height); - cread_16 (fh, NULL); /* VELOCITY_DESC padding */ + read_16 (fh, &GSPtr->velocity.TravelAngle); + read_16s (fh, &GSPtr->velocity.vector.width); + read_16s (fh, &GSPtr->velocity.vector.height); + read_16s (fh, &GSPtr->velocity.fract.width); + read_16s (fh, &GSPtr->velocity.fract.height); + read_16s (fh, &GSPtr->velocity.error.width); + read_16s (fh, &GSPtr->velocity.error.height); + read_16s (fh, &GSPtr->velocity.incr.width); + read_16s (fh, &GSPtr->velocity.incr.height); + read_16 (fh, NULL); /* VELOCITY_DESC padding */ - cread_32 (fh, &GSPtr->BattleGroupRef); + read_32 (fh, &GSPtr->BattleGroupRef); DummyLoadQueue (&GSPtr->avail_race_q, fh); DummyLoadQueue (&GSPtr->npc_built_ship_q, fh); @@ -483,17 +423,17 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) DummyLoadQueue (&GSPtr->encounter_q, fh); DummyLoadQueue (&GSPtr->built_ship_q, fh); - cread_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); + read_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); assert (sizeof (GSPtr->GameState) % 4 == 3); - cread_8 (fh, NULL); /* GAME_STATE alignment padding */ + read_8 (fh, NULL); /* GAME_STATE alignment padding */ } static BOOLEAN -LoadSisState (SIS_STATE *SSPtr, void *fp, SDWORD first, BOOLEAN legacy) +LoadSisState (SIS_STATE *SSPtr, void *fp) { - SSPtr->log_x = first; if ( + read_32s (fp, &SSPtr->log_x) != 1 || read_32s (fp, &SSPtr->log_y) != 1 || read_32 (fp, &SSPtr->ResUnits) != 1 || read_32 (fp, &SSPtr->FuelOnBoard) != 1 || @@ -511,45 +451,33 @@ LoadSisState (SIS_STATE *SSPtr, void *fp, SDWORD first, BOOLEAN legacy) read_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE) != 1 ) return FALSE; - if (legacy && (read_16 (fp, NULL) != 1)) - return FALSE; return TRUE; } static BOOLEAN -LoadSummary (SUMMARY_DESC *SummPtr, void *fp, BOOLEAN *legacy_ptr) +LoadSummary (SUMMARY_DESC *SummPtr, void *fp) { SDWORD magic; DWORD nameSize = 0; - BOOLEAN legacy; 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; } else { - // Otherwise, we're legacy and the "magic" number was - // really LoadSisState's first value - legacy = TRUE; - *legacy_ptr = TRUE; + return FALSE; } - if (!LoadSisState (&SummPtr->SS, fp, magic, legacy)) + if (!LoadSisState (&SummPtr->SS, fp)) return FALSE; - if ( - read_8 (fp, &SummPtr->Activity) != 1 || + if ( read_8 (fp, &SummPtr->Activity) != 1 || read_8 (fp, &SummPtr->Flags) != 1 || read_8 (fp, &SummPtr->day_index) != 1 || read_8 (fp, &SummPtr->month_index) != 1 || @@ -563,42 +491,33 @@ LoadSummary (SUMMARY_DESC *SummPtr, void *fp, BOOLEAN *legacy_ptr) ) return FALSE; - if (!legacy) + if (nameSize < SAVE_NAME_SIZE) { - 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; - } + if (read_a8 (fp, SummPtr->SaveName, nameSize) != 1) + return FALSE; + SummPtr->SaveName[nameSize] = 0; } else { - SummPtr->SaveName[0] = 0; - if (read_16 (fp, NULL) != 1) /* padding */ + 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; } return TRUE; } static void -LoadStarDesc (STAR_DESC *SDPtr, DECODE_REF fh) +LoadStarDesc (STAR_DESC *SDPtr, void *fh) { - cread_16s(fh, &SDPtr->star_pt.x); - cread_16s(fh, &SDPtr->star_pt.y); - cread_8 (fh, &SDPtr->Type); - cread_8 (fh, &SDPtr->Index); - cread_8 (fh, &SDPtr->Prefix); - cread_8 (fh, &SDPtr->Postfix); + read_16s(fh, &SDPtr->star_pt.x); + read_16s(fh, &SDPtr->star_pt.y); + read_8 (fh, &SDPtr->Type); + read_8 (fh, &SDPtr->Index); + read_8 (fh, &SDPtr->Prefix); + read_8 (fh, &SDPtr->Postfix); } BOOLEAN @@ -609,23 +528,20 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) char buf[256]; SUMMARY_DESC loc_sd; GAME_STATE_FILE *fp; - DECODE_REF fh; COUNT num_links; STAR_DESC SD; ACTIVITY Activity; - BOOLEAN legacy; DWORD chunk, chunkSize; - sprintf (file, "starcon2.%02u", which_game); + sprintf (file, "uqmsave.%02u", which_game); in_fp = res_OpenResFile (saveDir, file, "rb"); if (!in_fp) - return FALSE; + return LoadLegacyGame (which_game, SummPtr); - if (!LoadSummary (&loc_sd, in_fp, &legacy)) + if (!LoadSummary (&loc_sd, in_fp)) { - log_add (log_Error, "Warning: Savegame is corrupt"); res_CloseResFile (in_fp); - return FALSE; + return LoadLegacyGame (which_game, SummPtr); } if (!SummPtr) @@ -639,54 +555,27 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) return TRUE; } - // Crude check for big-endian/little-endian incompatibilities. - // year_index is suitable as it's a multi-byte value within - // a specific recognisable range. - if (SummPtr->year_index < START_YEAR || - SummPtr->year_index >= START_YEAR + - YEARS_TO_KOHRAH_VICTORY + 1 /* Utwig intervention */ + - 1 /* time to destroy all races, plenty */ + - 25 /* for cheaters */) - { - log_add (log_Error, "Warning: Savegame corrupt or from " - "an incompatible platform."); - res_CloseResFile (in_fp); - return FALSE; - } - GlobData.SIS_state = SummPtr->SS; - if (!legacy) + chunk = 0; + while (chunk != OMNIBUS_MAGIC) { - chunk = 0; - while (chunk != OMNIZIP_MAGIC) + if (read_32(in_fp, &chunk) != 1) { - 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; + res_CloseResFile (in_fp); + 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 (read_32(in_fp, &chunkSize) != 1) + { + res_CloseResFile (in_fp); + return FALSE; + } + if (chunk == OMNIBUS_MAGIC) + break; - if ((fh = copen (in_fp, FILE_STREAM, STREAM_READ)) == 0) - { - res_CloseResFile (in_fp); - return FALSE; + log_add (log_Debug, "Skipping chunk of tag %08X (size %u)", chunk, chunkSize); + if (skip_8(in_fp, chunkSize) != 1) + return FALSE; } ReinitQueue (&GLOBAL (GameClock.event_q)); @@ -697,11 +586,11 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) memset (&GLOBAL (GameState[0]), 0, sizeof (GLOBAL (GameState))); Activity = GLOBAL (CurrentActivity); - LoadGameState (&GlobData.Game_state, fh); + LoadGameState (&GlobData.Game_state, in_fp); NextActivity = GLOBAL (CurrentActivity); GLOBAL (CurrentActivity) = Activity; - LoadRaceQueue (fh, &GLOBAL (avail_race_q)); + LoadRaceQueue (in_fp, &GLOBAL (avail_race_q)); // START_INTERPLANETARY is only set when saving from Homeworld // encounter screen. When the game is loaded, the // GenerateOrbitalFunction for the current star system will @@ -709,22 +598,22 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) if (!(NextActivity & START_INTERPLANETARY)) { if (NextActivity & START_ENCOUNTER) - LoadShipQueue (fh, &GLOBAL (npc_built_ship_q)); + LoadShipQueue (in_fp, &GLOBAL (npc_built_ship_q)); else if (LOBYTE (NextActivity) == IN_INTERPLANETARY) // XXX: Technically, this queue does not need to be // saved/loaded at all. IP groups will be reloaded // from group state files. But the original code did, // and so will we until we can prove we do not need to. - LoadGroupQueue (fh, &GLOBAL (ip_group_q)); + LoadGroupQueue (in_fp, &GLOBAL (ip_group_q)); else // XXX: The empty queue read is only needed to maintain // the savegame compatibility - LoadEmptyQueue (fh); + LoadEmptyQueue (in_fp); } - LoadShipQueue (fh, &GLOBAL (built_ship_q)); + LoadShipQueue (in_fp, &GLOBAL (built_ship_q)); // Load the game events (compressed) - cread_16 (fh, &num_links); + read_16 (in_fp, &num_links); { #ifdef DEBUG_LOAD log_add (log_Debug, "EVENTS:"); @@ -737,7 +626,7 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) hEvent = AllocEvent (); LockEvent (hEvent, &EventPtr); - LoadEvent (EventPtr, fh); + LoadEvent (EventPtr, in_fp); #ifdef DEBUG_LOAD log_add (log_Debug, "\t%u/%u/%u -- %u", @@ -752,7 +641,7 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) } // Load the encounters (black globes in HS/QS (compressed)) - cread_16 (fh, &num_links); + read_16 (in_fp, &num_links); { while (num_links--) { @@ -762,7 +651,7 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) hEncounter = AllocEncounter (); LockEncounter (hEncounter, &EncounterPtr); - LoadEncounter (EncounterPtr, fh); + LoadEncounter (EncounterPtr, in_fp); UnlockEncounter (hEncounter); PutEncounter (hEncounter); @@ -775,13 +664,13 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) { DWORD flen; - cread_32 (fh, &flen); + read_32 (in_fp, &flen); while (flen) { COUNT num_bytes; num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - cread (buf, num_bytes, 1, fh); + read_a8 (in_fp, buf, num_bytes); WriteStateFile (buf, num_bytes, 1, fp); flen -= num_bytes; @@ -795,13 +684,13 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) { DWORD flen; - cread_32 (fh, &flen); + read_32 (in_fp, &flen); while (flen) { COUNT num_bytes; num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - cread (buf, num_bytes, 1, fh); + read_a8 (in_fp, buf, num_bytes); WriteStateFile (buf, num_bytes, 1, fp); flen -= num_bytes; @@ -815,13 +704,13 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) { DWORD flen; - cread_32 (fh, &flen); + read_32 (in_fp, &flen); while (flen) { COUNT num_bytes; num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - cread (buf, num_bytes, 1, fh); + read_a8 (in_fp, buf, num_bytes); WriteStateFile (buf, num_bytes, 1, fp); flen -= num_bytes; @@ -829,9 +718,8 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) CloseStateFile (fp); } - LoadStarDesc (&SD, fh); + LoadStarDesc (&SD, in_fp); - cclose (fh); res_CloseResFile (in_fp); EncounterGroup = 0; diff --git a/sc2/src/uqm/load_legacy.c b/sc2/src/uqm/load_legacy.c new file mode 100644 index 000000000..3c7a7e702 --- /dev/null +++ b/sc2/src/uqm/load_legacy.c @@ -0,0 +1,761 @@ +//Copyright Paul Reiche, Fred Ford. 1992-2002 + +/* + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + */ + +#include + +#include "build.h" +#include "libs/declib.h" +#include "encount.h" +#include "starmap.h" +#include "libs/file.h" +#include "globdata.h" +#include "options.h" +#include "save.h" +#include "setup.h" +#include "state.h" +#include "grpinfo.h" + +#include "libs/tasklib.h" +#include "libs/log.h" +#include "libs/misc.h" + +//#define DEBUG_LOAD + +// XXX: these should handle endian conversions later +static inline COUNT +cread_8 (DECODE_REF fh, BYTE *v) +{ + BYTE t; + if (!v) /* read value ignored */ + v = &t; + return cread (v, 1, 1, fh); +} + +static inline COUNT +cread_16 (DECODE_REF fh, UWORD *v) +{ + UWORD t; + if (!v) /* read value ignored */ + v = &t; + return cread (v, 2, 1, fh); +} + +static inline COUNT +cread_16s (DECODE_REF fh, SWORD *v) +{ + UWORD t; + COUNT ret; + // value was converted to unsigned when saved + ret = cread_16 (fh, &t); + // unsigned to signed conversion + if (v) + *v = t; + return ret; +} + +static inline COUNT +cread_32 (DECODE_REF fh, DWORD *v) +{ + DWORD t; + if (!v) /* read value ignored */ + v = &t; + return cread (v, 4, 1, fh); +} + +static inline COUNT +cread_32s (DECODE_REF fh, SDWORD *v) +{ + DWORD t; + COUNT ret; + // value was converted to unsigned when saved + ret = cread_32 (fh, &t); + // unsigned to signed conversion + if (v) + *v = t; + return ret; +} + +static inline COUNT +cread_ptr (DECODE_REF fh) +{ + DWORD t; + return cread_32 (fh, &t); /* ptrs are useless in saves */ +} + +static inline COUNT +cread_a8 (DECODE_REF fh, BYTE *ar, COUNT count) +{ + assert (ar != NULL); + return cread (ar, 1, count, fh) == count; +} + +static inline size_t +read_8 (void *fp, BYTE *v) +{ + BYTE t; + if (!v) /* read value ignored */ + v = &t; + return ReadResFile (v, 1, 1, fp); +} + +static inline size_t +read_16 (void *fp, UWORD *v) +{ + UWORD t; + if (!v) /* read value ignored */ + v = &t; + return ReadResFile (v, 2, 1, fp); +} + +static inline size_t +read_32 (void *fp, DWORD *v) +{ + DWORD t; + if (!v) /* read value ignored */ + v = &t; + return ReadResFile (v, 4, 1, fp); +} + +static inline size_t +read_32s (void *fp, SDWORD *v) +{ + DWORD t; + COUNT ret; + // value was converted to unsigned when saved + ret = read_32 (fp, &t); + // unsigned to signed conversion + if (v) + *v = t; + return ret; +} + +static inline size_t +read_ptr (void *fp) +{ + DWORD t; + return read_32 (fp, &t); /* ptrs are useless in saves */ +} + +static inline size_t +read_a8 (void *fp, BYTE *ar, COUNT count) +{ + assert (ar != NULL); + return ReadResFile (ar, 1, count, fp) == count; +} + +static inline size_t +read_str (void *fp, char *str, COUNT count) +{ + // no type conversion needed for strings + return read_a8 (fp, (BYTE *)str, count); +} + +static inline size_t +read_a16 (void *fp, UWORD *ar, COUNT count) +{ + assert (ar != NULL); + + for ( ; count > 0; --count, ++ar) + { + if (read_16 (fp, ar) != 1) + return 0; + } + return 1; +} + +static void +LoadEmptyQueue (DECODE_REF fh) +{ + COUNT num_links; + + cread_16 (fh, &num_links); + if (num_links) + { + log_add (log_Error, "LoadEmptyQueue(): BUG: the queue is not empty!"); +#ifdef DEBUG + explode (); +#endif + } +} + +static void +LoadShipQueue (DECODE_REF fh, QUEUE *pQueue) +{ + COUNT num_links; + + cread_16 (fh, &num_links); + + while (num_links--) + { + HSHIPFRAG hStarShip; + SHIP_FRAGMENT *FragPtr; + COUNT Index; + BYTE tmpb; + + cread_16 (fh, &Index); + + hStarShip = CloneShipFragment (Index, pQueue, 0); + FragPtr = LockShipFrag (pQueue, hStarShip); + + // Read SHIP_FRAGMENT elements + cread_16 (fh, NULL); /* unused: was which_side */ + cread_8 (fh, &FragPtr->captains_name_index); + cread_8 (fh, NULL); /* padding */ + cread_16 (fh, NULL); /* unused: was ship_flags */ + cread_8 (fh, &FragPtr->race_id); + cread_8 (fh, &FragPtr->index); + // XXX: reading crew as BYTE to maintain savegame compatibility + cread_8 (fh, &tmpb); + FragPtr->crew_level = tmpb; + cread_8 (fh, &tmpb); + FragPtr->max_crew = tmpb; + cread_8 (fh, &FragPtr->energy_level); + cread_8 (fh, &FragPtr->max_energy); + cread_16 (fh, NULL); /* unused; was loc.x */ + cread_16 (fh, NULL); /* unused; was loc.y */ + + UnlockShipFrag (pQueue, hStarShip); + } +} + +static void +LoadRaceQueue (DECODE_REF fh, QUEUE *pQueue) +{ + COUNT num_links; + + cread_16 (fh, &num_links); + + while (num_links--) + { + HFLEETINFO hStarShip; + FLEET_INFO *FleetPtr; + COUNT Index; + BYTE tmpb; + + cread_16 (fh, &Index); + + hStarShip = GetStarShipFromIndex (pQueue, Index); + FleetPtr = LockFleetInfo (pQueue, hStarShip); + + // Read FLEET_INFO elements + cread_16 (fh, &FleetPtr->allied_state); + cread_8 (fh, &FleetPtr->days_left); + cread_8 (fh, &FleetPtr->growth_fract); + cread_8 (fh, &tmpb); + FleetPtr->crew_level = tmpb; + cread_8 (fh, &tmpb); + FleetPtr->max_crew = tmpb; + cread_8 (fh, &FleetPtr->growth); + cread_8 (fh, &FleetPtr->max_energy); + cread_16s(fh, &FleetPtr->loc.x); + cread_16s(fh, &FleetPtr->loc.y); + + cread_16 (fh, &FleetPtr->actual_strength); + cread_16 (fh, &FleetPtr->known_strength); + cread_16s(fh, &FleetPtr->known_loc.x); + cread_16s(fh, &FleetPtr->known_loc.y); + cread_8 (fh, &FleetPtr->growth_err_term); + cread_8 (fh, &FleetPtr->func_index); + cread_16s(fh, &FleetPtr->dest_loc.x); + cread_16s(fh, &FleetPtr->dest_loc.y); + cread_16 (fh, NULL); /* alignment padding */ + + UnlockFleetInfo (pQueue, hStarShip); + } +} + +static void +LoadGroupQueue (DECODE_REF fh, QUEUE *pQueue) +{ + COUNT num_links; + + cread_16 (fh, &num_links); + + while (num_links--) + { + HIPGROUP hGroup; + IP_GROUP *GroupPtr; + BYTE tmpb; + + cread_16 (fh, NULL); /* unused; was race_id */ + + hGroup = BuildGroup (pQueue, 0); + GroupPtr = LockIpGroup (pQueue, hGroup); + + cread_16 (fh, NULL); /* unused; was which_side */ + cread_8 (fh, NULL); /* unused; was captains_name_index */ + cread_8 (fh, NULL); /* padding; for savegame compat */ + cread_16 (fh, &GroupPtr->group_counter); + cread_8 (fh, &GroupPtr->race_id); + cread_8 (fh, &tmpb); /* was var2 */ + GroupPtr->sys_loc = LONIBBLE (tmpb); + GroupPtr->task = HINIBBLE (tmpb); + cread_8 (fh, &GroupPtr->in_system); /* was crew_level */ + cread_8 (fh, NULL); /* unused; was max_crew */ + cread_8 (fh, &tmpb); /* was energy_level */ + GroupPtr->dest_loc = LONIBBLE (tmpb); + GroupPtr->orbit_pos = HINIBBLE (tmpb); + cread_8 (fh, &GroupPtr->group_id); /* was max_energy */ + cread_16s(fh, &GroupPtr->loc.x); + cread_16s(fh, &GroupPtr->loc.y); + + UnlockIpGroup (pQueue, hGroup); + } +} + +static void +LoadEncounter (ENCOUNTER *EncounterPtr, DECODE_REF fh) +{ + COUNT i; + BYTE tmpb; + + cread_ptr (fh); /* useless ptr; HENCOUNTER pred */ + EncounterPtr->pred = 0; + cread_ptr (fh); /* useless ptr; HENCOUNTER succ */ + EncounterPtr->succ = 0; + cread_ptr (fh); /* useless ptr; HELEMENT hElement */ + EncounterPtr->hElement = 0; + cread_16s (fh, &EncounterPtr->transition_state); + cread_16s (fh, &EncounterPtr->origin.x); + cread_16s (fh, &EncounterPtr->origin.y); + cread_16 (fh, &EncounterPtr->radius); + // former STAR_DESC fields + cread_16s (fh, &EncounterPtr->loc_pt.x); + cread_16s (fh, &EncounterPtr->loc_pt.y); + cread_8 (fh, &EncounterPtr->race_id); + cread_8 (fh, &tmpb); + EncounterPtr->num_ships = tmpb & ENCOUNTER_SHIPS_MASK; + EncounterPtr->flags = tmpb & ENCOUNTER_FLAGS_MASK; + cread_16 (fh, NULL); /* alignment padding */ + + // Load each entry in the BRIEF_SHIP_INFO array + for (i = 0; i < MAX_HYPER_SHIPS; i++) + { + BRIEF_SHIP_INFO *ShipInfo = &EncounterPtr->ShipList[i]; + + cread_16 (fh, NULL); /* useless; was SHIP_INFO.ship_flags */ + cread_8 (fh, &ShipInfo->race_id); + cread_8 (fh, NULL); /* useless; was SHIP_INFO.var2 */ + // XXX: reading crew as BYTE to maintain savegame compatibility + cread_8 (fh, &tmpb); + ShipInfo->crew_level = tmpb; + cread_8 (fh, &tmpb); + ShipInfo->max_crew = tmpb; + cread_8 (fh, NULL); /* useless; was SHIP_INFO.energy_level */ + cread_8 (fh, &ShipInfo->max_energy); + cread_16 (fh, NULL); /* useless; was SHIP_INFO.loc.x */ + cread_16 (fh, NULL); /* useless; was SHIP_INFO.loc.y */ + cread_32 (fh, NULL); /* useless val; STRING race_strings */ + cread_ptr (fh); /* useless ptr; FRAME icons */ + cread_ptr (fh); /* useless ptr; FRAME melee_icon */ + } + + // Load the stuff after the BRIEF_SHIP_INFO array + cread_32s (fh, &EncounterPtr->log_x); + cread_32s (fh, &EncounterPtr->log_y); +} + +static void +LoadEvent (EVENT *EventPtr, DECODE_REF fh) +{ + cread_ptr (fh); /* useless ptr; HEVENT pred */ + EventPtr->pred = 0; + cread_ptr (fh); /* useless ptr; HEVENT succ */ + EventPtr->succ = 0; + cread_8 (fh, &EventPtr->day_index); + cread_8 (fh, &EventPtr->month_index); + cread_16 (fh, &EventPtr->year_index); + cread_8 (fh, &EventPtr->func_index); + cread_8 (fh, NULL); /* padding */ + cread_16 (fh, NULL); /* padding */ +} + +static void +DummyLoadQueue (QUEUE *QueuePtr, DECODE_REF fh) +{ + /* QUEUE should never actually be loaded since it contains + * purely internal representation and the lists + * involved are actually loaded separately */ + (void)QueuePtr; /* silence compiler */ + + /* QUEUE format with QUEUE_TABLE defined -- UQM default */ + cread_ptr (fh); /* HLINK head */ + cread_ptr (fh); /* HLINK tail */ + cread_ptr (fh); /* BYTE* pq_tab */ + cread_ptr (fh); /* HLINK free_list */ + cread_16 (fh, NULL); /* MEM_HANDLE hq_tab */ + cread_16 (fh, NULL); /* COUNT object_size */ + cread_8 (fh, NULL); /* BYTE num_objects */ + + cread_8 (fh, NULL); /* padding */ + cread_16 (fh, NULL); /* padding */ +} + +static void +LoadClockState (CLOCK_STATE *ClockPtr, DECODE_REF fh) +{ + cread_8 (fh, &ClockPtr->day_index); + cread_8 (fh, &ClockPtr->month_index); + cread_16 (fh, &ClockPtr->year_index); + cread_16s (fh, &ClockPtr->tick_count); + cread_16s (fh, &ClockPtr->day_in_ticks); + cread_ptr (fh); /* not loading ptr; Semaphore clock_sem */ + cread_ptr (fh); /* not loading ptr; Task clock_task */ + cread_32 (fh, NULL); /* not loading; DWORD TimeCounter */ + + DummyLoadQueue (&ClockPtr->event_q, fh); +} + +static void +LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) +{ + BYTE dummy8; + + cread_8 (fh, &dummy8); /* obsolete */ + cread_8 (fh, &GSPtr->glob_flags); + cread_8 (fh, &GSPtr->CrewCost); + cread_8 (fh, &GSPtr->FuelCost); + cread_a8 (fh, GSPtr->ModuleCost, NUM_MODULES); + cread_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); + cread_ptr (fh); /* not loading ptr; PRIMITIVE *DisplayArray */ + cread_16 (fh, &GSPtr->CurrentActivity); + + cread_16 (fh, NULL); /* CLOCK_STATE alignment padding */ + LoadClockState (&GSPtr->GameClock, fh); + + cread_16s (fh, &GSPtr->autopilot.x); + cread_16s (fh, &GSPtr->autopilot.y); + cread_16s (fh, &GSPtr->ip_location.x); + cread_16s (fh, &GSPtr->ip_location.y); + /* STAMP ShipStamp */ + cread_16s (fh, &GSPtr->ShipStamp.origin.x); + cread_16s (fh, &GSPtr->ShipStamp.origin.y); + cread_16 (fh, &GSPtr->ShipFacing); + cread_8 (fh, &GSPtr->ip_planet); + cread_8 (fh, &GSPtr->in_orbit); + + /* VELOCITY_DESC velocity */ + cread_16 (fh, &GSPtr->velocity.TravelAngle); + cread_16s (fh, &GSPtr->velocity.vector.width); + cread_16s (fh, &GSPtr->velocity.vector.height); + cread_16s (fh, &GSPtr->velocity.fract.width); + cread_16s (fh, &GSPtr->velocity.fract.height); + cread_16s (fh, &GSPtr->velocity.error.width); + cread_16s (fh, &GSPtr->velocity.error.height); + cread_16s (fh, &GSPtr->velocity.incr.width); + cread_16s (fh, &GSPtr->velocity.incr.height); + cread_16 (fh, NULL); /* VELOCITY_DESC padding */ + + cread_32 (fh, &GSPtr->BattleGroupRef); + + DummyLoadQueue (&GSPtr->avail_race_q, fh); + DummyLoadQueue (&GSPtr->npc_built_ship_q, fh); + // Not loading ip_group_q, was not there originally + DummyLoadQueue (&GSPtr->encounter_q, fh); + DummyLoadQueue (&GSPtr->built_ship_q, fh); + + cread_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); + + assert (sizeof (GSPtr->GameState) % 4 == 3); + cread_8 (fh, NULL); /* GAME_STATE alignment padding */ +} + +static BOOLEAN +LoadSisState (SIS_STATE *SSPtr, void *fp) +{ + if ( + read_32s (fp, &SSPtr->log_x) != 1 || + read_32s (fp, &SSPtr->log_y) != 1 || + read_32 (fp, &SSPtr->ResUnits) != 1 || + read_32 (fp, &SSPtr->FuelOnBoard) != 1 || + read_16 (fp, &SSPtr->CrewEnlisted) != 1 || + read_16 (fp, &SSPtr->TotalElementMass) != 1 || + read_16 (fp, &SSPtr->TotalBioMass) != 1 || + read_a8 (fp, SSPtr->ModuleSlots, NUM_MODULE_SLOTS) != 1 || + read_a8 (fp, SSPtr->DriveSlots, NUM_DRIVE_SLOTS) != 1 || + read_a8 (fp, SSPtr->JetSlots, NUM_JET_SLOTS) != 1 || + read_8 (fp, &SSPtr->NumLanders) != 1 || + read_a16 (fp, SSPtr->ElementAmounts, NUM_ELEMENT_CATEGORIES) != 1 || + + 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 */ + ) + return FALSE; + else + return TRUE; +} + +static BOOLEAN +LoadSummary (SUMMARY_DESC *SummPtr, void *fp) +{ + if (!LoadSisState (&SummPtr->SS, fp)) + return FALSE; + + if ( + read_8 (fp, &SummPtr->Activity) != 1 || + read_8 (fp, &SummPtr->Flags) != 1 || + read_8 (fp, &SummPtr->day_index) != 1 || + read_8 (fp, &SummPtr->month_index) != 1 || + read_16 (fp, &SummPtr->year_index) != 1 || + read_8 (fp, &SummPtr->MCreditLo) != 1 || + read_8 (fp, &SummPtr->MCreditHi) != 1 || + read_8 (fp, &SummPtr->NumShips) != 1 || + read_8 (fp, &SummPtr->NumDevices) != 1 || + read_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS) != 1 || + read_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES) != 1 || + + read_16 (fp, NULL) != 1 /* padding */ + ) + return FALSE; + else + return TRUE; +} + +static void +LoadStarDesc (STAR_DESC *SDPtr, DECODE_REF fh) +{ + cread_16s(fh, &SDPtr->star_pt.x); + cread_16s(fh, &SDPtr->star_pt.y); + cread_8 (fh, &SDPtr->Type); + cread_8 (fh, &SDPtr->Index); + cread_8 (fh, &SDPtr->Prefix); + cread_8 (fh, &SDPtr->Postfix); +} + +BOOLEAN +LoadLegacyGame (COUNT which_game, SUMMARY_DESC *SummPtr) +{ + uio_Stream *in_fp; + char file[PATH_MAX]; + char buf[256]; + SUMMARY_DESC loc_sd; + GAME_STATE_FILE *fp; + DECODE_REF fh; + COUNT num_links; + STAR_DESC SD; + ACTIVITY Activity; + + sprintf (file, "starcon2.%02u", which_game); + in_fp = res_OpenResFile (saveDir, file, "rb"); + if (!in_fp) + return FALSE; + + loc_sd.SaveName[0] = '\0'; + if (!LoadSummary (&loc_sd, in_fp)) + { + log_add (log_Error, "Warning: Savegame is corrupt"); + res_CloseResFile (in_fp); + return FALSE; + } + + if (!SummPtr) + { + SummPtr = &loc_sd; + } + else + { // only need summary for displaying to user + memcpy (SummPtr, &loc_sd, sizeof (*SummPtr)); + res_CloseResFile (in_fp); + return TRUE; + } + + // Crude check for big-endian/little-endian incompatibilities. + // year_index is suitable as it's a multi-byte value within + // a specific recognisable range. + if (SummPtr->year_index < START_YEAR || + SummPtr->year_index >= START_YEAR + + YEARS_TO_KOHRAH_VICTORY + 1 /* Utwig intervention */ + + 1 /* time to destroy all races, plenty */ + + 25 /* for cheaters */) + { + log_add (log_Error, "Warning: Savegame corrupt or from " + "an incompatible platform."); + res_CloseResFile (in_fp); + return FALSE; + } + + GlobData.SIS_state = SummPtr->SS; + + if ((fh = copen (in_fp, FILE_STREAM, STREAM_READ)) == 0) + { + res_CloseResFile (in_fp); + return FALSE; + } + + ReinitQueue (&GLOBAL (GameClock.event_q)); + ReinitQueue (&GLOBAL (encounter_q)); + ReinitQueue (&GLOBAL (ip_group_q)); + ReinitQueue (&GLOBAL (npc_built_ship_q)); + ReinitQueue (&GLOBAL (built_ship_q)); + + memset (&GLOBAL (GameState[0]), 0, sizeof (GLOBAL (GameState))); + Activity = GLOBAL (CurrentActivity); + LoadGameState (&GlobData.Game_state, fh); + NextActivity = GLOBAL (CurrentActivity); + GLOBAL (CurrentActivity) = Activity; + + LoadRaceQueue (fh, &GLOBAL (avail_race_q)); + // START_INTERPLANETARY is only set when saving from Homeworld + // encounter screen. When the game is loaded, the + // GenerateOrbitalFunction for the current star system will + // create the encounter anew and populate the npc queue. + if (!(NextActivity & START_INTERPLANETARY)) + { + if (NextActivity & START_ENCOUNTER) + LoadShipQueue (fh, &GLOBAL (npc_built_ship_q)); + else if (LOBYTE (NextActivity) == IN_INTERPLANETARY) + // XXX: Technically, this queue does not need to be + // saved/loaded at all. IP groups will be reloaded + // from group state files. But the original code did, + // and so will we until we can prove we do not need to. + LoadGroupQueue (fh, &GLOBAL (ip_group_q)); + else + // XXX: The empty queue read is only needed to maintain + // the savegame compatibility + LoadEmptyQueue (fh); + } + LoadShipQueue (fh, &GLOBAL (built_ship_q)); + + // Load the game events (compressed) + cread_16 (fh, &num_links); + { +#ifdef DEBUG_LOAD + log_add (log_Debug, "EVENTS:"); +#endif /* DEBUG_LOAD */ + while (num_links--) + { + HEVENT hEvent; + EVENT *EventPtr; + + hEvent = AllocEvent (); + LockEvent (hEvent, &EventPtr); + + LoadEvent (EventPtr, fh); + +#ifdef DEBUG_LOAD + log_add (log_Debug, "\t%u/%u/%u -- %u", + EventPtr->month_index, + EventPtr->day_index, + EventPtr->year_index, + EventPtr->func_index); +#endif /* DEBUG_LOAD */ + UnlockEvent (hEvent); + PutEvent (hEvent); + } + } + + // Load the encounters (black globes in HS/QS (compressed)) + cread_16 (fh, &num_links); + { + while (num_links--) + { + HENCOUNTER hEncounter; + ENCOUNTER *EncounterPtr; + + hEncounter = AllocEncounter (); + LockEncounter (hEncounter, &EncounterPtr); + + LoadEncounter (EncounterPtr, fh); + + UnlockEncounter (hEncounter); + PutEncounter (hEncounter); + } + } + + // Copy the star info file from the compressed stream + fp = OpenStateFile (STARINFO_FILE, "wb"); + if (fp) + { + DWORD flen; + + cread_32 (fh, &flen); + while (flen) + { + COUNT num_bytes; + + num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; + cread (buf, num_bytes, 1, fh); + WriteStateFile (buf, num_bytes, 1, fp); + + flen -= num_bytes; + } + CloseStateFile (fp); + } + + // Copy the defined groupinfo file from the compressed stream + fp = OpenStateFile (DEFGRPINFO_FILE, "wb"); + if (fp) + { + DWORD flen; + + cread_32 (fh, &flen); + while (flen) + { + COUNT num_bytes; + + num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; + cread (buf, num_bytes, 1, fh); + WriteStateFile (buf, num_bytes, 1, fp); + + flen -= num_bytes; + } + CloseStateFile (fp); + } + + // Copy the random groupinfo file from the compressed stream + fp = OpenStateFile (RANDGRPINFO_FILE, "wb"); + if (fp) + { + DWORD flen; + + cread_32 (fh, &flen); + while (flen) + { + COUNT num_bytes; + + num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; + cread (buf, num_bytes, 1, fh); + WriteStateFile (buf, num_bytes, 1, fp); + + flen -= num_bytes; + } + CloseStateFile (fp); + } + + LoadStarDesc (&SD, fh); + + cclose (fh); + res_CloseResFile (in_fp); + + EncounterGroup = 0; + EncounterRace = -1; + + ReinitQueue (&race_q[0]); + ReinitQueue (&race_q[1]); + CurStarDescPtr = FindStar (NULL, &SD.star_pt, 0, 0); + if (!(NextActivity & START_ENCOUNTER) + && LOBYTE (NextActivity) == IN_INTERPLANETARY) + NextActivity |= START_INTERPLANETARY; + + return TRUE; +} + + diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index 509515c15..bfaa140f5 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -41,38 +41,63 @@ #include "libs/inplib.h" #include "libs/log.h" #include "libs/memlib.h" -#include "libs/declib.h" +typedef struct savebuf_struct +{ + char *buf; + size_t bufsize; + int index, error; +} SAVEBUF; // XXX: these should handle endian conversions later static inline COUNT -cwrite_8 (DECODE_REF fh, BYTE v) +cwrite_8 (SAVEBUF *fh, BYTE v) { - return cwrite (&v, 1, 1, fh); + if (!fh) + return 0; + if (!fh->error && fh->index < fh->bufsize) + { + fh->buf[fh->index++] = v; + return 1; + } + fh->error = 1; + return 0; } static inline COUNT -cwrite_16 (DECODE_REF fh, UWORD v) +cwrite_16 (SAVEBUF *fh, UWORD v) { - return cwrite (&v, 2, 1, fh); + if (cwrite_8 (fh, (BYTE)(v & 0xFF)) && + cwrite_8 (fh, (BYTE)((v >> 8) & 0xFF))) + return 1; + return 0; } static inline COUNT -cwrite_32 (DECODE_REF fh, DWORD v) +cwrite_32 (SAVEBUF *fh, DWORD v) { - return cwrite (&v, 4, 1, fh); + if (cwrite_8 (fh, (BYTE)(v & 0xFF)) && + cwrite_8 (fh, (BYTE)((v >> 8) & 0xFF)) && + cwrite_8 (fh, (BYTE)((v >> 16) & 0xFF)) && + cwrite_8 (fh, (BYTE)((v >> 24) & 0xFF))) + return 1; + return 0; } static inline COUNT -cwrite_ptr (DECODE_REF fh) +cwrite_ptr (SAVEBUF *fh) { return cwrite_32 (fh, 0); /* ptrs are useless in saves */ } static inline COUNT -cwrite_a8 (DECODE_REF fh, const BYTE *ar, COUNT count) +cwrite_a8 (SAVEBUF *fh, const BYTE *ar, COUNT count) { - return cwrite (ar, 1, count, fh) == count; + int i; + for (i = 0; i < count; ++i) + if (!cwrite_8 (fh, ar[i])) + return 0; + return 1; } static inline size_t @@ -124,7 +149,7 @@ write_a16 (void *fp, const UWORD *ar, COUNT count) } static void -SaveEmptyQueue (DECODE_REF fh) +SaveEmptyQueue (SAVEBUF *fh) { COUNT num_links = 0; @@ -133,7 +158,7 @@ SaveEmptyQueue (DECODE_REF fh) } static void -SaveShipQueue (DECODE_REF fh, QUEUE *pQueue) +SaveShipQueue (SAVEBUF *fh, QUEUE *pQueue) { COUNT num_links; HSHIPFRAG hStarShip; @@ -178,7 +203,7 @@ SaveShipQueue (DECODE_REF fh, QUEUE *pQueue) } static void -SaveRaceQueue (DECODE_REF fh, QUEUE *pQueue) +SaveRaceQueue (SAVEBUF *fh, QUEUE *pQueue) { COUNT num_links; HFLEETINFO hFleet; @@ -228,7 +253,7 @@ SaveRaceQueue (DECODE_REF fh, QUEUE *pQueue) } static void -SaveGroupQueue (DECODE_REF fh, QUEUE *pQueue) +SaveGroupQueue (SAVEBUF *fh, QUEUE *pQueue) { HIPGROUP hGroup, hNextGroup; @@ -266,7 +291,7 @@ SaveGroupQueue (DECODE_REF fh, QUEUE *pQueue) } static void -SaveEncounter (const ENCOUNTER *EncounterPtr, DECODE_REF fh) +SaveEncounter (const ENCOUNTER *EncounterPtr, SAVEBUF *fh) { COUNT i; @@ -312,7 +337,7 @@ SaveEncounter (const ENCOUNTER *EncounterPtr, DECODE_REF fh) } static void -SaveEvent (const EVENT *EventPtr, DECODE_REF fh) +SaveEvent (const EVENT *EventPtr, SAVEBUF *fh) { cwrite_ptr (fh); /* useless ptr; HEVENT pred */ cwrite_ptr (fh); /* useless ptr; HEVENT succ */ @@ -325,7 +350,7 @@ SaveEvent (const EVENT *EventPtr, DECODE_REF fh) } static void -DummySaveQueue (const QUEUE *QueuePtr, DECODE_REF fh) +DummySaveQueue (const QUEUE *QueuePtr, SAVEBUF *fh) { /* QUEUE should never actually be saved since it contains * purely internal representation and the lists @@ -346,7 +371,7 @@ DummySaveQueue (const QUEUE *QueuePtr, DECODE_REF fh) } static void -SaveClockState (const CLOCK_STATE *ClockPtr, DECODE_REF fh) +SaveClockState (const CLOCK_STATE *ClockPtr, SAVEBUF *fh) { cwrite_8 (fh, ClockPtr->day_index); cwrite_8 (fh, ClockPtr->month_index); @@ -361,7 +386,7 @@ SaveClockState (const CLOCK_STATE *ClockPtr, DECODE_REF fh) } static void -SaveGameState (const GAME_STATE *GSPtr, DECODE_REF fh) +SaveGameState (const GAME_STATE *GSPtr, SAVEBUF *fh) { cwrite_8 (fh, 0); /* obsolete; BYTE cur_state */ cwrite_8 (fh, GSPtr->glob_flags); @@ -470,7 +495,7 @@ SaveSummary (const SUMMARY_DESC *SummPtr, void *fp) } static void -SaveStarDesc (const STAR_DESC *SDPtr, DECODE_REF fh) +SaveStarDesc (const STAR_DESC *SDPtr, SAVEBUF *fh) { cwrite_16 (fh, SDPtr->star_pt.x); cwrite_16 (fh, SDPtr->star_pt.y); @@ -644,14 +669,14 @@ SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name) { BOOLEAN success, made_room; void *out_fp, *h; - DECODE_REF fh; + SAVEBUF fh_backing, *fh; success = TRUE; made_room = FALSE; + fh = &fh_backing; RetrySave: - h = HMalloc (10 * 1024); - if (h == 0 - || (fh = copen (h, MEMORY_STREAM, STREAM_WRITE)) == 0) + h = HMalloc (128 * 1024); + if (h == 0) { if (success) { @@ -677,6 +702,10 @@ RetrySave: STAR_DESC SD; char buf[256], file[PATH_MAX]; + fh->buf = h; + fh->bufsize = 128 * 1024; + fh->index = 0; + fh->error = 0; success = TRUE; if (CurStarDescPtr) SD = *CurStarDescPtr; @@ -773,6 +802,7 @@ RetrySave: { flen = LengthStateFile (fp); // Write the uncompressed size. + printf ("Star Info: %u bytes\n", flen); cwrite_32 (fh, flen); while (flen) { @@ -780,7 +810,7 @@ RetrySave: num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; ReadStateFile (buf, num_bytes, 1, fp); - cwrite (buf, num_bytes, 1, fh); + cwrite_a8 (fh, buf, num_bytes); flen -= num_bytes; } @@ -793,6 +823,7 @@ RetrySave: { flen = LengthStateFile (fp); // Write the uncompressed size. + printf ("Defined Group Info: %u bytes\n", flen); cwrite_32 (fh, flen); while (flen) { @@ -800,7 +831,7 @@ RetrySave: num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; ReadStateFile (buf, num_bytes, 1, fp); - cwrite (buf, num_bytes, 1, fh); + cwrite_a8 (fh, buf, num_bytes); flen -= num_bytes; } @@ -813,6 +844,7 @@ RetrySave: { flen = LengthStateFile (fp); // Write the uncompressed size. + printf ("Random Group Info: %u bytes\n", flen); cwrite_32 (fh, flen); while (flen) { @@ -820,7 +852,7 @@ RetrySave: num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; ReadStateFile (buf, num_bytes, 1, fp); - cwrite (buf, num_bytes, 1, fh); + cwrite_a8 (fh, buf, num_bytes); flen -= num_bytes; } @@ -830,10 +862,10 @@ RetrySave: // Write the current star desc into the memory file (compressed). SaveStarDesc (&SD, fh); - flen = cclose (fh); + flen = fh->index; // Write the memory file to the actual savegame file. - sprintf (file, "starcon2.%02u", which_game); + sprintf (file, "uqmsave.%02u", which_game); log_add (log_Debug, "'%s' is %u bytes long", file, flen + 181 + strlen(SummPtr->SaveName)); if (flen && (out_fp = res_OpenResFile (saveDir, file, "wb"))) @@ -842,7 +874,7 @@ RetrySave: success = SaveSummary (SummPtr, out_fp); // Then write the rest of the data. - if (success && write_32 (out_fp, OMNIZIP_MAGIC) != 1) + if (success && write_32 (out_fp, OMNIBUS_MAGIC) != 1) success = FALSE; if (success && write_32 (out_fp, flen) != 1) success = FALSE; diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index f06c4a051..62602eb90 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -33,7 +33,7 @@ extern "C" { #define MAX_EXCLUSIVE_DEVICES 16 #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 OMNIBUS_MAGIC 0x696e6d4f // "Omni": All data, uncompressed #define SAVE_NAME_SIZE 64 typedef struct @@ -53,6 +53,7 @@ typedef struct extern ACTIVITY NextActivity; extern BOOLEAN LoadGame (COUNT which_game, SUMMARY_DESC *summary_desc); +extern BOOLEAN LoadLegacyGame (COUNT which_game, SUMMARY_DESC *summary_desc); extern void SaveProblem (void); extern BOOLEAN SaveGame (COUNT which_game, SUMMARY_DESC *summary_desc, const char *name); From 876daf0827f1ad8b8f2b5229cab7e278a821e315 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Mon, 7 Oct 2013 00:39:06 -0700 Subject: [PATCH 07/18] Remove padding and unused data from savegame format. --- sc2/src/uqm/load.c | 95 +++------------------------------- sc2/src/uqm/save.c | 126 ++++----------------------------------------- 2 files changed, 17 insertions(+), 204 deletions(-) diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index 081ce9526..d7788c8e0 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -86,13 +86,6 @@ read_32s (void *fp, SDWORD *v) return ret; } -static inline size_t -read_ptr (void *fp) -{ - DWORD t; - return read_32 (fp, &t); /* ptrs are useless in saves */ -} - static inline size_t read_a8 (void *fp, BYTE *ar, COUNT count) { @@ -167,21 +160,13 @@ LoadShipQueue (void *fh, QUEUE *pQueue) FragPtr = LockShipFrag (pQueue, hStarShip); // Read SHIP_FRAGMENT elements - read_16 (fh, NULL); /* unused: was which_side */ read_8 (fh, &FragPtr->captains_name_index); - read_8 (fh, NULL); /* padding */ - read_16 (fh, NULL); /* unused: was ship_flags */ read_8 (fh, &FragPtr->race_id); read_8 (fh, &FragPtr->index); - // XXX: reading crew as BYTE to maintain savegame compatibility - read_8 (fh, &tmpb); - FragPtr->crew_level = tmpb; - read_8 (fh, &tmpb); - FragPtr->max_crew = tmpb; + read_16 (fh, &FragPtr->crew_level); + read_16 (fh, &FragPtr->max_crew); read_8 (fh, &FragPtr->energy_level); read_8 (fh, &FragPtr->max_energy); - read_16 (fh, NULL); /* unused; was loc.x */ - read_16 (fh, NULL); /* unused; was loc.y */ UnlockShipFrag (pQueue, hStarShip); } @@ -227,7 +212,6 @@ LoadRaceQueue (void *fh, QUEUE *pQueue) read_8 (fh, &FleetPtr->func_index); read_16s(fh, &FleetPtr->dest_loc.x); read_16s(fh, &FleetPtr->dest_loc.y); - read_16 (fh, NULL); /* alignment padding */ UnlockFleetInfo (pQueue, hStarShip); } @@ -246,21 +230,15 @@ LoadGroupQueue (void *fh, QUEUE *pQueue) IP_GROUP *GroupPtr; BYTE tmpb; - read_16 (fh, NULL); /* unused; was race_id */ - hGroup = BuildGroup (pQueue, 0); GroupPtr = LockIpGroup (pQueue, hGroup); - read_16 (fh, NULL); /* unused; was which_side */ - read_8 (fh, NULL); /* unused; was captains_name_index */ - read_8 (fh, NULL); /* padding; for savegame compat */ read_16 (fh, &GroupPtr->group_counter); read_8 (fh, &GroupPtr->race_id); read_8 (fh, &tmpb); /* was var2 */ GroupPtr->sys_loc = LONIBBLE (tmpb); GroupPtr->task = HINIBBLE (tmpb); read_8 (fh, &GroupPtr->in_system); /* was crew_level */ - read_8 (fh, NULL); /* unused; was max_crew */ read_8 (fh, &tmpb); /* was energy_level */ GroupPtr->dest_loc = LONIBBLE (tmpb); GroupPtr->orbit_pos = HINIBBLE (tmpb); @@ -278,11 +256,8 @@ LoadEncounter (ENCOUNTER *EncounterPtr, void *fh) COUNT i; BYTE tmpb; - read_ptr (fh); /* useless ptr; HENCOUNTER pred */ EncounterPtr->pred = 0; - read_ptr (fh); /* useless ptr; HENCOUNTER succ */ EncounterPtr->succ = 0; - read_ptr (fh); /* useless ptr; HELEMENT hElement */ EncounterPtr->hElement = 0; read_16s (fh, &EncounterPtr->transition_state); read_16s (fh, &EncounterPtr->origin.x); @@ -292,31 +267,18 @@ LoadEncounter (ENCOUNTER *EncounterPtr, void *fh) read_16s (fh, &EncounterPtr->loc_pt.x); read_16s (fh, &EncounterPtr->loc_pt.y); read_8 (fh, &EncounterPtr->race_id); - read_8 (fh, &tmpb); - EncounterPtr->num_ships = tmpb & ENCOUNTER_SHIPS_MASK; - EncounterPtr->flags = tmpb & ENCOUNTER_FLAGS_MASK; - read_16 (fh, NULL); /* alignment padding */ + read_8 (fh, &EncounterPtr->num_ships); + read_8 (fh, &EncounterPtr->flags); // Load each entry in the BRIEF_SHIP_INFO array for (i = 0; i < MAX_HYPER_SHIPS; i++) { BRIEF_SHIP_INFO *ShipInfo = &EncounterPtr->ShipList[i]; - read_16 (fh, NULL); /* useless; was SHIP_INFO.ship_flags */ read_8 (fh, &ShipInfo->race_id); - read_8 (fh, NULL); /* useless; was SHIP_INFO.var2 */ - // XXX: reading crew as BYTE to maintain savegame compatibility - read_8 (fh, &tmpb); - ShipInfo->crew_level = tmpb; - read_8 (fh, &tmpb); - ShipInfo->max_crew = tmpb; - read_8 (fh, NULL); /* useless; was SHIP_INFO.energy_level */ + read_16 (fh, &ShipInfo->crew_level); + read_16 (fh, &ShipInfo->max_crew); read_8 (fh, &ShipInfo->max_energy); - read_16 (fh, NULL); /* useless; was SHIP_INFO.loc.x */ - read_16 (fh, NULL); /* useless; was SHIP_INFO.loc.y */ - read_32 (fh, NULL); /* useless val; STRING race_strings */ - read_ptr (fh); /* useless ptr; FRAME icons */ - read_ptr (fh); /* useless ptr; FRAME melee_icon */ } // Load the stuff after the BRIEF_SHIP_INFO array @@ -327,37 +289,12 @@ LoadEncounter (ENCOUNTER *EncounterPtr, void *fh) static void LoadEvent (EVENT *EventPtr, void *fh) { - read_ptr (fh); /* useless ptr; HEVENT pred */ EventPtr->pred = 0; - read_ptr (fh); /* useless ptr; HEVENT succ */ EventPtr->succ = 0; read_8 (fh, &EventPtr->day_index); read_8 (fh, &EventPtr->month_index); read_16 (fh, &EventPtr->year_index); read_8 (fh, &EventPtr->func_index); - read_8 (fh, NULL); /* padding */ - read_16 (fh, NULL); /* padding */ -} - -static void -DummyLoadQueue (QUEUE *QueuePtr, void *fh) -{ - /* QUEUE should never actually be loaded since it contains - * purely internal representation and the lists - * involved are actually loaded separately */ - (void)QueuePtr; /* silence compiler */ - - /* QUEUE format with QUEUE_TABLE defined -- UQM default */ - read_ptr (fh); /* HLINK head */ - read_ptr (fh); /* HLINK tail */ - read_ptr (fh); /* BYTE* pq_tab */ - read_ptr (fh); /* HLINK free_list */ - read_16 (fh, NULL); /* MEM_HANDLE hq_tab */ - read_16 (fh, NULL); /* COUNT object_size */ - read_8 (fh, NULL); /* BYTE num_objects */ - - read_8 (fh, NULL); /* padding */ - read_16 (fh, NULL); /* padding */ } static void @@ -368,28 +305,18 @@ LoadClockState (CLOCK_STATE *ClockPtr, void *fh) read_16 (fh, &ClockPtr->year_index); read_16s (fh, &ClockPtr->tick_count); read_16s (fh, &ClockPtr->day_in_ticks); - read_ptr (fh); /* not loading ptr; Semaphore clock_sem */ - read_ptr (fh); /* not loading ptr; Task clock_task */ - read_32 (fh, NULL); /* not loading; DWORD TimeCounter */ - - DummyLoadQueue (&ClockPtr->event_q, fh); } static void LoadGameState (GAME_STATE *GSPtr, void *fh) { - BYTE dummy8; - - read_8 (fh, &dummy8); /* obsolete */ read_8 (fh, &GSPtr->glob_flags); read_8 (fh, &GSPtr->CrewCost); read_8 (fh, &GSPtr->FuelCost); read_a8 (fh, GSPtr->ModuleCost, NUM_MODULES); read_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); - read_ptr (fh); /* not loading ptr; PRIMITIVE *DisplayArray */ read_16 (fh, &GSPtr->CurrentActivity); - read_16 (fh, NULL); /* CLOCK_STATE alignment padding */ LoadClockState (&GSPtr->GameClock, fh); read_16s (fh, &GSPtr->autopilot.x); @@ -413,20 +340,10 @@ LoadGameState (GAME_STATE *GSPtr, void *fh) read_16s (fh, &GSPtr->velocity.error.height); read_16s (fh, &GSPtr->velocity.incr.width); read_16s (fh, &GSPtr->velocity.incr.height); - read_16 (fh, NULL); /* VELOCITY_DESC padding */ read_32 (fh, &GSPtr->BattleGroupRef); - DummyLoadQueue (&GSPtr->avail_race_q, fh); - DummyLoadQueue (&GSPtr->npc_built_ship_q, fh); - // Not loading ip_group_q, was not there originally - DummyLoadQueue (&GSPtr->encounter_q, fh); - DummyLoadQueue (&GSPtr->built_ship_q, fh); - read_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); - - assert (sizeof (GSPtr->GameState) % 4 == 3); - read_8 (fh, NULL); /* GAME_STATE alignment padding */ } static BOOLEAN diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index bfaa140f5..8b2791a17 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -84,12 +84,6 @@ cwrite_32 (SAVEBUF *fh, DWORD v) return 0; } -static inline COUNT -cwrite_ptr (SAVEBUF *fh) -{ - return cwrite_32 (fh, 0); /* ptrs are useless in saves */ -} - static inline COUNT cwrite_a8 (SAVEBUF *fh, const BYTE *ar, COUNT count) { @@ -118,12 +112,6 @@ write_32 (void *fp, DWORD v) return WriteResFile (&v, 4, 1, fp); } -static inline size_t -write_ptr (void *fp) -{ - return write_32 (fp, 0); /* ptrs are useless in saves */ -} - static inline size_t write_a8 (void *fp, const BYTE *ar, COUNT count) { @@ -183,19 +171,13 @@ SaveShipQueue (SAVEBUF *fh, QUEUE *pQueue) cwrite_16 (fh, Index); // Write SHIP_FRAGMENT elements - cwrite_16 (fh, 0); /* unused; was which_side */ cwrite_8 (fh, FragPtr->captains_name_index); - cwrite_8 (fh, 0); /* padding */ - cwrite_16 (fh, 0); /* unused: was ship_flags */ cwrite_8 (fh, FragPtr->race_id); cwrite_8 (fh, FragPtr->index); - // XXX: writing crew as BYTE to maintain savegame compatibility - cwrite_8 (fh, FragPtr->crew_level); - cwrite_8 (fh, FragPtr->max_crew); + cwrite_16 (fh, FragPtr->crew_level); + cwrite_16 (fh, FragPtr->max_crew); cwrite_8 (fh, FragPtr->energy_level); cwrite_8 (fh, FragPtr->max_energy); - cwrite_16 (fh, 0); /* unused; was loc.x */ - cwrite_16 (fh, 0); /* unused; was loc.y */ UnlockShipFrag (pQueue, hStarShip); hStarShip = hNextShip; @@ -245,7 +227,6 @@ SaveRaceQueue (SAVEBUF *fh, QUEUE *pQueue) cwrite_8 (fh, FleetPtr->func_index); cwrite_16 (fh, FleetPtr->dest_loc.x); cwrite_16 (fh, FleetPtr->dest_loc.y); - cwrite_16 (fh, 0); /* alignment padding */ UnlockFleetInfo (pQueue, hFleet); hFleet = hNextFleet; @@ -267,18 +248,12 @@ SaveGroupQueue (SAVEBUF *fh, QUEUE *pQueue) GroupPtr = LockIpGroup (pQueue, hGroup); hNextGroup = _GetSuccLink (GroupPtr); - cwrite_16 (fh, GroupPtr->race_id); /* unused; for old versions */ - - cwrite_16 (fh, 0); /* unused; was which_side */ - cwrite_8 (fh, 0); /* unused; was captains_name_index */ - cwrite_8 (fh, 0); /* padding; for savegame compat */ cwrite_16 (fh, GroupPtr->group_counter); cwrite_8 (fh, GroupPtr->race_id); assert (GroupPtr->sys_loc < 0x10 && GroupPtr->task < 0x10); cwrite_8 (fh, MAKE_BYTE (GroupPtr->sys_loc, GroupPtr->task)); /* was var2 */ cwrite_8 (fh, GroupPtr->in_system); /* was crew_level */ - cwrite_8 (fh, 0); /* unused; was max_crew */ assert (GroupPtr->dest_loc < 0x10 && GroupPtr->orbit_pos < 0x10); cwrite_8 (fh, MAKE_BYTE (GroupPtr->dest_loc, GroupPtr->orbit_pos)); /* was energy_level */ @@ -295,9 +270,6 @@ SaveEncounter (const ENCOUNTER *EncounterPtr, SAVEBUF *fh) { COUNT i; - cwrite_ptr (fh); /* useless ptr; HENCOUNTER pred */ - cwrite_ptr (fh); /* useless ptr; HENCOUNTER succ */ - cwrite_ptr (fh); /* useless ptr; HELEMENT hElement */ cwrite_16 (fh, EncounterPtr->transition_state); cwrite_16 (fh, EncounterPtr->origin.x); cwrite_16 (fh, EncounterPtr->origin.y); @@ -306,29 +278,18 @@ SaveEncounter (const ENCOUNTER *EncounterPtr, SAVEBUF *fh) cwrite_16 (fh, EncounterPtr->loc_pt.x); cwrite_16 (fh, EncounterPtr->loc_pt.y); cwrite_8 (fh, EncounterPtr->race_id); - // XXX: writing combined fields to maintain savegame compatibility - cwrite_8 (fh, (EncounterPtr->num_ships & ENCOUNTER_SHIPS_MASK) - | (EncounterPtr->flags & ENCOUNTER_FLAGS_MASK)); - cwrite_16 (fh, 0); /* alignment padding */ + cwrite_8 (fh, EncounterPtr->num_ships); + cwrite_8 (fh, EncounterPtr->flags); // Save each entry in the BRIEF_SHIP_INFO array for (i = 0; i < MAX_HYPER_SHIPS; i++) { const BRIEF_SHIP_INFO *ShipInfo = &EncounterPtr->ShipList[i]; - cwrite_16 (fh, 0); /* useless; was SHIP_INFO.ship_flags */ cwrite_8 (fh, ShipInfo->race_id); - cwrite_8 (fh, 0); /* useless; was SHIP_INFO.var2 */ - // XXX: writing crew as BYTE to maintain savegame compatibility - cwrite_8 (fh, ShipInfo->crew_level); - cwrite_8 (fh, ShipInfo->max_crew); - cwrite_8 (fh, 0); /* useless; was SHIP_INFO.energy_level */ + cwrite_16 (fh, ShipInfo->crew_level); + cwrite_16 (fh, ShipInfo->max_crew); cwrite_8 (fh, ShipInfo->max_energy); - cwrite_16 (fh, 0); /* useless; was SHIP_INFO.loc.x */ - cwrite_16 (fh, 0); /* useless; was SHIP_INFO.loc.y */ - cwrite_32 (fh, 0); /* useless val; STRING race_strings */ - cwrite_ptr (fh); /* useless ptr; FRAME icons */ - cwrite_ptr (fh); /* useless ptr; FRAME melee_icon */ } // Save the stuff after the BRIEF_SHIP_INFO array @@ -339,35 +300,10 @@ SaveEncounter (const ENCOUNTER *EncounterPtr, SAVEBUF *fh) static void SaveEvent (const EVENT *EventPtr, SAVEBUF *fh) { - cwrite_ptr (fh); /* useless ptr; HEVENT pred */ - cwrite_ptr (fh); /* useless ptr; HEVENT succ */ cwrite_8 (fh, EventPtr->day_index); cwrite_8 (fh, EventPtr->month_index); cwrite_16 (fh, EventPtr->year_index); cwrite_8 (fh, EventPtr->func_index); - cwrite_8 (fh, 0); /* padding */ - cwrite_16 (fh, 0); /* padding */ -} - -static void -DummySaveQueue (const QUEUE *QueuePtr, SAVEBUF *fh) -{ - /* QUEUE should never actually be saved since it contains - * purely internal representation and the lists - * involved are actually saved separately */ - (void)QueuePtr; /* silence compiler */ - - /* QUEUE format with QUEUE_TABLE defined -- UQM default */ - cwrite_ptr (fh); /* HLINK head */ - cwrite_ptr (fh); /* HLINK tail */ - cwrite_ptr (fh); /* BYTE* pq_tab */ - cwrite_ptr (fh); /* HLINK free_list */ - cwrite_16 (fh, 0); /* MEM_HANDLE hq_tab */ - cwrite_16 (fh, 0); /* COUNT object_size */ - cwrite_8 (fh, 0); /* BYTE num_objects */ - - cwrite_8 (fh, 0); /* padding */ - cwrite_16 (fh, 0); /* padding */ } static void @@ -378,26 +314,18 @@ SaveClockState (const CLOCK_STATE *ClockPtr, SAVEBUF *fh) cwrite_16 (fh, ClockPtr->year_index); cwrite_16 (fh, ClockPtr->tick_count); cwrite_16 (fh, ClockPtr->day_in_ticks); - cwrite_ptr (fh); /* useless ptr; Semaphore clock_sem */ - cwrite_ptr (fh); /* useless ptr; Task clock_task */ - cwrite_32 (fh, 0); /* useless value; DWORD TimeCounter */ - - DummySaveQueue (&ClockPtr->event_q, fh); } static void SaveGameState (const GAME_STATE *GSPtr, SAVEBUF *fh) { - cwrite_8 (fh, 0); /* obsolete; BYTE cur_state */ cwrite_8 (fh, GSPtr->glob_flags); cwrite_8 (fh, GSPtr->CrewCost); cwrite_8 (fh, GSPtr->FuelCost); cwrite_a8 (fh, GSPtr->ModuleCost, NUM_MODULES); cwrite_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); - cwrite_ptr (fh); /* useless ptr; PRIMITIVE *DisplayArray */ cwrite_16 (fh, GSPtr->CurrentActivity); - cwrite_16 (fh, 0); /* CLOCK_STATE alignment padding */ SaveClockState (&GSPtr->GameClock, fh); cwrite_16 (fh, GSPtr->autopilot.x); @@ -421,20 +349,10 @@ SaveGameState (const GAME_STATE *GSPtr, SAVEBUF *fh) cwrite_16 (fh, GSPtr->velocity.error.height); cwrite_16 (fh, GSPtr->velocity.incr.width); cwrite_16 (fh, GSPtr->velocity.incr.height); - cwrite_16 (fh, 0); /* VELOCITY_DESC padding */ cwrite_32 (fh, GSPtr->BattleGroupRef); - DummySaveQueue (&GSPtr->avail_race_q, fh); - DummySaveQueue (&GSPtr->npc_built_ship_q, fh); - // Not saving ip_group_q, was not there originally - DummySaveQueue (&GSPtr->encounter_q, fh); - DummySaveQueue (&GSPtr->built_ship_q, fh); - cwrite_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); - - assert (sizeof (GSPtr->GameState) % 4 == 3); - cwrite_8 (fh, 0); /* GAME_STATE alignment padding */ } static BOOLEAN @@ -469,7 +387,7 @@ SaveSummary (const SUMMARY_DESC *SummPtr, void *fp) if ( write_32 (fp, SAVE_MAGIC) != 1 || write_32 (fp, SUMMARY_MAGIC) != 1 || - write_32 (fp, 161 + strlen(SummPtr->SaveName)) != 1 + write_32 (fp, 160 + strlen(SummPtr->SaveName)) != 1 ) return FALSE; if (!SaveSisState (&SummPtr->SS, fp)) @@ -487,7 +405,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, strlen(SummPtr->SaveName)+1) != 1 + write_a8 (fp, SummPtr->SaveName, strlen(SummPtr->SaveName)) != 1 ) return FALSE; else @@ -667,31 +585,16 @@ SaveFlagshipState (void) BOOLEAN SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name) { - BOOLEAN success, made_room; + BOOLEAN success; void *out_fp, *h; SAVEBUF fh_backing, *fh; success = TRUE; - made_room = FALSE; fh = &fh_backing; -RetrySave: h = HMalloc (128 * 1024); if (h == 0) { - if (success) - { - success = FALSE; - made_room = TRUE; - HFree (h); - - FreeSC2Data (); - log_add (log_Debug, "Insufficient room for save buffers" - " -- RETRYING"); - goto RetrySave; - } - else - log_add (log_Debug, "Insufficient room for save buffers" - " -- GIVING UP!"); + log_add (log_Debug, "Insufficient room for save buffers"); } else { @@ -742,7 +645,6 @@ RetrySave: // from group state files. But the original code did, // and so will we until we can prove we do not need to. SaveGroupQueue (fh, &GLOBAL (ip_group_q)); - //SaveEmptyQueue (fh); else // XXX: empty queue write-out is only needed to maintain // the savegame compatibility @@ -802,7 +704,6 @@ RetrySave: { flen = LengthStateFile (fp); // Write the uncompressed size. - printf ("Star Info: %u bytes\n", flen); cwrite_32 (fh, flen); while (flen) { @@ -823,7 +724,6 @@ RetrySave: { flen = LengthStateFile (fp); // Write the uncompressed size. - printf ("Defined Group Info: %u bytes\n", flen); cwrite_32 (fh, flen); while (flen) { @@ -844,7 +744,6 @@ RetrySave: { flen = LengthStateFile (fp); // Write the uncompressed size. - printf ("Random Group Info: %u bytes\n", flen); cwrite_32 (fh, flen); while (flen) { @@ -867,7 +766,7 @@ RetrySave: // Write the memory file to the actual savegame file. sprintf (file, "uqmsave.%02u", which_game); log_add (log_Debug, "'%s' is %u bytes long", file, - flen + 181 + strlen(SummPtr->SaveName)); + flen + 180 + strlen(SummPtr->SaveName)); if (flen && (out_fp = res_OpenResFile (saveDir, file, "wb"))) { PrepareSummary (SummPtr, name); @@ -894,8 +793,5 @@ RetrySave: HFree (h); - if (made_room) - LoadSC2Data (); - return (success); } From eca23397c3ecfa62b02e67d0bd2ac39d778d224c Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Wed, 9 Oct 2013 00:34:35 -0700 Subject: [PATCH 08/18] Output bytes now track field sizes. This removes the clumsy local variable pointer stuff. --- sc2/src/uqm/load.c | 30 +++++++++++------------------- sc2/src/uqm/save.c | 20 +++++++++----------- sc2/src/uqm/save.h | 8 +++++--- 3 files changed, 25 insertions(+), 33 deletions(-) diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index d7788c8e0..14234ee3c 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -152,7 +152,6 @@ LoadShipQueue (void *fh, QUEUE *pQueue) HSHIPFRAG hStarShip; SHIP_FRAGMENT *FragPtr; COUNT Index; - BYTE tmpb; read_16 (fh, &Index); @@ -184,7 +183,6 @@ LoadRaceQueue (void *fh, QUEUE *pQueue) HFLEETINFO hStarShip; FLEET_INFO *FleetPtr; COUNT Index; - BYTE tmpb; read_16 (fh, &Index); @@ -195,10 +193,8 @@ LoadRaceQueue (void *fh, QUEUE *pQueue) read_16 (fh, &FleetPtr->allied_state); read_8 (fh, &FleetPtr->days_left); read_8 (fh, &FleetPtr->growth_fract); - read_8 (fh, &tmpb); - FleetPtr->crew_level = tmpb; - read_8 (fh, &tmpb); - FleetPtr->max_crew = tmpb; + read_16 (fh, &FleetPtr->crew_level); + read_16 (fh, &FleetPtr->max_crew); read_8 (fh, &FleetPtr->growth); read_8 (fh, &FleetPtr->max_energy); read_16s(fh, &FleetPtr->loc.x); @@ -228,20 +224,17 @@ LoadGroupQueue (void *fh, QUEUE *pQueue) { HIPGROUP hGroup; IP_GROUP *GroupPtr; - BYTE tmpb; hGroup = BuildGroup (pQueue, 0); GroupPtr = LockIpGroup (pQueue, hGroup); read_16 (fh, &GroupPtr->group_counter); read_8 (fh, &GroupPtr->race_id); - read_8 (fh, &tmpb); /* was var2 */ - GroupPtr->sys_loc = LONIBBLE (tmpb); - GroupPtr->task = HINIBBLE (tmpb); + read_8 (fh, &GroupPtr->sys_loc); + read_8 (fh, &GroupPtr->task); read_8 (fh, &GroupPtr->in_system); /* was crew_level */ - read_8 (fh, &tmpb); /* was energy_level */ - GroupPtr->dest_loc = LONIBBLE (tmpb); - GroupPtr->orbit_pos = HINIBBLE (tmpb); + read_8 (fh, &GroupPtr->dest_loc); + read_8 (fh, &GroupPtr->orbit_pos); read_8 (fh, &GroupPtr->group_id); /* was max_energy */ read_16s(fh, &GroupPtr->loc.x); read_16s(fh, &GroupPtr->loc.y); @@ -254,7 +247,6 @@ static void LoadEncounter (ENCOUNTER *EncounterPtr, void *fh) { COUNT i; - BYTE tmpb; EncounterPtr->pred = 0; EncounterPtr->succ = 0; @@ -378,11 +370,11 @@ LoadSummary (SUMMARY_DESC *SummPtr, void *fp) DWORD nameSize = 0; if (!read_32s (fp, &magic)) return FALSE; - if (magic == SAVE_MAGIC) + if (magic == SAVEFILE_TAG) { - if (read_32 (fp, &magic) != 1 || magic != SUMMARY_MAGIC) + if (read_32 (fp, &magic) != 1 || magic != SUMMARY_TAG) return FALSE; - if (read_32 (fp, &magic) != 1 || magic < 161) + if (read_32 (fp, &magic) != 1 || magic < 160) return FALSE; nameSize = magic - 160; } @@ -475,7 +467,7 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) GlobData.SIS_state = SummPtr->SS; chunk = 0; - while (chunk != OMNIBUS_MAGIC) + while (chunk != OMNIBUS_TAG) { if (read_32(in_fp, &chunk) != 1) { @@ -487,7 +479,7 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) res_CloseResFile (in_fp); return FALSE; } - if (chunk == OMNIBUS_MAGIC) + if (chunk == OMNIBUS_TAG) break; log_add (log_Debug, "Skipping chunk of tag %08X (size %u)", chunk, chunkSize); diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index 8b2791a17..27505e4cb 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -212,8 +212,8 @@ SaveRaceQueue (SAVEBUF *fh, QUEUE *pQueue) cwrite_16 (fh, FleetPtr->allied_state); cwrite_8 (fh, FleetPtr->days_left); cwrite_8 (fh, FleetPtr->growth_fract); - cwrite_8 (fh, FleetPtr->crew_level); - cwrite_8 (fh, FleetPtr->max_crew); + cwrite_16 (fh, FleetPtr->crew_level); + cwrite_16 (fh, FleetPtr->max_crew); cwrite_8 (fh, FleetPtr->growth); cwrite_8 (fh, FleetPtr->max_energy); cwrite_16 (fh, FleetPtr->loc.x); @@ -250,13 +250,11 @@ SaveGroupQueue (SAVEBUF *fh, QUEUE *pQueue) cwrite_16 (fh, GroupPtr->group_counter); cwrite_8 (fh, GroupPtr->race_id); - assert (GroupPtr->sys_loc < 0x10 && GroupPtr->task < 0x10); - cwrite_8 (fh, MAKE_BYTE (GroupPtr->sys_loc, GroupPtr->task)); - /* was var2 */ + cwrite_8 (fh, GroupPtr->sys_loc); + cwrite_8 (fh, GroupPtr->task); cwrite_8 (fh, GroupPtr->in_system); /* was crew_level */ - assert (GroupPtr->dest_loc < 0x10 && GroupPtr->orbit_pos < 0x10); - cwrite_8 (fh, MAKE_BYTE (GroupPtr->dest_loc, GroupPtr->orbit_pos)); - /* was energy_level */ + cwrite_8 (fh, GroupPtr->dest_loc); + cwrite_8 (fh, GroupPtr->orbit_pos); cwrite_8 (fh, GroupPtr->group_id); /* was max_energy */ cwrite_16 (fh, GroupPtr->loc.x); cwrite_16 (fh, GroupPtr->loc.y); @@ -385,8 +383,8 @@ static BOOLEAN SaveSummary (const SUMMARY_DESC *SummPtr, void *fp) { if ( - write_32 (fp, SAVE_MAGIC) != 1 || - write_32 (fp, SUMMARY_MAGIC) != 1 || + write_32 (fp, SAVEFILE_TAG) != 1 || + write_32 (fp, SUMMARY_TAG) != 1 || write_32 (fp, 160 + strlen(SummPtr->SaveName)) != 1 ) return FALSE; @@ -773,7 +771,7 @@ SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name) success = SaveSummary (SummPtr, out_fp); // Then write the rest of the data. - if (success && write_32 (out_fp, OMNIBUS_MAGIC) != 1) + if (success && write_32 (out_fp, OMNIBUS_TAG) != 1) success = FALSE; if (success && write_32 (out_fp, flen) != 1) success = FALSE; diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index 62602eb90..d96669c63 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -31,11 +31,13 @@ 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 // "UMS\x01": UQM Save version 1 -#define SUMMARY_MAGIC 0x6d6d7553 // "Summ": Summary. Must be first! -#define OMNIBUS_MAGIC 0x696e6d4f // "Omni": All data, uncompressed #define SAVE_NAME_SIZE 64 +// The savefile tag numbers. +#define SAVEFILE_TAG 0x01534d55 // "UMS\x01": UQM Save version 1 +#define OMNIBUS_TAG 0x696e6d4f // "Omni": All data, uncompressed +#define SUMMARY_TAG 0x6d6d7553 // "Summ": Summary. Must be first! + typedef struct { SIS_STATE SS; From 7138aeca64840a107843265aaeb83717b57a5d89 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sun, 13 Oct 2013 05:07:04 -0700 Subject: [PATCH 09/18] Major revision to save format This accomplishes what I set out to do, but doesn't actually solve the problem well enough given what I learned while doing it. Documentation forthcoming in future commit. --- sc2/src/uqm/load.c | 317 +++++++++----------- sc2/src/uqm/save.c | 733 ++++++++++++++++++++------------------------- sc2/src/uqm/save.h | 13 +- 3 files changed, 474 insertions(+), 589 deletions(-) diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index 14234ee3c..4a4953c62 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -126,26 +126,9 @@ read_a16 (void *fp, UWORD *ar, COUNT count) } static void -LoadEmptyQueue (void *fh) +LoadShipQueue (void *fh, QUEUE *pQueue, DWORD size) { - COUNT num_links; - - read_16 (fh, &num_links); - if (num_links) - { - log_add (log_Error, "LoadEmptyQueue(): BUG: the queue is not empty!"); -#ifdef DEBUG - explode (); -#endif - } -} - -static void -LoadShipQueue (void *fh, QUEUE *pQueue) -{ - COUNT num_links; - - read_16 (fh, &num_links); + COUNT num_links = size / 11; while (num_links--) { @@ -172,11 +155,9 @@ LoadShipQueue (void *fh, QUEUE *pQueue) } static void -LoadRaceQueue (void *fh, QUEUE *pQueue) +LoadRaceQueue (void *fh, QUEUE *pQueue, DWORD size) { - COUNT num_links; - - read_16 (fh, &num_links); + COUNT num_links = size / 30; while (num_links--) { @@ -214,11 +195,9 @@ LoadRaceQueue (void *fh, QUEUE *pQueue) } static void -LoadGroupQueue (void *fh, QUEUE *pQueue) +LoadGroupQueue (void *fh, QUEUE *pQueue, DWORD size) { - COUNT num_links; - - read_16 (fh, &num_links); + COUNT num_links = size / 13; while (num_links--) { @@ -299,9 +278,21 @@ LoadClockState (CLOCK_STATE *ClockPtr, void *fh) read_16s (fh, &ClockPtr->day_in_ticks); } -static void +static BOOLEAN LoadGameState (GAME_STATE *GSPtr, void *fh) { + DWORD magic; + read_32 (fh, &magic); + if (magic != GLOBAL_STATE_TAG) + { + return FALSE; + } + read_32 (fh, &magic); + if (magic != 79) + { + /* Chunk is the wrong size. */ + return FALSE; + } read_8 (fh, &GSPtr->glob_flags); read_8 (fh, &GSPtr->CrewCost); read_8 (fh, &GSPtr->FuelCost); @@ -335,7 +326,23 @@ LoadGameState (GAME_STATE *GSPtr, void *fh) read_32 (fh, &GSPtr->BattleGroupRef); - read_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); + read_32 (fh, &magic); + if (magic != GAME_STATE_TAG) + { + return FALSE; + } + memset (GSPtr->GameState, 0, sizeof (GSPtr->GameState)); + read_32 (fh, &magic); + if (magic > sizeof (GSPtr->GameState)) + { + read_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); + skip_8 (fh, magic - sizeof (GSPtr->GameState)); + } + else + { + read_a8 (fh, GSPtr->GameState, magic); + } + return TRUE; } static BOOLEAN @@ -429,14 +436,33 @@ LoadStarDesc (STAR_DESC *SDPtr, void *fh) read_8 (fh, &SDPtr->Postfix); } +static void +LoadStateFile (int file_type, void *fh, DWORD flen) +{ + GAME_STATE_FILE *fp = OpenStateFile (file_type, "wb"); + char buf[256]; + if (fp) + { + while (flen) + { + COUNT num_bytes; + + num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; + read_a8 (fh, buf, num_bytes); + WriteStateFile (buf, num_bytes, 1, fp); + + flen -= num_bytes; + } + CloseStateFile (fp); + } +} + BOOLEAN LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) { uio_Stream *in_fp; char file[PATH_MAX]; - char buf[256]; SUMMARY_DESC loc_sd; - GAME_STATE_FILE *fp; COUNT num_links; STAR_DESC SD; ACTIVITY Activity; @@ -466,27 +492,6 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) GlobData.SIS_state = SummPtr->SS; - chunk = 0; - while (chunk != OMNIBUS_TAG) - { - 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 == OMNIBUS_TAG) - break; - - log_add (log_Debug, "Skipping chunk of tag %08X (size %u)", chunk, chunkSize); - if (skip_8(in_fp, chunkSize) != 1) - return FALSE; - } - ReinitQueue (&GLOBAL (GameClock.event_q)); ReinitQueue (&GLOBAL (encounter_q)); ReinitQueue (&GLOBAL (ip_group_q)); @@ -495,140 +500,104 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) memset (&GLOBAL (GameState[0]), 0, sizeof (GLOBAL (GameState))); Activity = GLOBAL (CurrentActivity); - LoadGameState (&GlobData.Game_state, in_fp); + if (!LoadGameState (&GlobData.Game_state, in_fp)) + { + res_CloseResFile (in_fp); + return FALSE; + } NextActivity = GLOBAL (CurrentActivity); GLOBAL (CurrentActivity) = Activity; - LoadRaceQueue (in_fp, &GLOBAL (avail_race_q)); - // START_INTERPLANETARY is only set when saving from Homeworld - // encounter screen. When the game is loaded, the - // GenerateOrbitalFunction for the current star system will - // create the encounter anew and populate the npc queue. - if (!(NextActivity & START_INTERPLANETARY)) + chunk = 0; + while (TRUE) { - if (NextActivity & START_ENCOUNTER) - LoadShipQueue (in_fp, &GLOBAL (npc_built_ship_q)); - else if (LOBYTE (NextActivity) == IN_INTERPLANETARY) - // XXX: Technically, this queue does not need to be - // saved/loaded at all. IP groups will be reloaded - // from group state files. But the original code did, - // and so will we until we can prove we do not need to. - LoadGroupQueue (in_fp, &GLOBAL (ip_group_q)); - else - // XXX: The empty queue read is only needed to maintain - // the savegame compatibility - LoadEmptyQueue (in_fp); - } - LoadShipQueue (in_fp, &GLOBAL (built_ship_q)); - - // Load the game events (compressed) - read_16 (in_fp, &num_links); - { -#ifdef DEBUG_LOAD - log_add (log_Debug, "EVENTS:"); -#endif /* DEBUG_LOAD */ - while (num_links--) + if (read_32(in_fp, &chunk) != 1) { - HEVENT hEvent; - EVENT *EventPtr; + break; + } + if (read_32(in_fp, &chunkSize) != 1) + { + res_CloseResFile (in_fp); + return FALSE; + } + switch (chunk) + { + case RACE_Q_TAG: + LoadRaceQueue (in_fp, &GLOBAL (avail_race_q), chunkSize); + break; + case IP_GRP_Q_TAG: + LoadGroupQueue (in_fp, &GLOBAL (ip_group_q), chunkSize); + break; + case ENCOUNTERS_TAG: + num_links = chunkSize / 65; + while (num_links--) + { + HENCOUNTER hEncounter; + ENCOUNTER *EncounterPtr; - hEvent = AllocEvent (); - LockEvent (hEvent, &EventPtr); + hEncounter = AllocEncounter (); + LockEncounter (hEncounter, &EncounterPtr); - LoadEvent (EventPtr, in_fp); + LoadEncounter (EncounterPtr, in_fp); + + UnlockEncounter (hEncounter); + PutEncounter (hEncounter); + } + break; + case EVENTS_TAG: + num_links = chunkSize / 5; +#ifdef DEBUG_LOAD + log_add (log_Debug, "EVENTS:"); +#endif /* DEBUG_LOAD */ + while (num_links--) + { + HEVENT hEvent; + EVENT *EventPtr; + + hEvent = AllocEvent (); + LockEvent (hEvent, &EventPtr); + + LoadEvent (EventPtr, in_fp); #ifdef DEBUG_LOAD - log_add (log_Debug, "\t%u/%u/%u -- %u", - EventPtr->month_index, - EventPtr->day_index, - EventPtr->year_index, - EventPtr->func_index); + log_add (log_Debug, "\t%u/%u/%u -- %u", + EventPtr->month_index, + EventPtr->day_index, + EventPtr->year_index, + EventPtr->func_index); #endif /* DEBUG_LOAD */ - UnlockEvent (hEvent); - PutEvent (hEvent); + UnlockEvent (hEvent); + PutEvent (hEvent); + } + break; + case STAR_TAG: + LoadStarDesc (&SD, in_fp); + break; + case NPC_SHIP_Q_TAG: + LoadShipQueue (in_fp, &GLOBAL (npc_built_ship_q), chunkSize); + break; + case SHIP_Q_TAG: + LoadShipQueue (in_fp, &GLOBAL (built_ship_q), chunkSize); + break; + case STAR_SF_TAG: + LoadStateFile (STARINFO_FILE, in_fp, chunkSize); + break; + case DEFGRP_SF_TAG: + LoadStateFile (DEFGRPINFO_FILE, in_fp, chunkSize); + break; + case RANDGRP_SF_TAG: + LoadStateFile (RANDGRPINFO_FILE, in_fp, chunkSize); + break; + default: + log_add (log_Debug, "Skipping chunk of tag %08X (size %u)", chunk, chunkSize); + if (skip_8(in_fp, chunkSize) != 1) + { + res_CloseResFile (in_fp); + return FALSE; + } + break; } } - - // Load the encounters (black globes in HS/QS (compressed)) - read_16 (in_fp, &num_links); - { - while (num_links--) - { - HENCOUNTER hEncounter; - ENCOUNTER *EncounterPtr; - - hEncounter = AllocEncounter (); - LockEncounter (hEncounter, &EncounterPtr); - - LoadEncounter (EncounterPtr, in_fp); - - UnlockEncounter (hEncounter); - PutEncounter (hEncounter); - } - } - - // Copy the star info file from the compressed stream - fp = OpenStateFile (STARINFO_FILE, "wb"); - if (fp) - { - DWORD flen; - - read_32 (in_fp, &flen); - while (flen) - { - COUNT num_bytes; - - num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - read_a8 (in_fp, buf, num_bytes); - WriteStateFile (buf, num_bytes, 1, fp); - - flen -= num_bytes; - } - CloseStateFile (fp); - } - - // Copy the defined groupinfo file from the compressed stream - fp = OpenStateFile (DEFGRPINFO_FILE, "wb"); - if (fp) - { - DWORD flen; - - read_32 (in_fp, &flen); - while (flen) - { - COUNT num_bytes; - - num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - read_a8 (in_fp, buf, num_bytes); - WriteStateFile (buf, num_bytes, 1, fp); - - flen -= num_bytes; - } - CloseStateFile (fp); - } - - // Copy the random groupinfo file from the compressed stream - fp = OpenStateFile (RANDGRPINFO_FILE, "wb"); - if (fp) - { - DWORD flen; - - read_32 (in_fp, &flen); - while (flen) - { - COUNT num_bytes; - - num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - read_a8 (in_fp, buf, num_bytes); - WriteStateFile (buf, num_bytes, 1, fp); - - flen -= num_bytes; - } - CloseStateFile (fp); - } - - LoadStarDesc (&SD, in_fp); - res_CloseResFile (in_fp); EncounterGroup = 0; diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index 27505e4cb..51fc56f9c 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -42,118 +42,78 @@ #include "libs/log.h" #include "libs/memlib.h" -typedef struct savebuf_struct -{ - char *buf; - size_t bufsize; - int index, error; -} SAVEBUF; +// Status boolean. If for some insane reason you need to +// save games in different threads, you'll need to +// protect your calls to SaveGame with a mutex. + +// It's arguably over-paranoid to check for error on +// every single write, but this preserves the older +// behavior. + +static BOOLEAN io_ok = TRUE; // XXX: these should handle endian conversions later -static inline COUNT -cwrite_8 (SAVEBUF *fh, BYTE v) -{ - if (!fh) - return 0; - if (!fh->error && fh->index < fh->bufsize) - { - fh->buf[fh->index++] = v; - return 1; - } - fh->error = 1; - return 0; -} - -static inline COUNT -cwrite_16 (SAVEBUF *fh, UWORD v) -{ - if (cwrite_8 (fh, (BYTE)(v & 0xFF)) && - cwrite_8 (fh, (BYTE)((v >> 8) & 0xFF))) - return 1; - return 0; -} - -static inline COUNT -cwrite_32 (SAVEBUF *fh, DWORD v) -{ - if (cwrite_8 (fh, (BYTE)(v & 0xFF)) && - cwrite_8 (fh, (BYTE)((v >> 8) & 0xFF)) && - cwrite_8 (fh, (BYTE)((v >> 16) & 0xFF)) && - cwrite_8 (fh, (BYTE)((v >> 24) & 0xFF))) - return 1; - return 0; -} - -static inline COUNT -cwrite_a8 (SAVEBUF *fh, const BYTE *ar, COUNT count) -{ - int i; - for (i = 0; i < count; ++i) - if (!cwrite_8 (fh, ar[i])) - return 0; - return 1; -} - -static inline size_t +static inline void write_8 (void *fp, BYTE v) { - return WriteResFile (&v, 1, 1, fp); + if (io_ok) + if (WriteResFile (&v, 1, 1, fp) != 1) + io_ok = FALSE; } -static inline size_t +static inline void write_16 (void *fp, UWORD v) { - return WriteResFile (&v, 2, 1, fp); + if (io_ok) + if (WriteResFile (&v, 2, 1, fp) != 1) + io_ok = FALSE; } -static inline size_t +static inline void write_32 (void *fp, DWORD v) { - return WriteResFile (&v, 4, 1, fp); + if (io_ok) + if (WriteResFile (&v, 4, 1, fp) != 1) + io_ok = FALSE; } -static inline size_t +static inline void write_a8 (void *fp, const BYTE *ar, COUNT count) { - return WriteResFile (ar, 1, count, fp) == count; + if (io_ok) + if (WriteResFile (ar, 1, count, fp) != count) + io_ok = FALSE; } -static inline size_t +static inline void write_str (void *fp, const char *str, COUNT count) { // no type conversion needed for strings - return write_a8 (fp, (const BYTE *)str, count); + write_a8 (fp, (const BYTE *)str, count); } -static inline size_t +static inline void write_a16 (void *fp, const UWORD *ar, COUNT count) { for ( ; count > 0; --count, ++ar) { - if (write_16 (fp, *ar) != 1) - return 0; + if (!io_ok) + break; + write_16 (fp, *ar); } - return 1; } static void -SaveEmptyQueue (SAVEBUF *fh) -{ - COUNT num_links = 0; - - // Write the number of entries in the queue. - cwrite_16 (fh, num_links); -} - -static void -SaveShipQueue (SAVEBUF *fh, QUEUE *pQueue) +SaveShipQueue (uio_Stream *fh, QUEUE *pQueue, DWORD tag) { COUNT num_links; HSHIPFRAG hStarShip; - // Write the number of entries in the queue. num_links = CountLinks (pQueue); - cwrite_16 (fh, num_links); + if (num_links == 0) + return; + write_32 (fh, tag); + write_32 (fh, num_links * 11); // Size of chunk: each entry is 11 bytes long. hStarShip = GetHeadLink (pQueue); while (num_links--) @@ -168,16 +128,16 @@ SaveShipQueue (SAVEBUF *fh, QUEUE *pQueue) Index = FragPtr->race_id; // Write the number identifying this ship type. // See races.h; look for the enum containing NUM_AVAILABLE_RACES. - cwrite_16 (fh, Index); + write_16 (fh, Index); // Write SHIP_FRAGMENT elements - cwrite_8 (fh, FragPtr->captains_name_index); - cwrite_8 (fh, FragPtr->race_id); - cwrite_8 (fh, FragPtr->index); - cwrite_16 (fh, FragPtr->crew_level); - cwrite_16 (fh, FragPtr->max_crew); - cwrite_8 (fh, FragPtr->energy_level); - cwrite_8 (fh, FragPtr->max_energy); + write_8 (fh, FragPtr->captains_name_index); + write_8 (fh, FragPtr->race_id); + write_8 (fh, FragPtr->index); + write_16 (fh, FragPtr->crew_level); + write_16 (fh, FragPtr->max_crew); + write_8 (fh, FragPtr->energy_level); + write_8 (fh, FragPtr->max_energy); UnlockShipFrag (pQueue, hStarShip); hStarShip = hNextShip; @@ -185,14 +145,17 @@ SaveShipQueue (SAVEBUF *fh, QUEUE *pQueue) } static void -SaveRaceQueue (SAVEBUF *fh, QUEUE *pQueue) +SaveRaceQueue (uio_Stream *fh, QUEUE *pQueue) { COUNT num_links; HFLEETINFO hFleet; - // Write the number of entries in the queue. num_links = CountLinks (pQueue); - cwrite_16 (fh, num_links); + if (num_links == 0) + return; + write_32 (fh, RACE_Q_TAG); + // Write chunk size: 30 bytes per entry + write_32 (fh, num_links * 30); hFleet = GetHeadLink (pQueue); while (num_links--) @@ -206,27 +169,27 @@ SaveRaceQueue (SAVEBUF *fh, QUEUE *pQueue) Index = GetIndexFromStarShip (pQueue, hFleet); // The index is the position in the queue. - cwrite_16 (fh, Index); + write_16 (fh, Index); // Write FLEET_INFO elements - cwrite_16 (fh, FleetPtr->allied_state); - cwrite_8 (fh, FleetPtr->days_left); - cwrite_8 (fh, FleetPtr->growth_fract); - cwrite_16 (fh, FleetPtr->crew_level); - cwrite_16 (fh, FleetPtr->max_crew); - cwrite_8 (fh, FleetPtr->growth); - cwrite_8 (fh, FleetPtr->max_energy); - cwrite_16 (fh, FleetPtr->loc.x); - cwrite_16 (fh, FleetPtr->loc.y); + write_16 (fh, FleetPtr->allied_state); + write_8 (fh, FleetPtr->days_left); + write_8 (fh, FleetPtr->growth_fract); + write_16 (fh, FleetPtr->crew_level); + write_16 (fh, FleetPtr->max_crew); + write_8 (fh, FleetPtr->growth); + write_8 (fh, FleetPtr->max_energy); + write_16 (fh, FleetPtr->loc.x); + write_16 (fh, FleetPtr->loc.y); - cwrite_16 (fh, FleetPtr->actual_strength); - cwrite_16 (fh, FleetPtr->known_strength); - cwrite_16 (fh, FleetPtr->known_loc.x); - cwrite_16 (fh, FleetPtr->known_loc.y); - cwrite_8 (fh, FleetPtr->growth_err_term); - cwrite_8 (fh, FleetPtr->func_index); - cwrite_16 (fh, FleetPtr->dest_loc.x); - cwrite_16 (fh, FleetPtr->dest_loc.y); + write_16 (fh, FleetPtr->actual_strength); + write_16 (fh, FleetPtr->known_strength); + write_16 (fh, FleetPtr->known_loc.x); + write_16 (fh, FleetPtr->known_loc.y); + write_8 (fh, FleetPtr->growth_err_term); + write_8 (fh, FleetPtr->func_index); + write_16 (fh, FleetPtr->dest_loc.x); + write_16 (fh, FleetPtr->dest_loc.y); UnlockFleetInfo (pQueue, hFleet); hFleet = hNextFleet; @@ -234,12 +197,16 @@ SaveRaceQueue (SAVEBUF *fh, QUEUE *pQueue) } static void -SaveGroupQueue (SAVEBUF *fh, QUEUE *pQueue) +SaveGroupQueue (uio_Stream *fh, QUEUE *pQueue) { HIPGROUP hGroup, hNextGroup; + COUNT num_links; - // Write the number of entries in the queue. - cwrite_16 (fh, CountLinks (pQueue)); + num_links = CountLinks (pQueue); + if (num_links == 0) + return; + write_32 (fh, IP_GRP_Q_TAG); + write_32 (fh, num_links * 13); // 13 bytes per element right now for (hGroup = GetHeadLink (pQueue); hGroup; hGroup = hNextGroup) { @@ -248,177 +215,220 @@ SaveGroupQueue (SAVEBUF *fh, QUEUE *pQueue) GroupPtr = LockIpGroup (pQueue, hGroup); hNextGroup = _GetSuccLink (GroupPtr); - cwrite_16 (fh, GroupPtr->group_counter); - cwrite_8 (fh, GroupPtr->race_id); - cwrite_8 (fh, GroupPtr->sys_loc); - cwrite_8 (fh, GroupPtr->task); - cwrite_8 (fh, GroupPtr->in_system); /* was crew_level */ - cwrite_8 (fh, GroupPtr->dest_loc); - cwrite_8 (fh, GroupPtr->orbit_pos); - cwrite_8 (fh, GroupPtr->group_id); /* was max_energy */ - cwrite_16 (fh, GroupPtr->loc.x); - cwrite_16 (fh, GroupPtr->loc.y); + write_16 (fh, GroupPtr->group_counter); + write_8 (fh, GroupPtr->race_id); + write_8 (fh, GroupPtr->sys_loc); + write_8 (fh, GroupPtr->task); + write_8 (fh, GroupPtr->in_system); /* was crew_level */ + write_8 (fh, GroupPtr->dest_loc); + write_8 (fh, GroupPtr->orbit_pos); + write_8 (fh, GroupPtr->group_id); /* was max_energy */ + write_16 (fh, GroupPtr->loc.x); + write_16 (fh, GroupPtr->loc.y); UnlockIpGroup (pQueue, hGroup); } } static void -SaveEncounter (const ENCOUNTER *EncounterPtr, SAVEBUF *fh) +SaveEncounters (uio_Stream *fh) { - COUNT i; + COUNT num_links; + HENCOUNTER hEncounter; + num_links = CountLinks (&GLOBAL (encounter_q)); + if (num_links == 0) + return; + write_32 (fh, ENCOUNTERS_TAG); + write_32 (fh, 65 * num_links); - cwrite_16 (fh, EncounterPtr->transition_state); - cwrite_16 (fh, EncounterPtr->origin.x); - cwrite_16 (fh, EncounterPtr->origin.y); - cwrite_16 (fh, EncounterPtr->radius); - // former STAR_DESC fields - cwrite_16 (fh, EncounterPtr->loc_pt.x); - cwrite_16 (fh, EncounterPtr->loc_pt.y); - cwrite_8 (fh, EncounterPtr->race_id); - cwrite_8 (fh, EncounterPtr->num_ships); - cwrite_8 (fh, EncounterPtr->flags); - - // Save each entry in the BRIEF_SHIP_INFO array - for (i = 0; i < MAX_HYPER_SHIPS; i++) + hEncounter = GetHeadLink (&GLOBAL (encounter_q)); + while (num_links--) { - const BRIEF_SHIP_INFO *ShipInfo = &EncounterPtr->ShipList[i]; + HENCOUNTER hNextEncounter; + ENCOUNTER *EncounterPtr; + COUNT i; - cwrite_8 (fh, ShipInfo->race_id); - cwrite_16 (fh, ShipInfo->crew_level); - cwrite_16 (fh, ShipInfo->max_crew); - cwrite_8 (fh, ShipInfo->max_energy); + LockEncounter (hEncounter, &EncounterPtr); + hNextEncounter = GetSuccEncounter (EncounterPtr); + + write_16 (fh, EncounterPtr->transition_state); + write_16 (fh, EncounterPtr->origin.x); + write_16 (fh, EncounterPtr->origin.y); + write_16 (fh, EncounterPtr->radius); + // former STAR_DESC fields + write_16 (fh, EncounterPtr->loc_pt.x); + write_16 (fh, EncounterPtr->loc_pt.y); + write_8 (fh, EncounterPtr->race_id); + write_8 (fh, EncounterPtr->num_ships); + write_8 (fh, EncounterPtr->flags); + + // Save each entry in the BRIEF_SHIP_INFO array + for (i = 0; i < MAX_HYPER_SHIPS; i++) + { + const BRIEF_SHIP_INFO *ShipInfo = &EncounterPtr->ShipList[i]; + + write_8 (fh, ShipInfo->race_id); + write_16 (fh, ShipInfo->crew_level); + write_16 (fh, ShipInfo->max_crew); + write_8 (fh, ShipInfo->max_energy); + } + + // Save the stuff after the BRIEF_SHIP_INFO array + write_32 (fh, EncounterPtr->log_x); + write_32 (fh, EncounterPtr->log_y); + + UnlockEncounter (hEncounter); + hEncounter = hNextEncounter; } - - // Save the stuff after the BRIEF_SHIP_INFO array - cwrite_32 (fh, EncounterPtr->log_x); - cwrite_32 (fh, EncounterPtr->log_y); } static void -SaveEvent (const EVENT *EventPtr, SAVEBUF *fh) +SaveEvents (uio_Stream *fh) { - cwrite_8 (fh, EventPtr->day_index); - cwrite_8 (fh, EventPtr->month_index); - cwrite_16 (fh, EventPtr->year_index); - cwrite_8 (fh, EventPtr->func_index); + COUNT num_links; + HEVENT hEvent; + num_links = CountLinks (&GLOBAL (GameClock.event_q)); + if (num_links == 0) + return; + write_32 (fh, EVENTS_TAG); + write_32 (fh, num_links * 5); /* Event chunks are five bytes each */ + + hEvent = GetHeadLink (&GLOBAL (GameClock.event_q)); + while (num_links--) + { + HEVENT hNextEvent; + EVENT *EventPtr; + + LockEvent (hEvent, &EventPtr); + hNextEvent = GetSuccEvent (EventPtr); + + write_8 (fh, EventPtr->day_index); + write_8 (fh, EventPtr->month_index); + write_16 (fh, EventPtr->year_index); + write_8 (fh, EventPtr->func_index); + + UnlockEvent (hEvent); + hEvent = hNextEvent; + } } +/* The clock state is folded in with the game state chunk. */ static void -SaveClockState (const CLOCK_STATE *ClockPtr, SAVEBUF *fh) +SaveClockState (const CLOCK_STATE *ClockPtr, uio_Stream *fh) { - cwrite_8 (fh, ClockPtr->day_index); - cwrite_8 (fh, ClockPtr->month_index); - cwrite_16 (fh, ClockPtr->year_index); - cwrite_16 (fh, ClockPtr->tick_count); - cwrite_16 (fh, ClockPtr->day_in_ticks); + write_8 (fh, ClockPtr->day_index); + write_8 (fh, ClockPtr->month_index); + write_16 (fh, ClockPtr->year_index); + write_16 (fh, ClockPtr->tick_count); + write_16 (fh, ClockPtr->day_in_ticks); } +/* Save out the game state chunks. There are two of these; the Global + * State chunk is fixed size, but the Game State tag can be extended + * by modders. */ static void -SaveGameState (const GAME_STATE *GSPtr, SAVEBUF *fh) +SaveGameState (const GAME_STATE *GSPtr, uio_Stream *fh) { - cwrite_8 (fh, GSPtr->glob_flags); - cwrite_8 (fh, GSPtr->CrewCost); - cwrite_8 (fh, GSPtr->FuelCost); - cwrite_a8 (fh, GSPtr->ModuleCost, NUM_MODULES); - cwrite_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); - cwrite_16 (fh, GSPtr->CurrentActivity); + write_32 (fh, GLOBAL_STATE_TAG); + write_32 (fh, 79); + write_8 (fh, GSPtr->glob_flags); + write_8 (fh, GSPtr->CrewCost); + write_8 (fh, GSPtr->FuelCost); + write_a8 (fh, GSPtr->ModuleCost, NUM_MODULES); + write_a8 (fh, GSPtr->ElementWorth, NUM_ELEMENT_CATEGORIES); + write_16 (fh, GSPtr->CurrentActivity); SaveClockState (&GSPtr->GameClock, fh); - cwrite_16 (fh, GSPtr->autopilot.x); - cwrite_16 (fh, GSPtr->autopilot.y); - cwrite_16 (fh, GSPtr->ip_location.x); - cwrite_16 (fh, GSPtr->ip_location.y); + write_16 (fh, GSPtr->autopilot.x); + write_16 (fh, GSPtr->autopilot.y); + write_16 (fh, GSPtr->ip_location.x); + write_16 (fh, GSPtr->ip_location.y); /* STAMP ShipStamp */ - cwrite_16 (fh, GSPtr->ShipStamp.origin.x); - cwrite_16 (fh, GSPtr->ShipStamp.origin.y); - cwrite_16 (fh, GSPtr->ShipFacing); - cwrite_8 (fh, GSPtr->ip_planet); - cwrite_8 (fh, GSPtr->in_orbit); + write_16 (fh, GSPtr->ShipStamp.origin.x); + write_16 (fh, GSPtr->ShipStamp.origin.y); + write_16 (fh, GSPtr->ShipFacing); + write_8 (fh, GSPtr->ip_planet); + write_8 (fh, GSPtr->in_orbit); /* VELOCITY_DESC velocity */ - cwrite_16 (fh, GSPtr->velocity.TravelAngle); - cwrite_16 (fh, GSPtr->velocity.vector.width); - cwrite_16 (fh, GSPtr->velocity.vector.height); - cwrite_16 (fh, GSPtr->velocity.fract.width); - cwrite_16 (fh, GSPtr->velocity.fract.height); - cwrite_16 (fh, GSPtr->velocity.error.width); - cwrite_16 (fh, GSPtr->velocity.error.height); - cwrite_16 (fh, GSPtr->velocity.incr.width); - cwrite_16 (fh, GSPtr->velocity.incr.height); + write_16 (fh, GSPtr->velocity.TravelAngle); + write_16 (fh, GSPtr->velocity.vector.width); + write_16 (fh, GSPtr->velocity.vector.height); + write_16 (fh, GSPtr->velocity.fract.width); + write_16 (fh, GSPtr->velocity.fract.height); + write_16 (fh, GSPtr->velocity.error.width); + write_16 (fh, GSPtr->velocity.error.height); + write_16 (fh, GSPtr->velocity.incr.width); + write_16 (fh, GSPtr->velocity.incr.height); - cwrite_32 (fh, GSPtr->BattleGroupRef); + write_32 (fh, GSPtr->BattleGroupRef); - cwrite_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); + /* The Game state bits. Vanilla UQM uses 155 bytes here. */ + write_32 (fh, GAME_STATE_TAG); + write_32 (fh, sizeof (GSPtr->GameState)); + write_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); } -static BOOLEAN +/* This is folded into the Summary chunk */ +static void SaveSisState (const SIS_STATE *SSPtr, void *fp) { - if ( - write_32 (fp, SSPtr->log_x) != 1 || - write_32 (fp, SSPtr->log_y) != 1 || - write_32 (fp, SSPtr->ResUnits) != 1 || - write_32 (fp, SSPtr->FuelOnBoard) != 1 || - write_16 (fp, SSPtr->CrewEnlisted) != 1 || - write_16 (fp, SSPtr->TotalElementMass) != 1 || - write_16 (fp, SSPtr->TotalBioMass) != 1 || - write_a8 (fp, SSPtr->ModuleSlots, NUM_MODULE_SLOTS) != 1 || - write_a8 (fp, SSPtr->DriveSlots, NUM_DRIVE_SLOTS) != 1 || - write_a8 (fp, SSPtr->JetSlots, NUM_JET_SLOTS) != 1 || - write_8 (fp, SSPtr->NumLanders) != 1 || - write_a16 (fp, SSPtr->ElementAmounts, NUM_ELEMENT_CATEGORIES) != 1 || + write_32 (fp, SSPtr->log_x); + write_32 (fp, SSPtr->log_y); + write_32 (fp, SSPtr->ResUnits); + write_32 (fp, SSPtr->FuelOnBoard); + write_16 (fp, SSPtr->CrewEnlisted); + write_16 (fp, SSPtr->TotalElementMass); + write_16 (fp, SSPtr->TotalBioMass); + write_a8 (fp, SSPtr->ModuleSlots, NUM_MODULE_SLOTS); + write_a8 (fp, SSPtr->DriveSlots, NUM_DRIVE_SLOTS); + write_a8 (fp, SSPtr->JetSlots, NUM_JET_SLOTS); + write_8 (fp, SSPtr->NumLanders); + write_a16 (fp, SSPtr->ElementAmounts, NUM_ELEMENT_CATEGORIES); - 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 - ) - return FALSE; - else - return TRUE; + write_str (fp, SSPtr->ShipName, SIS_NAME_SIZE); + write_str (fp, SSPtr->CommanderName, SIS_NAME_SIZE); + write_str (fp, SSPtr->PlanetName, SIS_NAME_SIZE); } +/* Write out the Summary Chunk. This is variable length because of the + savegame name */ static BOOLEAN SaveSummary (const SUMMARY_DESC *SummPtr, void *fp) { - if ( - write_32 (fp, SAVEFILE_TAG) != 1 || - write_32 (fp, SUMMARY_TAG) != 1 || - write_32 (fp, 160 + strlen(SummPtr->SaveName)) != 1 - ) - return FALSE; - if (!SaveSisState (&SummPtr->SS, fp)) - return FALSE; + write_32 (fp, SUMMARY_TAG); + write_32 (fp, 160 + strlen(SummPtr->SaveName)); + SaveSisState (&SummPtr->SS, fp); - if ( - write_8 (fp, SummPtr->Activity) != 1 || - write_8 (fp, SummPtr->Flags) != 1 || - write_8 (fp, SummPtr->day_index) != 1 || - write_8 (fp, SummPtr->month_index) != 1 || - write_16 (fp, SummPtr->year_index) != 1 || - write_8 (fp, SummPtr->MCreditLo) != 1 || - write_8 (fp, SummPtr->MCreditHi) != 1 || - write_8 (fp, SummPtr->NumShips) != 1 || - 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, strlen(SummPtr->SaveName)) != 1 - ) - return FALSE; - else - return TRUE; + write_8 (fp, SummPtr->Activity); + write_8 (fp, SummPtr->Flags); + write_8 (fp, SummPtr->day_index); + write_8 (fp, SummPtr->month_index); + write_16 (fp, SummPtr->year_index); + write_8 (fp, SummPtr->MCreditLo); + write_8 (fp, SummPtr->MCreditHi); + write_8 (fp, SummPtr->NumShips); + write_8 (fp, SummPtr->NumDevices); + write_a8 (fp, SummPtr->ShipList, MAX_BUILT_SHIPS); + write_a8 (fp, SummPtr->DeviceList, MAX_EXCLUSIVE_DEVICES); + write_a8 (fp, SummPtr->SaveName, strlen(SummPtr->SaveName)); } +/* Save the Star Description chunk. This is not to be confused with + * the Star *Info* chunk, which records which planetary features you + * have exploited with your lander */ static void -SaveStarDesc (const STAR_DESC *SDPtr, SAVEBUF *fh) +SaveStarDesc (const STAR_DESC *SDPtr, uio_Stream *fh) { - cwrite_16 (fh, SDPtr->star_pt.x); - cwrite_16 (fh, SDPtr->star_pt.y); - cwrite_8 (fh, SDPtr->Type); - cwrite_8 (fh, SDPtr->Index); - cwrite_8 (fh, SDPtr->Prefix); - cwrite_8 (fh, SDPtr->Postfix); + write_32 (fh, STAR_TAG); + write_32 (fh, 8); + write_16 (fh, SDPtr->star_pt.x); + write_16 (fh, SDPtr->star_pt.y); + write_8 (fh, SDPtr->Type); + write_8 (fh, SDPtr->Index); + write_8 (fh, SDPtr->Prefix); + write_8 (fh, SDPtr->Postfix); } static void @@ -578,57 +588,71 @@ SaveFlagshipState (void) } } +static void +SaveStateFile (DWORD statefileId, DWORD tag, uio_Stream *fh) +{ + GAME_STATE_FILE *fp; + char buf[256]; + fp = OpenStateFile (statefileId, "rb"); + if (fp) + { + DWORD flen = LengthStateFile (fp); + write_32 (fh, tag); + write_32 (fh, flen); + while (flen) + { + COUNT num_bytes; + + num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; + ReadStateFile (buf, num_bytes, 1, fp); + write_a8 (fh, buf, num_bytes); + + flen -= num_bytes; + } + CloseStateFile (fp); + } +} + // This function first writes to a memory file, and then writes the whole // lot to the actual save file at once. BOOLEAN SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name) { - BOOLEAN success; - void *out_fp, *h; - SAVEBUF fh_backing, *fh; - - success = TRUE; - fh = &fh_backing; - h = HMalloc (128 * 1024); - if (h == 0) - { - log_add (log_Debug, "Insufficient room for save buffers"); - } + uio_Stream *out_fp; + POINT pt; + STAR_DESC SD; + char file[PATH_MAX]; + if (CurStarDescPtr) + SD = *CurStarDescPtr; else + memset (&SD, 0, sizeof (SD)); + + // XXX: Backup: SaveFlagshipState() overwrites ip_location + pt = GLOBAL (ip_location); + SaveFlagshipState (); + if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY + && !(GLOBAL (CurrentActivity) + & (START_ENCOUNTER | START_INTERPLANETARY))) + PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); + + // Write the memory file to the actual savegame file. + sprintf (file, "uqmsave.%02u", which_game); + if ((out_fp = res_OpenResFile (saveDir, file, "wb"))) { - GAME_STATE_FILE *fp; - DWORD flen; - COUNT num_links; - POINT pt; - STAR_DESC SD; - char buf[256], file[PATH_MAX]; + io_ok = TRUE; + write_32 (out_fp, SAVEFILE_TAG); - fh->buf = h; - fh->bufsize = 128 * 1024; - fh->index = 0; - fh->error = 0; - success = TRUE; - if (CurStarDescPtr) - SD = *CurStarDescPtr; - else - memset (&SD, 0, sizeof (SD)); + PrepareSummary (SummPtr, name); + SaveSummary (SummPtr, out_fp); - // XXX: Backup: SaveFlagshipState() overwrites ip_location - pt = GLOBAL (ip_location); - SaveFlagshipState (); - if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY - && !(GLOBAL (CurrentActivity) - & (START_ENCOUNTER | START_INTERPLANETARY))) - PutGroupInfo (GROUPS_RANDOM, GROUP_SAVE_IP); - - SaveGameState (&GlobData.Game_state, fh); + SaveGameState (&GlobData.Game_state, out_fp); // XXX: Restore GLOBAL (ip_location) = pt; // Only relevant when loading a game and must be cleaned GLOBAL (in_orbit) = 0; - SaveRaceQueue (fh, &GLOBAL (avail_race_q)); + SaveRaceQueue (out_fp, &GLOBAL (avail_race_q)); // START_INTERPLANETARY is only set when saving from Homeworld // encounter screen. When the game is loaded, the // GenerateOrbitalFunction for the current star system @@ -636,160 +660,41 @@ SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name) if (!(GLOBAL (CurrentActivity) & START_INTERPLANETARY)) { if (GLOBAL (CurrentActivity) & START_ENCOUNTER) - SaveShipQueue (fh, &GLOBAL (npc_built_ship_q)); + SaveShipQueue (out_fp, &GLOBAL (npc_built_ship_q), NPC_SHIP_Q_TAG); else if (LOBYTE (GLOBAL (CurrentActivity)) == IN_INTERPLANETARY) // XXX: Technically, this queue does not need to be // saved/loaded at all. IP groups will be reloaded // from group state files. But the original code did, // and so will we until we can prove we do not need to. - SaveGroupQueue (fh, &GLOBAL (ip_group_q)); - else - // XXX: empty queue write-out is only needed to maintain - // the savegame compatibility - SaveEmptyQueue (fh); + SaveGroupQueue (out_fp, &GLOBAL (ip_group_q)); } - SaveShipQueue (fh, &GLOBAL (built_ship_q)); + SaveShipQueue (out_fp, &GLOBAL (built_ship_q), SHIP_Q_TAG); - // Save the number of game events (compressed). - num_links = CountLinks (&GLOBAL (GameClock.event_q)); - cwrite_16 (fh, num_links); - // Save the game events themselves (compressed): + // Save the game event chunk + SaveEvents (out_fp); + + // Save the encounter chunk (black globes in HS/QS) + SaveEncounters (out_fp); + + // Save out the state file chunks. + SaveStateFile (STARINFO_FILE, STAR_SF_TAG, out_fp); + SaveStateFile (DEFGRPINFO_FILE, DEFGRP_SF_TAG, out_fp); + SaveStateFile (RANDGRPINFO_FILE, RANDGRP_SF_TAG, out_fp); + + // Save out the Star Descriptor + SaveStarDesc (&SD, out_fp); + + res_CloseResFile (out_fp); + if (!io_ok) { - HEVENT hEvent; - - hEvent = GetHeadLink (&GLOBAL (GameClock.event_q)); - while (num_links--) - { - HEVENT hNextEvent; - EVENT *EventPtr; - - LockEvent (hEvent, &EventPtr); - hNextEvent = GetSuccEvent (EventPtr); - - SaveEvent (EventPtr, fh); - - UnlockEvent (hEvent); - hEvent = hNextEvent; - } + DeleteResFile(saveDir, file); + return FALSE; } - - // Save the number of encounters (black globes in HS/QS (compressed)) - num_links = CountLinks (&GLOBAL (encounter_q)); - // Save the encounters themselves (compressed): - cwrite_16 (fh, num_links); - { - HENCOUNTER hEncounter; - - hEncounter = GetHeadLink (&GLOBAL (encounter_q)); - while (num_links--) - { - HENCOUNTER hNextEncounter; - ENCOUNTER *EncounterPtr; - - LockEncounter (hEncounter, &EncounterPtr); - hNextEncounter = GetSuccEncounter (EncounterPtr); - - SaveEncounter (EncounterPtr, fh); - - UnlockEncounter (hEncounter); - hEncounter = hNextEncounter; - } - } - - // Copy the star info file to the memory file (compressed). - fp = OpenStateFile (STARINFO_FILE, "rb"); - if (fp) - { - flen = LengthStateFile (fp); - // Write the uncompressed size. - cwrite_32 (fh, flen); - while (flen) - { - COUNT num_bytes; - - num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - ReadStateFile (buf, num_bytes, 1, fp); - cwrite_a8 (fh, buf, num_bytes); - - flen -= num_bytes; - } - CloseStateFile (fp); - } - - // Copy the defined groupinfo file into the memory file (compressed) - fp = OpenStateFile (DEFGRPINFO_FILE, "rb"); - if (fp) - { - flen = LengthStateFile (fp); - // Write the uncompressed size. - cwrite_32 (fh, flen); - while (flen) - { - COUNT num_bytes; - - num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - ReadStateFile (buf, num_bytes, 1, fp); - cwrite_a8 (fh, buf, num_bytes); - - flen -= num_bytes; - } - CloseStateFile (fp); - } - - // Copy the random groupinfo file into the memory file (compressed) - fp = OpenStateFile (RANDGRPINFO_FILE, "rb"); - if (fp) - { - flen = LengthStateFile (fp); - // Write the uncompressed size. - cwrite_32 (fh, flen); - while (flen) - { - COUNT num_bytes; - - num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - ReadStateFile (buf, num_bytes, 1, fp); - cwrite_a8 (fh, buf, num_bytes); - - flen -= num_bytes; - } - CloseStateFile (fp); - } - - // Write the current star desc into the memory file (compressed). - SaveStarDesc (&SD, fh); - - flen = fh->index; - - // Write the memory file to the actual savegame file. - sprintf (file, "uqmsave.%02u", which_game); - log_add (log_Debug, "'%s' is %u bytes long", file, - flen + 180 + 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, OMNIBUS_TAG) != 1) - success = FALSE; - if (success && write_32 (out_fp, flen) != 1) - success = FALSE; - if (success && WriteResFile (h, flen, 1, out_fp) != 1) - success = FALSE; - - if (res_CloseResFile ((uio_Stream *)out_fp) == 0) - success = FALSE; - - } - else - success = FALSE; - - if (!success) - DeleteResFile (saveDir, file); + } + else + { + return FALSE; } - HFree (h); - - return (success); + return TRUE; } diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index d96669c63..3d0ac1329 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -35,8 +35,19 @@ extern "C" { // The savefile tag numbers. #define SAVEFILE_TAG 0x01534d55 // "UMS\x01": UQM Save version 1 -#define OMNIBUS_TAG 0x696e6d4f // "Omni": All data, uncompressed #define SUMMARY_TAG 0x6d6d7553 // "Summ": Summary. Must be first! +#define GLOBAL_STATE_TAG 0x74536c47 // "GlSt": Global State. Must be 2nd! +#define GAME_STATE_TAG 0x74536d47 // "GmSt": Game State Bits. Must be 3rd! +#define EVENTS_TAG 0x73747645 // "Evts": Events +#define ENCOUNTERS_TAG 0x74636e45 // "Enct": Encounters +#define RACE_Q_TAG 0x51636152 // "RacQ": avail_race_q +#define IP_GRP_Q_TAG 0x51704749 // "IGpQ": ip_group_q +#define NPC_SHIP_Q_TAG 0x5163704e // "NpcQ": npc_built_ship_q +#define SHIP_Q_TAG 0x51706853 // "ShpQ": built_ship_q +#define STAR_TAG 0x72617453 // "Star": STAR_DESC +#define STAR_SF_TAG 0x46534953 // "SISF": Star Info State File +#define DEFGRP_SF_TAG 0x46534744 // "DGSF": Defined Group State File +#define RANDGRP_SF_TAG 0x46534752 // "RGSF": Random Group State File typedef struct { From 634ec1ae7d377f0ac5a3202c8d1d288852a7fc9d Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sun, 13 Oct 2013 05:44:09 -0700 Subject: [PATCH 10/18] Document the new savefile format. --- sc2/doc/devel/savefile | 145 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 sc2/doc/devel/savefile diff --git a/sc2/doc/devel/savefile b/sc2/doc/devel/savefile new file mode 100644 index 000000000..4a2c3acd3 --- /dev/null +++ b/sc2/doc/devel/savefile @@ -0,0 +1,145 @@ + SAVEFILE FORMAT + --------------- + +This document represents a work in progress. The save format described +here will evolve before finalization for 0.8. + +The old save file format used a custom compressor and was very finicky +about padding and alignment for machines that were not even in use +anymore. It was also extremely fragile and hard for modders to extend. + +The new save format seeks to alleviate these problems. + +GENERAL FORMAT +-------------- + +All multibyte values are little-endian. There are no alignment +restrictions inherent in the format. + +The save file begins with a 32-bit identifier and version number (a +"magic number") that identifies it as a particular version of an UQM +save file. If the format changes in a way that older versions of UQM +cannot read it, either the identifier or the version number should +change. + +The vanilla UQM system has a save number of 0x01534d55, which, if +interpreted as a byte stream is UMS (Ur-quan Masters Save) and a +binary 1 (version 1). Vanilla UQM reserves the tags "UMSx" for all x +for use to evolve the core save format. Modders are encouraged, but +not required, to also use the fourth bit as a version number. + +Following the 32-bit file identifier comes a series of chunks. All +chunks have the same general format: a 32-bit tag, similar to that of +the file as a whole, followed by a 32-bit integer specifying the size +of the chunk, and then that many bytes of data. + +Bytes in a chunk tag should all be in the range 0x20-0x7E -- that is, +they should be printable ASCII characters. Chunks are traditionally +referred to by their tag names. + +Chunks whose tag has a least significant byte in the range 0x41-0x5a, +inclusive---that is to say, whose names start with a capital +letter---are mandatory. If you extend the set of mandatory chunks, you +must increase the version of the file. Tags that do not start with a +capital letter may be ignored by other versions of UQM that otherwise +understand that data version. + +(Why would you want to have ignorable bits of save file? Such chunks +may contain helpful but ancillary information. For instance, at the +time of this writing, there is an outstanding bug that life forms are +un-stunned if you save and load in orbit. One could add a new chunk +that tracks the stunned status of life forms on the planet you're in +orbit around, and just revert to the old behavior if it's not there or +if you're running on a version without that fix. Such an ancillary +data chunk would have a tag like "stun" or "biot".) + +Note also that despite being "mandatory", it is not the case that all +chunks will be present in all save files. Different data is saved out +depending on the situation you saved in. + +Unless otherwise specified, chunks may be stored in any order in the +save file. + + CHUNK INVENTORY + --------------- + +- "Summ": Summary. This chunk must come first. This chunk + carries the flagship configuration information and some overview + information that is displayed on the savegame view screen. It is of + variable length, because the last element of this chunk is the name + of the save as chosen by the user. + +- "GlSt": Global State. This chunk must come second, after + Summ. Represents most of the data in the global state structure in + globdata.h. + +- "GmSt": Game State. This chunk must come third, after GlSt. This is + the gigantic bitfield that the GET_GAME_STATE macros modify. It is + variably-sized; excess bytes in this array will be ignored, and if + there are insufficient bytes in the save file, the remaining bits + will be initialized to zero. Modders setting extra event flags may + be able to import a legacy game into a sensible state by choosing + their defaults judiciously. + +- "Evts": Events. An array of values describing scripted future + events. + +- "Enct": Encounters. Details of battle groups that are pursuing you + through HyperSpace. + +- "RacQ": Available Race Queue. Which species are active in the game, + where they are, etc. Corresponds to avail_race_q. + +- "IGpQ": Interplanetary Group Queue. Battlegroup information for + ships in your current star system, if you're in a current star + system. It is currently unclear whether or not this ever needs to + exist, but in keeping with legacy logic, it will appear whenever you + are in a star system but not in the middle of an encounter. + +- "NpcQ": NPC Queue. Battlegroup information for ships you are in the + middle of encountering. This should only appear if your loaded + activity is "IN_ENCOUNTER" and loading the game will trigger the red + alert. + +- "ShpQ": Ship Queue. Battlegroup information for your flagship's + escort fleet. + +- "Star": Star Description. Basic indexing information to indicate + which star system you are in. + +- "SISF": Star Info State File. An index into which planetside + resources you have investigated and collected. See + doc/devel/statefiles for more details. + +- "DGSF": Defined Group State File. Battlegroup information for + space-based encounters dictated by the plot. See + doc/devel/statefiles for more details. + +- "RGSF": Random Group State File. Battlegroup information for random + encounters in interplanetary space. See doc/devel/statefiles for + more details. + +THINGS LEFT TO DO BEFORE MERGING +-------------------------------- + +- Actually enforce little-endianness of everything that hits the disk. + - This is a simple matter of improving the read_* and write_* + functions in load.c and save.c. + +- There are 448 bits in GmSt that are actually indices into DGSF. They + shouldn't be there. In fact, they shouldn't even be in the + GAME_STATE array either. They should be an array of DWORDs living + independently in the global state + - First, we should juggle the order of the state bits so that + they're all at the end. That way we can ultimately just truncate + GmSt and everything will be fine. We can do that without + complicating legacy loads much. + +- The State Files are (except maybe for Star Info) a horrible mess and + we should not be replicating them in the save file. We should + instead be regenerating them from more structured forms. + - This is complicated by the fact that there are references to the + _GSF chunks in both GlSt (BattleGroupRef) and GmSt + (*_GRPOFFSET*). I have a good handle on what needs to be done to + GmSt, but the significance of the GlSt pointer still requires + research. From e7d30008a2133523ea510213c4420d263b3ed48d Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sun, 13 Oct 2013 16:36:41 -0700 Subject: [PATCH 11/18] Endian safety in all savegame chunks except statefiles. --- sc2/src/uqm/load.c | 50 +++++++++++++++++++++++++++------------------- sc2/src/uqm/save.c | 12 +++++------ 2 files changed, 36 insertions(+), 26 deletions(-) diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index 4a4953c62..48f0d4312 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -49,41 +49,51 @@ read_8 (void *fp, BYTE *v) static inline size_t read_16 (void *fp, UWORD *v) { - UWORD t; - if (!v) /* read value ignored */ - v = &t; - return ReadResFile (v, 2, 1, fp); + UWORD t = 0; + int shift, i; + for (i = 0, shift = 0; i < 2; ++i, shift += 8) + { + BYTE b; + if (read_8 (fp, &b) != 1) + return 0; + t |= ((UWORD)b) << shift; + } + + if (v) + *v = t; + + return 1; } static inline size_t read_16s (void *fp, SWORD *v) { - SWORD t; - if (!v) /* read value ignored */ - v = &t; - return ReadResFile (v, 2, 1, fp); + return read_16 (fp, v); } static inline size_t read_32 (void *fp, DWORD *v) { - DWORD t; - if (!v) /* read value ignored */ - v = &t; - return ReadResFile (v, 4, 1, fp); + DWORD t = 0; + int shift, i; + for (i = 0, shift = 0; i < 4; ++i, shift += 8) + { + BYTE b; + if (read_8 (fp, &b) != 1) + return 0; + t |= ((DWORD)b) << shift; + } + + if (v) + *v = t; + + return 1; } static inline size_t read_32s (void *fp, SDWORD *v) { - DWORD t; - COUNT ret; - // value was converted to unsigned when saved - ret = read_32 (fp, &t); - // unsigned to signed conversion - if (v) - *v = t; - return ret; + return read_32 (fp, v); } static inline size_t diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index 51fc56f9c..1345be27b 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -64,17 +64,17 @@ write_8 (void *fp, BYTE v) static inline void write_16 (void *fp, UWORD v) { - if (io_ok) - if (WriteResFile (&v, 2, 1, fp) != 1) - io_ok = FALSE; + write_8 (fp, (BYTE)( v & 0xff)); + write_8 (fp, (BYTE)((v >> 8) & 0xff)); } static inline void write_32 (void *fp, DWORD v) { - if (io_ok) - if (WriteResFile (&v, 4, 1, fp) != 1) - io_ok = FALSE; + write_8 (fp, (BYTE)( v & 0xff)); + write_8 (fp, (BYTE)((v >> 8) & 0xff)); + write_8 (fp, (BYTE)((v >> 16) & 0xff)); + write_8 (fp, (BYTE)((v >> 24) & 0xff)); } static inline void From 40dcd65afa6b4b1f96f8e1e6ecfc99fd27366971 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sun, 13 Oct 2013 18:49:54 -0700 Subject: [PATCH 12/18] Generalize the getGameState functions. --- sc2/src/uqm/globdata.c | 26 +++++++++++++------------- sc2/src/uqm/globdata.h | 20 ++++++++++---------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/sc2/src/uqm/globdata.c b/sc2/src/uqm/globdata.c index 20e39a470..a387313c1 100644 --- a/sc2/src/uqm/globdata.c +++ b/sc2/src/uqm/globdata.c @@ -48,31 +48,31 @@ GLOBDATA GlobData; BYTE -getGameState (int startBit, int endBit) +getGameState (BYTE *state, int startBit, int endBit) { return (BYTE) (((startBit >> 3) == (endBit >> 3) - ? (GLOBAL (GameState[startBit >> 3]) >> (startBit & 7)) - : ((GLOBAL (GameState[startBit >> 3]) >> (startBit & 7)) - | (GLOBAL (GameState[endBit >> 3]) + ? (state[startBit >> 3] >> (startBit & 7)) + : ((state[startBit >> 3] >> (startBit & 7)) + | (state[endBit >> 3] << (endBit - startBit - (endBit & 7))))) & ((1 << (endBit - startBit + 1)) - 1)); } void -setGameState (int startBit, int endBit, BYTE val +setGameState (BYTE *state, int startBit, int endBit, BYTE val #ifdef STATE_DEBUG , const char *name #endif ) { - GLOBAL (GameState[startBit >> 3]) = - (GLOBAL (GameState[startBit >> 3]) + state[startBit >> 3] = + (state[startBit >> 3] & (BYTE) ~(((1 << (endBit - startBit + 1)) - 1) << (startBit & 7))) | (BYTE)((val) << (startBit & 7)); if ((startBit >> 3) < (endBit >> 3)) { - GLOBAL (GameState[endBit >> 3]) = - (GLOBAL (GameState[endBit >> 3]) + state[endBit >> 3] = + (state[endBit >> 3] & (BYTE)~((1 << ((endBit & 7) + 1)) - 1)) | (BYTE)((val) >> (endBit - startBit - (endBit & 7))); } @@ -82,21 +82,21 @@ setGameState (int startBit, int endBit, BYTE val } DWORD -getGameState32 (int startBit) +getGameState32 (BYTE *state, int startBit) { DWORD v; int shift; for (v = 0, shift = 0; shift < 32; shift += 8, startBit += 8) { - v |= getGameState (startBit, startBit + 7) << shift; + v |= getGameState (state, startBit, startBit + 7) << shift; } return v; } void -setGameState32 (int startBit, DWORD val +setGameState32 (BYTE *state, int startBit, DWORD val #ifdef STATE_DEBUG , const char *name #endif @@ -107,7 +107,7 @@ setGameState32 (int startBit, DWORD val for (i = 0; i < 4; ++i, v >>= 8, startBit += 8) { - setGameState (startBit, startBit + 7, v & 0xff + setGameState (state, startBit, startBit + 7, v & 0xff #ifdef STATE_DEBUG , "(ignored)" #endif diff --git a/sc2/src/uqm/globdata.h b/sc2/src/uqm/globdata.h index 031b57df7..60b528b83 100644 --- a/sc2/src/uqm/globdata.h +++ b/sc2/src/uqm/globdata.h @@ -972,36 +972,36 @@ extern GLOBDATA GlobData; //#define STATE_DEBUG -extern BYTE getGameState (int startBit, int endBit); -extern void setGameState (int startBit, int endBit, BYTE val +extern BYTE getGameState (BYTE *state, int startBit, int endBit); +extern void setGameState (BYTE *state, int startBit, int endBit, BYTE val #ifdef STATE_DEBUG , const char *name #endif ); -#define GET_GAME_STATE(SName) getGameState ((SName), (END_##SName)) +#define GET_GAME_STATE(SName) getGameState (GLOBAL(GameState), (SName), (END_##SName)) #ifdef STATE_DEBUG # define SET_GAME_STATE(SName, val) \ - setGameState ((SName), (END_##SName), (val), #SName) + setGameState (GLOBAL(GameState), (SName), (END_##SName), (val), #SName) #else # define SET_GAME_STATE(SName, val) \ - setGameState ((SName), (END_##SName), (val)) + setGameState (GLOBAL(GameState), (SName), (END_##SName), (val)) #endif -extern DWORD getGameState32 (int startBit); -extern void setGameState32 (int startBit, DWORD val +extern DWORD getGameState32 (BYTE *state, int startBit); +extern void setGameState32 (BYTE *state, int startBit, DWORD val #ifdef STATE_DEBUG , const char *name #endif ); -#define GET_GAME_STATE_32(SName) getGameState32 ((SName)) +#define GET_GAME_STATE_32(SName) getGameState32 (GLOBAL(GameState), (SName)) #ifdef STATE_DEBUG # define SET_GAME_STATE_32(SName, val) \ - setGameState32 ((SName), (val), #SName) + setGameState32 (GLOBAL(GameState), (SName), (val), #SName) #else # define SET_GAME_STATE_32(SName, val) \ - setGameState32 ((SName), (val)) + setGameState32 (GLOBAL(GameState), (SName), (val)) #endif From 60a9ca61cfd58c9fa5623be476586464c937c58c Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sun, 13 Oct 2013 23:19:13 -0700 Subject: [PATCH 13/18] Reorganize the GameState array. This is preparation work so that when the state files are removed from the savegame format (or possibly removed entirely) it will not be a savegame compatibility-breaking change. Legacy support has been modified so that the GameState bits are reordered as part of load, so loading legacy saves still works. --- sc2/src/uqm/globdata.c | 16 ++++ sc2/src/uqm/globdata.h | 167 ++++++++++++++++++++++---------------- sc2/src/uqm/load_legacy.c | 66 ++++++++++++++- 3 files changed, 176 insertions(+), 73 deletions(-) diff --git a/sc2/src/uqm/globdata.c b/sc2/src/uqm/globdata.c index a387313c1..ff9edc2fb 100644 --- a/sc2/src/uqm/globdata.c +++ b/sc2/src/uqm/globdata.c @@ -119,6 +119,22 @@ setGameState32 (BYTE *state, int startBit, DWORD val #endif } +void +copyGameState (BYTE *dest, DWORD target, BYTE *src, DWORD begin, DWORD end) +{ + while (begin < end) + { + BYTE b; + DWORD delta = 7; + if (begin + delta > end) + delta = end - begin; + b = getGameState (src, begin, begin + delta); + setGameState (dest, target, target + delta, b); + begin += 8; + target += 8; + } +} + static void CreateRadar (void) { diff --git a/sc2/src/uqm/globdata.h b/sc2/src/uqm/globdata.h index 60b528b83..952b5fcc9 100644 --- a/sc2/src/uqm/globdata.h +++ b/sc2/src/uqm/globdata.h @@ -255,61 +255,6 @@ START_GAME_STATE ADD_GAME_STATE (LANDER_SHIELDS, 4) - ADD_GAME_STATE (SHOFIXTI_GRPOFFS0, 8) - ADD_GAME_STATE (SHOFIXTI_GRPOFFS1, 8) - ADD_GAME_STATE (SHOFIXTI_GRPOFFS2, 8) - ADD_GAME_STATE (SHOFIXTI_GRPOFFS3, 8) - - ADD_GAME_STATE (ZOQFOT_GRPOFFS0, 8) - ADD_GAME_STATE (ZOQFOT_GRPOFFS1, 8) - ADD_GAME_STATE (ZOQFOT_GRPOFFS2, 8) - ADD_GAME_STATE (ZOQFOT_GRPOFFS3, 8) - - ADD_GAME_STATE (MELNORME0_GRPOFFS0, 8) - ADD_GAME_STATE (MELNORME0_GRPOFFS1, 8) - ADD_GAME_STATE (MELNORME0_GRPOFFS2, 8) - ADD_GAME_STATE (MELNORME0_GRPOFFS3, 8) - - ADD_GAME_STATE (MELNORME1_GRPOFFS0, 8) - ADD_GAME_STATE (MELNORME1_GRPOFFS1, 8) - ADD_GAME_STATE (MELNORME1_GRPOFFS2, 8) - ADD_GAME_STATE (MELNORME1_GRPOFFS3, 8) - - ADD_GAME_STATE (MELNORME2_GRPOFFS0, 8) - ADD_GAME_STATE (MELNORME2_GRPOFFS1, 8) - ADD_GAME_STATE (MELNORME2_GRPOFFS2, 8) - ADD_GAME_STATE (MELNORME2_GRPOFFS3, 8) - - ADD_GAME_STATE (MELNORME3_GRPOFFS0, 8) - ADD_GAME_STATE (MELNORME3_GRPOFFS1, 8) - ADD_GAME_STATE (MELNORME3_GRPOFFS2, 8) - ADD_GAME_STATE (MELNORME3_GRPOFFS3, 8) - - ADD_GAME_STATE (MELNORME4_GRPOFFS0, 8) - ADD_GAME_STATE (MELNORME4_GRPOFFS1, 8) - ADD_GAME_STATE (MELNORME4_GRPOFFS2, 8) - ADD_GAME_STATE (MELNORME4_GRPOFFS3, 8) - - ADD_GAME_STATE (MELNORME5_GRPOFFS0, 8) - ADD_GAME_STATE (MELNORME5_GRPOFFS1, 8) - ADD_GAME_STATE (MELNORME5_GRPOFFS2, 8) - ADD_GAME_STATE (MELNORME5_GRPOFFS3, 8) - - ADD_GAME_STATE (MELNORME6_GRPOFFS0, 8) - ADD_GAME_STATE (MELNORME6_GRPOFFS1, 8) - ADD_GAME_STATE (MELNORME6_GRPOFFS2, 8) - ADD_GAME_STATE (MELNORME6_GRPOFFS3, 8) - - ADD_GAME_STATE (MELNORME7_GRPOFFS0, 8) - ADD_GAME_STATE (MELNORME7_GRPOFFS1, 8) - ADD_GAME_STATE (MELNORME7_GRPOFFS2, 8) - ADD_GAME_STATE (MELNORME7_GRPOFFS3, 8) - - ADD_GAME_STATE (MELNORME8_GRPOFFS0, 8) - ADD_GAME_STATE (MELNORME8_GRPOFFS1, 8) - ADD_GAME_STATE (MELNORME8_GRPOFFS2, 8) - ADD_GAME_STATE (MELNORME8_GRPOFFS3, 8) - ADD_GAME_STATE (MET_MELNORME, 1) ADD_GAME_STATE (MELNORME_RESCUE_REFUSED, 1) ADD_GAME_STATE (MELNORME_RESCUE_COUNT, 3) @@ -329,10 +274,6 @@ START_GAME_STATE ADD_GAME_STATE (MELNORME_PISSED_COUNT, 2) ADD_GAME_STATE (MELNORME_HATE_COUNT, 2) - ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS0, 8) - ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS1, 8) - ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS2, 8) - ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS3, 8) ADD_GAME_STATE (PROBE_MESSAGE_DELIVERED, 1) ADD_GAME_STATE (PROBE_ILWRATH_ENCOUNTER, 1) @@ -647,11 +588,6 @@ START_GAME_STATE ADD_GAME_STATE (URQUAN_PROTECTING_SAMATRA, 1) - ADD_GAME_STATE (COLONY_GRPOFFS0, 8) - ADD_GAME_STATE (COLONY_GRPOFFS1, 8) - ADD_GAME_STATE (COLONY_GRPOFFS2, 8) - ADD_GAME_STATE (COLONY_GRPOFFS3, 8) - #define THRADDASH_BODY_THRESHOLD 25 ADD_GAME_STATE (THRADDASH_BODY_COUNT, 5) @@ -671,11 +607,6 @@ START_GAME_STATE ADD_GAME_STATE (ILWRATH_WORSHIP, 2) ADD_GAME_STATE (ILWRATH_FIGHT_THRADDASH, 1) - ADD_GAME_STATE (SAMATRA_GRPOFFS0, 8) - ADD_GAME_STATE (SAMATRA_GRPOFFS1, 8) - ADD_GAME_STATE (SAMATRA_GRPOFFS2, 8) - ADD_GAME_STATE (SAMATRA_GRPOFFS3, 8) - ADD_GAME_STATE (READY_TO_CONFUSE_URQUAN, 1) ADD_GAME_STATE (URQUAN_HYPNO_VISITS, 1) ADD_GAME_STATE (MENTIONED_PET_COMPULSION, 1) @@ -877,6 +808,79 @@ START_GAME_STATE ADD_GAME_STATE (ORZ_STACK0, 1) ADD_GAME_STATE (ORZ_STACK1, 1) + +/* These state bits are actually offsets into defgrp.dat. They really + * shouldn't be part of the serialized Game State array! --MCM */ + ADD_GAME_STATE (SHOFIXTI_GRPOFFS0, 8) + ADD_GAME_STATE (SHOFIXTI_GRPOFFS1, 8) + ADD_GAME_STATE (SHOFIXTI_GRPOFFS2, 8) + ADD_GAME_STATE (SHOFIXTI_GRPOFFS3, 8) + + ADD_GAME_STATE (ZOQFOT_GRPOFFS0, 8) + ADD_GAME_STATE (ZOQFOT_GRPOFFS1, 8) + ADD_GAME_STATE (ZOQFOT_GRPOFFS2, 8) + ADD_GAME_STATE (ZOQFOT_GRPOFFS3, 8) + + ADD_GAME_STATE (MELNORME0_GRPOFFS0, 8) + ADD_GAME_STATE (MELNORME0_GRPOFFS1, 8) + ADD_GAME_STATE (MELNORME0_GRPOFFS2, 8) + ADD_GAME_STATE (MELNORME0_GRPOFFS3, 8) + + ADD_GAME_STATE (MELNORME1_GRPOFFS0, 8) + ADD_GAME_STATE (MELNORME1_GRPOFFS1, 8) + ADD_GAME_STATE (MELNORME1_GRPOFFS2, 8) + ADD_GAME_STATE (MELNORME1_GRPOFFS3, 8) + + ADD_GAME_STATE (MELNORME2_GRPOFFS0, 8) + ADD_GAME_STATE (MELNORME2_GRPOFFS1, 8) + ADD_GAME_STATE (MELNORME2_GRPOFFS2, 8) + ADD_GAME_STATE (MELNORME2_GRPOFFS3, 8) + + ADD_GAME_STATE (MELNORME3_GRPOFFS0, 8) + ADD_GAME_STATE (MELNORME3_GRPOFFS1, 8) + ADD_GAME_STATE (MELNORME3_GRPOFFS2, 8) + ADD_GAME_STATE (MELNORME3_GRPOFFS3, 8) + + ADD_GAME_STATE (MELNORME4_GRPOFFS0, 8) + ADD_GAME_STATE (MELNORME4_GRPOFFS1, 8) + ADD_GAME_STATE (MELNORME4_GRPOFFS2, 8) + ADD_GAME_STATE (MELNORME4_GRPOFFS3, 8) + + ADD_GAME_STATE (MELNORME5_GRPOFFS0, 8) + ADD_GAME_STATE (MELNORME5_GRPOFFS1, 8) + ADD_GAME_STATE (MELNORME5_GRPOFFS2, 8) + ADD_GAME_STATE (MELNORME5_GRPOFFS3, 8) + + ADD_GAME_STATE (MELNORME6_GRPOFFS0, 8) + ADD_GAME_STATE (MELNORME6_GRPOFFS1, 8) + ADD_GAME_STATE (MELNORME6_GRPOFFS2, 8) + ADD_GAME_STATE (MELNORME6_GRPOFFS3, 8) + + ADD_GAME_STATE (MELNORME7_GRPOFFS0, 8) + ADD_GAME_STATE (MELNORME7_GRPOFFS1, 8) + ADD_GAME_STATE (MELNORME7_GRPOFFS2, 8) + ADD_GAME_STATE (MELNORME7_GRPOFFS3, 8) + + ADD_GAME_STATE (MELNORME8_GRPOFFS0, 8) + ADD_GAME_STATE (MELNORME8_GRPOFFS1, 8) + ADD_GAME_STATE (MELNORME8_GRPOFFS2, 8) + ADD_GAME_STATE (MELNORME8_GRPOFFS3, 8) + + ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS0, 8) + ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS1, 8) + ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS2, 8) + ADD_GAME_STATE (URQUAN_PROBE_GRPOFFS3, 8) + + ADD_GAME_STATE (COLONY_GRPOFFS0, 8) + ADD_GAME_STATE (COLONY_GRPOFFS1, 8) + ADD_GAME_STATE (COLONY_GRPOFFS2, 8) + ADD_GAME_STATE (COLONY_GRPOFFS3, 8) + + ADD_GAME_STATE (SAMATRA_GRPOFFS0, 8) + ADD_GAME_STATE (SAMATRA_GRPOFFS1, 8) + ADD_GAME_STATE (SAMATRA_GRPOFFS2, 8) + ADD_GAME_STATE (SAMATRA_GRPOFFS3, 8) + END_GAME_STATE // Values for GAME_STATE.glob_flags: @@ -970,14 +974,37 @@ extern GLOBDATA GlobData; #define MAX_ENCOUNTERS 16 #define MAX_BATTLE_GROUPS 32 +/* DEFGRP enumeration. These identify scripted TrueSpace encounters + * more consistently than offsets into the DEFGRPINFO_FILE state + * file. */ +enum { + DEFGRP_NONE, + DEFGRP_SHOFIXTI, + DEFGRP_ZOQFOT, + DEFGRP_MELNORME0, + DEFGRP_MELNORME1, + DEFGRP_MELNORME2, + DEFGRP_MELNORME3, + DEFGRP_MELNORME4, + DEFGRP_MELNORME5, + DEFGRP_MELNORME6, + DEFGRP_MELNORME7, + DEFGRP_MELNORME8, + DEFGRP_URQUAN_PROBE, + DEFGRP_COLONY, + DEFGRP_SAMATRA, + NUM_DEFGRPS +}; + //#define STATE_DEBUG - + extern BYTE getGameState (BYTE *state, int startBit, int endBit); extern void setGameState (BYTE *state, int startBit, int endBit, BYTE val #ifdef STATE_DEBUG , const char *name #endif ); +extern void copyGameState (BYTE *dest, DWORD target, BYTE *src, DWORD begin, DWORD end); #define GET_GAME_STATE(SName) getGameState (GLOBAL(GameState), (SName), (END_##SName)) #ifdef STATE_DEBUG diff --git a/sc2/src/uqm/load_legacy.c b/sc2/src/uqm/load_legacy.c index 3c7a7e702..6470a52f1 100644 --- a/sc2/src/uqm/load_legacy.c +++ b/sc2/src/uqm/load_legacy.c @@ -178,6 +178,66 @@ read_a16 (void *fp, UWORD *ar, COUNT count) return 1; } +typedef struct struct_GAMESTATE_TRANSPOSE { + int start, end, target; +} GAMESTATE_TRANSPOSE; + +#define LEGACY_GAMESTATE_SIZE 155 + +/* The *_GRPOFFS* states are no longer intermingled with the rest of + * the state. We need to shuffle all the rest of the state data + * down. */ +static GAMESTATE_TRANSPOSE transpose[] = { + { 0, 51, 0 }, + { 404, 450, 52 }, + { 483, 878, 99 }, + { 911, 930, 495 }, + { 963, 1237, 515 }, + { -1, -1, -1 } }; + +static DWORD old_defgrp_offsets[] = { 0, 52, 84, 116, 148, 180, 212, 244, + 276, 308, 340, 372, 451, 879, 931 }; + +static DWORD new_defgrp_offsets[] = { + 0, + SHOFIXTI_GRPOFFS0, + ZOQFOT_GRPOFFS0, + MELNORME0_GRPOFFS0, + MELNORME1_GRPOFFS0, + MELNORME2_GRPOFFS0, + MELNORME3_GRPOFFS0, + MELNORME4_GRPOFFS0, + MELNORME5_GRPOFFS0, + MELNORME6_GRPOFFS0, + MELNORME7_GRPOFFS0, + MELNORME8_GRPOFFS0, + URQUAN_PROBE_GRPOFFS0, + COLONY_GRPOFFS0, + SAMATRA_GRPOFFS0 +}; + +static void +InterpretLegacyGameState (BYTE *result, BYTE *legacy) +{ + int i; + DWORD grpoffs[NUM_DEFGRPS]; + GAMESTATE_TRANSPOSE *t = &transpose[0]; + grpoffs[0] = 0; + for (i = 1; i < NUM_DEFGRPS; ++i) + { + grpoffs[i] = getGameState32 (legacy, old_defgrp_offsets[i]); + } + while (t->start >= 0) + { + copyGameState (result, t->target, legacy, t->start, t->end); + ++t; + } + for (i = 1; i < NUM_DEFGRPS; ++i) + { + setGameState32 (result, new_defgrp_offsets[i], grpoffs[i]); + } +} + static void LoadEmptyQueue (DECODE_REF fh) { @@ -424,7 +484,7 @@ LoadClockState (CLOCK_STATE *ClockPtr, DECODE_REF fh) static void LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) { - BYTE dummy8; + BYTE dummy8, oldstate[LEGACY_GAMESTATE_SIZE]; cread_8 (fh, &dummy8); /* obsolete */ cread_8 (fh, &GSPtr->glob_flags); @@ -469,9 +529,9 @@ LoadGameState (GAME_STATE *GSPtr, DECODE_REF fh) DummyLoadQueue (&GSPtr->encounter_q, fh); DummyLoadQueue (&GSPtr->built_ship_q, fh); - cread_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); + cread_a8 (fh, oldstate, LEGACY_GAMESTATE_SIZE); + InterpretLegacyGameState (GSPtr->GameState, oldstate); - assert (sizeof (GSPtr->GameState) % 4 == 3); cread_8 (fh, NULL); /* GAME_STATE alignment padding */ } From 9f3a70985ef5d4a797945df646b74451b17f7f93 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sun, 13 Oct 2013 23:22:20 -0700 Subject: [PATCH 14/18] Update savefile spec based on new findings. --- sc2/doc/devel/savefile | 47 ++++++++++++++++++++++++++++-------------- 1 file changed, 32 insertions(+), 15 deletions(-) diff --git a/sc2/doc/devel/savefile b/sc2/doc/devel/savefile index 4a2c3acd3..8b7b4a577 100644 --- a/sc2/doc/devel/savefile +++ b/sc2/doc/devel/savefile @@ -10,8 +10,8 @@ anymore. It was also extremely fragile and hard for modders to extend. The new save format seeks to alleviate these problems. -GENERAL FORMAT --------------- + GENERAL FORMAT + -------------- All multibyte values are little-endian. There are no alignment restrictions inherent in the format. @@ -119,27 +119,44 @@ save file. encounters in interplanetary space. See doc/devel/statefiles for more details. -THINGS LEFT TO DO BEFORE MERGING --------------------------------- +- "Scan": Scanner Masks. NOT YET IMPLEMENTED; WILL REPLACE SISF. It + might just be a rename with better endianness enforcement, though; + the SISF dump is pretty well-structured as it is. + +- "DGrp": Defined Group. NOT YET IMPLEMENTED; WILL REPLACE DGSF. Will + carry an enumeration id to indicate which of the defgrps it is + (that's currently intrinsic to the game state bit array) and will + also have a bit indicating whether or not it is the "current" + defgrp. If no defgrp is current, BattleGroupRef is 0; otherwise, + BattleGroupRef is the defgrp file offset that corresponds to the + current system. There will be one DGrp chunk for each system with an + active defgrp record. + +- NOT YET DESIGNED: RGSF also needs to be replaced with one or more + chunk types. This will likely be similar to the DGrp chunks, but + there are stronger constraints on repetition here. + + THINGS LEFT TO DO BEFORE MERGING + -------------------------------- - Actually enforce little-endianness of everything that hits the disk. - - This is a simple matter of improving the read_* and write_* - functions in load.c and save.c. + - Everything that isn't a State File now respects this. + - State files are going to require more work (see below). - There are 448 bits in GmSt that are actually indices into DGSF. They shouldn't be there. In fact, they shouldn't even be in the GAME_STATE array either. They should be an array of DWORDs living independently in the global state - - First, we should juggle the order of the state bits so that - they're all at the end. That way we can ultimately just truncate - GmSt and everything will be fine. We can do that without - complicating legacy loads much. + - In preparation for this, the GRPOFFS state has been moved to the + end of the state array. We'll be able to eradicate it once the + state files are no longer part of the save. - The State Files are (except maybe for Star Info) a horrible mess and we should not be replicating them in the save file. We should instead be regenerating them from more structured forms. - - This is complicated by the fact that there are references to the - _GSF chunks in both GlSt (BattleGroupRef) and GmSt - (*_GRPOFFSET*). I have a good handle on what needs to be done to - GmSt, but the significance of the GlSt pointer still requires - research. + - Once we do this we can start computing new values for the GRPOFFS + state and the BattleGroupRef based on the new chunks. We can then + remove the GRPOFFS bits from the GmSt chunk and the BattleGroupRef + from the GlSt chunk. From there we can see our way to eradicating + the state file abstraction entirely, but that will be quite a bit + more work. From fe0a9a11267a9ba0c959d2a16daeeca54dabf840 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Fri, 18 Oct 2013 22:38:39 -0700 Subject: [PATCH 15/18] Expose some of the statefile structure to load and save. --- sc2/src/uqm/grpinfo.c | 49 ++++++------------------------------ sc2/src/uqm/grpintrn.h | 56 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 63 insertions(+), 42 deletions(-) create mode 100644 sc2/src/uqm/grpintrn.h diff --git a/sc2/src/uqm/grpinfo.c b/sc2/src/uqm/grpinfo.c index dc7be0f4c..43f1b22b6 100644 --- a/sc2/src/uqm/grpinfo.c +++ b/sc2/src/uqm/grpinfo.c @@ -23,7 +23,7 @@ #include "globdata.h" #include "intel.h" #include "state.h" -#include "grpinfo.h" +#include "grpintrn.h" #include "libs/mathlib.h" #include "libs/log.h" @@ -35,42 +35,7 @@ static BYTE LastEncGroup; // Last encountered group, saved into state files -//#define DEBUG_GROUPS - -// A group header describes battle groups present in a star system. There is -// at most 1 group header per system. -// 'Random' group info file (RANDGRPINFO_FILE) always contains only one -// group header record, which describes the last-visited star system, -// (which may be the current system). Thus the randomly generated groups -// are valid for 7 days (set in PutGroupInfo) after the player leaves -// the system, or until the player enters another star system. -typedef struct -{ - BYTE NumGroups; - BYTE day_index, month_index; - COUNT star_index, year_index; - // day_index, month_index, year_index specify when - // random groups expire (if you were to leave the system - // by going to HSpace and stay there till such time) - // star_index is the index of a star this group header - // applies to; ~0 means uninited - DWORD GroupOffset[NUM_SAVED_BATTLE_GROUPS + 1]; - // Absolute offsets of group definitions in a state file - // Group 0 is a list of groups present in solarsys - // (RANDGRPINFO_FILE only) - // Groups 1..max are definitions of actual battle groups - // containing ship makeup and status - - // Each group has the following format: - // 1 byte, RaceType (LastEncGroup in Group 0) - // 1 byte, NumShips (NumGroups in Group 0) - // Ships follow: - // 1 byte, RaceType - // 16 bytes, part of SHIP_FRAGMENT struct - -} GROUP_HEADER; - -static void +void ReadGroupHeader (GAME_STATE_FILE *fp, GROUP_HEADER *pGH) { sread_8 (fp, &pGH->NumGroups); @@ -82,7 +47,7 @@ ReadGroupHeader (GAME_STATE_FILE *fp, GROUP_HEADER *pGH) sread_a32 (fp, pGH->GroupOffset, NUM_SAVED_BATTLE_GROUPS + 1); } -static void +void WriteGroupHeader (GAME_STATE_FILE *fp, const GROUP_HEADER *pGH) { swrite_8 (fp, pGH->NumGroups); @@ -94,7 +59,7 @@ WriteGroupHeader (GAME_STATE_FILE *fp, const GROUP_HEADER *pGH) swrite_a32 (fp, pGH->GroupOffset, NUM_SAVED_BATTLE_GROUPS + 1); } -static void +void ReadShipFragment (GAME_STATE_FILE *fp, SHIP_FRAGMENT *FragPtr) { BYTE tmpb; @@ -116,7 +81,7 @@ ReadShipFragment (GAME_STATE_FILE *fp, SHIP_FRAGMENT *FragPtr) sread_16 (fp, NULL); /* unused; was loc.y */ } -static void +void WriteShipFragment (GAME_STATE_FILE *fp, const SHIP_FRAGMENT *FragPtr) { swrite_16 (fp, 0); /* unused: was which_side */ @@ -134,7 +99,7 @@ WriteShipFragment (GAME_STATE_FILE *fp, const SHIP_FRAGMENT *FragPtr) swrite_16 (fp, 0); /* unused; was loc.y */ } -static void +void ReadIpGroup (GAME_STATE_FILE *fp, IP_GROUP *GroupPtr) { BYTE tmpb; @@ -157,7 +122,7 @@ ReadIpGroup (GAME_STATE_FILE *fp, IP_GROUP *GroupPtr) sread_16s(fp, &GroupPtr->loc.y); } -static void +void WriteIpGroup (GAME_STATE_FILE *fp, const IP_GROUP *GroupPtr) { swrite_16 (fp, 0); /* unused; was which_side */ diff --git a/sc2/src/uqm/grpintrn.h b/sc2/src/uqm/grpintrn.h new file mode 100644 index 000000000..d2136a507 --- /dev/null +++ b/sc2/src/uqm/grpintrn.h @@ -0,0 +1,56 @@ +#ifndef _GRPINTRN_H +#define _GRPINTRN_H + +// For IPGROUP +#include "grpinfo.h" + +// For SHIP_FRAGMENT +#include "races.h" + +// For GAME_STATE_FILE +#include "state.h" + +//#define DEBUG_GROUPS + +// A group header describes battle groups present in a star system. There is +// at most 1 group header per system. +// 'Random' group info file (RANDGRPINFO_FILE) always contains only one +// group header record, which describes the last-visited star system, +// (which may be the current system). Thus the randomly generated groups +// are valid for 7 days (set in PutGroupInfo) after the player leaves +// the system, or until the player enters another star system. +typedef struct +{ + BYTE NumGroups; + BYTE day_index, month_index; + COUNT star_index, year_index; + // day_index, month_index, year_index specify when + // random groups expire (if you were to leave the system + // by going to HSpace and stay there till such time) + // star_index is the index of a star this group header + // applies to; ~0 means uninited + DWORD GroupOffset[NUM_SAVED_BATTLE_GROUPS + 1]; + // Absolute offsets of group definitions in a state file + // Group 0 is a list of groups present in solarsys + // (RANDGRPINFO_FILE only) + // Groups 1..max are definitions of actual battle groups + // containing ship makeup and status + + // Each group has the following format: + // 1 byte, RaceType (LastEncGroup in Group 0) + // 1 byte, NumShips (NumGroups in Group 0) + // Ships follow: + // 1 byte, RaceType + // 16 bytes, part of SHIP_FRAGMENT struct + // (part of IP_GROUP struct in Group 0) + +} GROUP_HEADER; + +void ReadGroupHeader (GAME_STATE_FILE *fp, GROUP_HEADER *pGH); +void WriteGroupHeader (GAME_STATE_FILE *fp, const GROUP_HEADER *pGH); +void ReadShipFragment (GAME_STATE_FILE *fp, SHIP_FRAGMENT *FragPtr); +void WriteShipFragment (GAME_STATE_FILE *fp, const SHIP_FRAGMENT *FragPtr); +void ReadIpGroup (GAME_STATE_FILE *fp, IP_GROUP *GroupPtr); +void WriteIpGroup (GAME_STATE_FILE *fp, const IP_GROUP *GroupPtr); + +#endif From c30fd90cb444ca87278414e81139796d4074052b Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sat, 19 Oct 2013 01:29:02 -0700 Subject: [PATCH 16/18] Intermediate testing save format. Rationalized statefile chunks are now written out alongside the original statefile chunks. The loader still relies on the latter for now. This will make correctness cross-checking a bit easier. --- sc2/src/uqm/save.c | 147 ++++++++++++++++++++++++++++++++++++++++++++- sc2/src/uqm/save.h | 3 + 2 files changed, 148 insertions(+), 2 deletions(-) diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index 1345be27b..d05a10c98 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -32,7 +32,7 @@ #include "shipcont.h" #include "setup.h" #include "state.h" -#include "grpinfo.h" +#include "grpintrn.h" #include "util.h" #include "hyper.h" // for SaveSisHyperState() @@ -613,6 +613,146 @@ SaveStateFile (DWORD statefileId, DWORD tag, uio_Stream *fh) } } +static void +SaveStarInfo (uio_Stream *fh) +{ + GAME_STATE_FILE *fp; + fp = OpenStateFile (STARINFO_FILE, "rb"); + if (fp) + { + DWORD flen = LengthStateFile (fp); + if (flen % 4) + { + log_add (log_Warning, "Unexpected Star Info length! Expected an integral number of DWORDS.\n"); + } + else + { + write_32 (fh, SCAN_TAG); + write_32 (fh, flen); + while (flen) + { + DWORD val; + sread_32 (fp, &val); + write_32 (fh, val); + flen -= 4; + } + } + CloseStateFile (fp); + } +} + +static void +SaveBattleGroup (GAME_STATE_FILE *fp, DWORD encounter_id, DWORD grpoffs, uio_Stream *fh) +{ + GROUP_HEADER h; + DWORD size = 12; + int i; + SeekStateFile (fp, grpoffs, SEEK_SET); + ReadGroupHeader (fp, &h); + for (i = 1; i <= h.NumGroups; ++i) + { + BYTE NumShips; + SeekStateFile (fp, h.GroupOffset[i], SEEK_SET); + sread_8 (fp, NULL); + sread_8 (fp, &NumShips); + size += 2 + 10 * NumShips; + } + write_32 (fh, BATTLE_GROUP_TAG); + write_32 (fh, size); + write_32 (fh, encounter_id); + write_8 (fh, (grpoffs && (GLOBAL (BattleGroupRef) == grpoffs)) ? 1 : 0); // current + write_16 (fh, h.star_index); + write_8 (fh, h.day_index); + write_8 (fh, h.month_index); + write_16 (fh, h.year_index); + write_8 (fh, h.NumGroups); + for (i = 1; i <= h.NumGroups; ++i) + { + int j; + BYTE b; + SeekStateFile (fp, h.GroupOffset[i], SEEK_SET); + sread_8 (fp, &b); // Group race icon + write_8 (fh, b); + sread_8 (fp, &b); // NumShips + write_8 (fh, b); + for (j = 0; j < b; ++j) + { + BYTE race_outer; + SHIP_FRAGMENT sf; + sread_8 (fp, &race_outer); + ReadShipFragment (fp, &sf); + write_8 (fh, race_outer); + write_8 (fh, sf.captains_name_index); + write_8 (fh, sf.race_id); + write_8 (fh, sf.index); + write_16 (fh, sf.crew_level); + write_16 (fh, sf.max_crew); + write_8 (fh, sf.energy_level); + write_8 (fh, sf.max_energy); + } + } +} + +static void +SaveGroups (uio_Stream *fh) +{ + GAME_STATE_FILE *fp; + fp = OpenStateFile (RANDGRPINFO_FILE, "rb"); + if (fp && LengthStateFile (fp) > 0) + { + GROUP_HEADER h; + BYTE lastenc, count; + int i; + ReadGroupHeader (fp, &h); + /* Group List */ + SeekStateFile (fp, h.GroupOffset[0], SEEK_SET); + sread_8 (fp, &lastenc); + sread_8 (fp, &count); + write_32 (fh, GROUP_LIST_TAG); + write_32 (fh, 2 + 14 * count); // Chunk size + write_8 (fh, lastenc); + write_8 (fh, count); + for (i = 0; i < count; ++i) + { + BYTE race_outer; + IP_GROUP ip; + sread_8 (fp, &race_outer); + ReadIpGroup (fp, &ip); + + write_8 (fh, race_outer); + write_16 (fh, ip.group_counter); + write_8 (fh, ip.race_id); + write_8 (fh, ip.sys_loc); + write_8 (fh, ip.task); + write_8 (fh, ip.in_system); + write_8 (fh, ip.dest_loc); + write_8 (fh, ip.orbit_pos); + write_8 (fh, ip.group_id); + write_16 (fh, ip.loc.x); + write_16 (fh, ip.loc.y); + } + SaveBattleGroup (fp, 0, 0, fh); + CloseStateFile (fp); + } + fp = OpenStateFile (DEFGRPINFO_FILE, "rb"); + if (fp && LengthStateFile (fp) > 0) + { + int state_index = SHOFIXTI_GRPOFFS0; + int encounter_index = 1; + while (state_index < NUM_GAME_STATE_BITS) + { + DWORD grpoffs = GET_GAME_STATE_32 (state_index); + if (grpoffs) + { + SaveBattleGroup (fp, encounter_index, grpoffs, fh); + } + ++encounter_index; + state_index += 32; + } + CloseStateFile (fp); + } +} + // This function first writes to a memory file, and then writes the whole // lot to the actual save file at once. BOOLEAN @@ -680,7 +820,10 @@ SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name) SaveStateFile (STARINFO_FILE, STAR_SF_TAG, out_fp); SaveStateFile (DEFGRPINFO_FILE, DEFGRP_SF_TAG, out_fp); SaveStateFile (RANDGRPINFO_FILE, RANDGRP_SF_TAG, out_fp); - + + SaveStarInfo (out_fp); + SaveGroups (out_fp); + // Save out the Star Descriptor SaveStarDesc (&SD, out_fp); diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index 3d0ac1329..c917ca8d8 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -45,6 +45,9 @@ extern "C" { #define NPC_SHIP_Q_TAG 0x5163704e // "NpcQ": npc_built_ship_q #define SHIP_Q_TAG 0x51706853 // "ShpQ": built_ship_q #define STAR_TAG 0x72617453 // "Star": STAR_DESC +#define SCAN_TAG 0x6e616353 // "Scan": Scan Masks (stuff picked up) +#define BATTLE_GROUP_TAG 0x70477442 // "BtGp": Battle Group definition +#define GROUP_LIST_TAG 0x73707247 // "Grps": Group List #define STAR_SF_TAG 0x46534953 // "SISF": Star Info State File #define DEFGRP_SF_TAG 0x46534744 // "DGSF": Defined Group State File #define RANDGRP_SF_TAG 0x46534752 // "RGSF": Random Group State File From f271e8270cc21ca574165a0a3769af722994eee5 Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sat, 19 Oct 2013 15:52:19 -0700 Subject: [PATCH 17/18] Tentative loading logic for the new statefile format. --- sc2/src/uqm/load.c | 178 ++++++++++++++++++++++++++++++++++++++++++++- sc2/src/uqm/save.c | 3 +- 2 files changed, 178 insertions(+), 3 deletions(-) diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index 48f0d4312..96075f8a8 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -27,7 +27,7 @@ #include "save.h" #include "setup.h" #include "state.h" -#include "grpinfo.h" +#include "grpintrn.h" #include "libs/tasklib.h" #include "libs/log.h" @@ -467,6 +467,160 @@ LoadStateFile (int file_type, void *fh, DWORD flen) } } +static void +LoadScanInfo (uio_Stream *fh, DWORD flen) +{ + GAME_STATE_FILE *fp = OpenStateFile (STARINFO_FILE, "wb"); + if (fp) + { + while (flen) + { + DWORD val; + read_32 (fh, &val); + swrite_32 (fp, val); + flen -= 4; + } + CloseStateFile (fp); + } +} + +static void +LoadGroupList (uio_Stream *fh, DWORD chunksize) +{ + GAME_STATE_FILE *fp = OpenStateFile (RANDGRPINFO_FILE, "rb"); + if (fp) + { + GROUP_HEADER h; + BYTE LastEnc, NumGroups; + int i; + ReadGroupHeader (fp, &h); + /* There's only supposed to be one of these, so group 0 should be + * zero here whenever we're here. We add the group list to the + * end here. */ + h.GroupOffset[0] = LengthStateFile (fp); + SeekStateFile (fp, 0, SEEK_SET); + WriteGroupHeader (fp, &h); + SeekStateFile (fp, h.GroupOffset[0], SEEK_SET); + read_8 (fh, &LastEnc); + NumGroups = (chunksize - 1) / 14; + swrite_8 (fp, LastEnc); + swrite_8 (fp, NumGroups); + for (i = 0; i < NumGroups; ++i) + { + BYTE race_outer; + IP_GROUP ip; + read_8 (fh, &race_outer); + read_16 (fh, &ip.group_counter); + read_8 (fh, &ip.race_id); + read_8 (fh, &ip.sys_loc); + read_8 (fh, &ip.task); + read_8 (fh, &ip.in_system); + read_8 (fh, &ip.dest_loc); + read_8 (fh, &ip.orbit_pos); + read_8 (fh, &ip.group_id); + read_16 (fh, &ip.loc.x); + read_16 (fh, &ip.loc.y); + + swrite_8 (fp, race_outer); + WriteIpGroup (fp, &ip); + } + CloseStateFile (fp); + } +} + +static void +LoadBattleGroup (uio_Stream *fh, DWORD chunksize) +{ + GAME_STATE_FILE *fp; + GROUP_HEADER h; + DWORD encounter, offset; + BYTE current; + int i; + + read_32 (fh, &encounter); + read_8 (fh, ¤t); + chunksize -= 5; + if (encounter) + { + /* This is a defined group, so it's new */ + fp = OpenStateFile (DEFGRPINFO_FILE, "rb"); + offset = LengthStateFile (fp); + memset (&h, 0, sizeof (GROUP_HEADER)); + } + else + { + /* This is the random group. Load in what was there, + * as we might have already seen the Group List. */ + fp = OpenStateFile (RANDGRPINFO_FILE, "rb"); + current = FALSE; + offset = 0; + ReadGroupHeader (fp, &h); + } + if (!fp) + { + skip_8 (fh, chunksize); + return; + } + read_16 (fh, &h.star_index); + read_8 (fh, &h.day_index); + read_8 (fh, &h.month_index); + read_16 (fh, &h.year_index); + read_8 (fh, &h.NumGroups); + chunksize -= 7; + /* Write out the half-finished state file so that we can use + * the file size to compute group offsets */ + SeekStateFile (fp, offset, SEEK_SET); + WriteGroupHeader (fp, &h); + for (i = 1; i <= h.NumGroups; ++i) + { + int j; + BYTE icon, NumShips; + read_8 (fh, &icon); + read_8 (fh, &NumShips); + chunksize -= 2; + h.GroupOffset[i] = LengthStateFile (fp); + SeekStateFile (fp, h.GroupOffset[i], SEEK_SET); + swrite_8 (fp, icon); + swrite_8 (fp, NumShips); + for (j = 0; j < NumShips; ++j) + { + BYTE race_outer; + SHIP_FRAGMENT sf; + read_8 (fh, &race_outer); + read_8 (fh, &sf.captains_name_index); + read_8 (fh, &sf.race_id); + read_8 (fh, &sf.index); + read_16 (fh, &sf.crew_level); + read_16 (fh, &sf.max_crew); + read_8 (fh, &sf.energy_level); + read_8 (fh, &sf.max_energy); + chunksize -= 10; + + swrite_8 (fp, race_outer); + WriteShipFragment (fp, &sf); + } + } + /* Now that the GroupOffset array is properly initialized, + * write the header back out. */ + SeekStateFile (fp, offset, SEEK_SET); + WriteGroupHeader (fp, &h); + CloseStateFile (fp); + /* And update the gamestate accordingly, if we're a defined group. */ + if (encounter) + { + SET_GAME_STATE_32 (SHOFIXTI_GRPOFFS0 + (encounter - 1) * 32, offset); + if (current) + { + GLOBAL (BattleGroupRef) = offset; + } + } + /* Consistency check. */ + if (chunksize) + { + log_add (log_Warning, "BattleGroup chunk mis-sized!"); + } +} + BOOLEAN LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) { @@ -477,6 +631,7 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) STAR_DESC SD; ACTIVITY Activity; DWORD chunk, chunkSize; + BOOLEAN first_group_spec = TRUE; sprintf (file, "uqmsave.%02u", which_game); in_fp = res_OpenResFile (saveDir, file, "rb"); @@ -589,6 +744,27 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) case SHIP_Q_TAG: LoadShipQueue (in_fp, &GLOBAL (built_ship_q), chunkSize); break; + case SCAN_TAG: + LoadScanInfo (in_fp, chunkSize); + break; + case GROUP_LIST_TAG: + if (first_group_spec) + { + InitGroupInfo (TRUE); + GLOBAL (BattleGroupRef) = 0; + first_group_spec = FALSE; + } + LoadGroupList (in_fp, chunkSize); + break; + case BATTLE_GROUP_TAG: + if (first_group_spec) + { + InitGroupInfo (TRUE); + GLOBAL (BattleGroupRef) = 0; + first_group_spec = FALSE; + } + LoadBattleGroup (in_fp, chunkSize); + break; case STAR_SF_TAG: LoadStateFile (STARINFO_FILE, in_fp, chunkSize); break; diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index d05a10c98..6976ddac0 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -709,9 +709,8 @@ SaveGroups (uio_Stream *fh) sread_8 (fp, &lastenc); sread_8 (fp, &count); write_32 (fh, GROUP_LIST_TAG); - write_32 (fh, 2 + 14 * count); // Chunk size + write_32 (fh, 1 + 14 * count); // Chunk size write_8 (fh, lastenc); - write_8 (fh, count); for (i = 0; i < count; ++i) { BYTE race_outer; From f66cbbcd89888540858137c6885d4e1284d5b95a Mon Sep 17 00:00:00 2001 From: Michael Martin Date: Sat, 19 Oct 2013 16:59:36 -0700 Subject: [PATCH 18/18] Finalize the work on the new save file format. Remove all references to state files from the format. Some still leaks in in the GmSt chunk, but those are ignored or overwritten by later loading phases. --- sc2/doc/devel/savefile | 99 ++++++++++++++++++------------------------ sc2/src/uqm/load.c | 34 +-------------- sc2/src/uqm/save.c | 14 +++--- sc2/src/uqm/save.h | 3 -- 4 files changed, 49 insertions(+), 101 deletions(-) diff --git a/sc2/doc/devel/savefile b/sc2/doc/devel/savefile index 8b7b4a577..306c1ab10 100644 --- a/sc2/doc/devel/savefile +++ b/sc2/doc/devel/savefile @@ -1,5 +1,5 @@ - SAVEFILE FORMAT - --------------- + SAVEFILE FORMAT + --------------- This document represents a work in progress. The save format described here will evolve before finalization for 0.8. @@ -10,8 +10,8 @@ anymore. It was also extremely fragile and hard for modders to extend. The new save format seeks to alleviate these problems. - GENERAL FORMAT - -------------- + GENERAL FORMAT + -------------- All multibyte values are little-endian. There are no alignment restrictions inherent in the format. @@ -60,8 +60,8 @@ depending on the situation you saved in. Unless otherwise specified, chunks may be stored in any order in the save file. - CHUNK INVENTORY - --------------- + CHUNK INVENTORY + --------------- - "Summ": Summary. This chunk must come first. This chunk carries the flagship configuration information and some overview @@ -71,7 +71,8 @@ save file. - "GlSt": Global State. This chunk must come second, after Summ. Represents most of the data in the global state structure in - globdata.h. + globdata.h. BattleGroupRef is excised from this; its value is + computed later on. - "GmSt": Game State. This chunk must come third, after GlSt. This is the gigantic bitfield that the GET_GAME_STATE macros modify. It is @@ -107,56 +108,42 @@ save file. - "Star": Star Description. Basic indexing information to indicate which star system you are in. -- "SISF": Star Info State File. An index into which planetside - resources you have investigated and collected. See - doc/devel/statefiles for more details. +- "Scan": Scanner Masks. This is a semi-structured tree of DWORDs that + represents which planetary resources have been captured or + removed. It is a format roughly similar to the old star info + statefile format (see doc/devel/statefile) but little-endianness is + enforced, making this chunk endian-safe where the old statefile dump + was not. -- "DGSF": Defined Group State File. Battlegroup information for - space-based encounters dictated by the plot. See - doc/devel/statefiles for more details. +- "BtGp": Battle Group. Defines the relevant information for all ships + in a given star system. This includes an "encounter ID" - randomly + generated fleets have an encounter ID of zero, and ones built by the + plot have an 32-bit identifier. Vanilla UQM reserves the first 32 + encounter IDs for itself, and uses 15 of them (random encounter, + Ur-Quan Probe, Shofixti Survivor, Zoq-Fot-Pik Emissary, Unzervalt + Guardian, nine Melnorme Traders, and the final boss). This also + includes the expiration date for random encounters and which system + they are relevant to. Much of this information was originally stored + in randgrp.dat, but it has echoes in defgrp.dat as well. The data + here is a ragged 2D array of a slight extension of the SHIP_FRAGMENT + structure. There is one BtGp chunk per defined group. (Since one of + these is defined at game start, and the random encounter structure + has values that mean 'no encounter present', there should always be + at least two of these chunks in any save.) -- "RGSF": Random Group State File. Battlegroup information for random - encounters in interplanetary space. See doc/devel/statefiles for - more details. +- "Grps": Active Battle Groups. These are IP_GROUP structures to + supplement the SHIP_FRAGMENTs specified in BtGp chunks. They give + more detailed information about the precise location and disposition + of each ship in the system you are either in or most recently left. -- "Scan": Scanner Masks. NOT YET IMPLEMENTED; WILL REPLACE SISF. It - might just be a rename with better endianness enforcement, though; - the SISF dump is pretty well-structured as it is. + THINGS LEFT TO DO + ----------------- -- "DGrp": Defined Group. NOT YET IMPLEMENTED; WILL REPLACE DGSF. Will - carry an enumeration id to indicate which of the defgrps it is - (that's currently intrinsic to the game state bit array) and will - also have a bit indicating whether or not it is the "current" - defgrp. If no defgrp is current, BattleGroupRef is 0; otherwise, - BattleGroupRef is the defgrp file offset that corresponds to the - current system. There will be one DGrp chunk for each system with an - active defgrp record. - -- NOT YET DESIGNED: RGSF also needs to be replaced with one or more - chunk types. This will likely be similar to the DGrp chunks, but - there are stronger constraints on repetition here. - - THINGS LEFT TO DO BEFORE MERGING - -------------------------------- - -- Actually enforce little-endianness of everything that hits the disk. - - Everything that isn't a State File now respects this. - - State files are going to require more work (see below). - -- There are 448 bits in GmSt that are actually indices into DGSF. They - shouldn't be there. In fact, they shouldn't even be in the - GAME_STATE array either. They should be an array of DWORDs living - independently in the global state - - In preparation for this, the GRPOFFS state has been moved to the - end of the state array. We'll be able to eradicate it once the - state files are no longer part of the save. - -- The State Files are (except maybe for Star Info) a horrible mess and - we should not be replicating them in the save file. We should - instead be regenerating them from more structured forms. - - Once we do this we can start computing new values for the GRPOFFS - state and the BattleGroupRef based on the new chunks. We can then - remove the GRPOFFS bits from the GmSt chunk and the BattleGroupRef - from the GlSt chunk. From there we can see our way to eradicating - the state file abstraction entirely, but that will be quite a bit - more work. +The last 448 bits in GmSt probably shouldn't exist. However, we should +not remove them from the source base (and thus the save file) until +after other pending commits have been merged. When this happens, we +can break out those bits into an array of DWORDs instead. The loading +code basically ignores those GmSt bits by overwriting them while +loading later chunks, and GmSt is an expandable array in the first +place, so removing those final bits from the Game State array should +be compatible in both directions. diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index 96075f8a8..9d434c4b2 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -298,7 +298,7 @@ LoadGameState (GAME_STATE *GSPtr, void *fh) return FALSE; } read_32 (fh, &magic); - if (magic != 79) + if (magic != 75) { /* Chunk is the wrong size. */ return FALSE; @@ -334,8 +334,6 @@ LoadGameState (GAME_STATE *GSPtr, void *fh) read_16s (fh, &GSPtr->velocity.incr.width); read_16s (fh, &GSPtr->velocity.incr.height); - read_32 (fh, &GSPtr->BattleGroupRef); - read_32 (fh, &magic); if (magic != GAME_STATE_TAG) { @@ -446,27 +444,6 @@ LoadStarDesc (STAR_DESC *SDPtr, void *fh) read_8 (fh, &SDPtr->Postfix); } -static void -LoadStateFile (int file_type, void *fh, DWORD flen) -{ - GAME_STATE_FILE *fp = OpenStateFile (file_type, "wb"); - char buf[256]; - if (fp) - { - while (flen) - { - COUNT num_bytes; - - num_bytes = flen >= sizeof (buf) ? sizeof (buf) : (COUNT)flen; - read_a8 (fh, buf, num_bytes); - WriteStateFile (buf, num_bytes, 1, fp); - - flen -= num_bytes; - } - CloseStateFile (fp); - } -} - static void LoadScanInfo (uio_Stream *fh, DWORD flen) { @@ -765,15 +742,6 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) } LoadBattleGroup (in_fp, chunkSize); break; - case STAR_SF_TAG: - LoadStateFile (STARINFO_FILE, in_fp, chunkSize); - break; - case DEFGRP_SF_TAG: - LoadStateFile (DEFGRPINFO_FILE, in_fp, chunkSize); - break; - case RANDGRP_SF_TAG: - LoadStateFile (RANDGRPINFO_FILE, in_fp, chunkSize); - break; default: log_add (log_Debug, "Skipping chunk of tag %08X (size %u)", chunk, chunkSize); if (skip_8(in_fp, chunkSize) != 1) diff --git a/sc2/src/uqm/save.c b/sc2/src/uqm/save.c index 6976ddac0..102b0309a 100644 --- a/sc2/src/uqm/save.c +++ b/sc2/src/uqm/save.c @@ -330,7 +330,7 @@ static void SaveGameState (const GAME_STATE *GSPtr, uio_Stream *fh) { write_32 (fh, GLOBAL_STATE_TAG); - write_32 (fh, 79); + write_32 (fh, 75); write_8 (fh, GSPtr->glob_flags); write_8 (fh, GSPtr->CrewCost); write_8 (fh, GSPtr->FuelCost); @@ -362,9 +362,9 @@ SaveGameState (const GAME_STATE *GSPtr, uio_Stream *fh) write_16 (fh, GSPtr->velocity.incr.width); write_16 (fh, GSPtr->velocity.incr.height); - write_32 (fh, GSPtr->BattleGroupRef); - - /* The Game state bits. Vanilla UQM uses 155 bytes here. */ + /* The Game state bits. Vanilla UQM uses 155 bytes here at + * present. Only the first 99 bytes are significant, though; + * the rest will be overwritten by the BtGp chunks. */ write_32 (fh, GAME_STATE_TAG); write_32 (fh, sizeof (GSPtr->GameState)); write_a8 (fh, GSPtr->GameState, sizeof (GSPtr->GameState)); @@ -815,11 +815,7 @@ SaveGame (COUNT which_game, SUMMARY_DESC *SummPtr, const char *name) // Save the encounter chunk (black globes in HS/QS) SaveEncounters (out_fp); - // Save out the state file chunks. - SaveStateFile (STARINFO_FILE, STAR_SF_TAG, out_fp); - SaveStateFile (DEFGRPINFO_FILE, DEFGRP_SF_TAG, out_fp); - SaveStateFile (RANDGRPINFO_FILE, RANDGRP_SF_TAG, out_fp); - + // Save out the data that used to be in state files SaveStarInfo (out_fp); SaveGroups (out_fp); diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index c917ca8d8..c6b4fc972 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -48,9 +48,6 @@ extern "C" { #define SCAN_TAG 0x6e616353 // "Scan": Scan Masks (stuff picked up) #define BATTLE_GROUP_TAG 0x70477442 // "BtGp": Battle Group definition #define GROUP_LIST_TAG 0x73707247 // "Grps": Group List -#define STAR_SF_TAG 0x46534953 // "SISF": Star Info State File -#define DEFGRP_SF_TAG 0x46534744 // "DGSF": Defined Group State File -#define RANDGRP_SF_TAG 0x46534752 // "RGSF": Random Group State File typedef struct {