Ultima Underworld Internal Formats

This page documents the Ultima Underworld engine's internal formats (used in Ultima Underworld and Ultima Underworld II). System Shock used an overhauled, vastly different engine that is not discussed here.

If not specifically noted, anything here refers to Ultima Underworld 1 (UW1) only. Ultima Underworld 2 (UW2) changes some formats, which will be documented later.

General Types
These types are used in this page:


 * char - A 7-bit (1-byte) ASCII character code.
 * charz - As with char, but a nul character (code 0) terminates a string.
 * uint8 - Unsigned 8-bit (1-byte) integer, between 0 and 255 (2^8-1).
 * uint16 - Unsigned 16-bit (2-byte) integer, between 0 and 65535 (2^16-1), in little-endian byte order.
 * uint32 - Unsigned 32-bit (4-byte) integer, between 0 and 4294967295 (2^32-1), in little-endian byte order.

Files
This is a hierarchical index of the files in Ultima Underworld and what is known about them.


 * uw.exe
 * crit/ - Creature animations.
 * cr##page.n##
 * cuts/ - Cutscenes.
 * cuts/cs###.n##
 * data/ - General static data.
 * data/3dwin.gr
 * data/allpals.dat - A collection of uint8 that is used by various graphics to map from a 4-bit value to the 8-bit palette. See the Graphics section for details.
 * data/animo.gr - Spell effect animation graphics (palette 0).
 * data/armor_f.gr - Female armor paper doll graphics (palette 0).
 * data/armor_m.gr - Male armor paper doll graphics (palette 0).
 * data/babglobs.dat
 * data/bodies.gr - Paper doll body graphics (palette 0); five male, then five female.
 * data/buttons.gr - Contains editor buttons (palette 0).
 * data/chains.gr - Chain animation graphics (palette 0).
 * data/charhead.gr - Character portrait graphics (palette 0), each 34x34.
 * data/chrbtns.gr - Character creation screen graphics (palette 3).
 * data/chrgen.dat
 * data/cmb.dat
 * data/cnv.ark
 * data/comobj.dat
 * data/compass.gr - Compass graphics (palette 0).
 * data/converse.gr - Conversation pane graphics (palette 0).
 * data/cursors.gr - Cursor graphics (palette 0). In order, they are the crosshair, forward, backward, left, right, turn left, turn right, bear left, bear right, circle, x, writing quill, well, quill cursor, ankh frame 1, ankh frame 2, ankh frame 3, and ankh frame 4.
 * data/doors.gr - Door textures (palette 0); each 32x64. This does not include the portcullis texture.
 * data/dragons.gr - Graphics for the dragons on the borders of the screen (palette 0).
 * data/eyes.gr - Graphics for the eyes on the borders of the screen (palette 0).
 * data/flasks.gr - Health/mana/poisoned flask graphics (palette 0).
 * data/font4x5p.sys - A 5x4 font (see the Font section). Note the name lists the height first.
 * data/font5x6i.sys - A 6x5 italicised font (see the Font section).
 * data/font5x6p.sys - A 6x5 font (see the Font section).
 * data/fontbig.sys - An 11x15 font (see the Font section).
 * data/fontbutn.sys - A 9x6 font only containing capital A-Z (see the Font section).
 * data/fontchar.sys - A 9x10 font (see the Font section).
 * data/genhead.gr - Generic character portrait graphics (palette 0); many entries are empty.
 * data/grave.dat
 * data/heads.gr - Player portrait graphics (palette 0).
 * data/inv.gr - Apparently unused graphics (palette 0). Mostly a bunch of yellow rectangles and a cut-off window.
 * data/lev.ark - The initial game state archive.
 * data/lfti.gr - Game action button graphics (palette 0). These are options, options highlight, speak, speak highlight, move, move highlight, look, look highlight, combat, combat highlight, use, and use highlight.
 * data/light.dat
 * data/lights.dat
 * data/mono.dat
 * data/objects.dat
 * data/objects.gr - Object icon graphics (palette 0). This includes some editing icons, or possibly icons that would be displayed in-game with a special mode.
 * data/opbtn.gr - Title screen button graphics (palette 2).
 * data/optb.gr - More option screen button graphics (palette 0).
 * data/optbtns.gr - Option screen button graphics (palette 0); see the Tables section for the list of buttons.
 * data/pals.dat - The 8 palettes used throughout the normal game. 0 is the main game palette, 1 is the map screen palette, 2 is the title screen palette, 3 is the character creation screen palette, 4 is unknown, 5 is the splash screen palette, 6 is unknown, and 7 is the win screen palette.
 * data/panels.gr - Panel graphics file (palette 0, explicit sizes 83x114, 83x114, 83x114, and 6x60). Images are the paper doll background, item list backgruond, character sheet background, and some kind of borders.
 * data/player.dat
 * data/power.gr - Strike power animation graphics (palette 0).
 * data/question.gr - Question cursor graphics (palette 0); contains one image, which is the question cursor.
 * data/scrledge.gr - Ledger scroll edge animation graphics (palette 0).
 * data/shades.dat
 * data/skills.dat
 * data/spells.gr - Spell icon graphics (palette 0); each 16x18.
 * data/strings.pak - Game strings file (see the Strings section).
 * data/terrain.dat
 * data/tmflat.gr - Flats graphics (palette 0). These are the buttons and levers shown on dungeon walls. Each is 16x16.
 * data/tmobj.gr - Miscellaneous graphics (palette 0). This contains a variety of graphics - door sides, dial flats, tombstone textures, signs, a door, and big numbers that appear to be developer-related (considering the yellow 'HI' written on one).
 * data/uw.cfg
 * data/views.gr - Only contains an icon labeled "MV".
 * data/weapons.cm (UW1) or weap.cm (UW2)
 * data/weapons.dat (UW1) or weap.dat (UW2) - Weapon animation offsets from the bottom-left corner of the screen. The format is (WeaponOffset[8]), where WeaponOffset is for each weapon and has the format (int8[28] xOffsets, int8[28] yOffsets).
 * data/weapons.gr (UW1) or weap.gr (UW2) - Weapon animation graphics (palette 0). There are 8 weapons, 28 images per weapon. "data/weapons.dat" contains the offsets.
 * data/win1.byt - Win screen with text as a raw 320x200 graphic (palette 7).
 * data/win2.byt - Win screen without text as a raw 320x200 graphic (palette 7).
 * data/xfer.dat
 * save#/ - Save files.
 * save#/bglobals.dat
 * save#/lev.ark - The dynamic game world.
 * sound/ - Sound files and drivers.
 * sound/*.adv - Sound device drivers.
 * sound/sounds.dat
 * sound/uw.ad
 * sound/uw.mt
 * install.exe
 * uw.exe
 * uwsound.exe

These files are only in Ultima Underworld I:


 * uw2.exe
 * crit/
 * crit/assoc.anm
 * sound/
 * data/blnkmap.byt - Map background as a raw 320x200 graphic (palette 1).
 * data/chargen.byt - Character creation screen background as a raw 320x200 graphic (palette 3).
 * data/conv.byt - Sample conversation as a raw 320x200 graphic (palette 0). This is obviously not used.
 * data/f16.tr - Floor texture graphics (palette 0); each is 16x16. The ceiling texture is index 15. This is the same as "data/f32.tr", just 1/4th the size.
 * data/f32.tr - Floor texture graphics (palette 0); each is 32x32. The ceiling texture is index 15. This is the same as "data/f16.tr", just 4 times the size.
 * data/main.byt - Game play background as a raw 320x200 graphic (palette 0).
 * data/opscr.byt - Title screen background as a raw 320x200 graphic (palette 2).
 * data/pres1.byt - First splash screen as a raw 320x200 graphic (palette 5); this is the Origin title.
 * data/pres2.byt - Second splash screen as a raw 320x200 graphic (palette 5); this is the Blue Sky Productions title.
 * data/w16.tr - Wall texture graphics (palette 0); each is 16x16. These are the same as "data/w64.tr", just 1/16th the size.
 * data/w64.tr - Wall texture graphics (palette 0); each is 64x64. These are the same as "data/w16.tr", just 16 times the size.
 * sound/
 * sound/aw##.xmi
 * sound/uw##.xmi
 * sound/##.voc

These files are only in Ultima Underworld II:


 * crit/
 * crit/as.an
 * crit/cr.an
 * crit/pg.mp
 * cuts/
 * cuts/lback00#.byt (# is 0 to 7)
 * data/
 * data/byt.ark
 * data/controls.dat
 * data/dl.dat
 * data/gempt.gr
 * data/ghed.gr
 * data/lighting.dat
 * data/scd.ark
 * data/t64.tr
 * save#/
 * save#/desc
 * save#/player.dat
 * save#/scd.arc
 * sound/
 * sound/bsp##.voc
 * sound/sample.opl
 * sound/sp##.voc
 * sound/uw.opl
 * sound/uwa##.xmi
 * sound/uwr##.xmi

Font files (.sys extensions)
Fonts are stored in a binary format.

Level Archives (data/lev.ark and save#/lev.ark)
This contains the entire state of the levels of the game. Its header has the following format:

The type of the chunk depends upon how many bytes it is, which you can find out by subtracting the next chunk offset from this one, or the file size from this offset if this is the last chunk. All chunks of one type are in order. For example, in UUW1 there are nine 31752-byte Level chunks followed by nine 384-byte unknown chunks, followed by 9 122-byte TextureList chunks, followed by 0-9 4096-byte map data chunks.

Level Chunk (31752 bytes)
In the below, MobileCount is 256 and ObjectCount is 768.

This is the bit format of a Tile, which is 4 bytes long and treated as a uint32:

In the game, each height unit is 11 world units and tiles are 48 units across. However, the game used a 320x200 resolution with non-square pixels, so the aspect ratio needs to be corrected (multiplied by 4/3 in the perspective matrix) or the image will look squashed. These are the tile types:

Slope tiles raise the edge they're sloping towards by one unit. For example, if the tile height is 4 and the tile Type is SlopeN, then the northwest and northeast corners are height 5 and the southwest and southeast corners are height 4.

This is the format of an Object, which is 8 bytes long:

For an object index, values from 0-255 are a mobile, and values from 256-1023 are an object.

The Mobile type is complex enough that it warrants a different table. It starts with an Object header, then is followed by a packed 19 bytes:

Texture list chunk (122 bytes)
This maps floor, wall, and door textures from their values in the tiles and entities to graphics from the "data/f16.tr" or "data/f32.tr" files for floors, and the "data/w16.tr" or "data/w64.tr" files for walls. This chunk has the following format:

Graphics Files (.gr and .tr extensions)
These contain sets of images. They start with a header:

If the image has an explicit size, either due to IsForcedSize or the file itself, then at the image offset is (uint8[Width * Height] Data), where Data are in [x + y * Width] order. Otherwise at each image offset is this structure:

The Format field dictates the structure of the following data.

Format 4 - Raw
This has the simple format (uint16 DataSize, uint8[DataSize] Data), where DataSize must match (Width * Height) and Data are in [x + y * Width] order.

Format 6 - 5-bit RLE
This uses the RLE format discussed below with a 5-bit code size. This is always encountered with an explicit auxiliary palette.

Format 8 - 4-bit RLE
This uses the RLE format with a 4-bit code size. If there is no explicit auxiliary palette, then a (uint8 AuxiliaryPalette) index, multiplied by 16, indexes the "static/allpals.dat" auxiliary palette.

These formats both start with (uint16 DataSize) (after the auxiliary palette index, if present). Pseudo-code would explain reading bit values most clearly: int CodeBits = 4 or 5, depending upon the format int Buffer = 0, BufferBits = 0;

int ReadCode { BufferBits -= CodeBits; if(BufferBits &lt; 0) { Buffer = (Buffer &lt;&lt; 8) | ReadByte; BufferBits += 8; }   return (Buffer &gt;&gt; BufferBits) &amp; ((1 &lt;&lt; CodeBits) - 1); }

// Note that this does not use CodeBits for the shift as it should. // This is an error in the RLE format itself. int ReadCode2 { int code1 = ReadCode, code2 = ReadCode; return (code1 &lt;&lt; 4) | code2; }

int ReadCode3 { int code1 = ReadCode, code2 = ReadCode, code3 = ReadCode; return (code1 &lt;&lt; 8) | (code2 &lt;&lt; 4) | code3; }

int ReadCount { int value = ReadCode; if(value != 0) return value; value = ReadCode2; if(value != 0) return value; return ReadCode3; }

int ReadAux { return AuxPalette[ReadCode]; } Pseudo-code also explains the code itself: void OutputLine(int value, int count) { while(count-- &gt; 0) Output(value); }

bool state = false; int count, repeats, value;

while(NotEnded) { count = ReadCount;

state = !state; if(state) { if(count == 2) { int repeats = ReadCount; while(repeats-- &gt; 0) { count = ReadCount; value = ReadAux; OutputLine(value, count); }       } else if(count &gt; 0) { value = ReadAux; OutputLine(value, count); }   } else { while(count-- &gt; 0) Output(ReadAux); } }

Format 10 - 4-bit
This uses the system auxiliary palette from "static/allpals.dat". It starts with a header of (uint8 AuxiliaryPalette, uint16 DataSize), where AuxiliaryPalette is multiplied by 16 to compute a byte index. This is then followed by (Width * Height * 4 + 7) / 8 bytes. The top nibble (mask F0h) is the first pixel's index into the auxiliary palette, and the bottom nibble (mask 0Fh) is the second pixel's index.

Models (uw.exe, uw2.exe)
Models (chairs, tables, stones, shrines) are stored in the executables as instruction streams. The first task is to locate the table. For UW1 (including the demo), search for the byte sequence (B6h, 4Ah, 06h, 40h); the TableOffset (discussed later) is from the end of the magic sequence plus 138. For UW2 search for (D4h, 64h, AAh, 59h); the TableOffset is the offset from the end of the magic sequence plus 150.

There are a number of data types unique to models:


 * fixed - 8.8 fixed-point stored as an int16. To convert to float, divide by 256f.
 * bigfixed - 24.8 fixed-point stored as an int32. To convert to float, divide by 256f.
 * normal - A value between -1 and 1 stored as int16. To convert to float, divide by 32767f. -32768 doesn't seem to occur.
 * unormal - A value between 0 and 1 stored as uint16. To convert to float, divide by 65535f.
 * vertex - A uint16 multiplied by 8. Divide it by 8 to get the vertex index. This is probably a data index.

Then rewind four bytes and read (uint16[64] ModelOffsets) - that is, the magic numbers were the first two model offset values. Each offset is based off the TableOffset, which is where each Model is located.

This is the format of a model at this offset:

In UW1, model indices 14 and 15 (zero-based index) point into the middle of other models and are therefore invalid.

Each instruction begins with (uint16 opcode), and has varying types of arguments. This has one special type, vertex, which is encoded as uint16 and multiplied by 8 (there is no remainder). The opcode are:

Palette Files (data/pals.dat)
This contains 8 palettes, each 256 colours in RGB order, with components from 0 to 63.

Palettes 0 and 2 have rotating colours, where a span of colours is rotated once per period. For example, if the rotating colors are represented as [ABCD], then they will become [BCDA], then [CDAB], then [DABC], then the pattern will repeat.

Palette 0 has two rotating spans, from 16 to 23 (inclusive) and 48 to 51 (inclusive). Palette 2 has one rotating span, from 64 to 127 (inclusive), and it rotates in blocks of 16 colours.

Palette 0 rotates 5 colours per second. Palette 2 rotates 10 colours per second, which is 0.625 blocks per second (since it rotates 16 colours at a time).

Strings (data/strings.pak)
Game strings are stored in this Huffman-coded file. It has the following format:

At the string offset is the string itself. It's much easier simply to show pseudocode than to describe the format: int bits = 0, value = 0; string result = "";

while(true) { Node node = Nodes[NodeCount - 1]; while(node.Left != 255) { if(bits == 0) { value = ReadByte; bits = 8; }

if((value &amp; 128) == 0) node = node.Left; else node = node.Right;

value *= 2; bits -= 1; }

if(node.Value == '|') break; Output(node.Value); }

Enumerations
This appendix contains lists of data that are important but would break the flow of the main body.

"data/optbtns.gr" images
These are the images for the options screen.

Object Types
Object types are a messy combination of builtin understanding of what numbers correspond to what type of object, builtin data regarding texture indices and other features (called "inherent parameters" here), and data stored in the game files themselves ("stored parameters").

Many objects of different types have shared behaviours, which is given as the "behavior" field.

Stored Parameters
Here are the stored parameters based upon the behavior for an object type, stored in the "data/objects.dat" file.