summaryrefslogtreecommitdiff
path: root/src/uqm/save.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/uqm/save.c')
-rw-r--r--src/uqm/save.c813
1 files changed, 813 insertions, 0 deletions
diff --git a/src/uqm/save.c b/src/uqm/save.c
new file mode 100644
index 0000000..8e39401
--- /dev/null
+++ b/src/uqm/save.c
@@ -0,0 +1,813 @@
+//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 <assert.h>
+
+#include "save.h"
+
+#include "build.h"
+#include "controls.h"
+#include "starmap.h"
+#include "encount.h"
+#include "libs/file.h"
+#include "gamestr.h"
+#include "globdata.h"
+#include "options.h"
+#include "races.h"
+#include "shipcont.h"
+#include "setup.h"
+#include "state.h"
+#include "grpintrn.h"
+#include "util.h"
+#include "hyper.h"
+ // for SaveSisHyperState()
+#include "planets/planets.h"
+ // for SaveSolarSysLocation() and tests
+#include "libs/inplib.h"
+#include "libs/log.h"
+#include "libs/memlib.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 void
+write_8 (void *fp, BYTE v)
+{
+ if (io_ok)
+ if (WriteResFile (&v, 1, 1, fp) != 1)
+ io_ok = FALSE;
+}
+
+static inline void
+write_16 (void *fp, UWORD v)
+{
+ write_8 (fp, (BYTE)( v & 0xff));
+ write_8 (fp, (BYTE)((v >> 8) & 0xff));
+}
+
+static inline void
+write_32 (void *fp, DWORD v)
+{
+ write_8 (fp, (BYTE)( v & 0xff));
+ write_8 (fp, (BYTE)((v >> 8) & 0xff));
+ write_8 (fp, (BYTE)((v >> 16) & 0xff));
+ write_8 (fp, (BYTE)((v >> 24) & 0xff));
+}
+
+static inline void
+write_a8 (void *fp, const BYTE *ar, COUNT count)
+{
+ if (io_ok)
+ if (WriteResFile (ar, 1, count, fp) != count)
+ io_ok = FALSE;
+}
+
+static inline void
+write_str (void *fp, const char *str, COUNT count)
+{
+ // no type conversion needed for strings
+ write_a8 (fp, (const BYTE *)str, count);
+}
+
+static inline void
+write_a16 (void *fp, const UWORD *ar, COUNT count)
+{
+ for ( ; count > 0; --count, ++ar)
+ {
+ if (!io_ok)
+ break;
+ write_16 (fp, *ar);
+ }
+}
+
+static void
+SaveShipQueue (uio_Stream *fh, QUEUE *pQueue, DWORD tag)
+{
+ COUNT num_links;
+ HSHIPFRAG hStarShip;
+
+ num_links = CountLinks (pQueue);
+ 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--)
+ {
+ HSHIPFRAG hNextShip;
+ SHIP_FRAGMENT *FragPtr;
+ COUNT Index;
+
+ FragPtr = LockShipFrag (pQueue, hStarShip);
+ hNextShip = _GetSuccLink (FragPtr);
+
+ Index = FragPtr->race_id;
+ // Write the number identifying this ship type.
+ // See races.h; look for the enum containing NUM_AVAILABLE_RACES.
+ write_16 (fh, Index);
+
+ // Write SHIP_FRAGMENT elements
+ 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;
+ }
+}
+
+static void
+SaveRaceQueue (uio_Stream *fh, QUEUE *pQueue)
+{
+ COUNT num_links;
+ HFLEETINFO hFleet;
+
+ num_links = CountLinks (pQueue);
+ 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--)
+ {
+ HFLEETINFO hNextFleet;
+ FLEET_INFO *FleetPtr;
+ COUNT Index;
+
+ FleetPtr = LockFleetInfo (pQueue, hFleet);
+ hNextFleet = _GetSuccLink (FleetPtr);
+
+ Index = GetIndexFromStarShip (pQueue, hFleet);
+ // The index is the position in the queue.
+ write_16 (fh, Index);
+
+ // Write FLEET_INFO elements
+ 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);
+
+ 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;
+ }
+}
+
+static void
+SaveGroupQueue (uio_Stream *fh, QUEUE *pQueue)
+{
+ HIPGROUP hGroup, hNextGroup;
+ COUNT num_links;
+
+ 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)
+ {
+ IP_GROUP *GroupPtr;
+
+ GroupPtr = LockIpGroup (pQueue, hGroup);
+ hNextGroup = _GetSuccLink (GroupPtr);
+
+ 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
+SaveEncounters (uio_Stream *fh)
+{
+ 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);
+
+ hEncounter = GetHeadLink (&GLOBAL (encounter_q));
+ while (num_links--)
+ {
+ HENCOUNTER hNextEncounter;
+ ENCOUNTER *EncounterPtr;
+ COUNT i;
+
+ 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;
+ }
+}
+
+static void
+SaveEvents (uio_Stream *fh)
+{
+ COUNT num_links;
+ HEVENT hEvent;
+ num_links = CountLinks (&GLOBAL (GameClock.event_q));
+ if (num_links == 0)
+ return;
+ write_32 (fh, EVENTS_TAG);
+ write_32 (fh, num_links * 5); /* Event chunks are five bytes each */
+
+ hEvent = GetHeadLink (&GLOBAL (GameClock.event_q));
+ while (num_links--)
+ {
+ HEVENT hNextEvent;
+ EVENT *EventPtr;
+
+ LockEvent (hEvent, &EventPtr);
+ hNextEvent = GetSuccEvent (EventPtr);
+
+ write_8 (fh, EventPtr->day_index);
+ write_8 (fh, EventPtr->month_index);
+ write_16 (fh, EventPtr->year_index);
+ write_8 (fh, EventPtr->func_index);
+
+ UnlockEvent (hEvent);
+ hEvent = hNextEvent;
+ }
+}
+
+/* The clock state is folded in with the game state chunk. */
+static void
+SaveClockState (const CLOCK_STATE *ClockPtr, uio_Stream *fh)
+{
+ write_8 (fh, ClockPtr->day_index);
+ write_8 (fh, ClockPtr->month_index);
+ write_16 (fh, ClockPtr->year_index);
+ write_16 (fh, ClockPtr->tick_count);
+ write_16 (fh, ClockPtr->day_in_ticks);
+}
+
+/* Save out the game state chunks. There are two of these; the Global
+ * State chunk is fixed size, but the Game State tag can be extended
+ * by modders. */
+static void
+SaveGameState (const GAME_STATE *GSPtr, uio_Stream *fh)
+{
+ 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);
+
+ SaveClockState (&GSPtr->GameClock, fh);
+
+ 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 */
+ 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 */
+ 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);
+
+ /* 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));
+}
+
+/* This is folded into the Summary chunk */
+static void
+SaveSisState (const SIS_STATE *SSPtr, void *fp)
+{
+ 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);
+ 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 void
+SaveSummary (const SUMMARY_DESC *SummPtr, void *fp)
+{
+ write_32 (fp, SUMMARY_TAG);
+ write_32 (fp, 160 + strlen(SummPtr->SaveName));
+ SaveSisState (&SummPtr->SS, fp);
+
+ 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, (BYTE *) 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, uio_Stream *fh)
+{
+ 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, const char *name)
+{
+ SummPtr->SS = GlobData.SIS_state;
+
+ SummPtr->Activity = LOBYTE (GLOBAL (CurrentActivity));
+ switch (SummPtr->Activity)
+ {
+ case IN_HYPERSPACE:
+ if (inQuasiSpace ())
+ SummPtr->Activity = IN_QUASISPACE;
+ break;
+ case IN_INTERPLANETARY:
+ // Get a better planet name for summary
+ GetPlanetOrMoonName (SummPtr->SS.PlanetName,
+ sizeof (SummPtr->SS.PlanetName));
+ if (GET_GAME_STATE (GLOBAL_FLAGS_AND_DATA) == (BYTE)~0)
+ SummPtr->Activity = IN_STARBASE;
+ else if (playerInPlanetOrbit ())
+ SummPtr->Activity = IN_PLANET_ORBIT;
+ break;
+ case IN_LAST_BATTLE:
+ utf8StringCopy (SummPtr->SS.PlanetName,
+ sizeof (SummPtr->SS.PlanetName),
+ GAME_STRING (PLANET_NUMBER_BASE + 32)); // Sa-Matra
+ break;
+ }
+
+ SummPtr->MCreditLo = GET_GAME_STATE (MELNORME_CREDIT0);
+ SummPtr->MCreditHi = GET_GAME_STATE (MELNORME_CREDIT1);
+
+ {
+ HSHIPFRAG hStarShip, hNextShip;
+
+ for (hStarShip = GetHeadLink (&GLOBAL (built_ship_q)), SummPtr->NumShips = 0;
+ hStarShip; hStarShip = hNextShip, ++SummPtr->NumShips)
+ {
+ SHIP_FRAGMENT *StarShipPtr;
+
+ StarShipPtr = LockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ hNextShip = _GetSuccLink (StarShipPtr);
+ SummPtr->ShipList[SummPtr->NumShips] = StarShipPtr->race_id;
+ UnlockShipFrag (&GLOBAL (built_ship_q), hStarShip);
+ }
+ }
+
+ SummPtr->NumDevices = InventoryDevices (SummPtr->DeviceList,
+ MAX_EXCLUSIVE_DEVICES);
+
+ SummPtr->Flags = GET_GAME_STATE (LANDER_SHIELDS)
+ | (GET_GAME_STATE (IMPROVED_LANDER_SPEED) << (4 + 0))
+ | (GET_GAME_STATE (IMPROVED_LANDER_CARGO) << (4 + 1))
+ | (GET_GAME_STATE (IMPROVED_LANDER_SHOT) << (4 + 2))
+ | ((GET_GAME_STATE (CHMMR_BOMB_STATE) < 2 ? 0 : 1) << (4 + 3));
+
+ 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
+SaveProblemMessage (STAMP *MsgStamp)
+{
+#define MAX_MSG_LINES 1
+ RECT r = {{0, 0}, {0, 0}};
+ COUNT i;
+ TEXT t;
+ UNICODE *ppStr[MAX_MSG_LINES];
+
+ // TODO: This should probably just use DoPopupWindow()
+
+ ppStr[0] = GAME_STRING (SAVEGAME_STRING_BASE + 2);
+
+ SetContextFont (StarConFont);
+
+ t.baseline.x = t.baseline.y = 0;
+ t.align = ALIGN_CENTER;
+ for (i = 0; i < MAX_MSG_LINES; ++i)
+ {
+ RECT tr;
+
+ t.pStr = ppStr[i];
+ if (*t.pStr == '\0')
+ break;
+ t.CharCount = (COUNT)~0;
+ TextRect (&t, &tr, NULL);
+ if (i == 0)
+ r = tr;
+ else
+ BoxUnion (&tr, &r, &r);
+ t.baseline.y += 11;
+ }
+ t.baseline.x = ((SIS_SCREEN_WIDTH >> 1) - (r.extent.width >> 1))
+ - r.corner.x;
+ t.baseline.y = ((SIS_SCREEN_HEIGHT >> 1) - (r.extent.height >> 1))
+ - r.corner.y;
+ r.corner.x += t.baseline.x - 4;
+ r.corner.y += t.baseline.y - 4;
+ r.extent.width += 8;
+ r.extent.height += 8;
+
+ *MsgStamp = SaveContextFrame (&r);
+
+ BatchGraphics ();
+ DrawStarConBox (&r, 2,
+ BUILD_COLOR (MAKE_RGB15 (0x10, 0x10, 0x10), 0x19),
+ BUILD_COLOR (MAKE_RGB15 (0x08, 0x08, 0x08), 0x1F),
+ TRUE, BUILD_COLOR (MAKE_RGB15 (0x0A, 0x0A, 0x0A), 0x08));
+ SetContextForeGroundColor (
+ BUILD_COLOR (MAKE_RGB15 (0x14, 0x14, 0x14), 0x0F));
+
+ for (i = 0; i < MAX_MSG_LINES; ++i)
+ {
+ t.pStr = ppStr[i];
+ if (*t.pStr == '\0')
+ break;
+ t.CharCount = (COUNT)~0;
+ font_DrawText (&t);
+ t.baseline.y += 11;
+ }
+ UnbatchGraphics ();
+}
+
+void
+SaveProblem (void)
+{
+ STAMP s;
+ CONTEXT OldContext;
+
+ OldContext = SetContext (SpaceContext);
+ SaveProblemMessage (&s);
+ FlushGraphics ();
+
+ WaitForAnyButton (TRUE, WAIT_INFINITE, FALSE);
+
+ // Restore the screen under the message
+ DrawStamp (&s);
+ SetContext (OldContext);
+ DestroyDrawable (ReleaseDrawable (s.frame));
+}
+
+static void
+SaveFlagshipState (void)
+{
+ if (inHQSpace ())
+ {
+ // Player is in HyperSpace or QuasiSpace.
+ SaveSisHyperState ();
+ }
+ else if (playerInSolarSystem ())
+ {
+ SaveSolarSysLocation ();
+ }
+}
+
+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, const char *name)
+{
+ 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")))
+ {
+ io_ok = TRUE;
+ write_32 (out_fp, SAVEFILE_TAG);
+
+ PrepareSummary (SummPtr, name);
+ SaveSummary (SummPtr, out_fp);
+
+ 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 (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
+ // create the encounter anew and populate the npc queue.
+ if (!(GLOBAL (CurrentActivity) & START_INTERPLANETARY))
+ {
+ if (GLOBAL (CurrentActivity) & START_ENCOUNTER)
+ 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 (out_fp, &GLOBAL (ip_group_q));
+ }
+ SaveShipQueue (out_fp, &GLOBAL (built_ship_q), SHIP_Q_TAG);
+
+ // 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)
+ {
+ DeleteResFile(saveDir, file);
+ return FALSE;
+ }
+ }
+ else
+ {
+ return FALSE;
+ }
+
+ return TRUE;
+}