Ultima Underworld Internal Formats

This page documents the Ultima Underworld engine's internal formats (used in Ultima Underworld, Ultima Underworld II, and System Shock).

General Types
These types are used in this page:
 * char - An 8-bit (1-byte) character code in Windows-1252 encoding.
 * 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.


 * crit/ - Creature animations.
 * crit/assoc.anm
 * cr##page.n##
 * cuts/ - Cutscenes.
 * cuts/cs###.n##
 * data/ - General static data.
 * data/3dwin.gr
 * data/allpals.dat
 * data/animo.gr
 * data/armor_f.gr
 * data/armor_m.gr
 * data/babglobs.dat
 * data/blnkmap.byt
 * data/bodies.gr
 * data/buttons.gr - Contains editing buttons.
 * data/chains.gr
 * data/chargen.byt
 * data/charhead.gr
 * data/chrbtns.gr
 * data/chrgen.dat
 * data/cmb.dat
 * data/cnv.ark
 * data/comobj.dat
 * data/compass.gr
 * data/conv.byt
 * data/converse.gr
 * data/cursors.gr
 * data/doors.gr
 * data/dragons.gr
 * data/eyes.gr
 * data/f16.tr - Redundant with "data/f32.tr".
 * data/f32.tr
 * data/flasks.gr
 * data/font4x5p.sys
 * data/font5x6i.sys
 * data/font5x6p.sys
 * data/fontbig.sys
 * data/fontbutn.sys
 * data/fontchar.sys
 * data/genhead.gr
 * data/grave.dat
 * data/heads.gr
 * data/inv.gr
 * data/lev.ark
 * data/lfti.gr
 * data/light.dat
 * data/lights.dat
 * data/main.byt
 * data/mono.dat
 * data/objects.dat
 * data/objects.gr
 * data/opbtn.gr
 * data/opscr.byt
 * data/optb.gr
 * data/optbtns.gr
 * data/pals.dat - The 8 palettes used throughout the normal game.
 * data/panels.gr
 * data/player.dat
 * data/power.gr
 * data/pres1.byt
 * data/pres2.byt
 * data/question.gr
 * data/scrledge.gr
 * data/shades.dat
 * data/skills.dat
 * data/spells.gr
 * data/strings.pak
 * data/terrain.dat
 * data/tmflat.gr
 * data/tmobj.gr
 * data/uw.cfg
 * data/views.gr - Only contains an icon labeled "MV".
 * data/w16.tr - Redundant with "data/w64.tr".
 * data/w64.tr
 * data/weapons.cm
 * data/weapons.dat
 * data/weapons.gr
 * data/win1.byt
 * data/win2.byt
 * data/xfer.dat
 * save#/ - Save files.
 * save#/bglobals.dat
 * save#/lev.ark
 * sound/ - Sound files and drivers.
 * sound/##.voc
 * sound/*.adv - Sound device drivers.
 * sound/aw##.xmi
 * sound/uw##.xmi
 * sound/sounds.dat
 * sound/uw.ad
 * sound/uw.mt
 * install.exe
 * uw.exe
 * uwsound.exe

=File Formats=

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

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.

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).