Nomad reverse engineering
- Published: 2020-03-24
- Last updated: 2021-02-23
Nomad is an obscure space-exploration PC game, developed by Papyrus Design Group and Intense! Interactive, and published by GameTek in 1993. My copy was given to me by a classmate who had found it in a discount bin for $4 not long after its release (thanks Pete!) I'd been curious about the nature of the game engine and its data files for a long time, so I eventually spent some time reverse engineering a good portion of it. The main tools I used for this effort were the debugger in DOSBox and IDA Freeware Version 5.0 (a copy of which is hosted by the ScummVM project).
I credit my dad (Dan) with introducing me to this type of hacking in the early '90s, and for encouraging the technical curiosity that I have had ever since.
This work is licensed under the Creative Commons Attribution-ShareAlike 4.0 International License, and I've also added much of it to the DOS Game Modding Wiki and The Cutting Room Floor.
Updates
- 2021-02-23: Broadened scope of DAT file format usage, added info about main executable format (toward the end of the section on game versions), added link to experimental multi-display build of DOSBox in the debug code section
- 2021-02-09: Note about some debug code that was left in the game
- 2020-12-11: Clarification in LZSS algorithm description, better description of command byte 12h in mission and conversation text.
- 2020-10-21: Mention of conversation text file for Kenelm, link to The Cutting Room Floor, and a few more fields in SHIP.TAB.
- 2020-08-07: Added sections on the hidden EGA mode, and some sound file notes.
Nomad Resource Explorer
I've created a graphical tool that you can use to browse/view the game resources. The code is available from my GitHub repo, and you can also download executables for Linux and Windows. This utility is licensed GNU GPL v3.
Although the information here could certainly be used to create saved-game editors or game resource editors, that's beyond the scope of this project. I have contributed some code to Adam Nielsen's gamearchivejs and gamecompjs projects, and I would encourage others to contribute to his suite of software as well. As always, if you have additional information about Nomad that you could contribute to this page, please contact me.
Notes to other developers and hackers
Disassembly methodology
Disassembly of the main executable was a substantial task given its size and its use of code overlays. Early in the process, I focused on identifying code that loaded data tables from the game's resource files. Labeling the segments/offsets where these tables were stored in memory was very useful in identifying routines that handled that data.
The game's system for defining the layout of dialog boxes uses several different structs that carry information about X,Y positioning and the types of widgets to display. These dialog data structs also contain function pointers to routines that do the drawing for certain regions. There are a large number of fields across several different structure definitions used by the game, and I have identified and documented most of these fields.
You may have noticed that there's no information in this article about the game's 3D ship models. I personally have no experience with 3D rendering engines, but if anyone out there can shed some light on the details of the game's 3D engine or (especially) the format of the .BIN models (from TEST.DAT), please get in touch.
Update 2020-07-10: I've reversed enough of the 3D model .BIN format to be able to render them with OpenGL in the Resource Explorer. See the section on 3D models.
Resource Explorer source code
The Nomad Resource Explorer code relies on tightly packed struct definitions (without padding) to match up to the binary file formats. This struct packing is not a feature that is strictly defined by the C/C++ language, but rather is implemented differently depending on the compiler. The code currently uses the __attribute__((packed))
directive, which is respected by the GCC family of compilers as well as LLVM/Clang. MinGW also works, although some versions apparently require the GCC parameter -mno-ms-bitfields
to force the packing. (This is therefore added as a compiler option in CMakeLists.txt.) If anyone attempts to use a Microsoft compiler, note that some additional work will be required to introduce the #pragma pack(1)
directive.
Modifying the original game
With the information available here, it would certainly be possible to modify the original game's resources. To simplify matters, you should be able to skip the step of LZ-compressing the files packed into the DAT archives. These archives support storage of uncompressed files as well (as long as their index entry fields are set appropriately.) Storing only uncompressed data would result in significantly larger DAT archives, but it would also allow changing the game's data files directly within their container and without unpacking/repacking. If you want to simply change a few values with a hex editor, this would be much more convenient.
Related resources
- MultimediaWiki: documents the technical details on a wide range of audio/video codecs
- DOS Game Modding Wiki: centralized collection of information (including file format documentation) obtained through reverse-engineering of classic DOS games, along with editors and tools to modify game resources
- OSDev Wiki
- XeNTaX Game Research Forum
- Video Game Music Preservation Foundation
- Sound Blaster Series - Hardware Programming Guide
Game versions
All of my work here was done with the following version of this game, which is the v1.01 bugfix patch to the original 3.5" disk release:
Nomad v1.01.00
Compiled: 14:16:48 on February 01 1994
The files packaged with this release can be identified with the following SHA-256 fingerprints:
Filename | SHA-256 |
---|---|
NOMAD.EXE | 8fdc6d59d102fe786cbbf471bfdd94b7b1fcf7df3c886f52c098720e20f56454 |
ANIM.DAT | 8c24057919509c082b6bd707f99de5ee3c112234b0ed579befe24b0e9a7418e5 |
CONVERSE.DAT | 1c28539c94ea77da7bb083372fbf4db2f53317590d2266faac11b1eded3b16ea |
INVENT.DAT | eed24c1bfbc9d3edc70255f7f044fdd09e9083373d43415834b37e03ba3a4130 |
SAMPLES.DAT | 73d8e924d6f42ca1ee0699cd595c512b16f8179fcc79cf6e49d03be5042c6edc |
TEST.DAT | 7e6a0c773c1d46837b2ec63d32981263ce155bb2a572f7f21af3904168b0d6d7 |
NOMAD.EXE
is a 16-bit DOS MZ executable that was built with Borland Turbo C 2.0. It is not compressed and does not use a DOS extender, instead relying on Borland's overlay manager to swap out its several dozen overlay segments.
In the routines used by the game engine to manage buffer regions, the marker bytes 52 47
("RG") and 4F 4B
("OK") are used. These might be the signatures of two programmers that worked on the game -- Richard Garcia and Omar Khudari.
Other releases
There are a number of different versions of the game that can be found in the wild:
- Pre-release/beta. This was released as Project Nomad by the scene group Razor1911 on seven 740KB disk images. It was followed up with
NOMADDOX.ZIP
, described as "quick docs" for the game. The .diz file and .nfo file are still available.- Identified by "Space Mountain v0.97.01 / Compiled: 18:07:10 on Oct 07 1993"
- The intro/endgame art for this release is very different, and shows the player's character without a helmet and in normal civilian clothes.
- North American release on four 1.44MB 3.5" disks
- Identified by "Nomad v1.00.00 / Compiled: 22:31:45 on Jan 04 1994"
- Demo. The DAT archives packaged with this version are stripped down to a minimum number of elements required for a demo experience.
- Identified by "Nomad Demo v1.00.00 / Compiled: 15:50:31 on Jan 28 1994"
- v1.01 patched version
- Identified by "Nomad v1.01.00 / Compiled: 14:16:48 on February 01 1994"
- Fixed a bug that prevented adding items to the player's inventory under certain circumstances.
- "3-in-1" CD-ROM compilation of GameTek titles, including Star Crusader, Nomad, and Bureau 13. The version of Nomad seems to be the unpatched v1.00.00. This release was apparently one of several GameTek CD-ROM compilations, with the others being:
- CD-ROM release (v1.02 or v1.03)
Both the 3.5" disk and CD-ROM releases were available in North America and Europe, with the European version having a slightly different box design. Nomad seems to have enjoyed more visibility in the German market, where it was the subject of several reviews in PC gaming magazines.
I own a box copy of the US CD-ROM release, and there is essentially no difference in the game content (versus the 3.5" disk version). Applied to the front of the box is a triangular red sticker proclaiming "With never before seen footage!", but this is a bit of a stretch. This "footage" consists -- in its entirety -- of a one minute intro animation that shows the GameTek and Papyrus logos, credits, and a starfield with a flyby of the protagonist's ship. This intro is actually shown instead of the previous introduction that also displayed the Intense! Interactive logo. The data files that contain the in-game content are exactly the same as those from the 3.5" disk release.
Update 2020-06-10: I just looked at this intro animation again, and the credits listing actually misspells three names -- David Kaemmer and Omar Khudari both have the first letter of their last name replaced with 'H', while Dan Scherlis is missing the 'h' from his last name. I don't know why all three errors involve the insertion or omission of an 'H'. Since the credits contain a grand total of eleven names, I feel like it should have been possible to do a little proofreading.
The CD-ROM version contains a read.me file that was not quite intended for the end user. It's a note from Richard Garcia to GameTek producer Rod Humble, dated March 14, 1994. In it, Rich explains the changes that were made to the game for the CD-ROM release, which he identifies as v1.02 (although the included game executable reports v1.03.) He points out two differences:
- The configuration file (NOMAD.CFG) has a new line that can be added to point to the data files if they are in a different location than the executable. This allows the data files to be left on the CD to save space on the hard drive.
- The intro sequence was reworked to use the animation described above.
Disney association
Some reviewers of this game have commented on the fact that it contains several references to the Disneyland "Space Mountain" attraction, apparently because of some early licensing agreement with Disney that did not survive to the final release. The in-game log is titled "Space Mountain Log", and the design of the player's ship matches that used in the Space Mountain roller coaster at the theme park.
I've read that some versions of the game have a very different introduction that specifically mentions Disneyland. Although I have never played such a version, I did find some unused audio files packed in the game's data archives in which a narrator does indeed mention the theme park by name. The narrator also establishes the game's premise: the player was the unwitting passenger on the Space Mountain ship as it was accidentally activated and departed Earth. (This game story was ultimately replaced by the one in which the ship crash-lands on Earth, and the player is an operative assigned by a shadowy international intelligence organization.)
The Disney connection may also help to explain the support for the Disney Sound Source hardware, which was otherwise not popular among game developers.
Update 2020-04-04: I've had a chance to look at the data files for the earliest (pre-release?) copy of the game, and they contain even more Disney material, including an alternate title screen with the name "Space Mountain":
DAT archive format
Apart from the main executable, NOMAD.EXE, the only interesting files are the five data archives:
- ANIM.DAT
- CONVERSE.DAT
- INVENT.DAT
- SAMPLES.DAT
- TEST.DAT
Each of these contains a number of compressed data files, generally grouped into categories: animations, conversation/mission text and data tables, inventory item images, audio, and everything else (including color palettes, other static images, and 3D models). The DAT archive format starts with a 16-bit count of the number of contained files. Immediately following this is an index with one 28-byte index entry per file. Each index entry is formatted like so:
uint16_t flags;
uint32_t uncompressed_size;
uint32_t compressed_size;
char filename[14]; // includes null terminator
uint32_t offset;
At least two of the flag bits are important for indicating the method used to store the file:
- flags.8 indicates whether the file is stored with the LZ compression algorithm described below. If this bit is clear, the
uncompressed_size
andcompressed_size
fields must match. In this case, the file is uncompressed and its content may be copied byte-for-byte from the .DAT. - flags.2 indicates whether the file is prefixed by a pair of uncompressed header words:
- If this bit is clear, the file is prefixed by two words (four bytes) that are not part of the LZ compressed data, so the LZ decompression must start at
offset + 4
. The only files in the game that are stored like this are the fullscreen raw VGA images, and these two words contain the width and height of the image, respectively. Note: thecompressed_size
anduncompressed_size
fields in the FAT do not include the extra four bytes, so the next file in the archive will start atoffset + compressed_size + 4
. - If this bit is set, these two words are not present and the enitrety of the data must be run through LZ decompression.
- If this bit is clear, the file is prefixed by two words (four bytes) that are not part of the LZ compressed data, so the LZ decompression must start at
The data for the first contained file starts immediately after the last index entry.
LZSS compression algorithm
The compressed files themselves are individually deflated with a modified 8-bit Lempel–Ziv–Storer–Szymanski (LZSS) algorithm. Data is grouped into chunks, and each chunk can consist of either a literal byte, or a back-reference to a string of bytes that was previously encountered during decompression. In this way, byte sequences that repeat can be represented by an abbreviated codeword, thereby saving space.
In addition to the output stream, the algorithm maintains a 4096-byte ring buffer that provides the source of data for the back-references. This buffer must be initialized with all bytes set to 20h, and the offset pointer into the buffer set to FEEh. This pointer value was chosen because it is 18 bytes from the end of the buffer, and that is the maximum length of a single back-reference sequence (as we will see later.) Therefore, at least one chunk will be decoded before the pointer wraps back to the start.
In the compressed source data, each sequence of eight chunks is preceded by a flag byte, in which each bit indicates the nature of one of the following chunks:
- 1: the chunk is a literal single byte, to be copied directly from the compressed source
- 0: the chunk is a two-byte coded back-reference into the ring buffer
These bits are checked in order of LSB to MSB. For example, a flag byte of F7h means that the next three chunks are literal, followed by a single reference, then four more literals.
As each byte is copied to the output, it is also copied to the 4K ring buffer. Whenever data is being read from or written to this buffer, the pointer is reset to position 0 when it reaches position 1000h.
To decode a back-reference, read the two-byte sequence as a little-endian pair. It contains two fields:
-
Byte count (bits 15:12): This four-bit count is actually the length minus three. Because the coded sequence itself occupies two bytes, there is no gain in encoding any fewer than three source bytes. A value of 0 in this field indicates a length of 3, and the max value of Fh indicates a length of 18 (12h).
-
Offset into ring buffer (bits 11:0): A zero-based offset from the start of the 4096-byte ring buffer at which to begin reading the number of bytes specified in the byte count field. As noted, the decoder must start filling this buffer from offset FEEh, so the first few back-references seen in a compressed file may be in this higher range (FEEh to FFFh).
Note that the encoding of these fields is somewhat different from other popular implementations of LZSS. Specifically, the nibbles in the second byte -- containing the byte count and the high-order bits of the offset -- are swapped. Other implementations (such as the Final Fantasy 7 LZSS) place the byte count field in the lowest four bits of the second byte.
DAT format usage in other games
Originally, I was calling this the "Nomad DAT format" because I was unaware of any use outside of Nomad. It turns out that the same format was used by several other Papyrus games, including J.R.R. Tolkien's Riders of Rohan and a few IndyCar and NASCAR racing simulations. From what I've seen, Riders of Rohan and Nomad use exactly the same format, while the later racing sims modified it slightly with a char filename[]
array that is one byte shorter, making each index entry 27 bytes long instead of 28. Furthermore, LZSS compression does not appear to be used at all in the DAT files for the racing sims.
Mission and conversation text
For the most part, the decompressed text in the game is human-readable ASCII, but many of the strings do contain special command bytes used to insert strings or modify game state. For example, a mission posting that mentions a particular planet will grant knowledge of that planet to the player when he reads the mission text. Once the player has knowledge of a topic, that topic can be used in conversation with aliens to uncover more facts. Mission and conversation text can also have embedded commands that add an item to the player's inventory, or change the alien character's temperament. Following is a table of the supported embedded command bytes and their functions:
Command byte | # of parameter bytes | Description |
---|---|---|
01 | 0 | Insert the Player's name. |
02 | 0 | Insert the name of the Player's ship. |
03 | 0 | Name of current location. |
04 | 0 | Insert the string <GAMESTR> |
05 | 2 | Insert meta text (e.g. a randomly chosen synonym, Shaasa phrase that may be translated, or Losten gateway code). The two parameter bytes are combined into a 16-bit little-endian value that specifies the number of the index in META.TAB. Note that this number is given as if META.TAB used 1-based indexing; since it actually uses 0-based indexing, this value must be decremented before being used. See sections on META.TAB and METATXT.TAB. |
06 | 1 | Add one item (specified by the parameter byte) to the Player's inventory. |
07 | 0 | Insert the string <METAMOVE> |
08 | 1 | Change temperament of alien speaking the text (by the value specified in the parameter byte.) |
09 | 1 | Grant knowledge of fact (specified by the parameter byte). |
0A | 1 | Grant knowledge of place (specified by the parameter byte or byte pair). If the first byte following the command byte has its MSB clear, then it may be used directly as an index into the Place Table. Otherwise, if the first byte has its MSB set, then it must be combined with the following byte to form the Place Table index: (firstbyte & 7Fh) | (secondbyte << 7) . |
0B | 1 | Set bit 0 in the first byte of the current Alien State Table entry. |
0C | 1 | Grant knowledge of item (specified by the parameter byte). |
0D | 0 | Set bit 1 in the first byte of the current Alien State Table entry. |
0E | 0 | Copy contents of 'encount_relate2' table to 'encount_relate' table. |
0F | 1 | Grant knowledge of race (specified by the parameter byte). |
10 | 2 | Modify 'encount_relate' tables in memory. |
11 | 1 | Store ship ID (specified by the parameter byte) in the Setup Table. The game doesn't track knowledge of ships in the same way as people, places, and objects. However, mentions of ships in conversation are usually followed with this command that causes the game to modify the Setup Table for that particular ship. |
12 | 2 | Store place ID (specified by the parameter bytes) in the Setup Table. |
13 | 0 | Set flag that controls alien conversation/behavior. |
14 | 0 | Set alien attribute (friendliness?) to maximum (64h). The alien table entry to be modified is selected by existing pointers in memory. |
15 | 0 | Set alien attribute (friendliness?) to minimum (00h). The alien table entry to be modified is selected by existing pointers in memory. |
16 | 1 | Modify mission table state for the mission specified by the parameter byte. In byte 12 of the MISSION.TAB entry: if bit 1 is set and bit 0 is clear, then set bit 7. |
Any nonzero byte less than 20h that appears in mission/conversation text and that isn't covered by the above table will cause the game to insert the string <HUH?>
.
Palettes
The palette data is stored in a number of .PAL files in the TEST.DAT archive. Each contains a three-byte header in which the first byte appears to always be 00h. The second byte is the VGA palette index at which the color data should be written. The third byte holds the number of palette entries, but only in the smaller palettes; it is 00h for the 256-color files. After this short header, the colors follow as 6-bit RGB triplets, with one color component per byte. Each palette is one of two different sizes:
- 64 colors (3 byte header, 3 bytes per color, 195 bytes total)
- 256 colors (3 byte header, 3 bytes per color, 771 bytes total)
A common convention for converting 6-bit RGB data to the more ubiquitous 8-bit RGB data involves shifting the value left by two, and then replicating the top two bits in the lower two:
r = (r_raw << 2) | (r_raw >> 4);
The starting palette index is important because image data can use colors from any location in the VGA palette (which is defaulted by the graphics hardware BIOS.) Different elements in the game use different index ranges in the palette. The planet textures use up to 64 colors in the range 80h to BFh. The 3D ship models use the range C0h to FFh.
Hidden EGA mode
The game contains a lot of code written to support 16-color EGA video (mode 0Dh), but the config file does not allow any option to switch to this mode. The executable is hardcoded to always use 256-color 320x200 VGA (mode 13h). However, it can be hacked to use EGA instead by changing a single operand. It seemed to work fine when I tested it.
To try this out on v1.01 of NOMAD.EXE, use a hex editor to change the 16-bit value at 468B6
from 13 00
to 0D 00
. You'll also need to add the line nointro
to NOMAD.CFG
as the intro sequence does not work properly in EGA mode.
Remaining debug code
As the game engine runs, it is constantly allocating/deallocating from a memory pool that it maintains for game resources. The developers monitored this memory activity by outputing diagnostic text to the Monochrome Display Adapter, which can actually be used simultaneously with the VGA if a second video card is installed. This code still exists in the final game, and can be enabled with the /aux
command line switch. To see the output, you will need a version of DOSBox with multi-display capability. It's not a feature officially supported by the DOSBox project, but this post in a Vogons.org thread has a link to an experimental build that includes it.
Static image data
Overlay images - stamps and stamp rolls (.STP, .ROL)
The images in Nomad that are treated as overlays on a background are called "stamps" and stored in files with an .STP extension. STP data is run-length-encoded (RLE) with 8 bits per pixel, indexing into a palette. (Note that the RLE compression is another layer inside the LZ compression of the archive, so many of these files are effectively compressed twice.) The game images stored in this format are the inventory objects, navigation star map background, individual starfield sprites, shield and explosion sprites, and overlays from the intro sequence.
The STP image format has a small amount of header information: it stores the image width and height as little-endian 16-bit values in the first four bytes. Following this are another two 16-bit values that represent the negative pixel offsets (for column and row, respectively) on screen at which the drawing should start. For example, if the game engine is preparing to draw a stamp at 100,100 and the .STP data contains 02 00 02 00
at offsets 04 through 07, then the stamp will actually be drawn with the upper left corner at 98,98.
The STP raster image data starts at offset 08. Each chunk of data in the file starts with a runlength byte, which describes that chunk as being one of three types:
- Pointer advance / background: Marked by bit 7 being set (bitmask 80h). In the game engine, this simply advances the pointer in the output buffer by the number of bytes specified in the lower seven bits of this RLE byte (bitmask 7Fh). For example, B8h will advance the output pointer by 38h bytes. The effect of this is that the existing content of the output buffer will persist, unchanged by the decoding sequence. The game engine can prefill the buffer with whatever values is appropriate for the type of image being displayed. In the case of inventory items, the buffer is mostly filled with 00h. The color at index 00h in most of the game's palettes is black, which provides a black background for the image.
- Repeat byte from input: Marked by bit 7 being clear and bit 6 being set. This loads the next byte from the input buffer, and repeats it to the output buffer the number of times specified in the lower six bits of this RLE byte (bitmask & 3Fh).
- Direct byte sequence copy from input: Marked by both bits 7 and 6 being clear. This copies the next n bytes, in sequence, from the input, where n is the value of this RLE byte.
The .ROL file format represents a "stamp roll", which is a simple packaging of related .STP files. The .ROL header is a simple sequence of 32-bit words, each one representing the start offset for one of the contained .STPs. The .ROL file header does not contain an explicit count of the number of contained files; this number is calculated by dividing the very first 32-bit word (which is the offset of the first STP file and therefore also the size of the ROL header) by 4. Overlay animations in the game's intro use stamp rolls, as do some of the diagrams that appear in the game's dialog boxes (engineering schematics of ships, flight control arrows).
Planet surface textures (.PLN)
The image data that represents the surface of planets is stored in the TEST.DAT archive as a series of .pln files. Each .pln file has at least one palette file (.pal) associated with it, and most of the .pln files have multiple possible palettes.
The image files are named "WORLD##a.pln", where ## is a two-digit, zero-prefixed decimal number in the range 00-50 (skipping 21, 22, and 23). The associated palette filenames use the .pal extension. They start with the same seven characters, but the lowercase 'a' suffix is incremented for every associated palette beyond the first. For example, WORLD20a.pln can use any palette from WORLD20a.pal through WORLD20m.pal. (WORLD20 happens to have the greatest number of associated palette files, at thirteen -- a through m.)
Inside each .pln file is an uncompressed 8-bit palette-indexed image. They are all the same size at 200x100 pixels. The only header information is a single little-endian 16-bit word containing the number of pixels per line. All .pln files therefore have the same header (C8 00) and same filesize, at 20002 bytes.
The mapping between planet IDs and their texture/palette files is not done by a data file, but a hardcoded table in the game's executable. In my copy, it appears as a series of two-byte pairs at offset DS:51F4
(or 35384h
in the binary.) The index of the pair matches the ID of the planet (see PLACE.TAB, below). The first byte in each pair is the base number for the texture/palette filename, and the second byte is the lower-case alpha character used as a suffix to select one of the multiple palette files. Places that are not valid travel destinations (i.e. the stars) do not have surface textures that are rendered in game, and their entries in this table are zeroed.
Alien animations
The aliens shown during conversation are drawn using composite animation frames built up from several different overlays. Each frame typically starts with a background image, over which different segments of the alien are drawn to create a complete picture. The alien's head, body, hands, and facial expressions are animated separately to allow for flexibility. Different color palettes are also available, allowing for a large amount of variety in building unique aliens by mixing different combinations of body parts and palettes.
Alien speech audio is (more or less) synchronized with the animation to complete the effect.
DEL files
The .DEL files contain the actual raster image data for the aliens and backgrounds. The first two words contain the image width and height, respectively. (The width and height must be the same for all .DEL files associated with a given alien race.) Each of the remaining data chunks in the file are one of four different types, and the type is determined by the lowest two bits of the first byte in the chunk (which hereafter is referred to as the "command byte"):
Cmd byte & 3 | Sequence type | Decode procedure |
---|---|---|
00 | Delta encoded | Determine a sequence length by isolating the top six bits of the command byte (cmd >> 2). The first byte of output in this sequence is copied directly from the input stream. For each of the following bytes: read the next nibble from the input stream (starting with the high order nibble in each byte), and use its value to index into a hardcoded table that provides the deltas (see below). Add this delta to the value of the last byte that was written to the output, and write the result as the next byte in the output. |
01 | Repeat byte | Copy the next single byte from the input to the output buffer n times, where n is (cmdbye >> 2). |
10 | Advance output | Shift the command byte right by two to isolate the top six bits (cmdbyte >> 2). If this value is nonzero, add it to the output buffer index. Otherwise, if the value in the top six bits is zero, read the next byte from the input and add its value to the output buffer position pointer. The effect of this is that the existing values are left at the skipped output buffer locations, in effect creating a transparency in the current layer. |
11 | Single copy | Copy a single byte from the input to the output buffer. |
Delta table
Index: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | B | C | D | E | F |
---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
Delta: | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | -8 | -7 | -6 | -5 | -4 | -3 | -2 | -1 |
ANM files
The ANM files contain index information that is used to build composite frames and sequence them together. An ANM file describes all the frames for a particular alien. The mapping between an alien's ID and its ANM file is done by a hardcoded table of 206 words at DS:2F20
. Each word points to a string in memory that forms the basis for a filename ("ARD15", "KOR02", "PHE20", etc.) to which the suffix .ANM
is then added. The player's own character does not have an ANM file because the player is not shown during normal gameplay.
Header
Byte offset | Description |
---|---|
00-0C | Null-terminated string containing the name of the palette file associated with this animation set |
0D-19 | Null-terminated string containing the name of a .ROL file that appears to be unused |
Data
This section contains two groups of 64 records, each record being sixteen bytes long (but with all unused bytes set to FFh). Any record that is completely unused will be zeroed.
In the first group, each record describes one complete frame, and lists the partial frames that are layered together to create it. Each byte in the record points to a single .del
file that are together drawn in sequence to complete the frame. The .del filename is in the format xx####.del
, where xx is the first two characters of the alien race (in lowercase) and #### is a zero-prefixed decimal representation of the .del number. (Examples: ar0003.del, mu0051.del)
The second group of ANM records begins at byte offset 41Ah
. The first byte of these records contains three fields:
- Bits 1-0: Alien temperament (equal to 1, 2, or 3)
- Bits 3-2:
01
in the first of several records for a temperament group,10
for all records within that group, and11
in the last record of that group - Bits 7-4: Typically increments for each record within a temperament group
I haven't confirmed the meaning of subsequent bytes in these records, but it's likely that they point to the composite frame numbers established by the first group of records in the same ANM file.
SQB files
These appear to be used to synchronize voice sound effects with the animations. There is a a single .SQB file per alien race (with the exception of Human and Kenelm.) All SQB files are in the same format and are of the same length.
Header
Byte offset | Description |
---|---|
00-0D | Null-terminated string for associated voice filename |
0E-0F | Number of 24-byte records in the SQB file that are used. This is 21 (15h) for all races other than Korok, and 11 (0Bh) for Korok. |
Sound
The game's audio files are packed into .NNV containers, stored in SAMPLES.DAT and TEST.DAT. The container format itself is very simple: the first byte is the number of contained files, and it is immediately followed by pairs of 32-bit words that contain the starting offset and size, respectively, for each of the files in the container. For example, an .NNV that contains four files will have a header of 33 bytes (one byte for the file count, plus four sets of two 32-bit words). Note that some NNVs have index entries that refer to contained files of zero length. This is valid, and simply means that the following file's data will start at the same offset as the zero-length file.
The sound data itself is encoded with a 4-bit delta PCM scheme. It represents a single channel of unsigned 8-bit PCM audio intended for playback at 7042Hz. (This unusual playback rate is the result of the time constant divisor math that is used by the Sound Blaster DSP programming interface.) The table of PCM delta values used by the algorithm is made up of fifteen sections, with each being sixteen bytes long. In my copy of the game, this table is stored in the main executable at offset 29705h
, which becomes seg060:1055
at runtime.
While doing some research, I found that DPCM delta arrays with the exact same values are used by the 1995 Sega Genesis game Comix Zone, and probably other games as well. It seemed that there must be a common reference that was used by the developers, and I then found that The Data Compression Book (Nelson, 1991) contains a description of audio compression techniques with these precise delta values in an example. They are derived from the following exponential transfer function:
output = 127.0 * ( pow(2.0, input / max_input_val) - 1.0 )
Decoding DPCM audio
The DPCM encoded data is read one nibble at a time, starting with the high nibble first. When a nibble of value 0
is encountered, the following nibble is used as an index to select one of the fifteen delta tables. Otherwise, when a nonzero nibble is encountered, it is used as an index into the currently selected delta table, and the resultant delta value is then added to the last byte written to this output. (This signed 8-bit value is allowed to wrap.) This becomes the next byte to write to the output. At the start of decoding, the "last value" is considered to be 80h
.
When the nibble sequence 0 F
is encountered, this cannot be used to set the delta table index because the value F
is beyond the index range. Instead, this sequence is interpreted as a command to repeat the last byte that was written to the output. To get the repeat count, the next two nibbles are read and combined into a single 8-bit value (regardless of whether they shared the same byte in the input stream.) If this value is 0, it is interpreted as a repeat count of 256. After repeating the last output byte this number of times, decoding continues with reading the next nibble from the input.
Example input sequence: 03 40 F0 41
- Read nibble
0
, which indicates the start of a command sequence. - Read nibble
3
. This is less thanF
, so it is used to select the fourth section of the delta table. - Read nibble
4
. Because this is not zero, it is used to select a single delta value from the currently selected delta table. In this case, the delta is-Bh
. This delta is added to the last output value (which we assume to be80h
) to create the next output value of75h
. This byte is written to the output stream. - Read nibble
0
, which indicates the start of a command sequence. - Read nibble
F
. This is the byte-repeat command. The next two nibbles are read and combined into a single 8-bit value (04h
) that is used as the repeat count. The last byte written to the output (75h
) is repeated to the output this number of times. - Read nibble
1
. This is another delta value selection. We are still using the same delta table section selected by the first two nibbles in this input stream, and the delta value at position1
in that section happens to be-17h
. This is added to75h
to compute the next output value of5Eh
This example input stream generates the following output: 75 75 75 75 75 5E
Sound file notes
There is a table of sound filenames that each correspond to one of the game's alien races. Each of these sounds is a short musical theme that plays when the player begins conversation with an alien. The table contains an entry for human (HUM.NNV
), although this file does not actually exist in the game's data and the player does not encounter other humans during gameplay. Also, the table contains a Kenelm entry (KEN.NNV
), but this file is also missing since the Kenelm have no musical theme and begin speaking immediately when communication is opened.
3D models
The 3D model data is stored in a series of .BIN files, all packed into TEST.DAT. (Note that the .BIN extension is not unique to the 3D models and is shared with the font data files, planet scale table, and copy protection data.) All the ship models are rendered untextured, with flat shading.
The model BIN format starts with a 16-bit word that provides the number of polygons in the model. Immediately following this is a series of polygon records. Each polygon record is a minimum of 18 bytes, and is two bytes longer for each vertex beyond the third. (Note that polygons may have an arbitrary number of points; there are examples with up to 22 vertices.)
Within each polygon record:
Offset | Field |
---|---|
00-01 02-03 04-05 |
Three signed 16-bit words that are almost certainly 3D coordinates. I originally thought that they were used to define a surface normal, but that may not be the case. Perhaps they're used to provide a direction vector for the surface during the ship explosion animation? |
06-07 | Unknown |
08-09 | Unknown |
0A | 7-bit vertex count (stored in the lower 7 bits). The high bit is also sometimes set, but its purpose is unknown. |
0B | Color for this polygon surface. This will be an index 00 through 07, which happens to match up to the first eight colors of the EGA palette. In reality, each of the these base colors will be one of eight shades when it is rendered on screen (as the 3D engine selects the appropriate one for highlights/shadows.) The 64 total colors that are used for drawing 3D models to the screen are provided by the palette SHIP.PAL. |
0C-0D | Start of the list of vertex indices. There will be at least three 16-bit words in this section, and possibly more, depending on the number of vertices in the polygon. Each word is simply a zero-based index into the list of vertices that comprises the last section of the .BIN file. Note that the vertices for a given polygon must appear in contiguous order, i.e. each listed vertex must be adjacent to the previous one. |
Immediately after the last vertex ID at the end of the polygon record, the next polygon record begins. After the last polygon record is a 16-bit count of the total number of 3D vertices used in the model. The data for these vertices then follows, with the X,Y,Z 3D coordinates being represented as triplets of signed 16-bit integers.
TEST.DAT contains three models that are not used in the game:
- BOTA.BIN: Labor bot capsule. This is an alternative to the familiar BOTB and BOTC models that are used during 3D animated scenes. They appear when completing a trade with an alien, or when sending/retrieving a labor bot. There are hardcoded offsets in the game executable (within
ovr114:112B
) that determine the selection of BOTB vs. BOTC depending on labor bot type, and one or more of these could easily by modified to use the previously unseen BOTA model instead. - SHABC.BIN: Shaasa ship, perhaps intended to be a fighter
- SHABS.BIN: Shaasa ship, and probably the strangest design of any ship in the game
Notes about 3D engine behavior
The game's 3D engine uses a series of transform buffers, each associated with one of the 3D models that are loaded into the scene. For the scripted 3D animation sequences -- including the snowfield crash from the intro sequence, the conclusion of a trade with an alien ship, and sending or retrieving a labor bot -- all of the data for positioning and movement of the 3D models is hardcoded as parameters to a series of transform functions. The sound effects associated with these animations are simply cued by function calls at the appropriate point in between model transforms. In other words, the sequence of movements in these scenes cannot be changed without modifying code in the game executable.
When the developers coded the 3D camera sequence in which the player's ship enters orbit around a planet, they wanted a natural-feeling flight path with coordinates that followed a smooth curve. Rather than compute them on the fly, these coordinates are precalculated and stored in a table of two hundred 16-bit words in seg056
.
Data tables
Nomad has a series of data tables that are read into memory and used to define attributes for ships, planets, aliens, inventory objects, engineering upgrades, and other aspects of the game. In addition to defining the initial state of the game universe, many of these tables contain fields that are updated dynamically while the game is being played; for example, the Object Table entries each contain a flag that is set when the player is granted knowledge of that object. When a player saves his progress, a .SAV file is created that contains a subset of the game's data tables plus some additional dynamic data. The .SAV files also include a few discrete pieces of data, such as the number of Korok vessels that have been destroyed, and a boolean flag indicating whether the Altec Hocker have been discovered by the player.
Enumerations and indexing
The game engine makes frequent use of enumerations to index table records. One of the more commonly encountered enums is that for the alien races:
Value | Race |
---|---|
00 | Altec Hocker |
01 | Arden |
02 | Bellicosian |
03 | Chanticleer |
04 | Human |
05 | Kenelm |
06 | Korok |
07 | Musin |
08 | Pahrump |
09 | Phelonese |
0A | Shaasa |
0B | Ursor |
The data tables that keep track of planets, ships, aliens, objects, etc. do not make use of the record at index 0. An index of 0 is typically interpreted by the code as "unused" or "invalid". This effectively means that most tables have 1-based indexing.
Many of the data tables describe locations, people, or objects that have a name used in-game. Most of these names are stored in GAMETEXT.TXT, and I found that the first word of data table entries is often an offset into GAMETEXT.
ALIEN.TAB
- Total size: 2048 (800h) bytes
- Record size: 8 bytes
- Record count: 256 (100h), of which 206 (CEh) are used, including one for the Player
In addition to the many aliens that are actually encountered during the game, ALIEN.TAB contains entries for the mythical figures from the legends told by the different alien species. For some reason, these characters are all assigned Chanticleer portrait animations, but this is of no consequence since they do not appear during gameplay.
Byte offset | Description |
---|---|
00-01 | Name of alien. Offset into GAMETEXT.TXT for null-terminated string. |
02 | Alien race. |
03 | Bytes 03 through 07 contain apparent percentage/scaling values, with the lowest being 00h and the highest being 64h. |
CCLASS.TAB - Communication Jammer Table
- Total size: 20 (14h) bytes
- Record size: 2 bytes
- Record count: 10 (Ah), of which 4 are used (indices 1 through 4)
ECLASS.TAB - Engine Class Table
- Total size: 60 (3Ch) bytes
- Record size: 6 bytes
- Record count: 10 (Ah), of which 6 are used (indices 1 through 6)
FACT.TAB
- Total size: 2048 (800h) bytes
- Record size: 16 (10h) bytes
- Record count: 128 (80h), 124 used
Byte offset | Description |
---|---|
00-01 | Offset into GAMETEXT.TXT for null-terminated string |
02-0D | Byte array indicating each alien race's receptivity to this fact |
0E | Bitfield: Bit 1: Fact is known by player |
INVENT.TAB
First section:
- Total size: 1024 (400h) bytes
- Record size: 4 bytes
- Record count: 256 (100h)
Second section:
- Total size: 9216 (2400h) bytes
- Record size: 4 bytes
- Record count: 2304 (900h)
Total size: 10240 (2800h) bytes
This table describes the inventory contents of each ship in the game. It is divided into two sections, both with four-byte records. The first section has 256 records -- one for each slot in the Ship Table -- and each record stores a pointer to the start of a linked list of records in the second section. The second section contains these linked list nodes, with each node containing an item ID, count, and link to the next node.
The index of the Player's Ship is FFh
(i.e. last position in the Ship Table), so the player's inventory list starts with a pointer stored in the last record of the Inventory Table's first section.
First section:
Byte offset | Description |
---|---|
00 | Unused; fixed at 00 |
01 | Number of item records associated with this ship |
02-03 | Index of first record for this ship's inventory. This must be multiplied by the record size (4 bytes) to get the offset into INVENT.TAB. |
Second section:
Byte offset | Description |
---|---|
00 | Object ID |
01 | Count |
02-03 | Index of next record for the current ship's inventory. This must be multiplied by the record size (4 bytes) to get the offset into INVENT.TAB. |
LBOOSTER.TAB - Labor Botbooster Data
- Total size: 60 (3Ch) bytes
- Record size: 6 bytes
- Record count: 10 (Ah), of which 6 are used (indices 1 through 5, and 7)
LCLASS.TAB - Labor Bot Class Data
- Total size: 120 (78h) bytes
- Record size: 12 (0Ch) bytes
- Record count: 10 (Ah), of which 6 are used (indices 1 through 6)
Byte offset | Description |
---|---|
05 | Gas collection efficiency |
06 | Ore collection efficiency |
07 | Farming efficiency |
08 | Ranching efficiency |
09 | Archaeological efficiency |
0A | Spy efficiency |
MCLASS.TAB - Missile Class Table
- Total size: 40 (28h) bytes
- Record size: 4 bytes
- Record count: 10 (Ah), of which 4 are used (indices 1 through 4)
Byte offset | Description |
---|---|
03 | Power |
META.TAB
- Total size: 276 (114h) bytes
- Record size: 4 bytes
- Record count: 69 (45h), of which 64 are used (indices 0 through 63)
The meta-text system in Nomad allows the game to randomly select from several equivalent phrases when building alien conversation text, thereby adding some variety to the dialogue. The text strings themselves are stored in GAMETEXT.TXT, and the offsets to them are stored in METATXT.TAB. This table -- META.TAB -- defines the type of each meta text section as well as the number of synonymous phrases available in each.
Byte offset | Description |
---|---|
00 | Number of different possible selections for this meta text entry. |
01 | Type of meta text. Three possible types are used by the game: 00h: Normal conversational synonym (e.g. the Musin will randomly select from the words "dastardly", "nefarious", "villainous", and "contemptible" when cursing someone of low moral fibre) 01h: Translation. About a dozen different words spoken by the Shaasa in their native language are automatically translated if the player has the Proto Uni-Translator in his inventory. 02h: Losten gateway code. Because the codes are generated randomly during each playthrough, this marker is used to generate a code (or recall a previously generated code.) |
02-03 | Index of record in METATXT.TAB. Multiply by 2 to compute byte offset in that file. |
METATXT.TAB
- Total size: 460 (1CCh) bytes
- Record size: 2 bytes
- Record count: 230 (E6h), of which 226 are used (indices 0 through 225)
This table is a simple list of 230 16-bit offsets into GAMETEXT.TXT. Each of the offsets points to the start of a string in the "meta" text section at the beginning of GAMETEXT. These text fragments fall into groups, and each group defines equivalent phrases from which the game will pick randomly while building alien conversation text.
Note that this file contains an offset for each individual meta text string (not merely the first string in each group). The indices for strings that belong to the same meta group are all contiguous in the file.
MISSION.TAB - Mission Table
- Total size: 3072 (C00h) bytes
- Record size: 24 (18h) bytes
- Record count: 128 (80h), 100 used
Byte offset | Description |
---|---|
00-01 | Place ID of the location that the Player must visit to either obtain or deliver a mission item |
02-07 | In-game time at which mission becomes available. One byte each for year, month, day, hour, minute, and a pad. |
08-0D | In-game time at which mission becomes unavailable. One byte each for year, month, day, hour, minute, and a pad. |
0F | ID of other mission -- prerequisite? |
10 | Action required: 00: No action required 02: Destroy ship 03: Deliver item |
11 | ID of target ship, or ID of required inventory object |
12-13 | Status flags |
14-15 | Index of MISTEXT.IDX record for text of mission start (multiply by 4 and add 4 for MISTEXT.IDX offset) |
16-17 | Index of MISTEXT.IDX record for text of mission completion (multiply by 4 and add 4 for MISTEXT.IDX offset) |
MISTEXT.IDX - Mission Text Index
- Total size: 804 (324h) bytes
- Record size: 4 bytes
- Record count: 200 (C8h)
Table of 32-bit offsets into MISTEXT.TXT, each one pointing to the start of a string containing mission text. In this file, the first two bytes are zeroed, followed by a 16-bit little-endian word containing the number of records in the file. The records containing the 32-bit offsets then follow immediately.
MISTEXT.IDX - Mission Text
Contains gametext that describes Alliance missions, both to start missions and acknowledge their completion.
MSYS.TAB - Missile System Table
- Total size: 20 (14h) bytes
- Record size: 2 bytes
- Record count: 10 (Ah)
OBJECT.TAB - Inventory Object Table
- Total size: 6144 (1800h) bytes
- Record size: 24 (18h) bytes
- Record count: 256 (1000h)
This table contains information about the game's inventory objects. It also reveals some interesting elements and a significant bug:
- In agreement with the storyline, the Altec Hocker do not place any value on any material items.
- A few of the objects are abstract concepts that do not physically appear in-game. These include "The Alliance", "Facts", "The Volm", and the W.A.P.. The first three of these actually have images associated with their IDs, although they look like unused images for items that were removed from the game.
- The relative-value fields for the Korok and the Kenelm are swapped! This is very apparent when inspecting the data. Based on the storyline, the Kenelm should place maximum value on the Eye of Kenelm but no value on any other object, while the Korok should place value primarily on raw materials and tactical equipment. However, exactly the opposite is true in the OBJECT.TAB data. Although this is a bug in the game, it only manifests as a tendency for the Korok to accept strange items in trade that shouldn't actually interest them.
Byte offset | Description |
---|---|
00-01 | Name of object. Offset into GAMETEXT.TXT for null-terminated string. |
02-03 | ? |
04-05 | ? |
06 | Boolean flag indicating whether the item is tradeable. |
07 | ? |
08 | Bit 7 is a flag indicating that the item is unique. The rest of the byte is a single 7-bit value: 00: Normal item without a special purpose 01: Normal item, but with informational text (via OBJTEXT.IDX / OBJTEXT.TXT) 02: Engine 03: Scanner 04: Jammer 05: Shield 06: Missile 07: Missile loader 0A: Ship botbooster 0B: Labor bot 0C: Labor bot enhancement (botbooster or aquatic kit) 0D: Award/medal/recognition 0E: Translator (used only by Proto Uni-translator) |
09 | Multipurpose ID information. If the item is of type 01 (i.e. it has informational text associated with it), then the value in this field is used as an index to a 4-byte record in OBJTEXT.IDX, which in turn provides an offset into OBJTEXT.TXT for the text. Otherwise, if the item is of another type, this field is interpreted as an enum value that maps to the different levels of shields, missiles, scanners, etc. |
0A | Bitfield. Bit 2 indicates that the object is known by the player. |
0B-16 | Array of byte values indicating the desirability of the object to each of the game's alien races. The range of values per byte in this array is 00-64h. Indexing into the array is done with the same alien race enum that is used elsewhere in the game. |
17 | Unused |
PCLASS.TAB - Place Class Data
- Total size: 3072 (C00h) bytes
- Record size: 48 (30h) bytes
- Record count: 64 (40h), of which 52 (34h) are used
The names are descriptive of the planet type or its attributes ("Barren", "Ice Planet Type A", "Abandoned", etc.)
Byte offset | Description |
---|---|
00-01 | Name of place class. Offset into GAMETEXT.TXT for null-terminated string. This will be a description of the place class like "Barren", "Ice Planet Type A", etc.) The description may also be "Sentient Life Forms", which is used for Alchem (home of the Kenelm.) |
06-07 | Temperature. Signed 16-bit value that is described by the game engine as being in one of six ranges: Below -120: Arctic -120 to 39: Frozen 40 to 99: Temperate 100 to 149: Tropic 150 to 899: Searing Above 899: Molten |
09 | Nonzero if the planet class is inhabited. If the class is uninhabited, it is considered "Previously Inhabited" only if archaeological artifacts are present. |
0A | Class type: 01: Terran 02: Gas 03: Gas giant 04: Ice 05: Molten 06: Water 0A: Home of sentient life (Alchem) |
0B-0D | Object IDs of farmable foods |
0E-10 | Agriculture for each of the farmable foods: 0: None 1 to 19: Barren 20 to 79: Arable 80 or above: Fertile |
11-13 | Object IDs of ore/minerals |
14-16 | Concentration of each of the ore/minerals: 0: None 1 to 19: Trace 20 to 59: Moderate 60 to 100: Abundant 100 and above: ? |
17-19 | Object IDs of archaeological artifacts |
1A-1C | Concentration of each of the archaeological artifacts: |
1D-1F | Object IDs of atmospheric gases |
20-22 | Concentration of each of the atmospheric gases: 0: None 1 to 39: Thin 40 to 59: Moderate 60 or above: Dense |
23-25 | Object IDs of livestock animals |
26-28 | Concentration of each of the livestock animals: 0: None 1 to 39: Sparse 40 to 59: Moderate 60 or above: Plentiful |
29-2B | Object IDs of intelligence items owned by the planet's current inhabitants (e.g. computer banks, diplomatic pouches) |
2C-2E | Concentration of each of the inhabitants' intelligence items |
PLACE.TAB
- Total size: 5120 (1400h) bytes
- Record size: 16 (10h) bytes
- Record count: 320 (140h)
Each entry describes a "place", which is a star or planet (or artificial space station, with the single example of this being Second Harmony.) The stars themselves are not valid travel destinations within the game, but they still have attributes that are recorded within this table. Of the 320 slots, only 310 are used by the game.
Byte offset | Description |
---|---|
00-01 | Name of place. Offset into GAMETEXT.TXT for null-terminated string. |
02-03 | Bits: 0: ? 1: Known by player 2: Visited by player 3: Labor robot site |
04 | Place class (index into PCLASS.TAB; multiply by 30h to get PCLASS offset) |
05 | Place type: 00h for star, 01h for planet |
06 | Place table index for parent star |
08 | Planet representative (ALIEN.TAB index; multiply by 8 for ALIEN.TAB offset) |
09 | Race |
0A-0B 0C-0D 0E-0F |
Three words that control the drawing locations for stars and planetary orbit tracks in the graphical navigation map. |
PODCLASS.TAB - Unused
This table is unused by the game. It is populated entirely with zeroes.
RCLASS.TAB - Robot Class Table
- Total size: 20 (14h) bytes
- Record size: 2 bytes
- Record count: 10 (Ah), of which 5 are used (indices 1 through 5)
Data for ship-repair robots. Each record only contains a single value, which translates to the robots' effectiveness.
SCCLASS.TAB - Scanner Class Table
- Total size: 60 (3Ch) bytes
- Record size: 6 bytes
- Record count: 10 (Ah), of which 6 are used (indices 1 through 6)
SCLASS.TAB - Ship Class Table
- Total size: 384 (180h) bytes
- Record size: 12 (0Ch) bytes
- Record count: 32 (20h)
Of the 32 record slots in this file, 30 are populated with data (08 and 31 being unused.) These slots correspond one-for-one with the first 31 entries in a table of 3D model filenames stored in the game executable. There are more ship classes than there are unique ship models, so some models are re-used. Note that the Altec Hocker have a class allocated to them although they do not have physical ships. (The game assigns them the same ship model as the Musin, but this is of no consequence since it is never spawned.) Because the Second Harmony star station is represented as a 3D model, its corresponding slot in SCLASS.TAB is skipped to keep the indexing consistent.
Byte offset | Description |
---|---|
00-01 | Name of ship class. Offset into GAMETEXT.TXT for null-terminated string. |
02 | Starting missile quantity/capacity? |
03 | Missile armament type. 00 = None. |
04 | Missile loader/system type |
05 | Shield type |
06 | Scanner type |
07 | Engine type |
08-09 | Starting shield (or hull?) strength |
0A-0B | Starting hull (or shield?) strength |
SHCLASS.TAB - Shield Class Table
- Total size: 40 (28h) bytes
- Record size: 4 bytes
- Record count: 10 (Ah), of which 7 are used (indices 1 through 7)
SHIP.TAB - Ship Data
- Total size: 17920 (4600h) bytes
- Record size: 70 (46h) bytes
- Record count: 256 (100h), of which 255 are used (including one for the Player's Ship)
The last entry is for the player's own ship, and the game executable has several routines with hardcoded access to specific fields in this record (at offset 45BAh
).
Byte offset | Description |
---|---|
00-01 | Name of ship. Offset into GAMETEXT.TXT for null-terminated string. |
02 | Pilot (index into ALIEN.TAB) |
03 | Ship class (index into SCLASS.TAB) |
04-05 | Bitfield. If bits 3, 9 or 13 are set, the vessel will not respond to communication hails. If bit 11 is set, the ship will not appear on the radar scope. |
06-07 | Offset to ? |
0C-0D | 3D model transform buffer pointer |
10-11 | Flight dynamics struct pointer |
12-13 | Pointer to other ship being targeted |
22-23 | Location (index into PLACE.TAB) |
24-25 | Pointer to another ship table entry for a ship that is somehow engaged with this one (targeting/hailing). |
26 | Weapon type |
27 | Weapon system damage (0 to 64h) |
28 | Missile loader type |
2A | Downcounter that is periodically decremented during the missile loading process. Reaches zero when a missile is loaded and the pilot may attempt missile lock. |
2B | Currently loaded missile type |
2C | Downcounter that is periodically decremented during the missile locking process. Reaches zero when missile lock is achieved (i.e. ready to fire.) |
2E-31 | Far pointer to another ship table entry |
32 | Shield status (raised/lowered)? |
33 | Shield system damage (0 to 64h) |
34 | Shield type (index into SHCLASS.TAB) |
35 | Shield strength? |
36-39 | Ship table pointer for shield-related activity? |
3A | Bitfield (or single boolean value)? |
3B | Scanner system damage (0 to 64h) |
3C | Scanner type |
3E-41 | Ship table pointer for scanner target |
42 | Engine system damage (0 to 64h) |
43 | Engine type |
44 | Jammer system damage (0 to 64h) |
45 | Jammer type |
STCLASS.TAB - Star Class Table
- Total size: 96 (60h) bytes
- Record size: 6 bytes
- Record count: 16 (10h), of which 14 are used (indices 1 through 14)
Byte offset | Description |
---|---|
00-01 | Name of star class. Offset into GAMETEXT.TXT for null-terminated string. |
Talk files
The game's conversation engine is fed with a large number of data tables. Some of these are common to all individuals of a given alien race, and some are specific to individuals. Entries in the tables reference indices in other tables to create links between lines of dialogue and manage the flow of conversation.
Talk tables with a 'C' as the fifth character of the filename are specific to an individual alien, and the numeric portion of the filename is the ID of that alien. Talk tables with an 'R' in this position are specific only to a race (rather than an individual) and the numeric portion is the enumeration value for that race.
TLKXCxxx.TXT/.IDX and TLKXRxxx.TXT/.IDX
Collection of strings, where each string is a line of alien dialogue. String data begins at offset 0 in the .TXT and consists of null-terminated strings using the same metatext system as elsewhere in the game.
The .IDX contains a list of 32-bit offsets into the .TXT, each one pointing to the start of a string. In the .IDX file, bytes 00-01 are always zeroed, followed by a 16-bit count (in bytes 02-03) of the total number of offsets in this file. The list of 32-bit offsets follows immediately, so string index 0 actually starts at byte offset 04.
Easter egg: in the file for the player's character ID (TLKXC255.TXT), there is only a single string which was apparently never intended to be seen in-game. It reads, "Go away!!!"
Also, TLKXR005.TXT -- the file for the Kenelm -- contains some unused conversational phrases.
The formats of the TLKXC and TLKXR files are identical, but the game's conversation engine will always check the TLKXC files first and use TLKXR as a fallback if the individual alien table did not have any entries matching the conversation topic.
TLKNCxxx.TAB and TLKNRxxx.TAB
- Record size: 6 bytes
These tables contains pointers to lines of dialogue, and optionally can indicate whether a line is a direct question to the player, causing the game to present the player with a pick-list to choose a response.
As with other talk table filenames, the 'C' designates individual-specific and the 'R' race-specific. The first 6-byte record (at index 0) is unused.
Byte offset | Description |
---|---|
00-01 | Index into TLKXCxxx.IDX or TLKXRxxx.IDX, which in turn points to a line of dialogue |
02 | ? |
03 | Nonzero for the lines that are a question to the player. The number in this field is the number of possible responses from which the player can choose. |
04 | ? |
05 | ? (always zero?) |
TLKPRxxx.TAB
The PR talk table creates links between statements and reactions, which is used when the player interactively chooses a response from a list during conversation. This table determines the line of dialogue with which an alien will respond when the player chooses a particular path.
Byte offset | Description |
---|---|
00-01 | Index in TLKXRxxx.IDX |
02-03 | Index in TLKNRxxx.TAB, which then points to an entry in TLKXRxxx.IDX |
TLKTCxxx.TAB and TLKTRxxx.TAB
Tables of ten-byte records that point to dialogue lines based on topic.
Byte offset | Description |
---|---|
00 | Type indicator: 00: Ask about person, place, or object, or give fact 01: ? 02: Alien sees object in player's inventory 03: Trades for object 04: Display object 05: Greeting (first meeting) 06: Greeting (subsequent meetings) 07: Ask about alien race 08: Give object |
01 | Priority. Compared between records, with the highest-numbered record being selected by the game. |
02 | Boolean marker used at runtime |
03 | Alien ID |
04-05 | Place ID |
06 | Object ID |
07 | Multipurpose ID: alien race if Type field is 07 , or fact ID if Type field is 00 |
08-09 | Index of record in the corresponding TLKNCxxx.TAB or TLKNRxxx.TAB |
SMFONT.BIN and LGFONT.BIN
These contain the raster font data for the small and large fonts, respectively. The first two bytes of each file are the width and height of each character in that font set. LGFONT.BIN is the larger of the two files not only because of the larger character size, but also because it includes both upper- and lower-case alpha characters. SMFONT.BIN contains upper-case only.
SAV file format
The sections in the .SAV file:
Internal name | Byte offset | Size (bytes, hex) | Description |
---|---|---|---|
(marker bytes) | 00 | 2 | Always 07 01 |
this_conv | 02 | 4 | |
player_name | 06 | 9 | Null-terminated string containing player's name |
player_ship | 0F | 15 | Null-terminated string containing name of player's ship |
current | 24 | 6 | |
setup_tab | 2A | 2BC | |
setup_next_ship | 2E6 | 2 | |
setup_next_free | 2E8 | 2 | |
altec_found | 2EA | 2 | Boolean flag indicating whether the Altec Hocker have been encountered (00 or 01) |
ship_armor | 2EC | 1 | Damage to player's ship armor. 0 is no damage; 100 (64h) is complete damage (i.e. ship is destroyed) |
belicosians_killed | 2ED | 2 | Number of Bellicosian ships destroyed by player |
korok_killed | 2EF | 2 | Number of Korok ships destroyed by player |
ship_heap | 2F1 | 4600 | Run-time instance of data from SHIP.TAB |
repair_system | 48F1 | 2 | System on player's ship currently being repaired |
current_place | 48F3 | 2 | Index in PLACE.TAB of player's current location |
pclass_heap | 48F5 | C00 | Run-time instance of data from PCLASS.TAB |
place_heap | 54F5 | 1400 | Run-time instance of data from PLACE.TAB |
object_heap | 68F5 | 1800 | Run-time instance of data from OBJECT.TAB |
mission_completed_tab | 80F5 | 80 | |
mission_heap | 8175 | C00 | Run-time instance of data from MISSION.TAB |
labor_heap | 8D75 | 1C0 | Labor robot locations and status |
players_cargo | 8F35 | 14 | |
invent_heap | 8F49 | 2800 | Run-time instance of data from INVENT.TAB |
fact_heap | B749 | 800 | Run-time instance of data from FACT.TAB |
race_killed | BF49 | D | Array of player's kill count; single-byte count per species |
race_known | BF56 | D | Array of boolean flags indicating whether the player has knowledge of each species in the game (one byte per race) |
astate_heap | BF63 | 400 | State of individual aliens and their relationship to the player |
alien_heap | C363 | 800 | Run-time instance of data from ALIEN.TAB |
encount_relate | CB63 | 90 | Relationship of each alien race with each of the other races |
encount_relate2 | CBF3 | 90 | |
losten_keys | CC83 | 4 | Array of the four Losten gateway key values, which are stored in the save file since they are randomly generated for each playthrough |
gate_open | CC87 | 1 | Boolean flag indicating whether the Losten gateway has been opened |
Alien State Heap (astate_heap)
- Total size: 1024 (400h) bytes
- Record size: 4 bytes
- Record count: 256 (100h) Each record corresponds to a single alien, using the same indexing as ALIEN.TAB.
Byte offset | Description |
---|---|
00 | Bitfield. Bit 2 indicates that the alien is known by the Player. Bit 3 indicates that the Player and alien have met. |
01 | Attitude/temperament: 00h to 1Eh: 1 1Fh to 46h: 3 47h and above: 2 |
02 | ? |
03 | ? |
Encounter/Relationship Tables (encount_relate and encount_relate2)
- Total size: Two tables, each of 144 (90h) bytes
- Record size: 12 (0Ch) bytes
- Record count: 12 (0Ch) per table
Tracks the temperament of alien races toward other races. Each byte is a value between 00 and 05, inclusive, and a higher value indicates a friendlier relationship.
Labor Robot Heap (labor_heap)
- Total size: 280 (118h) bytes
- Record size: 14 (0Eh) bytes
- Record count: 20 (14h)
Tracks the labor bots that are currently deployed to planets.
Byte offset | Description |
---|---|
00 | Boolean flag indicating whether this slot is used |
01 | Labor bot type (object ID) |
02 | Labor bot enhancement (object ID) |
03 | Object ID of cargo |
04 | Quantity of cargo |
05 | Percentage of cargo capacity filled |
06-07 | Rate of operation? |
08-09 | Place Table ID of location |
Setup Table (setup_tab)
- Total size: 700 (2BCh) bytes
- Record size: 14 (0Eh) bytes
- Record count: 50 (32h)
Byte offset | Description |
---|---|
00-01 | Place ID? |
02 | Ship ID? |
08-0C (or to 0D?) | Age-out time |
Demo file
The main executable contains some code that supports recording and playback of a demo file. The file, named SPACE.DMO, would have a maximum of 1000 (3E8h) records that are each 12 bytes long.
List of filenames in the original source
The debug code in the game executable reveals the name of the original source code modules:
- space.c
- save.c
- state.c
- ems.c
- sound.c
- show.c
- intro.c
- alien.c
- object.c
- place.c
- fact.c
- init.c
- talk.c
- talkd.c
- say.c
- invent.c
- inventd.c
- trade.c
- meta.c
- mission.c
- encount.c
- computer.c
- log.c
- planscan.c
- shipscan.c
- effect.c
- anim.c
- setup.c
- losten.c
- nav.c
- msg.c
- missiond.c
- fly.c
- robot.c
- labor.c
- pod.c
- ship.c
- com.c
- engine.c
- missile.c
- scan.c
- map.c
- shield.c
- combat.c
- ship3d.c
- part.c
- threed.c
- planet3d.c
- planet.c
- ctrl.c
- rtext.c
- pick.c
- based.c
- window.c
- dialog.c
- icon.c
- edit.c
- editor.c
- fractal.c
- bitmap.c
- stamp.c
- palette.c
- lib.c
- disk.c
- dual.c
- mcga.c
- event.c
- text.c
- font.c
- err.c