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/doc/devel/savefile b/sc2/doc/devel/savefile new file mode 100644 index 000000000..306c1ab10 --- /dev/null +++ b/sc2/doc/devel/savefile @@ -0,0 +1,149 @@ + 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. 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 + 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. + +- "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. + +- "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.) + +- "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. + + THINGS LEFT TO DO + ----------------- + +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/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/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 726f0b2ce..b604dd538 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" @@ -253,7 +252,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, // disallow the change return (FALSE); } - + PreUpdateFlashRect (); SetContextForeGroundColor (BackGround); @@ -264,7 +263,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 +283,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 +291,7 @@ DrawNameString (bool nameCaptain, UNICODE *Str, COUNT CursorPos, SetContextForeGroundColor (ForeGround); font_DrawText (&lf); - + PostUpdateFlashRect (); } @@ -347,15 +346,190 @@ 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; + 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; + 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 = fullStr; + lf.CharCount = (COUNT)~0; + + if (!(state & DDSHS_EDIT)) + { + 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 (&lf); + } + else + { // editing state + COUNT i, FullCursorPos; + RECT text_r; + BYTE char_deltas[256]; + 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; + + FullCursorPos = CursorPos + strlen(dateStr) - 1; + for (i = FullCursorPos; i > 0; --i) + text_r.corner.x += *pchar_deltas++; + + if (FullCursorPos < lf.CharCount) /* end of line */ + --text_r.corner.x; + + if (state & DDSHS_BLOCKCUR) + { // Use block cursor for keyboardless systems + if (FullCursorPos == lf.CharCount) + { // cursor at end-line -- use insertion point + text_r.extent.width = 1; + } + else if (FullCursorPos + 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 = strlen(buf); + 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 +785,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 +854,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 +871,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 +944,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 +999,7 @@ 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:" + snprintf (buf, sizeof buf, "%s: %s", buf2, desc->SaveName[0] ? desc->SaveName : GAME_STRING (SAVEGAME_STRING_BASE + 4)); } font_DrawText (&t); } @@ -928,7 +1100,7 @@ DoPickGame (MENU_STATE *pMS) SetContext (SpaceContext); RedrawPickDisplay (pickState, pMS->CurState); } - + SleepThreadUntil (TimeIn + ONE_SECOND / 30); } @@ -936,22 +1108,38 @@ 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[256]; 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); + { + // 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); + 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 +1193,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 +1216,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 +1229,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 +1248,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 +1335,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 +1344,3 @@ GameOptions (void) return !(GLOBAL (CurrentActivity) & (CHECK_ABORT | CHECK_LOAD)); } - diff --git a/sc2/src/uqm/globdata.c b/sc2/src/uqm/globdata.c index 20e39a470..ff9edc2fb 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 @@ -119,6 +119,22 @@ setGameState32 (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 031b57df7..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,38 +974,61 @@ 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 (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 + ); +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 +# define SET_GAME_STATE(SName, val) \ + setGameState (GLOBAL(GameState), (SName), (END_##SName), (val), #SName) +#else +# define SET_GAME_STATE(SName, val) \ + setGameState (GLOBAL(GameState), (SName), (END_##SName), (val)) +#endif + +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(SName) getGameState ((SName), (END_##SName)) -#ifdef STATE_DEBUG -# define SET_GAME_STATE(SName, val) \ - setGameState ((SName), (END_##SName), (val), #SName) -#else -# define SET_GAME_STATE(SName, val) \ - setGameState ((SName), (END_##SName), (val)) -#endif - -extern DWORD getGameState32 (int startBit); -extern void setGameState32 (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 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 diff --git a/sc2/src/uqm/load.c b/sc2/src/uqm/load.c index 98924f173..9d434c4b2 100644 --- a/sc2/src/uqm/load.c +++ b/sc2/src/uqm/load.c @@ -18,19 +18,16 @@ #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" +#include "grpintrn.h" #include "libs/tasklib.h" #include "libs/log.h" @@ -40,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) { @@ -120,39 +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) +{ + 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; -} - -static inline size_t -read_ptr (void *fp) -{ - DWORD t; - return read_32 (fp, &t); /* ptrs are useless in saves */ + return read_32 (fp, v); } static inline size_t @@ -162,6 +103,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) { @@ -183,300 +136,221 @@ read_a16 (void *fp, UWORD *ar, COUNT count) } static void -LoadEmptyQueue (DECODE_REF fh) +LoadShipQueue (void *fh, QUEUE *pQueue, DWORD size) { - 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); + COUNT num_links = size / 11; while (num_links--) { HSHIPFRAG hStarShip; SHIP_FRAGMENT *FragPtr; 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); - // 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 */ + read_8 (fh, &FragPtr->captains_name_index); + read_8 (fh, &FragPtr->race_id); + read_8 (fh, &FragPtr->index); + read_16 (fh, &FragPtr->crew_level); + read_16 (fh, &FragPtr->max_crew); + read_8 (fh, &FragPtr->energy_level); + read_8 (fh, &FragPtr->max_energy); UnlockShipFrag (pQueue, hStarShip); } } static void -LoadRaceQueue (DECODE_REF fh, QUEUE *pQueue) +LoadRaceQueue (void *fh, QUEUE *pQueue, DWORD size) { - COUNT num_links; - - cread_16 (fh, &num_links); + COUNT num_links = size / 30; while (num_links--) { HFLEETINFO hStarShip; FLEET_INFO *FleetPtr; 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); - 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); + read_16 (fh, &FleetPtr->allied_state); + read_8 (fh, &FleetPtr->days_left); + read_8 (fh, &FleetPtr->growth_fract); + 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); + 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); UnlockFleetInfo (pQueue, hStarShip); } } static void -LoadGroupQueue (DECODE_REF fh, QUEUE *pQueue) +LoadGroupQueue (void *fh, QUEUE *pQueue, DWORD size) { - COUNT num_links; - - cread_16 (fh, &num_links); + COUNT num_links = size / 13; 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); + read_16 (fh, &GroupPtr->group_counter); + read_8 (fh, &GroupPtr->race_id); + read_8 (fh, &GroupPtr->sys_loc); + read_8 (fh, &GroupPtr->task); + read_8 (fh, &GroupPtr->in_system); /* was crew_level */ + 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); 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 */ 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); + 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); - EncounterPtr->num_ships = tmpb & ENCOUNTER_SHIPS_MASK; - EncounterPtr->flags = tmpb & ENCOUNTER_FLAGS_MASK; - cread_16 (fh, NULL); /* alignment padding */ + read_16s (fh, &EncounterPtr->loc_pt.x); + read_16s (fh, &EncounterPtr->loc_pt.y); + read_8 (fh, &EncounterPtr->race_id); + 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]; - 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 */ + read_8 (fh, &ShipInfo->race_id); + read_16 (fh, &ShipInfo->crew_level); + read_16 (fh, &ShipInfo->max_crew); + read_8 (fh, &ShipInfo->max_energy); } - + // 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 */ 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 */ + read_8 (fh, &EventPtr->day_index); + read_8 (fh, &EventPtr->month_index); + read_16 (fh, &EventPtr->year_index); + read_8 (fh, &EventPtr->func_index); } static void -DummyLoadQueue (QUEUE *QueuePtr, DECODE_REF fh) +LoadClockState (CLOCK_STATE *ClockPtr, 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 */ - 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 */ + 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); } -static void -LoadClockState (CLOCK_STATE *ClockPtr, DECODE_REF fh) +static BOOLEAN +LoadGameState (GAME_STATE *GSPtr, 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 */ + DWORD magic; + read_32 (fh, &magic); + if (magic != GLOBAL_STATE_TAG) + { + return FALSE; + } + read_32 (fh, &magic); + if (magic != 75) + { + /* Chunk is the wrong size. */ + return FALSE; + } + 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_16 (fh, &GSPtr->CurrentActivity); - 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); + 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); - 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 */ + 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 @@ -498,23 +372,36 @@ LoadSisState (SIS_STATE *SSPtr, void *fp) 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; + return TRUE; } static BOOLEAN LoadSummary (SUMMARY_DESC *SummPtr, void *fp) { + SDWORD magic; + DWORD nameSize = 0; + if (!read_32s (fp, &magic)) + return FALSE; + if (magic == SAVEFILE_TAG) + { + if (read_32 (fp, &magic) != 1 || magic != SUMMARY_TAG) + return FALSE; + if (read_32 (fp, &magic) != 1 || magic < 160) + return FALSE; + nameSize = magic - 160; + } + else + { + return FALSE; + } + 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 || @@ -524,24 +411,191 @@ 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 (nameSize < SAVE_NAME_SIZE) + { + if (read_a8 (fp, SummPtr->SaveName, nameSize) != 1) + return FALSE; + SummPtr->SaveName[nameSize] = 0; + } else - return TRUE; + { + 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); +} + +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 @@ -549,24 +603,22 @@ 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; - DECODE_REF fh; COUNT num_links; STAR_DESC SD; ACTIVITY Activity; + DWORD chunk, chunkSize; + BOOLEAN first_group_spec = TRUE; - 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)) { - log_add (log_Error, "Warning: Savegame is corrupt"); res_CloseResFile (in_fp); - return FALSE; + return LoadLegacyGame (which_game, SummPtr); } if (!SummPtr) @@ -580,29 +632,8 @@ 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 ((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)); @@ -611,141 +642,116 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) memset (&GLOBAL (GameState[0]), 0, sizeof (GLOBAL (GameState))); Activity = GLOBAL (CurrentActivity); - LoadGameState (&GlobData.Game_state, fh); + if (!LoadGameState (&GlobData.Game_state, in_fp)) + { + res_CloseResFile (in_fp); + return FALSE; + } 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)) + chunk = 0; + while (TRUE) { - 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--) + 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, fh); + 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 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; + 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)) - 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; @@ -760,5 +766,3 @@ LoadGame (COUNT which_game, SUMMARY_DESC *SummPtr) return TRUE; } - - 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/load_legacy.c b/sc2/src/uqm/load_legacy.c new file mode 100644 index 000000000..6470a52f1 --- /dev/null +++ b/sc2/src/uqm/load_legacy.c @@ -0,0 +1,821 @@ +//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; +} + +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) +{ + 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, oldstate[LEGACY_GAMESTATE_SIZE]; + + 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, oldstate, LEGACY_GAMESTATE_SIZE); + InterpretLegacyGameState (GSPtr->GameState, oldstate); + + 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/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.c b/sc2/src/uqm/save.c index c49fe505e..102b0309a 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() @@ -41,106 +41,79 @@ #include "libs/inplib.h" #include "libs/log.h" #include "libs/memlib.h" -#include "libs/declib.h" +// 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 (DECODE_REF fh, BYTE v) -{ - return cwrite (&v, 1, 1, fh); -} - -static inline COUNT -cwrite_16 (DECODE_REF fh, UWORD v) -{ - return cwrite (&v, 2, 1, fh); -} - -static inline COUNT -cwrite_32 (DECODE_REF fh, DWORD v) -{ - return cwrite (&v, 4, 1, fh); -} - -static inline COUNT -cwrite_ptr (DECODE_REF fh) -{ - return cwrite_32 (fh, 0); /* ptrs are useless in saves */ -} - -static inline COUNT -cwrite_a8 (DECODE_REF fh, const BYTE *ar, COUNT count) -{ - return cwrite (ar, 1, count, fh) == count; -} - -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); + write_8 (fp, (BYTE)( v & 0xff)); + write_8 (fp, (BYTE)((v >> 8) & 0xff)); } -static inline size_t +static inline void write_32 (void *fp, DWORD v) { - return WriteResFile (&v, 4, 1, fp); + 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 size_t -write_ptr (void *fp) -{ - return write_32 (fp, 0); /* ptrs are useless in saves */ -} - -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 (DECODE_REF fh) -{ - COUNT num_links = 0; - - // Write the number of entries in the queue. - cwrite_16 (fh, num_links); -} - -static void -SaveShipQueue (DECODE_REF 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--) @@ -155,22 +128,16 @@ SaveShipQueue (DECODE_REF 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_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_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 */ + 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; @@ -178,14 +145,17 @@ SaveShipQueue (DECODE_REF fh, QUEUE *pQueue) } static void -SaveRaceQueue (DECODE_REF 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--) @@ -199,28 +169,27 @@ SaveRaceQueue (DECODE_REF 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_8 (fh, FleetPtr->crew_level); - cwrite_8 (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); - cwrite_16 (fh, 0); /* alignment padding */ + 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; @@ -228,12 +197,16 @@ SaveRaceQueue (DECODE_REF fh, QUEUE *pQueue) } static void -SaveGroupQueue (DECODE_REF 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) { @@ -242,243 +215,224 @@ SaveGroupQueue (DECODE_REF 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 */ - 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, DECODE_REF 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_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); - 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); - // 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 */ - - // 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_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_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 */ + 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, DECODE_REF fh) +SaveEvents (uio_Stream *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 */ + 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 -DummySaveQueue (const QUEUE *QueuePtr, DECODE_REF fh) +SaveClockState (const CLOCK_STATE *ClockPtr, uio_Stream *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 */ + 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 -SaveClockState (const CLOCK_STATE *ClockPtr, DECODE_REF fh) +SaveGameState (const GAME_STATE *GSPtr, 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); - cwrite_ptr (fh); /* useless ptr; Semaphore clock_sem */ - cwrite_ptr (fh); /* useless ptr; Task clock_task */ - cwrite_32 (fh, 0); /* useless value; DWORD TimeCounter */ + write_32 (fh, GLOBAL_STATE_TAG); + write_32 (fh, 75); + 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); - DummySaveQueue (&ClockPtr->event_q, fh); -} - -static void -SaveGameState (const GAME_STATE *GSPtr, DECODE_REF 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); - 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); - cwrite_16 (fh, 0); /* VELOCITY_DESC padding */ + 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); - - 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 */ + /* 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)); } -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 || - - write_16 (fp, 0) != 1 /* padding */ - ) - 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 (!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_16 (fp, 0) != 1 /* padding */ - ) - 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, DECODE_REF 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 -PrepareSummary (SUMMARY_DESC *SummPtr) +PrepareSummary (SUMMARY_DESC *SummPtr, const char *name) { SummPtr->SS = GlobData.SIS_state; @@ -535,6 +489,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 +505,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 +535,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 +561,7 @@ SaveProblem (void) { STAMP s; CONTEXT OldContext; - + OldContext = SetContext (SpaceContext); SaveProblemMessage (&s); FlushGraphics (); @@ -632,68 +588,210 @@ 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); + } +} + +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, 1 + 14 * count); // Chunk size + write_8 (fh, lastenc); + 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 -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; - DECODE_REF fh; - - success = TRUE; - made_room = FALSE; -RetrySave: - h = HMalloc (10 * 1024); - if (h == 0 - || (fh = copen (h, MEMORY_STREAM, STREAM_WRITE)) == 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!"); - } + 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); - 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 @@ -701,161 +799,40 @@ RetrySave: 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)); - //SaveEmptyQueue (fh); - 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 data that used to be in state files + SaveStarInfo (out_fp); + SaveGroups (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 (buf, num_bytes, 1, fh); - - 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 (buf, num_bytes, 1, fh); - - 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 (buf, num_bytes, 1, fh); - - flen -= num_bytes; - } - CloseStateFile (fp); - } - - // Write the current star desc into the memory file (compressed). - SaveStarDesc (&SD, fh); - - flen = cclose (fh); - - // 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)); - if (flen && (out_fp = res_OpenResFile (saveDir, file, "wb"))) - { - PrepareSummary (SummPtr); - - success = SaveSummary (SummPtr, out_fp); - // Then write the rest of the data. - 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); - - if (made_room) - LoadSC2Data (); - - return (success); + return TRUE; } - diff --git a/sc2/src/uqm/save.h b/sc2/src/uqm/save.h index bb7084b18..c6b4fc972 100644 --- a/sc2/src/uqm/save.h +++ b/sc2/src/uqm/save.h @@ -17,16 +17,59 @@ #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_NAME_SIZE 64 + +// The savefile tag numbers. +#define SAVEFILE_TAG 0x01534d55 // "UMS\x01": UQM Save version 1 +#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 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 + +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 BOOLEAN LoadLegacyGame (COUNT which_game, SUMMARY_DESC *summary_desc); + 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..ee07a81db 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, @@ -154,26 +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 - -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]; -} 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"