Ultima IV Internal Formats

Introduction
This is a reference to the various files comprising the DOS version of the game Ultima IV.

SHAPES.EGA
This file contains the bitmaps for each tile. Each tile is 16x16 pixels, and each byte represents two pixels. In other words, the first 8 bytes represent the 16 pixels on the first row of the first tile.

CHARSET.EGA
This contains the bitmaps for each character in the font. Each character is 8x8 pixels, and each byte represents two pixels. In other words, the first 4 bytes represent the 8 pixels on the first row of the first character.

The order is the standard ASCII mapping; 'A' is character 65, 'z' is character 122. The lowest 32 characters have the following glyphs:

EGA Files (RLE encoded)
The following .EGA files are RLE encoded: START.EGA, KEY7.EGA, RUNE_x.EGA (where x is between 0 and 5), STONCRCL.EGA, HONESTY.EGA, COMPASSN.EGA, VALOR.EGA, JUSTICE.EGA, SACRIFIC.EGA, HONOR.EGA, SPIRIT.EGA, HUMILITY.EGA, TRUTH.EGA, LOVE.EGA, and COURAGE.EGA. Each of these files represents a 16 color 320x200 bitmap. Each pixel is encoded in 4 bits, like SHAPES.EGA and CHARSET.EGA. However, runs of identical *bytes* (not pixels) are encoded with a special run indicator value (0x02) followed by a one byte count and a one byte value. This allows a single run of up to 255 bytes (510 pixels) to be represented as three bytes. For example, the byte values "28 28 28 28" could be represented as "02 04 28". If the value 0x02 needs to be represented, it *must* be encoded in a run ("02 01 02").

EGA Files (LZW encoded)
The remaining .EGA files are LZW encoded: ABACUS.EGA, ANIMATE.EGA, GYPSY.EGA, INSIDE.EGA, OUTSIDE.EGA, PORTAL.EGA, TREE.EGA, WAGON.EGA, HONCOM.EGA, SACHONOR.EGA, SPIRHUM.EGA, VALJUS.EGA, and TITLE.EGA. Also, the file SHAPES.EGZ is a LZW compressed version of SHAPES.EGA. Like the RLE encoded files, each of these files represents a 16 color 320x200 bitmap. LZW encoded files only seem to be used in the introduction sequence (title.exe); the images used by avatar.exe are RLE encoded instead. (Description of LZW algorithm to go here).

WORLD.MAP
This is the map of Britannia. It is 256x256 tiles in total, broken up into 64 32x32 chunks. The first chunk is in the top left corner, the next just below, and so on, until the last in the bottom right corner. Each tile is stored as a byte that maps to the tiles in SHAPES.EGA.

The "chunked" layout is an artifact of the limited memory on the original machines that ran Ultima 4. The whole map would take 64k, too much for a C64 or an Apple II, so the game would keep a limited number of 1k chunks in memory at a time. As the player moved around, old chunks were thrown out as new ones were swapped in.

ULT files
These contain town information. Specifically, a 32x32 map, plus the starting position, movement behavior, and conversation index of each NPC. The conversation index gives the starting position of the NPC's dialog block in the corresponding .TLK file. I'm not sure why some of the data is duplicated.

TLK files
These contain conversation information. The file has a 288 (0x120) byte block containing his or her dialogues, plus the words that trigger his or her responses (keywords). Most of the fields are variable length strings terminated by a single zero byte and the following field starts immediately after. The blocks are padded with zero bytes to 288 bytes.

0x0 1 NPC 0 question flag (0-no question, 3-job triggers, 4-health triggers, 5-keyword 1 triggers, 6-keyword 2 triggers) 0x1 1 NPC 0 question type (0-should answer yes, 0-should answer no) 0x2 1 NPC 0 turns away probability 0x3 var NPC 0 name (zero terminated) var var NPC 0 pronoun (zero terminated) var var NPC 0 description (zero terminated) var var NPC 0 job (zero terminated) var var NPC 0 response 1 (zero terminated) var var NPC 0 response 2 (zero terminated) var var NPC 0 question (zero terminated) var var NPC 0 yes answer (zero terminated) var var NPC 0 no answer (zero terminated) var 5 NPC 0 keyword 1 (zero terminated) var 5 NPC 0 keyword 2 (zero terminated) var var zero padding 0x120 3 NPC 1 question flag ...

