1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
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.
|