Difference between revisions of "AGI/Specifications/Savegame"
(note about endianness) |
Lance.ewing (talk | contribs) |
||
(8 intermediate revisions by 3 users not shown) | |||
Line 1: | Line 1: | ||
=Savegame Files= | |||
<span id="Intro"></span> | |||
==Introduction== | |||
AGI saved game files are named as follows: | |||
<gameId>SG.<num> | |||
where <gameId> is the ID of the game, e.g. KQ1, SQ2, etc., and where <num> is a number starting at 1. The format of the saved game file was relatively consistent between versions 2.4XX and 2.9XX apart from one minor addition. The specification below currently focuses on the format used by that version range. The format of AGI v3 saved games has not yet been explored for the purposes of this spec, but it is known that AGI v2.272 differs somewhat in format and almost certainly those versions prior to 2.272 will therefore be different as well. | |||
The saved game file consists of six sections. The first section contains only the description of the saved game. We'll call this the header. The other five sections follow a high level format where the first two bytes give the length of the section and the remainder is the data for that section. | |||
Note that the "Byte" offsets for each section are specified as being from the start of that section and not the start of the file. | |||
Where a numeric value is specified over multiple bytes, it is always ''little-endian'', unless otherwise noted. | Where a numeric value is specified over multiple bytes, it is always ''little-endian'', unless otherwise noted. | ||
=== Header | ==Saved Game Format== | ||
The six sections below appear one after the other in the order listed. | |||
====Header==== | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0-30</code>||<code>Savegame description. This can be up to 30 printable characters, and is padded out with NUL (\0) bytes to a total of 31 bytes.</code> | |||
|} | |||
</blockquote> | |||
====General State==== | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0-1</code>||<code>Length of general state section</code> | |||
|- | |||
|align="center"|<code>2-8</code>||<code>Game ID("SQ2", "KQ3", "LLLLL", etc.), NUL padded</code> | |||
|- | |||
|align="center"|<code>9-264</code>||<code>Variables, 1 variable per byte</code> | |||
|- | |||
|align="center"|<code>265-296</code>||<code>Flags, 8 flags per byte, starting with highest bit first</code> | |||
|- | |||
|align="center"|<code>297-300</code>||<code>Clock ticks since game started. 1 clock tick == 50ms</code> | |||
|- | |||
|align="center"|<code>301-302</code>||<code>Horizon</code> | |||
|- | |||
|align="center"|<code>303-304</code>||<code>Key Dir</code> | |||
|- | |||
|align="center"|<code>305-306</code>||<code>Upper left X position for active block</code> | |||
|- | |||
|align="center"|<code>307-308</code>||<code>Upper Left Y position for active block</code> | |||
|- | |||
|align="center"|<code>309-310</code>||<code>Lower Right X position for active block</code> | |||
|- | |||
|align="center"|<code>311-312</code>||<code>Lower Right Y position for active block</code> | |||
|- | |||
|align="center"|<code>313-314</code>||<code>Player control (1) / Program control (0)</code> | |||
|- | |||
|align="center"|<code>315-316</code>||<code>Current picture number</code> | |||
|- | |||
|align="center"|<code>317-318</code>||<code>Blocking flag (1 = true, 0 = false)</code> | |||
|- | |||
|align="center"|<code>319-320</code>||<code>Max drawn. Always set to 15. Maximum number of animated objects that can be drawn at a time. <ref>AGI v2.001 (as used by the booter version of Donald Duck's Playground) had a command called max.drawn() with opcode value 143 that allow this value to be set by a Logic script. This command is missing from subsequent AGI v2 versions. It is possible that the inclusion of the maximum number of animated objects value in the OBJECT file in later AGI v2 versions replaced the need for the max.drawn() command.</ref></code> | |||
|- | |||
|align="center"|<code>321-322</code>||<code>Script size. Set by script.size. Max number of script event entries. Default is 50.</code> | |||
|- | |||
|align="center"|<code>323-324</code>||<code>Current number of script event entries.</code> | |||
|- | |||
|align="center"|<code>325-524</code>||<code>Key to controller map (4 bytes each). First 2 bytes keycode, second 2 bytes controller num. <ref>The keycode values for this mapping are the same values as used by the set.key command.</ref><ref>Although the size (200 bytes) allows for 50 key mappings, in reality the interpreter appears to have a lower limit hard-coded internally of 39. The last 11 slots in the saved game file are unused.</ref></code> | |||
|- | |||
|align="center"|<code>525-1484</code>||<code>24 strings, each 40 bytes long <ref>Versions outside of the 2.4XX tp 2.9XX range may have had 12 strings. Version 2.272 is an example of this.</ref></code> | |||
|- | |||
|align="center"|<code>1485-1486</code>||<code>Text foreground colour. A value from 0-15 as set by set.text.attribute. Used in graphics mode.</code> | |||
|- | |||
|align="center"|<code>1487-1488</code>||<code>Text background colour. Will only be 0 (black) or 0xFF (for inverse). Used in graphics mode. <ref>When using set.text.attribute, the background colour can only be black. If any value above 0 is used, it is set to 0xFF instead, which indicates that an inverse mode should be used where text is displayed black with a white background.</ref></code> | |||
|- | |||
|align="center"|<code>1489-1490</code>||<code>Text Attribute value. In text.screen, background colour in top 4-bits, foreground in bottom 4-bits.<ref>Used mainly by text screen. In graphics mode, if background was set to value above 0 then this field is set to 0xFF to indicate inverse black on white. In graphics mode, if background is 0 then this field is set to the foreground colour.</ref></code> | |||
|- | |||
|align="center"|<code>1491-1492</code>||<code>Accept input = 1, Prevent input = 0</code> | |||
|- | |||
|align="center"|<code>1493-1494</code>||<code>User input row number on the screen (as set by configure.screen command)</code> | |||
|- | |||
|align="center"|<code>1495-1496</code>||<code>Cursor character</code> | |||
|- | |||
|align="center"|<code>1497-1498</code>||<code>Show status line = 1, Don't show status line = 0</code> | |||
|- | |||
|align="center"|<code>1499-1500</code>||<code>Status line row number on the screen (as set by configure.screen command)</code> | |||
|- | |||
|align="center"|<code>1501-1502</code>||<code>Picture top row number on the screen (as set by configure.screen command)</code> | |||
|- | |||
|align="center"|<code>1503-1504</code>||<code>Picture bottom row number on the screen (indirectly set by configure.screen)</code> | |||
|- | |||
|align="center"|<code>(1505-1506)</code>||<code>Stores a pushed position within the script event list. Used by push.script and pop.script. <ref>This field was not present in AGI versions prior to 2.9XX, i.e. it was not present prior to the inclusion of the push.script() and pop.script() commands. This is the only real difference between 2.4XX and 2.9XX saved game files.</ref></code> | |||
|} | |||
</blockquote> | |||
Notes: | |||
<references /> | |||
====Animated Objects==== | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0-1</code>||<code>Length of animated objects section. Dividing this by 43 gives the number of animated objects.</code> | |||
|} | |||
</blockquote> | |||
Following the two bytes above we have a 43-byte entry for each animated object, each of which conform to the following format: | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0</code>||<code>Step Time. Number of animation cycles between motion. </code> | |||
|- | |||
|align="center"|<code>1</code>||<code>Counter for number of animation cycles between motion. Counts down from Step Time above.</code> | |||
|- | |||
|align="center"|<code>2</code>||<code>Object number, i.e. index within the list of animated objects (or view table entries).</code> | |||
|- | |||
|align="center"|<code>3-4</code>||<code>Current X position</code> | |||
|- | |||
|align="center"|<code>5-6</code>||<code>Current Y position</code> | |||
|- | |||
|align="center"|<code>7</code>||<code>Current view number</code> | |||
|- | |||
|align="center"|<code>8-9</code>||<code>Pointer to current view</code> | |||
|- | |||
|align="center"|<code>10</code>||<code>Current loop number</code> | |||
|- | |||
|align="center"|<code>11</code>||<code>Number of loops in view</code> | |||
|- | |||
|align="center"|<code>12-13</code>||<code>Pointer to current loop</code> | |||
|- | |||
|align="center"|<code>14</code>||<code>Current cel in loop</code> | |||
|- | |||
|align="center"|<code>15</code>||<code>Number of cels in current loop</code> | |||
|- | |||
|align="center"|<code>16-17</code>||<code>Pointer to current cel</code> | |||
|- | |||
|align="center"|<code>18-19</code>||<code>Pointer to previous cel</code> | |||
|- | |||
|align="center"|<code>20-21</code>||<code>Pointer to background save area</code> | |||
|- | |||
|align="center"|<code>22-23</code>||<code>Previous X position</code> | |||
|- | |||
|align="center"|<code>24-25</code>||<code>Previous Y position</code> | |||
|- | |||
|align="center"|<code>26-27</code>||<code>X size (or width) of current cel</code> | |||
|- | |||
|align="center"|<code>28-29</code>||<code>Y size (or height) of current cel</code> | |||
|- | |||
|align="center"|<code>30</code>||<code>Step Size. Distance that object moves.</code> | |||
|- | |||
|align="center"|<code>31</code>||<code>Cycle Time. Number of animation cycles between cel change.</code> | |||
|- | |||
|align="center"|<code>32</code>||<code>Counter for determining when object cycles. Counts down from Cycle Time value above.</code> | |||
|- | |||
|align="center"|<code>33</code>||<code>Direction.</code> | |||
<pre> | |||
0 = stationary | |||
1 = north | |||
2 = north/east | |||
3 = east | |||
4 = south/east | |||
5 = south | |||
6 = south/west | |||
7 = west | |||
8 = north/west | |||
</pre> | |||
|- | |||
|align="center"|<code>34</code>||<code>Motion Type. </code> | |||
<pre> | |||
0 = normal.motion | |||
1 = wander | |||
2 = follow.ego | |||
3 = move.obj | |||
</pre> | |||
|- | |||
|align="center"|<code>35</code>||<code>Cycle Type.</code> | |||
<pre> | |||
0 = normal.cycle | |||
1 = end.of.loop | |||
2 = reverse.loop | |||
3 = reverse.cycle | |||
</pre> | |||
|- | |||
|align="center"|<code>36</code>||<code>Priority.</code> | |||
|- | |||
|align="center"|<code>37-38</code>||<code>Control bits, with bit masks defined as follows:</code> | |||
<pre> | |||
Drawn 0x0001 (is drawn on screen) | |||
IgnoreBlocks 0x0002 (ignores blocks) | |||
FixedPriority 0x0004 (has a fixed priority) | |||
IgnoreHorizon 0x0008 (ignores the horizon) | |||
Update 0x0010 (should be updated) | |||
Cycle 0x0020 (should cycle the object) | |||
Animated 0x0040 (can move) | |||
Blocked 0x0080 (is blocked) | |||
StayOnWater 0x0100 (must stay entirely on water control colour) | |||
IgnoreObjects 0x0200 (won't collide with other objects) | |||
Repositioned 0x0400 (is being repositioned in this cycle) | |||
StayOnLand 0x0800 (must not be entirely on water control colour) | |||
NoAdvance 0x1000 (do not advance object's cel in this loop) | |||
FixedLoop 0x2000 (loop is fixed) | |||
Stopped 0x4000 (did not move during last animation cycle) | |||
</pre> | |||
<code>The highest bit, i.e. 0x08000, is not used by the interpreter.</code> | |||
|- | |||
|align="center"|<code>39</code>||<code>Motion parameter 1. Used by the following commands as follows:</code> | |||
<pre> | |||
wander: Randomly chosen distance between 6 and 50 to move in chosen direction. | |||
follow.ego: Distance from ego which is considered to be completion of the motion. | |||
move.obj: Target X position. | |||
</pre> | |||
|- | |||
|align="center"|<code>40</code>||<code>Motion parameter 2. Used by the following commands as follows:</code> | |||
<pre> | |||
follow.ego: Flag to set on completion of the motion. | |||
move.obj: Target Y position. | |||
</pre> | |||
|- | |||
|align="center"|<code>41</code>||<code>Motion parameter 3. Used by the following commands as follows:</code> | |||
<pre> | |||
follow.ego: Distance to move in current direction (used for random search when stopped) | |||
move.obj: Old stepsize for this animated object, restored on completion. | |||
</pre> | |||
|- | |||
|align="center"|<code>42</code>||<code>Motion parameter 4. Used by the following commands as follows:</code> | |||
<pre> | |||
move.obj: Flag to set when this animated Object reaches the target position. | |||
</pre> | |||
|} | |||
</blockquote> | |||
It is obvious from looking at the above that this format matches the view table format described in the "View resources" section of the AGI specs. This isn't surprising since all the above really is is a dump of the view table entries for each of the animated objects from the memory of the interpreter. | |||
====Inventory Objects==== | |||
Almost an exact copy of the OBJECT file, but always unencrypted, and with the 3 byte header replaced by the 2 byte length of the section, and with room numbers reflecting the current location of each object (rather than starting location). | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0-1</code>||<code>Length of inventory objects section. Dividing this by 3 gives the number of objects.</code> | |||
|} | |||
</blockquote> | |||
Following the two bytes above we have a three byte entry for each inventory item all of which conform to the following format: | |||
<blockquote> | |||
{| class="wikitable" <!-- width="450" --> | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0-1</code>||<code>Offset of inventory item name i</code> | |||
|- | |||
|align="center"|<code>2</code>||<code>Current room number for inventory item i, or 255 for carried or 0 for limbo.</code> | |||
|} | |||
</blockquote> | |||
where ''i'' is the entry number starting at 0. All offsets are taken from the start of entry for inventory item 0 (not the start of the section). | |||
Then comes the textual names themselves. This is simply a list of NULL terminated strings. The offsets mentioned in the above section point to the first character in the string and the last character is the one before the 0x00. | |||
Note that the saving of the object names in the saved game file (and the offsets to those names) is completely redundant since the names cannot change during game play. But they're present regardless. | |||
====Script Events==== | |||
Stores a transcript of relevant logic script commands leading to the current state in the current room. | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0-1</code>||<code>Length of script events section. Dividing this by 2 gives the number of script events.</code> | |||
|} | |||
</blockquote> | |||
Following the two bytes above we have a two byte entry for each script event item, each of which conform to the following format: | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0</code>||<code>Script event type. A value from 0 to 8 defined as follows:</code> | |||
<pre> | |||
load.logics 0 | |||
load.view 1 | |||
load.pic 2 | |||
load.sound 3 | |||
draw.pic 4 | |||
add.to.pic 5 | |||
discard.pic 6 | |||
discard.view 7 | |||
overlay.pic 8 | |||
</pre> | |||
|- | |||
|align="center"|<code>1</code>||<code>The resource number that relates to the above event type. For add.to.pic this is 0.</code> | |||
|} | |||
</blockquote> | |||
The add.to.pic event type is a special case. It is stored over 8 bytes, i.e. over 4 script event entries. This appears to be a hack of sorts that the original AGI interpreter used for storing this event type. The other eight types have a clear action and resource number that the action applies to. The add.to.pic command doesn't follow this form. Instead it is stored as follows: | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0</code>||<code>0x05 (i.e. add.to.pic)</code> | |||
|- | |||
|align="center"|<code>1</code>||<code>0x00 (effectively unused)</code> | |||
|- | |||
|align="center"|<code>2</code>||<code>View number</code> | |||
|- | |||
|align="center"|<code>3</code>||<code>Loop number</code> | |||
|- | |||
|align="center"|<code>4</code>||<code>Cel number</code> | |||
|- | |||
|align="center"|<code>5</code>||<code>X position</code> | |||
|- | |||
|align="center"|<code>6</code>||<code>Y position</code> | |||
|- | |||
|align="center"|<code>7</code>||<code>Top 4 bits are control line colour, bottom 4 bits are priority band colour.</code> | |||
|} | |||
</blockquote> | |||
====Scan Start Offsets==== | |||
Every logic currently loaded will have an entry in this table. A logic script can use the set.scan.start command to set an offset at which the interpreter will scan from. If a logic has done that then it is this section that saves that value. It is quite common that a saved game has a 0 offset for all entries since it is only if a logic has used set.scan.start that you would see a non-zero value. | |||
<blockquote> | |||
{| class="wikitable" | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0-1</code>||<code>Length of scan start offsets section. Subtract 8 then divide by 4 gives the number of entries.</code> | |||
|- | |||
|align="center"|<code>2-5</code>||<code>Start of list. Always four zeros, i.e. 00 00 00 00</code> | |||
|- | |||
|align="center"|<code>...</code>||<code>A variable number of 4-byte scan start offset entries, format as described in table below</code> | |||
|- | |||
|align="center"|<code>?-?</code>||<code>End of list. Always FF FF 00 00</code> | |||
|} | |||
</blockquote> | |||
Each of the 4-byte scan start offset entries conforms to the following format: | |||
<blockquote> | |||
{| class="wikitable" <!-- width="450" --> | |||
|- | |||
!Byte!!Meaning | |||
|- | |||
|align="center"|<code>0-1</code>||<code>Logic number that the scan start offset is for.</code> | |||
|- | |||
|align="center"|<code>2-3</code>||<code>Current scan start offset value of the logic.</code> | |||
|} | |||
</blockquote> | |||
Notes: | |||
' | # Only logics that are currently loaded are in the scan offset list, i.e. they're removed from the list when the room changes. | ||
# The order logics appear in this list is the order that they were loaded. | |||
# The new.room command unloads all logics except for logic 0, so logic 0 never leaves this list and is always the first item, i.e. at offsets 6-9. | |||
# There is a defined maximum of 30 entries in the scan start offset list, but given that only loaded logics can set a scan start offset value, we're unlikely to see anywhere near that many. | |||
==Restoring a Game== | |||
The five sections of the format after the description are identical (apart from the 2-byte length) to the format that is used for storing that same data in the memory of the original AGI interpreter when it is running. So the first thing the original interpreter does to restore a saved game is to simply load each of those five sections in to the five areas of memory where they live in the running interpreter. This isn't enough though as there is further state not directly persisted that needs to be restored based on the new values stored in these loaded memory areas. | |||
The following are the high level steps that are followed by the original interpreter after loading the data: | |||
... | * Clears the lists of previously loaded logics, views, pictures, and sounds. | ||
* Frees all memory associated with the saved background areas of the animated objects that existed prior to restore.game being invoked. | |||
* Turns off script event recording. This is so that the replaying of the script event buffer below doesn't capture these events while they're being replayed. | |||
* Replays the contents of the script event buffer in order from the beginning. This will load the resources in the order they were loaded (and discarded) and execute the draw.pic, overlay.pic, and add.to.pic commands at the points at which they should be executed. | |||
* Turns on script event recording. | |||
* Iterates over the list of animated objects and does the following: | |||
** Re-establishes (or populates) the pointer fields based on the newly loaded view resources, i.e. pointers to current view, current loop, current cel and previous cel. | |||
** If the animated object has the "Animated" flag set: | |||
*** If the animated object also has the "Drawn" flag set: | |||
**** Perform a draw for this animated object (this will also populate the pointer to the background save area). If the motion type for the object is follow.ego, then reset motion parameter 3 to -1 to force re-initialisation of the follow path. | |||
**** If "Update" flag is not set then invoke stop.update | |||
* Copies the prepared background/shadow "screen" to visible screen (i.e. effectively a show.pic but without closing any left open window if there is one. Saved games don't save left open windows, so ideally you wouldn't allow either save or restore to happen while left open windows are present) | |||
* Clear the state of all controllers. | |||
* Set the restore in progress flag (number 12) to true. | |||
From what I can tell, the saved game format assumes that show.pic has been invoked already at the point the game was saved. This kind of makes sense as an assumption by the interpreter. But I guess if you were to create a logic script that purposely avoided executing show.pic (not a lot you can do without it though apart from perhaps some display text and windows), and you then invoked save.game in that state, it probably wouldn't end up restoring in the same state that it was saved. Text and text windows don't get saved. | |||
Latest revision as of 10:15, 12 March 2017
Savegame Files
Introduction
AGI saved game files are named as follows:
<gameId>SG.<num>
where <gameId> is the ID of the game, e.g. KQ1, SQ2, etc., and where <num> is a number starting at 1. The format of the saved game file was relatively consistent between versions 2.4XX and 2.9XX apart from one minor addition. The specification below currently focuses on the format used by that version range. The format of AGI v3 saved games has not yet been explored for the purposes of this spec, but it is known that AGI v2.272 differs somewhat in format and almost certainly those versions prior to 2.272 will therefore be different as well.
The saved game file consists of six sections. The first section contains only the description of the saved game. We'll call this the header. The other five sections follow a high level format where the first two bytes give the length of the section and the remainder is the data for that section.
Note that the "Byte" offsets for each section are specified as being from the start of that section and not the start of the file.
Where a numeric value is specified over multiple bytes, it is always little-endian, unless otherwise noted.
Saved Game Format
The six sections below appear one after the other in the order listed.
Header
Byte Meaning 0-30
Savegame description. This can be up to 30 printable characters, and is padded out with NUL (\0) bytes to a total of 31 bytes.
General State
Byte Meaning 0-1
Length of general state section
2-8
Game ID("SQ2", "KQ3", "LLLLL", etc.), NUL padded
9-264
Variables, 1 variable per byte
265-296
Flags, 8 flags per byte, starting with highest bit first
297-300
Clock ticks since game started. 1 clock tick == 50ms
301-302
Horizon
303-304
Key Dir
305-306
Upper left X position for active block
307-308
Upper Left Y position for active block
309-310
Lower Right X position for active block
311-312
Lower Right Y position for active block
313-314
Player control (1) / Program control (0)
315-316
Current picture number
317-318
Blocking flag (1 = true, 0 = false)
319-320
Max drawn. Always set to 15. Maximum number of animated objects that can be drawn at a time. [1]
321-322
Script size. Set by script.size. Max number of script event entries. Default is 50.
323-324
Current number of script event entries.
325-524
Key to controller map (4 bytes each). First 2 bytes keycode, second 2 bytes controller num. [2][3]
525-1484
24 strings, each 40 bytes long [4]
1485-1486
Text foreground colour. A value from 0-15 as set by set.text.attribute. Used in graphics mode.
1487-1488
Text background colour. Will only be 0 (black) or 0xFF (for inverse). Used in graphics mode. [5]
1489-1490
Text Attribute value. In text.screen, background colour in top 4-bits, foreground in bottom 4-bits.[6]
1491-1492
Accept input = 1, Prevent input = 0
1493-1494
User input row number on the screen (as set by configure.screen command)
1495-1496
Cursor character
1497-1498
Show status line = 1, Don't show status line = 0
1499-1500
Status line row number on the screen (as set by configure.screen command)
1501-1502
Picture top row number on the screen (as set by configure.screen command)
1503-1504
Picture bottom row number on the screen (indirectly set by configure.screen)
(1505-1506)
Stores a pushed position within the script event list. Used by push.script and pop.script. [7]
Notes:
- ↑ AGI v2.001 (as used by the booter version of Donald Duck's Playground) had a command called max.drawn() with opcode value 143 that allow this value to be set by a Logic script. This command is missing from subsequent AGI v2 versions. It is possible that the inclusion of the maximum number of animated objects value in the OBJECT file in later AGI v2 versions replaced the need for the max.drawn() command.
- ↑ The keycode values for this mapping are the same values as used by the set.key command.
- ↑ Although the size (200 bytes) allows for 50 key mappings, in reality the interpreter appears to have a lower limit hard-coded internally of 39. The last 11 slots in the saved game file are unused.
- ↑ Versions outside of the 2.4XX tp 2.9XX range may have had 12 strings. Version 2.272 is an example of this.
- ↑ When using set.text.attribute, the background colour can only be black. If any value above 0 is used, it is set to 0xFF instead, which indicates that an inverse mode should be used where text is displayed black with a white background.
- ↑ Used mainly by text screen. In graphics mode, if background was set to value above 0 then this field is set to 0xFF to indicate inverse black on white. In graphics mode, if background is 0 then this field is set to the foreground colour.
- ↑ This field was not present in AGI versions prior to 2.9XX, i.e. it was not present prior to the inclusion of the push.script() and pop.script() commands. This is the only real difference between 2.4XX and 2.9XX saved game files.
Animated Objects
Byte Meaning 0-1
Length of animated objects section. Dividing this by 43 gives the number of animated objects.
Following the two bytes above we have a 43-byte entry for each animated object, each of which conform to the following format:
Byte Meaning 0
Step Time. Number of animation cycles between motion.
1
Counter for number of animation cycles between motion. Counts down from Step Time above.
2
Object number, i.e. index within the list of animated objects (or view table entries).
3-4
Current X position
5-6
Current Y position
7
Current view number
8-9
Pointer to current view
10
Current loop number
11
Number of loops in view
12-13
Pointer to current loop
14
Current cel in loop
15
Number of cels in current loop
16-17
Pointer to current cel
18-19
Pointer to previous cel
20-21
Pointer to background save area
22-23
Previous X position
24-25
Previous Y position
26-27
X size (or width) of current cel
28-29
Y size (or height) of current cel
30
Step Size. Distance that object moves.
31
Cycle Time. Number of animation cycles between cel change.
32
Counter for determining when object cycles. Counts down from Cycle Time value above.
33
Direction.
0 = stationary 1 = north 2 = north/east 3 = east 4 = south/east 5 = south 6 = south/west 7 = west 8 = north/west34
Motion Type.
0 = normal.motion 1 = wander 2 = follow.ego 3 = move.obj35
Cycle Type.
0 = normal.cycle 1 = end.of.loop 2 = reverse.loop 3 = reverse.cycle36
Priority.
37-38
Control bits, with bit masks defined as follows:
Drawn 0x0001 (is drawn on screen) IgnoreBlocks 0x0002 (ignores blocks) FixedPriority 0x0004 (has a fixed priority) IgnoreHorizon 0x0008 (ignores the horizon) Update 0x0010 (should be updated) Cycle 0x0020 (should cycle the object) Animated 0x0040 (can move) Blocked 0x0080 (is blocked) StayOnWater 0x0100 (must stay entirely on water control colour) IgnoreObjects 0x0200 (won't collide with other objects) Repositioned 0x0400 (is being repositioned in this cycle) StayOnLand 0x0800 (must not be entirely on water control colour) NoAdvance 0x1000 (do not advance object's cel in this loop) FixedLoop 0x2000 (loop is fixed) Stopped 0x4000 (did not move during last animation cycle)
The highest bit, i.e. 0x08000, is not used by the interpreter.
39
Motion parameter 1. Used by the following commands as follows:
wander: Randomly chosen distance between 6 and 50 to move in chosen direction. follow.ego: Distance from ego which is considered to be completion of the motion. move.obj: Target X position.40
Motion parameter 2. Used by the following commands as follows:
follow.ego: Flag to set on completion of the motion. move.obj: Target Y position.41
Motion parameter 3. Used by the following commands as follows:
follow.ego: Distance to move in current direction (used for random search when stopped) move.obj: Old stepsize for this animated object, restored on completion.42
Motion parameter 4. Used by the following commands as follows:
move.obj: Flag to set when this animated Object reaches the target position.
It is obvious from looking at the above that this format matches the view table format described in the "View resources" section of the AGI specs. This isn't surprising since all the above really is is a dump of the view table entries for each of the animated objects from the memory of the interpreter.
Inventory Objects
Almost an exact copy of the OBJECT file, but always unencrypted, and with the 3 byte header replaced by the 2 byte length of the section, and with room numbers reflecting the current location of each object (rather than starting location).
Byte Meaning 0-1
Length of inventory objects section. Dividing this by 3 gives the number of objects.
Following the two bytes above we have a three byte entry for each inventory item all of which conform to the following format:
Byte Meaning 0-1
Offset of inventory item name i
2
Current room number for inventory item i, or 255 for carried or 0 for limbo.
where i is the entry number starting at 0. All offsets are taken from the start of entry for inventory item 0 (not the start of the section).
Then comes the textual names themselves. This is simply a list of NULL terminated strings. The offsets mentioned in the above section point to the first character in the string and the last character is the one before the 0x00.
Note that the saving of the object names in the saved game file (and the offsets to those names) is completely redundant since the names cannot change during game play. But they're present regardless.
Script Events
Stores a transcript of relevant logic script commands leading to the current state in the current room.
Byte Meaning 0-1
Length of script events section. Dividing this by 2 gives the number of script events.
Following the two bytes above we have a two byte entry for each script event item, each of which conform to the following format:
Byte Meaning 0
Script event type. A value from 0 to 8 defined as follows:
load.logics 0 load.view 1 load.pic 2 load.sound 3 draw.pic 4 add.to.pic 5 discard.pic 6 discard.view 7 overlay.pic 81
The resource number that relates to the above event type. For add.to.pic this is 0.
The add.to.pic event type is a special case. It is stored over 8 bytes, i.e. over 4 script event entries. This appears to be a hack of sorts that the original AGI interpreter used for storing this event type. The other eight types have a clear action and resource number that the action applies to. The add.to.pic command doesn't follow this form. Instead it is stored as follows:
Byte Meaning 0
0x05 (i.e. add.to.pic)
1
0x00 (effectively unused)
2
View number
3
Loop number
4
Cel number
5
X position
6
Y position
7
Top 4 bits are control line colour, bottom 4 bits are priority band colour.
Scan Start Offsets
Every logic currently loaded will have an entry in this table. A logic script can use the set.scan.start command to set an offset at which the interpreter will scan from. If a logic has done that then it is this section that saves that value. It is quite common that a saved game has a 0 offset for all entries since it is only if a logic has used set.scan.start that you would see a non-zero value.
Byte Meaning 0-1
Length of scan start offsets section. Subtract 8 then divide by 4 gives the number of entries.
2-5
Start of list. Always four zeros, i.e. 00 00 00 00
...
A variable number of 4-byte scan start offset entries, format as described in table below
?-?
End of list. Always FF FF 00 00
Each of the 4-byte scan start offset entries conforms to the following format:
Byte Meaning 0-1
Logic number that the scan start offset is for.
2-3
Current scan start offset value of the logic.
Notes:
- Only logics that are currently loaded are in the scan offset list, i.e. they're removed from the list when the room changes.
- The order logics appear in this list is the order that they were loaded.
- The new.room command unloads all logics except for logic 0, so logic 0 never leaves this list and is always the first item, i.e. at offsets 6-9.
- There is a defined maximum of 30 entries in the scan start offset list, but given that only loaded logics can set a scan start offset value, we're unlikely to see anywhere near that many.
Restoring a Game
The five sections of the format after the description are identical (apart from the 2-byte length) to the format that is used for storing that same data in the memory of the original AGI interpreter when it is running. So the first thing the original interpreter does to restore a saved game is to simply load each of those five sections in to the five areas of memory where they live in the running interpreter. This isn't enough though as there is further state not directly persisted that needs to be restored based on the new values stored in these loaded memory areas.
The following are the high level steps that are followed by the original interpreter after loading the data:
- Clears the lists of previously loaded logics, views, pictures, and sounds.
- Frees all memory associated with the saved background areas of the animated objects that existed prior to restore.game being invoked.
- Turns off script event recording. This is so that the replaying of the script event buffer below doesn't capture these events while they're being replayed.
- Replays the contents of the script event buffer in order from the beginning. This will load the resources in the order they were loaded (and discarded) and execute the draw.pic, overlay.pic, and add.to.pic commands at the points at which they should be executed.
- Turns on script event recording.
- Iterates over the list of animated objects and does the following:
- Re-establishes (or populates) the pointer fields based on the newly loaded view resources, i.e. pointers to current view, current loop, current cel and previous cel.
- If the animated object has the "Animated" flag set:
- If the animated object also has the "Drawn" flag set:
- Perform a draw for this animated object (this will also populate the pointer to the background save area). If the motion type for the object is follow.ego, then reset motion parameter 3 to -1 to force re-initialisation of the follow path.
- If "Update" flag is not set then invoke stop.update
- If the animated object also has the "Drawn" flag set:
- Copies the prepared background/shadow "screen" to visible screen (i.e. effectively a show.pic but without closing any left open window if there is one. Saved games don't save left open windows, so ideally you wouldn't allow either save or restore to happen while left open windows are present)
- Clear the state of all controllers.
- Set the restore in progress flag (number 12) to true.
From what I can tell, the saved game format assumes that show.pic has been invoked already at the point the game was saved. This kind of makes sense as an assumption by the interpreter. But I guess if you were to create a logic script that purposely avoided executing show.pic (not a lot you can do without it though apart from perhaps some display text and windows), and you then invoked save.game in that state, it probably wouldn't end up restoring in the same state that it was saved. Text and text windows don't get saved.