CON files
These files contain the 11x11 battleground maps shown when combat starts. It has the map itself plus starting positions for up to 16 monsters and 8 party members.

offset len notes 0x0 16 start_x for monsters 0-15 0x10 16 start_y for monsters 0-15 0x20 8 start_x for party members 0-7 0x28 8 start_y for party members 0-7 0x30 16 ??? 0x40 121 11x11 map matrix 0xB9 7 ???

DNG files
These files contain dungeon information. The first 512 bytes are the 8x8 maps for each of the 8 levels. These are used for the dungeons 3D mode. The rest of the file is a set of 16 (64 in the case of the ABYSS.DNG) 256 byte blocks that define the dungeon rooms.

offset len notes 0x0 64 level 1 8x8 map matrix 0x40 64 level 2 8x8 map matrix 0x80 64 level 3 8x8 map matrix 0xC0 64 level 4 8x8 map matrix 0x100 64 level 5 8x8 map matrix 0x140 64 level 6 8x8 map matrix 0x180 64 level 7 8x8 map matrix 0x1C0 64 level 8 8x8 map matrix 0x200 256 room 0 data 0x300 256 room 1 data 0x400 256 room 2 data 0x500 256 room 3 data 0x600 256 room 4 data ... ... ... 0x1100 256 room 15 data

For ABYSS.DNG only: 0x1200 256 room 16 data 0x1300 256 room 17 data ... 0x4100 256 room 63 data

Dungeon Room format
offset len notes 0x0 16 floor triggers (4 bytes each X 4 triggers possible) 0x10 16 tile for monsters 0-15 (0 means no monster and 0's come FIRST) 0x20 16 start_x for monsters 0-15 0x30 16 start_y for monsters 0-15 0x40 8 start_x for party member 0-7 (north entry) 0x48 8 start_y for party member 0-7 (north entry) 0x50 8 start_x for party member 0-7 (east entry) 0x58 8 start_y for party member 0-7 (east entry) 0x60 8 start_x for party member 0-7 (south entry) 0x68 8 start_y for party member 0-7 (south entry) 0x70 8 start_x for party member 0-7 (west entry) 0x78 8 start_y for party member 0-7 (west entry) 0x80 121 11x11 map matrix for room 0xF9 7 buffer

Trigger format
offset len notes 0x0 1 tile to be placed (0 means no trigger and 0's come LAST) 0x1 1 2 nibbles indicating the (x,y) coords of trigger 0x2 1 2 nibbles indicating the (x,y) coords of 1st tile to change 0x3 1 2 nibbles indicating the (x,y) coords of 2nd tile to change

TRIGGER EXAMPLE: 46 85 75 65 - Places a Fire Field at (7, 5) and (6, 5) when you step on (8, 5).

PARTY.SAV
This file stores the information that is stored when the game is saved. It includes party-wide information like gold and food, plus a 39 byte record for each character.

Character record format
offset len notes 0x0 2 hit points 0x2 2 hit points maximum 0x4 2 experience points 0x6 2 strength 0x8 2 dex 0xA 2 int 0xC 2 mp 0xE 2 ??? 0x10 2 weapon 0x12 2 armor 0x14 16 name 0x24 1 sex (0xb-male, 0xc-female) 0x25 1 class 0x26 1 status ('G'-good, 'P'-poisoned, 'S'-sleeping, 'D'-dead)

Party location: Since you can only save on the surface or in a dungeon, the only valid party locations are 0 and 0x11-0x18.

hex location 00 surface 01 Lord British's castle 02 The Lycaeum 03 Empath Abbey 04 Serpent's Hold 05 Moonglow 06 Britain 07 Jhelom 08 Yew 09 Minoc 0a Trinsic 0b Skara Brae 0c Magincia 0d Paws 0e Buccaneer's Den 0f Vesper 10 Cove 11 Deceit 12 Despise 13 Destard 14 Wrong 15 Covetous 16 Shame 17 Hythloth 18 Abyss 19 Shrine of Honesty 1a Shrine of Compassion 1b Shrine of Valor 1c Shrine of Justice 1d Shrine of Sacrifice 1e Shrine of Honor 1f Shrine of Spirituality 20 Shrine of Humility

MONSTERS.SAV
Ultima 4 maintains a structure (in memory) that I've decided to call the "monster table." When you Journey Onward, U4 loads MONSTERS.SAV into the monster table. When you Quit & Save, U4 writes the monster table to MONSTERS.SAV. The purpose of the monster table fields depends on the current party location (surface, dungeon, settlement).

Surface: On the surface, the monster table contains information about monsters and inanimate objects (chests, horses, ships, balloon). A table entry is free if (previousTile[i] == 0). However, when u4dos deletes and entry, it sets both currentTile[i] and previousTile[i] to 0. Slots 0-7 are for monsters, while slots 8-31 are for inanimate objects. Non-empty entries don't have to be contiguous. Whirlpools and twisters are special cases; they must be placed in slot 0-3, or they won't swallow ships / damage the party. When you leave Hythloth, u4dos creates the balloon at (233 / 242). If there is more than one inanimate object on a tile, objects closer to the beginning of the table are drawn on top of those closer to the end. Monsters and the party are always drawn on top of inanimate objects. The current tile and previous tile values are used during runtime. However, when the game is saved, the previous tile must be set to the monster's base tile. The current tile can be any of the monster's tiles, except for pirate ships, where the current tile determines which direction the pirate ship is facing. If the previous tile is not set to the monster's base tile, it won't behave properly in u4dos: monsters with ranged attacks won't use them in combat, twisters and whirlpools will attack the party, and pirates will appear as pirate ships in battle. For transports with more than one tile (horse, ship), the previous tile determines which direction the transport is facing, while the current tile can be any of the transport's tiles.

0x0 32 current tile 0x20 32 current x coordinate 0x40 32 current y coordinate 0x60 32 tile for previous turn 0x80 32 x coordinate for previous turn 0xA0 32 y coordinate for previous turn 0xC0 32 not used 0xE0 32 not used

Dungeons: In a dungeon, the monster table contains information about the monsters in the dungeon. A monster stored in the monster table is *also* stored in DNGMAP.SAV. Inanimate objects are not stored in the monster table; they are stored
 * only* in DNGMAP.SAV.

0x0 32 not used 0x20 32 current x coordinate 0x40 32 current y coordinate 0x60 32 current tile 0x80 32 x coordinate for previous turn 0xA0 32 y coordinate for previous turn 0xC0 32 dungeon level; range = 0-7 0xE0 32 not used

Settlements: In a castle/town/village, the monster table is loaded from the settlement's .ult file, starting at offset 0x400. It contains information about the settlement's NPC's.

0x0 32 current tile 0x20 32 current x coordinate 0x40 32 current y coordinate 0x60 32 tile for previous turn 0x80 32 x coordinate for previous turn 0xA0 32 y coordinate for previous turn 0xC0 32 movement behavior for NPCs 0-31 (0x0-fixed, 0x1-wander, 0x80-follow, 0xFF-attack) 0xE0 32 conversation index (tlk file) for NPCs 0-31

OUTMONST.SAV
OUTMONST.SAV is a backup of the monster table, created when you enter a dungeon or settlement. When you leave a dungeon or settlement, the monster table is reloaded from OUTMONST.SAV.

Exception: when you enter Hythloth though the ladder in LB's castle, the game does not write the current monster table to OUTMONST.SAV, because it already did that when you entered LB's castle.

DNGMAP.SAV
When you Quit & Save in a dungeon, the game saves objects (orbs, chests) and monsters in DNGMAP.SAV. DNGMAP.SAV contains the 8x8 maps for all 8 levels. Every tile is encoded as a byte. The upper nibble of a tile determines the tile type (see below). The lower nibble encodes the monster standing on the tile, except for tiles 0x80 0x90 0xA0 0xD0 (see below).

0x00 = nothing 0x10 = ladder up 0x20 = ladder down 0x30 = ladder up/down 0x40 = chest 0x50 = hole (ceiling) 0x60 = hole (floor) 0x70 = orb (see below) 0x80 = trap (magic winds) 0x81 = trap (falling rocks) 0x8E = trap (pit) 0x90 = fountain (no effect) 0x91 = fountain (healing) 0x92 = fountain (100 hit points damage) 0x93 = fountain (cure) 0x94 = fountain (poison and 100 hit points damage) 0xA0 = magic field (poison) 0xA1 = magic field (energy) 0xA2 = magic field (fire) 0xA3 = magic field (sleep) 0xB0 = altar (stone color depends on dungeon) 0xC0 = door 0xDn = dungeon room #n 0xE0 = illusionary wall 0xF0 = wall

Monsters:

0 = none 1 = rat 2 = bat 3 = spider 4 = ghost 5 = slime 6 = troll 7 = gremlin 8 = mimic 9 = reaper A = insects B = gazer C = phantom D = orc E = skeleton F = rogue

Orbs: When a PC touches an orb, one or more attributes are raised by 5 points each. Which attributes are raised depends on the dungeon.

dungeon attributes Deceit I-- Despise -D- Destard --S Wrong ID- Covetous -DS Shame I-S Hythloth IDS

TITLE.EXE
This executable is launched by ultima.com to display the introduction and create new characters. When "journey onward" is selected from the intro menu, it exits and ultima.com passes control off to avatar.exe, the main game executable. This .exe has lots of the data necessary for the intro screens embedded in it.

offset len notes 0x0 17445 ??? 0x4425 11088 text for introduction, gypsy dialog and gyspy questions (zero terminated strings) 0x6f75 1035 ??? 0x7380 184 list of frames sequences for beasties on intro screen 0x7438 54 ??? 0x746e 532 266 x/y pairs that are the coordinates for the pixels that make up the Lord British signature on the introductory title screen 0x7682 1 zero terminating byte 0x7683 95 tile values for the 19x5 map that appears on the introductory title screen 0x76e2 548 script data for the intro map animations 0x7906 ... ???

AVATAR.EXE
This is the main game executable, launched from ultima.com (or run on its own to skip the intro). Lots of the special case data is embedded in this .exe.

offset len notes 0x0 63843 ??? 0x0f963 1 volume on/off flag 0x0f964 27 13 two-byte code pointers to 13 sound generation routines plus one padding byte 0x0f97f 171 .ULT filenames; 16 zero terminated strings 0x0fa2a 48 stone colors; 8 zero terminated strings 0x0fa5a 91 .DNG filenames; 8 zero terminated strings 0x0fab5 28 ??? 0x0fad1 8 moongate x-coordinates (one byte for each moongate starting at Moonglow) 0x0fad9 8 moongate y-coordinates (one byte for each moongate starting at Moonglow) 0x0fae1 32 16 two-byte offsets to .ULT filenames (value + 62141 gives file offset) 0x0fb01 32 area x-coordinates (one byte for each town, city, castle, dungeon, shrine) 0x0fb21 32 area y-coordinates (one byte for each town, city, castle, dungeon, shrine) 0x0fb41 16 8 two-byte offsets to stone colors (value + 62141 gives file offset) 0x0fb51 16 8 two-byte offsets to .DNG filenames (value + 62141 gives file offset) 0x0fb61 72 ??? 0x0fba9 8 pirates cove ship x coordinates 0x0fbb1 8 pirates cove ship y coordinates 0x0fbb9 8 pirates cove ship tiles 0x0fbc1 37 zero terminated list of tiles that are walkable 0x0fbe6 149 14 zero-terminated navigation strings ("North", "Sail North", etc.) 0x0fc7b 539 Chamber of the Codex virtue questions 0x0fe96 78 ??? 0x0fee4 647 Chamber of the Codex dialog strings 0x1016b 28 ??? 0x10187 509 Chamber of the Codex dialog strings 0x10384 83 ??? 0x103d7 1280 abyss question dialog strings 0x108d7 22 11 two-byte offsets to abyss question answers (value + 62141 gives file offset) 0x108ed 80 ??? 0x1093d 169 .TLK filenames; 16 zero terminated strings 0x109e6 13 "OUTMONST.SAV" zero terminated string 0x109f3 32 16 two-byte offsets to .TLK filenames (value + 62139 gives file offset) 0x10a13 659 ??? 0x10ca6 246 monster names; 36 zero terminated strings 0x10d9c 129 weapon names; 16 zero terminated strings 0x10e1d 77 armor names; 8 zero terminated strings 0x10e6a 64 weapon abbreviations; 16 zero terminated strings 0x10eaa 55 character class names; 8 zero terminated strings 0x10ee1 58 npc type names; 8 zero terminated strings 0x10f1b 81 reagent names; 8 zero terminated strings 0x10f6c 434 spell names; 26 zero terminated strings 0x1101e 292 castle/city/town/dungeon/shrine names; 32 zero terminated strings 0x11142 11 ??? 0x1114d 72 36 two-byte offsets to monster names (value + 62131 gives file offset) 0x11195 32 16 two-byte offsets to weapon names (value + 62131 gives file offset) 0x111b5 16 8 two-byte offsets to armor names (value + 62131 gives file offset) 0x111c5 32 16 two-byte offsets to weapon abbreviations (value + 62131 gives file offset) 0x111e5 16 8 two-byte offsets to character class names (value + 62131 gives file offset) 0x111f5 330 ??? 0x1133f 26 mp required for each spell (one byte per spell) 0x11359 654 ??? 0x115e7 16 mask of which classes can use each weapon (one byte per weapon) 0x115f7 8 mask of which classes can use each type of armor (one byte per armor type) 0x115ff 134 ??? 0x11685 52 monster hp table 0x116b9 36 monster leader types (one byte per monster type) 0x116dd 36 monster encounter size table (one byte per monster type) 0x11701 2 ??? 0x11703 16 weapon value list (one byte per weapon type) 0x11713 8 armor value list (one byte per armor type) 0x1171b 16 weapon type list (one byte per weapon type; 0 = melee, 0xff = missile) 0x1172b 154 .CON filenames; 14 zero terminated strings 0x117c5 28 14 two-byte offsets to .CON filenames (value + 62131 gives file offset) 0x117e1 64 dungeon .CON filenames; 7 zero terminated strings 0x11821 164 ??? 0x118c5 12 altar room exit destinations 0-3 = truth (north, east, south, west), 4-7 = love, 8-11 = courage 0x118d1 146 ??? 0x11963 8 ambush monster types 0x1196b 190 miscellaneous text strings 0x11a29 26 spell reagent masks (one byte per spell) 0x11a43 364 miscellaneous text strings 0x11baf 16 city/runemask pairs (city id, corresponding rune bitmask) 0x11bbf 12 miscellaneous text string 0x11bcb 120 24 five-byte item location records (see below) 0x11c43 288 ??? 0x11d63 250 companion dialog text 0x11e5d 16 8 two-byte offsets to virtue adjectives for companion dialogs (value + 62123 gives file offset) 0x11e6d 1404 ??? 0x123e9 3486 Hawkwind dialog text; the first 40 zero terminated strings are his responses for 5 levels times 8 virtues, plus some other miscellaneous dialog strings 0x13187 80 40 two-byte offsets to Hawkwind dialog strings (value + 62075 gives file offset) 0x131d7 564 reagent vendor dialog text; x zero terminated strings 0x133eb 16 reagent vendor index for each city (one byte per castle/city/town) 0x133fb 8 4 two-byte offsets to reagent shop names (value + 62075 gives file offset) 0x13403 8 4 two-byte offsets to reagent shopkeeper names (value + 62075 gives file offset) 0x1340b 24 price for each of the first six reagents at Moonglow, Skara Brae, Paws, and Buccaneers Den respectively (one byte per price) 0x13423 1274 weapon vendor dialog text; 28 zero terminated strings 0x1391d 12 6 two-byte offsets to weapon shop names (value + 62075 gives file offset) 0x13929 12 6 two-byte offsets to weapon shopkeeper names (value + 62075 gives file offset) 0x13935 24 which four weapons each of the six weapons vendors can sell (one byte per weapon) 0x1394d 32 price of each weapon (two bytes for each price) 0x1396d 16 weapon vendor index for each city (one byte per castle/city/town) 0x1397d 28 14 two-byte offsets to weapon descriptions (value + 62075 gives file offset) for all weapons except hands and mystic 0x13999 522 more weapon vendor dialog text; 27 zero terminated strings 0x13ba3 646 armor vendor dialog text; 19 zero terminated strings 0x13e29 10 5 two-byte offsets to armor shop names (value + 62075 gives file offset) 0x13933 12 6 two-byte offsets to armor shopkeeper names (value + 62075 gives file offset) 0x13e3f 20 which four types of armor each of the five armor vendors can sell (one byte per armor type) 0x13e53 4 zero padding 0x13e57 16 price of each type of armor (two bytes for each price) 0x13e67 16 armor vendor index for each city (one byte per castle/city/town) 0x13e77 12 6 two-byte offsets to armor descriptions (value + 62075 gives file offset) for all armor types except skin and mystic 0x13e83 484 more armor vendor dialog text; 27 zero terminated strings 0x14067 318 horse vendor dialog text; 7 zero terminated strings 0x141a5 622 guild vendor dialog text; x zero terminated strings 0x14413 6 ??? 0x14419 8 price per unit of each guild item (two bytes for each price) 0x14521 8 quantity per unit of each guild item (two bytes for each quantity) 0x14429 4 2 two-byte offsets to guild shop names (value + 62075 gives file offset) 0x1442d 4 2 two-byte offsets to guild shopkeeper names (value + 62075 gives file offset) 0x14431 8 4 two-byte offsets to guild item inquiry responses (value + 62075 gives file offset) for torches, gems, keys, sextants 0x14439 702 inn dialog text; x zero terminated strings 0x146F7 16 inn index for each city (one byte per castle/city/town) 0x14707 8 inn sleep location x coordinate for seven inns plus zero pad 0x1470f 8 inn sleep location y coordinate for seven inns plus zero pad 0x14717 8 inn prices for seven inns plus zero pad 0x1471f 14 7 two-byte offsets to inn names (value + 62067 gives file offset) 0x1472d 14 7 two-byte offsets to inn names (value + 62067 gives file offset) 0x1473b 14 7 two-byte offsets to inn price dialogs (value + 62067 gives file offset) 0x14749 424 more inn dialog text; x zero terminated strings 0x148f1 266 healer dialog text; x zero terminated strings 0x149fb 16 healer index for each city (one byte per castle/city/town) 0x14a0b 20 10 two-byte offsets to healer shopkeeper names (value + 62067 gives file offset) 0x14a1f 20 10 two-byte offsets to healer shopkeeper names (value + 62067 gives file offset) 0x14b33 598 more healer dialog text; x zero terminated strings 0x14d89 978 tavern dialog text; x zero terminated strings 0x1515b 16 tavern index for each city (one byte per castle/city/town) 0x1516b 12 minimum price for information at each tavern (two bytes for each price) 0x15177 12 6 two-byte offsets to tavern shop names (value + 62067 gives file offset) 0x15183 12 6 two-byte offsets to tavern shopkeeper names (value + 62067 gives file offset) 0x1518f 12 6 two-byte offsets to tavern specialty names (value + 62067 gives file offset) 0x1519b 12 price for plate of food at each tavern (two bytes for each price) 0x151a7 12 7 two-byte offsets to tavern information topics (value + 62067 gives file offset) (??? why 7, not 6) 0x151b5 12 6 two-byte offsets to tavern information responses (value + 62067 gives file offset) 0x151c1 598 more tavern dialog text; x zero terminated strings 0x15417 456 food vendor dialog text; x zero terminated strings 0x155df 16 food vendor index for each city (one byte per castle/city/town) 0x155ef 10 price for 25 units of food at each vendor (two bytes for each price) for Moonglow, Britan, Yew, Skara Brae, and Paws, respectively 0x155f9 10 5 two-byte offsets to food shop names (value + 62067 gives file offset) 0x15603 10 5 two-byte offsets to food shopkeeper names (value + 62067 gives file offset) 0x1560d 189 Lord British dialog keywords; 27 zero terminated strings 0x156ca 2968 Lord British dialog text; 25 zero terminated strings ("He says", plus responses for last 24 of the above keywords) 0x16262 104 ??? 0x162ca 2037 Lord British "help" response dialog strings 0x16abf ... ???

Each item location record has the following structure:

offset len notes 0x0 1 item location (same encoding as party location in PARTY.SAV, e.g. 0 for surface) 0x1 1 item x coordinate 0x2 1 item y coordinate 0x3 2 ??? (a pointer?)