This guide was obtained from Underworld Adventures CVS.
+--------------------------------------------------------------------------+
Ultima Underworld 1 and 2 Formats Specification
Underworld Adventures
http://uwadv.sourceforge.net/
+--------------------------------------------------------------------------+
Document version:
$Id: uw-formats.txt,v 1.68 2006/04/11 21:26:42 vividos Exp $
Table of Contents
-----------------
1 Introduction
2 Overview
2.1 Remarks
2.2 Document conventions
2.3 Summary of game files
2.3.1 Ultima Underworld 1 files
2.3.2 Ultima Underworld 2 files
3 Graphics and visuals
3.1 Palettes
3.2 Images
3.3 Bitmaps
3.4 Textures
3.5 Fonts
3.6 Critter animations
3.7 Cutscene animations
3.8 Weapon animations
4 Level maps and object lists
4.1 File format
4.2 Level tilemap
4.2 Master object list
4.2.1 Enchantments
4.2.2 Item Owner
4.2.3 Mobile object extra info
4.3 Free lists
4.4 Texture mappings
4.5 Animation infos
4.6 Automap infos
4.7 Terrain texture properties
5 String resources
5.1 String block contents
6 Objects and items
6.1 List of all objects and items
6.2 Common object properties
6.3 Object class properties
6.4 Object combining
7 Conversations
7.1 Conversation file format
7.2 Private global variables
7.3 Memory layout
7.4 Assembler language opcodes
7.5 Text substitutions
7.6 Intrinsic functions
7.7 Imported variables
7.8 Quest flags
8 3d models
8.1 List of all nodes
9 Miscellaneous stuff
9.1 Ultima Underworld 2 compression scheme
9.2 Savegame format
9.3 Unknown files
9.4 Error codes
+--------------------------------------------------------------------------+
1 Introduction
This file tries to collect all file formats and data structures used in the
game Ultima Underworld 1. It origined from the "uw-specs.txt" file that I
found with "The System Shock Hack Project" (TSSHP). System Shock uses
almost the same data structures for the game and Jim decided to support the
underworldly games, too. With the start of the Underworld Adventures
project I started to extend the file as I found new things. It started with
a tiny 20kb text file.
As I further progressed to find out things I realized that the Underworld
games are not that simple done as I always thought. Many little things want
to be specified, configured and set. The Underworld games were built very
detailed, far more than other games at this time. The second game even
added to that complexity (and some say, story, too).
With this document I hope to present the interested reader a rather
detailed overview over the file formats, and even more the inner workings
of Ultima Underworld 1 and 2. Have as much fun reading it as I had fun to
find out things and writing it!
1.1 Credits
My thanks go out to Jim Cameron that started the original "uw-specs.txt"
file and subsequently found out more unknown data structures and discussed
about file formats with me. Many thanks, Jim!
Additional information came from Alistair Brown (basic object format, via
the Underworld II editor available at
), Ulf Wohlers (Objects and 3D
models) and Fabian Pache (about map details and 3d models, uw2 related,
http://uw2rev.sourceforge.net).
Further infos about weapon animation and miscellaneous bits about uw were
contributed by Telemachos from peroxide.dk.
I also like to thank Telemachos who pointed me to the TSSHP's file formats
file at the start of all of this.
This file is probably:
Copyright (c) 2000,2001,2002,2003 Jim Cameron
Copyright (c) 2002,2003,2004 Michael Fink
Copyright (c) 2004 Kasper Fauerby
+--------------------------------------------------------------------------+
2 Overview
This chapter gives a short overview over the game itself, as well as a
detailed summary about all game files used in Ultima Underworld 1 and 2.
2.1 Remarks
The game was released in the year 1992, just before Wolfenstein 3D came
out.was released. So it was the first true first-person game on the PC.
Ultima Underworld is a MS-DOS based game created in C with Turbo C++ 2.1
and Optasm as the assembler used (as far as we know). 3d models were done
with 3d studio max and converted and placed into the executable.
Ultima Underworld is a trademark of Origin, Inc.
2.2 Document conventions
This document follows some conventions, to keep a nice and well-to-read
format. The document is indented by 3 spaces; text must be wrapped at
line column 79 and tables can (but doesn't need to be) indented with 3
spaces, too. No tab spaces should be used. Special parts of the formats
specification that only refer to either Ultima Underworld 1 or 2 are marked
with [uw1] or [uw2].
All integer data values in the documentation are referred as Int8 for 8-bit
values, Int16 and Int32 for 16-bit and 32-bit values. All values are
unsigned unless noted otherwise. As the game was written and run on x86 PC
machines, all variables are stored in little-endian format, where e.g. the
lower byte of a 16-bit value is stored first, then the higher byte. All
data structures found so far use this scheme.
2.3 Summary of game files
This section lists all the files that are part of the Ultima Underworld
game installations.
2.3.1 Ultima Underworld 1 files
Here's a list of all files for Ultima Underworld 1:
Files in the main folder:
install.dat infos which files are installed
install.exe installer created with EZ-INSTALL 3.13
install.olb ANSI text strings for the installer
lha.doc LHA manual for 2.13
lha.exe LHA 2.13 to unpack installer game files
uw.exe ultima underworld 1 executable
uwsound.exe sound settings program
Files in the "data" folder:
3dwin.gr 3d window graphics
allpals.dat auxiliary 4-bit palette indices
animo.gr small animations
armor_f.gr female paperdoll armor graphics
armor_m.gr male paperdoll armor graphics
babglobs.dat initial conversation globals
blnkmap.byt blank map bitmap, palette #1
bodies.gr paperdoll bodies
buttons.gr buttons, seems to be from some map editor
chains.gr rotating chains for the stats window
chargen.byt character generation bitmap, palette #3
charhead.gr character images, for conversations
chrbtns.gr character generation graphics, palette #3
chrgen.dat
cmb.dat item combining rules
cnv.ark conversation scripts
comobj.dat common object properties
compass.gr compass graphics
conv.byt seems to be a conversation screenshot, palette #0
converse.gr conversation screen bitmaps
cursors.gr mouse cursor images
doors.gr door textures
dragons.gr scroll dragons animations
eyes.gr eyes from top screen
f16.tr floor/ceiling textures, size 16x16
f32.tr floor/ceiling textures, size 32x32
flasks.gr health and mana flask graphics
font4x5p.sys small font
font5x6i.sys italic font, used in character stats screen
font5x6p.sys normal font, used for scroll messages
fontbig.sys big font, for cutscenes
fontbutn.sys font for buttons (?)
fontchar.sys character generation font (?)
genhead.gr generic heads images
grave.dat grave IDs
heads.gr avatar character generation heads
inv.gr inventory graphics, scroll backgrounds (?)
lev.ark level maps, texture indices and object list
lfti.gr left menu buttons
light.dat palette mappings for 16 different light levels
lights.dat
main.byt main game screen bitmap, palette #0
mono.dat palette mapping for grayscale colors
objects.dat object data specific to an object class
objects.gr object graphics, some never seen in inventory (460 objects!)
opbtn.gr opening screen buttons, create new game, etc.; palette #2
opscr.byt opening screen, palette #2
optb.gr some options buttons
optbtns.gr all options buttons from the left menu
pals.dat eight palettes (#0 to #7; #5 and #6 are the same)
panels.gr some invalid type, 0x0 and 1x1 resolution images
player.dat initial player character called "gronkey"
power.gr hit power indicator graphics
pres1.byt "origin presents" screen, palette #5
pres2.byt "a blue sky prod. game" screen, palette #5
question.gr a single question mark
scrledge.gr scroll paper edges
shades.dat
skills.dat char. generation skills for all classes
spells.gr spells graphics
strings.pak all the game strings
terrain.dat terrain texture properties (see 4.7)
tmflat.gr wall switches and other decals
tmobj.gr more wall decals, 3d model textures
uw.cfg underworld configuration (audio, cut scenes)
views.gr the letters "mv", probably not used
w16.tr wall textures, size 16x16
w64.tr wall textures, size 64x64
weapons.cm weapon animation auxiliary palettes (see 3.8)
weapons.dat weapon animation x and y coordinates (see 3.8)
weapons.gr weapon hit animations, for left and right handedness
win1.byt winning screen with text, palette #7
win2.byt blank winning screen for character info, palette #7
xfer.dat color index translation tables (see 9.3)
Files in the "sound" folder:
XX.voc all cutscene audio files
uwXX.xmi underworld extended midi music
awXX.xmi the same, for adlib music (?)
sounds.dat sound effect midi commands (?)
uw.mt MT32 sound effects data
uw.ad adlib stuff
*.adv device driver for midi/wave output:
adlib.adv Ad Lib Music Synthesizer Card
mt32mpu.adv Roland MT-32 or compatible
pasdig.adv Pro Audio Spectrum Digital Sound
pasfm.adv Pro Audio Spectrum FM Sound
pcspkr.adv IBM PC or compatible internal speaker
sbdig.adv Sound Blaster Digital Sound
sbfm.adv Sound Blaster FM Sound
sbpdig.adv Sound Blaster Pro Digital Sound
sbpfm.adv Sound Blaster Pro FM Sound
tandy.adv Tandy 3-voice internal sound
Here's a list of song names for the .xmi files:
uw01.xmi Introduction
uw02.xmi Dark Abyss
uw03.xmi Descent
uw04.xmi Wanderer
uw05.xmi Battlefield
uw06.xmi Combat
uw07.xmi Injured
uw10.xmi Armed
uw11.xmi Victory
uw12.xmi Death
uw13.xmi Fleeing
uw15.xmi Maps & Legends
Files in the "cuts" folder:
* = not in "static" install
cs???.n00 anim control files (?)
cs000.n01 black screen
cs000.n02 * garamon in swirling air
cs000.n03 garamon talking
cs000.n04 * garamon talking
cs000.n05 * garamon talking
cs000.n06 * garamon in swirling air
cs000.n07 garamon appearing
cs000.n10 intro w/ tyball stealing princess, troll and guards etc.
cs000.n11 almric talking, on throne
cs000.n12 almric talking, closeup
cs000.n15 guard talking
cs000.n16 * guard talking
cs000.n17 * guard talking
cs000.n20 mountain scene, avatar taken to the abyss
cs000.n21 * mountain scene
cs000.n22 abyss doors closed, from outside
cs000.n23 doors closed, from inside, w/ avatar
cs000.n24 guard talking, with purple background
cs000.n25 * guard talking, with purple background
cs001.n01 ship approaching, abyss collapsing
cs001.n02 ship taking avatar on board
cs001.n03 almric talking, on ship
cs001.n04 almric talking, on ship
cs001.n05 * almric talking, on ship, birds in background
cs001.n06 arial talking
cs001.n07 arial talking
cs001.n10 abyss collapsing, ship sails away
cs002.n01 dying tyball talking
cs002.n02 * dying tyball talking
cs002.n03 * dying tyball talking
cs002.n04 * dying tyball, dying
cs003.n01 arial talking
cs003.n02 * arial talking
cs011.n01 "ultima underworld the stygian abyss" splash screen anim
cs012.n01 acknowledgements
cs013.n01 goblet with letters "in"
cs014.n01 goblet with letters "sa"
cs015.n01 goblet with letters "hn"
cs400.n01 "look" graphics for windows to abyss volcano core
cs401.n01 grave stones
cs402.n01 death skulls w/ silver sapling
cs403.n01 death skulls animation
cs403.n02 death skull end anim
cs404.n01 anvil graphics
cs410.n01 map piece showing some traps
Files in the "crit" folder:
assoc.anm animation associations
Files in a save game folder (e.g. "Save1"):
lev.ark modified level map
bglobals.dat conversation globals (initialized)
desc savegame name
player.dat character info
Files that only appear in the uw_demo "data" folder:
df??.tr floor textures
dmain.byt main screen
dplayer.??? encrypted player character info for self-running demo
dscript.??? scripts for self-running demo sequences
dterrain.dat terrain texture properties for the demo
dw??.tr wall textures
level13.anx object animation overlay info
level13.st tilemap and master object list for first level
level13.txm level texture usage for first level
newobj.dat
presd.byt "... presents a demo of ..." screen for the demo
2.3.2 Ultima Underworld 2 files
Here is a list of all files from Ultima Underworld 2 that differ in content
or are only available in the second game.
Files in the main folder:
uinstall.exe game installer (LZEXE 0.91 compressed)
uw2.exe Ultima Underworld 2 game
Files in the "data" folder:
byt.ark
cnv.ark compressed conversation archive
dl.dat
gempt.gr red gem parts (?)
ghed.gr
lev.ark compressed levelmap archive
lighting.dat
pals.dat 11 palettes
scd.ark
weap.cm weapon animation auxiliary palettes (see 3.8)
weap.dat weapon animation x and y coordinates (see 3.8)
Files in the "cuts" folder:
cs???.n?? cutscene animations
lback00x.byt (?) (x = 0..8)
Files in the "crit" folder:
as.an animation associations
cr.an animation segment list
pg.mp page file
crXX.YY critter animations, XX=animation(octal) YY=page
Files in the "sound" folder:
bsp??.voc guardian speech
dd??.adv digital sound drivers
dm??.adv digital music drivers
sample.opl
sp??.voc sound effects
uw.opl
uw??.voc guardian laughter
uwa??.xmi adlib music files
uwr??.xmi roland music files
Here's a list of song names for the .xmi files:
uwa01.xmi The Labyrinth of Worlds Theme
uwa02.xmi Enemy wounded
uwa03.xmi Combat
uwa04.xmi Dangerous Situation
uwa05.xmi Armed
uwa06.xmi Victory
uwa07.xmi Sewers
uwa10.xmi Talorus
uwa11.xmi Prison Tower / Goblin Prison Tower
uwa12.xmi Death
uwa13.xmi Killorn Keep
uwa14.xmi Ice Caverns / Blackrock Prison
uwa15.xmi Scintillus Academy / Ice Caves
uwa16.xmi Praecor Loth / Castle British
uwa17.xmi The Labyrinth of Worlds Theme (again)
uwa30.xmi Introduction
uwa31.xmi Guardian's Trap
The second names come from some mp3 files available here:
http://www.stygianabyss.com/uw/music/uw2.htm
+--------------------------------------------------------------------------+
3 Graphics and visuals
This chapter explains all visual formats used, e.g. wall/floor/ceiling
textures, user interface images or animations.
3.1 Palettes
Ultima Underworld uses several palettes for different purposes. There are
palettes with 256 indices, as well as auxiliary meta-palettes that have
16 or 32 entries that map indices to the 256-index palette. As the original
games directly load the palettes into the VGA registers, effects as color
flashes done with palette rotating are possible.
3.1.1 256 color palettes
In the file "pals.dat" there are stored 8 different palettes. A palette
has the following layout:
0000 Int8 red intensity, index 0, range [0..63]
0001 Int8 green intensity, index 0, range [0..63]
0002 Int8 blue intensity, index 0, range [0..63]
0003 Int8 red intensity, index 1, range [0..63]
0004 Int8 green intensity, index 1, range [0..63]
...
02fe Int8 green intensity, index 255, range [0..63]
02ff Int8 blue intensity, index 255, range [0..63]
In each palette there are stored color intensities for 256 colors. All 8
palettes are stored sequentially in the file.
Palette index 0 always means transparent color.
3.1.2 16 color auxiliary palette mappings
In "allpals.dat" there are several auxiliary palettes, used for 4-bit
images. All indices use palette #0.
0000 Int8 index to first color
0001 Int8 index to second color
...
000f Int8 index to 16th color
There are 16 values that are indices for palette #0. They build a 16 color
palette from selected colors of the palette #0.
In the file "allpals.dat" there are stored 0x1f (=31) such palettes.
The critter animations (explained in chapter 3.6) use 32 color auxiliary
palette mappings. They are stored within the animation files.
3.1.3 Palette mappings
Palette mappings for different light/darkness levels are stored in the
file "light.dat". The file consists of 16 blocks of palette mappings. Each
block contains 256 Int8 values which map colors to their palette indices in
the game palette. The first block is the mapping for original colors, and
the last one is for "almost black".
The file "mono.dat" contains palette mappings that maps colors to grayscale
values. It has the same format as the "light.dat" file and can be
interchanged to get a gray underworld look. It is used for the spell
"invisibility".
3.1.4 Palette rotation animations
In several places of the game the palette is used to create animated
effects, such as the lava and water textures. Here are the palette indices
that have to be rotated to produce the animations:
- Palette #0: in game graphics
indices 16 through 23: lava fire effect
indices 48 through 51: water effect
- Palette #2: game start screen
indices 64 through 127: "Ultima Underworld" logo warping effect
3.2 Images
Graphics are stored in "*.gr" files and can be stored with 8-bit or 4-bit
indices.
0000 Int8 Graphic file format:
01 .gr
02 .tr
03 .cr [uw2]
04 .sr [uw2]
05 .ar [uw2]
0001 Int16 number of bitmaps
0003 Int32 offset to bitmap #0
0007 Int32 offset to bitmap #1
...
Each bitmap has its own header:
0000 Int8 bitmap type:
04: 8-bit uncompressed
08: 4-bit run-length
0A: 4-bit uncompressed
0001 Int8 width
0002 Int8 height
For the 4-bit formats, there follows another Int8 that selects the
auxiliary palette to use (see 2.1).
000n Int16 size of data for the bitmap.
in 4-bit formats, this is the number of 4-bit nibbles, not
bytes.
The file "panels.gr" contains bitmaps that don't have a bitmap header, but
immediately starts with image data. The bitmaps are of type 04 and have
a width of 83 and a height of 114 pixels.
3.2.1 Uncompressed bitmaps (04: 8-bit, 0A: 4-bit)
All palette indices are stored sequentially, first one line, then the
next, and so on. For the 4-bit format, first take the upper nibble, then
the lower nibble.
3.2.2 Compressed bitmaps (type 08)
All pixels are run-length encoded. when a new byte has to be retrieved,
first take the high nibble, then the low nibble of that byte.
Data consists of repeat and run records. Repeat records let the decoder
repeat a single nibble a certain number of times. The run record takes
a certain number of next nibbles to be as uncompressed. The two records
alternate in the bitmap, starting with a repeat record.
For every record, first there is a count to retrieve. Get a nibble; if
it is not 0, it is a count. Otherwise, get two more nibbles, n1 and n2.
The count is c = (n1 << nibblesize) | n2. If the count is still zero,
take another three nibbles, and calculate the count:
c = (((n1 << nibblesize) | n2) << nibblesize) | n3;
A count is at most 6 nibbles long.
A run record consists of a count and then follows 'count' nibbles,
that are the raw pixel data. A repeat record consists of a count and
a single nibble, the nibble is then repeated 'count' times.
As there is no point in repeating a nibble <3 times, there are some
special meanings for count:
1: skip this record, the next one is a run record again. may be used at
the beginning of a file, when it should start with a run rather than
a repeat.
2: multiple repeats. get another count, and process 'count' times a
repeat record.
NOTE that there also exists a 5-bit compressed format which is exactly
the same as the above except that the word length is 5 bits instead of 4.
This is used for critter animation frames in the crit/ folder. The
auxiliary palette contains 32 entries and is stored with the animation.
3.3 Bitmaps
Bitmaps in Ultima Underworld 1 are stored in "*.byt" files. They just are
320x200 bitmaps using different palettes. Here's a list of all bitmap
files:
blnkmap.byt blank map bitmap, palette #1
chargen.byt character generation bitmap, palette #3
conv.byt seems to be a conversation screenshot, palette #0
main.byt main game screen bitmap, palette #0
opscr.byt opening screen, palette #2
pres1.byt "origin presents" screen, palette #5
pres2.byt "a blue sky prod. game" screen, palette #5
win1.byt winning screen with text, palette #7
win2.byt blank winning screen for character info, palette #7
Ultima Underworld demo contains two separate bitmaps:
dmain.byt main demo screen bitmap, palette #0
presd.byt "origin and blue sky prod. present..." screen, palette #5
Underworld 2 has no "*.byt" files. Instead there is one "byt.ark" that
contains all images. Like every other "*.ark" file this starts with some
tables followed by the data, in this case the images themselves. There are
11 entries, of which only 9 are valid:
entry palette usage
0 1 Map framework - background, crystal, and the like
1 0 Character generation
2 0 Bartering
3 -unused-
4 0 HUD - the frame, bottles, scroll
5 0 Underworld 2 Main menu - without menu entries
6 5 Origin presents
7 5 Looking Glass Technologies
8 0 Congratulation screen
9 0 like the above but without the text
10 -unused-
3.4 Textures
Textures are stored in "*.tr" files, where "fXX.tr" files are floor/ceiling
textures, and "wXX.tr" are wall textures. XX describes the width and height
resolution of the texture (textures are always square).
0000 Int8 unknown, always seems to be 2
0001 Int8 x and y resolution
0002 Int16 number of textures in file
0004 Int32 offset to texture #0
0008 Int32 offset to texture #1
...
The offset of each texture points to the actual texture palette indices,
which are xyres^2 bytes long. Textures always use palette #0.
Texture names are stored in string block 10, where wall textures start at
position 0, and ceiling textures start at 510, going backwards. The string
at position 511 is reserved for the ceiling.
3.5 Fonts
Fonts are stored in "font*.sys" files, and can be non-proportional (chars
can have different lengths). The header looks as this:
0000 Int16 unknown, always 1 (might be size of character width field)
0002 Int16 size of single character, in bytes (=charsize)
0004 Int16 width of the blank (space) character, in pixels
0006 Int16 font height in pixels
0008 Int16 width of a character row in bytes
000A Int16 maximum width of a character in pixels (=maxwidth)
Then follow all bitmaps for each character. The number of chars can be
determined by (filelen-12) / (charsize+1). Bitmaps are stored as 1-bit
patterns, starting at the most significant bit in the current byte. When
a new line in character bitmap begins, remaining bits are unused and a new
byte in the file is taken.
After 'charsize' number of bytes, there is another Int8 that says the width
for the current character in pixels.
Note: at least the fonts "fontbig.sys" and "font5x6p.sys" contain overly
large characters, and for these the remaining bits at a line are used. The
maxwidth field should be corrected for loading.
3.6 Critter animations
Critter animations are stored in the folder "crit". The file "assoc.anm"
holds data for each of the 32 animations and for the 64 NPC types. The file
starts with 8 bytes for the name of each animation. Shorter strings are
padded with zeros. Empty strings denote animations not available (e.g. in
the "uw_demo").
Next comes a table of infos for each NPC. The table is 64 entries (0x0080)
long:
0000 Int8 anim
0001 Int8 auxpal
The "anim" field specifies which one of the 32 animations to take for a
given NPC number (NPC object ID - 0x0040). A value of 0xff indicates that
no animation is available.
The "auxpal" value describes which auxiliary palette to use. There are
several critters that share the same animations but use different palettes,
e.g. the bat and the vampire bat uses the same anim value, but different
auxiliary palettes.
[uw2] The critter associations are stored in the file "as.an"; it doesn't
contain the critter names, only the 64 entries for "anim" and "auxpal" are
stored.
Animation for each critter is stored in a file named "CrXXpage.nYY", where
XX = animation number (=anim), YY = page number
XX and YY are octal numbers
There may be more than one page for each animation file.
The file starts with a header:
pos length desc.
0000 Int8 anim slot base
0001 Int8 number of anim slots (=nslot)
0002 nslot*Int8 list of segment indices
After this, a list of segment follows which contains up to 8 frame indices
for every segment.
nslot+2 Int8 number of anim segments (=nsegs)
8*nsegs anim frame indices
Then the auxiliary palettes follow:
Int8 number of aux palettes (=npals)
npals*32 allaux palette indices in blocks of 32
Next is a list of all frames and their offsets into the file:
Int8 number of frame offsets (=noffsets)
Int8 compression type? (always 06)
noffsets*Int16 absolute offsets to frame headers
Each animation is stored in a segment which can contain a number of
frames (stored in the "animation frame indices"). Each list is padded with
0xFF entries.
frame header
0000 Int8 width
0001 Int8 height
0002 Int8 hotspot x
0003 Int8 hotspot y
0004 Int8 compression type; (06: 5-bit word size)
0005 Int16 data length in number of words
0007 start of rle-encoded image data (see 2.3)
The hotspot coordinates are to "pin" the image at a specific position. The
hotspot coordinates in the image should always be on the same place when
rendered. The compression type can be 06, which is 5-bit run-length
encoding, or 08, which is 4-bit run-length encoding (see 2.3. for more).
The slot lists group together segments of animations for various actions.
Here's a list of slots and their actions:
slot action
00 combat idle
01 attack, bash (?)
02 attack, slash (?)
03 attack, thrust
05 second weapon attack
07 walking / running towards player
0c death
0d ??
20 idle, facing away from player (180 degrees)
21 idle, 135 deg.
22 idle, angle 90 deg.
23 idle, angle 45 deg.
24 idle, facing towards player, 0 deg.
25 idle, angle -45 deg.
26 idle, angle -90 deg.
27 idle, angle -135 deg.
Segment indices at 80-87 are the same as above, except that these are the
walking animations. For the animations used in the Ethereal Void (level 9)
the slot list is somewhat different.
Here is a list of animation files and their contents:
file assoc name auxpals used in
cr00 * "gngob32" 4 green goblin
cr01 "skela" 1 skeleton
cr02 "lizman" 3 green, red and gray lizardman
cr03 * "bat" 3 cave bat, vampire bat
cr04 "wiza" 5 yellow male mage
cr05 * "spider" 3 giant, wolf, dread spider
cr06 "gazer" 1 gazer
cr07 "troll" 3 troll, fereal troll, great troll
cr10 "femwiz" 4 female mage
cr11 * "slug" 2 flesh slug, acid slug
cr12 "fire" 2 fire elemental
cr13 "ghoul" 3 ghoul, dark ghoul
cr14 "demon" 1 Slasher of the Veils
cr15 * "ghost" 4 ghost, dire ghost
cr16 * "graygob" 2 gray goblin
cr17 "reaper" 1 reaper
cr20 * "rat" 2 giant rat
cr21 "femfite" 3 female fighter
cr22 * "imp" 2 imp, mongbat
cr23 "golem" 3 earth, stone and metal golem
cr24 * "hedless" 1 headless
cr25 "wizb" 3 blue female mage
cr26 * "rotgrub" 2 green rotworm, bloodworm
cr27 "wisp" 1 wisp
cr30 "batskull" 2 bat, teeth, vortex, hound (level 9)
cr31 * "Lurk" 2 lurker, deep lurker
cr32 * "fight32" 4 male fighter, outcast, adventurer
cr33 "dwarf32" 3 mountainman
cr34 "shadow" 2 shadow beast
cr35 "tybal" 1 tyball
cr36 "eye" 1 eye, skull (level 9)
cr37 "litening" 1 lightning, fish (level 9)
The animations marked with * are available in the uw_demo, too.
3.7 Cutscene animations
Cutscene animations are stored in folder "cuts". Text strings for cutscenes
can be found in string blocks 0c00 through 0c21. Chapter 2.2.1 lists all
animations available in Ultima Underworld 1.
The cutscene files are done with Amiga's DeluxePaint Animator (file
extension *.anm). The complete description can be found in the zip file
"anmformt.zip" in the "misc" folder. Here's a short overview for usage in
Ultima Underworld 1:
The files, "large page files", consist of a header, followed by one or
more large pages, where each page can store one or more animation frames.
A large page is always 64k big, except for the last page (which is
truncated at the end of usable data).
The file starts with a "large page file header":
0000 Int32 file ID, always contains "LPF "
...
0006 Int16 number of large pages in the file
0008 Int32 number of records in the file
...
0010 Int32 content type, always contains "ANIM"
0014 Int16 width in pixels
0016 Int16 height in pixels
The whole header is 128 bytes long. After the header color cycling info
follows (which also is 128 bytes long), which is not used in uw1. Then
comes the color palette:
0000 Int8 intensity for blue, ranges from 0..255
0001 Int8 intensity for green
0002 Int8 intensity for red
0003 Int8 padding byte
... repeated for all 256 color indices
After the palette an array with 256 "large page descriptors" follow:
0000 Int16 number of first record in the large page
0002 Int16 number of records in the large page
0004 Int16 total number of bytes, excluding header
Unused descriptors contain no information. After the array, the large pages
start. A "large page" has the following layout:
0000 lpdesc large page descriptor
0006 Int16 empty
0008 Int16 length of first record
000A Int16 length of second record
...
The large page descriptor is repeated for the current large page. A record
contains a frame (which may depend on the previous frame). The start can be
calculated by summing up the length of the previous records. A "record" has
the following structure:
0000 Int8 unknown
0001 Int8 flag
0002 Int16 extra offset, when flag != 0
0004 start of compressed data
The extra offset must be even (when odd, add an extra 1).
The compression scheme is a variation of run-length encoding, with some
extras. There are "dump", "run" and "skip" records. "dump" records just
copy the next bytes to the output buffer. "run" records get the next byte
and repeat them according to the count. A "skip" record skips pixels in the
output buffer (it is assumed that the previous decoded image is still in
the buffer).
First, read a signed Int8. If it is positive, dump that many bytes.
If it is 0, the next two Int8's are the count and the pixel byte for a
"run" record. For negative values, remove the sign bit. If the resulting
byte is != 0, skip that many bytes in the output, else a "long" operation
is started.
For the long operation, retrieve the next two Int8's, treating as a little
endian signed Int16. If the value is 0, the decoding ends. If the value
is > 0, skip that many bytes in the output. For values < 0, remove the sign
bit. If the resulting value is >= 0x4000, we have a "run" record with
count = value & 0x3fff and the next Int8 as the pixel index. If the value
is <= 0x4000, we have a long "dump" record.
Here is some meta-C code to describe the decoding:
while(true)
{
Int8 cnt = get_next_src8();
if (cnt>0) dump_pixels(cnt);
if (cnt==0) run_pixels(get_next_src8(),get_next_src8());
if (cnt<0)
{
cnt &= 0x7f;
if (cnt!=0) skip_pixels(cnt);
else
{
// we have a "long" operation
Int8 cnt2 = get_next_src16();
if (cnt2>0)
skip_pixels(cnt2);
else
if (cnt2==0)
break;
else
{
cnt2 &= 0x7fff;
if (cnt2>=0x4000)
run_pixels(cnt2-0x4000,get_next_src());
else
dump_pixels(cnt2);
}
}
}
}
3.9 Weapon animations
Attack animations:
------------------
The file weapons.gr contains animation frames for attacks with the various
weapon types.
The file contains 224 image frames, split into 112 for right-handed attacks
and 112 for left handed.
For each weapon, including the fist, 3 animations are stored: slash, stab
and hack - even if identical. After these three animations one "ready"
frame is stored.
Each animation has 4 "power-up" frames and 5 "attack frames", some of which
can be black (a small 2x2 image)
Therefore, each weapon type has 3*9+1 = 28 frames, 4 attack types gives 112
images.
TODO: mace seems not to fit this exactly!!!
Weapons.dat:
------------
This file stores 8-bit coordinates for the frames of the various attack
animations. For each attack type first 28 x-coordinates are stored, then
28 y-coordinates. There are 8 such sets of coordinates:
right hand sword
right hand axe
right hand mace
right hand fist
left hand sword
left hand axe
left hand mace
left hand fist
Coordinates pin-point the upper-left corner of the attack frame and are
relative to the lower left corner of the 3d-view area.
Weapons.cm:
-----------
This file stores two aux 16-color pallettes for the attack animation
frames. These are needed to tint the weapon attack frames according to the
skin color of the selected character. I haven't checked, but probably one
of these match the "standard" aux pallette used for the .gr files...
+--------------------------------------------------------------------------+
4 Level maps and object lists
Level map information is stored in the file "lev.ark". A default map is
in the "data" folder and is loaded after character creation. During
gameplay, the map is stored in the folder "Save0".
4.1 File format
The file is a container for several differently-sized blocks that contain
different infos of the level maps. Some blocks may be unused, e.g. automap
blocks.
The file header looks like this:
0000 Int16 number of blocks in file
0002 Int32 file offset to block 0
0006 Int32 file offset to block 1
... etc.
File offsets are absolute offsets into the file. When an offset is 0, the
block is not available.
Ultima Underworld 1 has 135 (0x0087) entries (9 levels x 15 blocks). The
block layout is as following:
<9 blocks level tilemap/master object list>
<9 blocks object animation overlay info>
<9 blocks texture mapping>
<9 blocks automap infos>
<9 blocks map notes>
The remaining 9 x 10 blocks are unused.
The Ultima Underworld Demo uses three separate files to store the map.
Here's a list of the files and what blocks they contain:
level13.st level tilemap/master object list block
level13.txm texture mapping
level13.anx object animation overlay info
Ultima Underworld 2 has 320 (0x0140) entries (80 levels x 4 blocks). These
can be split into 4 sets of 80 entries each:
0.. 79 level maps
80..159 texture mappings
160..239 automap infos
240..319 map notes
[uw2] Data blocks for Ultima Underworld 2 are compressed using the uw2
compression scheme described in chapter 9.1.
The "level tilemap/master object list" for each level contains infos about
the level architecture (tilemap) and the objects which live in it:
offset size description
0000 4000 tilemap (64 x 64 x 4 bytes)
4000 1b00 mobile object information (objects 0000-00ff, 256 x 27 bytes)
5b00 1800 static object information (objects 0100-03ff, 768 x 8 bytes)
7300 01fc free list for mobile objects (objects 0002-00ff, 254 x 2 bytes)
74fc 0600 free list for static objects (objects 0100-03ff, 768 x 2 bytes)
7afc 0104 unknown (260 bytes)
7c00 0002
7c02 0002 no. entries in mobile free list minus 1
7c04 0002 no. entries in static free list minus 1
7c06 0002 0x7775 ('uw')
4.2 Level tilemap
Each underworld level consists of a 64x64 tile map (just like on a chess
board). A tile can be of different types and can have various floor
heights. The ceiling height is fixed. A tile can have an index
into the master object list that is the start of an object chain with
objects in this tile.
The first 0x4000 bytes of each "level tilemap/master object list" contain
the tilemap info bytes. For each tile there are two Int16 that describe a
tile's properties. The map's origin is at the lower left tile, going to the
right, each line in turn.
The two Int16 values can be split into bits:
0000 tile properties / flags:
bits len description
0- 3 4 tile type (0-9, see below)
4- 7 4 floor height
8 1 unknown (?? special light feature ??) always 0 in uw1
9 1 0, never used in uw1
10-13 4 floor texture index (into texture mapping)
14 1 when set, no magic is allowed to cast/to be casted upon
15 1 door bit (when 1, a door is present)
0002 tile properties 2 / object list link
bits len description
0- 5 6 wall texture index (into texture mapping)
6-15 10 first object in tile (index into master object list)
about word 0000, bit 8:
For UW2 Bit 8 is set pretty often, and if set the light level changes.
Ironically 1 sometimes means daylight (in Lord British Castle Lv 1) but
sometimes 0 means daylight (LBC Lv 5). But the areas are exactly right.
Underworld tile types are:
00 Solid (wall tile)
01 Open (square tile of empty space)
02 Diagonal, open SE
03 Diagonal, open SW
04 Diagonal, open NE
05 Diagonal, open NW
06 Sloping up to the north
07 Sloping up to the south
08 Sloping up to the east
09 Sloping up to the west
4.2 Master object list
The master object list is stored after the tilemap data. There are 1024
(0x0400) list positions. The first 256 are reserved for "mobile objects"
that have extra NPC info. The rest of the list is used for "static
objects". The list entries are allocated from the end to the beginning of
the lists. Item positions that are free are stored in the "free lists",
described in chapter 4.3.
Entry 0 is never allocated and is used to test against item links of value
0. Entry 1 is partly used to store the player's informations, e.g.
direction or in-tile x/y positions.
Each object entry has a "general object info" block consisting of 4 Int16
words. The 256 "mobile objects" are followed by 19 bytes "mobile object
extra info", resulting in entries of 27 bytes length. The remaining 768
entries only have 8 bytes each. The "mobile object extra info" is described
in chapter 4.2.3.
The "general object info" block looks as following:
bits size field description
0000 objid / flags
0- 8 9 "item_id" Object ID (see below)
9-12 4 "flags" Flags
12 1 "enchant" Enchantment flag (enchantable objects only)
13 1 "doordir" Direction flag (doors)
14 1 "invis" Invisible flag (don't draw this object)
15 1 "is_quant" Quantity flag (link field is quantity/special)
0002 position
0- 6 7 "zpos" Object Z position (0-127)
7- 9 3 "heading" Heading (*45 deg)
10-12 3 "ypos" Object Y position (0-7)
13-15 3 "xpos" Object X position (0-7)
0004 quality / chain
0- 5 6 "quality" Quality
6-15 10 "next" Index of next object in chain
0006 link / special
0- 5 6 "owner" Owner / special
6-15 10 (*) Quantity / special link / special property
All field names listed are used later to refer to these fields in the
object's information words.
Object IDs can be split up for classification purposes. Read more in
chapter 6 about it.
Objects in a tile are stored as a linked list, where the "Index of next
object in chain" points to the next object in list, or contains 0 for the
end of the linked list. The first object in the list is determined by the
tile's object index value (see above, at "Tile map").
(*) The "Quantity" field in word 0006 can have several meanings. If the
"is_quant" field is 0 (unset), it contains the index of an associated
object. The exact meaning varies, but is generally a "has-a" type
relationship (contents, trap to set off, spell). The field name "sp_link"
is used in this document if that type of field is meant.
If the "is_quant" flag is set, the field is a quantity or a special
property. If the value is < 512 or 0x0200 it gives the number of stacked
items present. Identical objects may be stacked up to 256 objects at a
time. The field name "quantity" is used for this.
If the value is > 512, the value minus 512 is a special property; the
object type defines the further meaning of this value (see chapter 6.1 for
all special objects in Ultima Underworld). The field name "property" is
used for this type of value.
Note that the term "object" and "item" are used concurrently in the
document and always mean the same thing.
4.2.1 Enchantments
If the enchantment flag is set and the object is enchantable, then the
link field (less 512) determines the enchantment. Enchantment names are
stored in strings chunk 5. The way in which the link value maps onto
spells in this chunk depends on the object type.
Most objects seem to use spells 256-320 (add 256) if the enchantment
number is in the range 0-63, otherwise they add 144 to use spells 208 and
up. Healing fountains, however, don't use a correction at all.
Weapons and armour have a more complex mapping. Most enchanted weapons and
pieces of armour have an enhancement for Accuracy, Damage, Protection or
Toughness, which are spells 448-479 in the main spell list. These map to
special property values 192-207. Yes, there are only 16 values for 32
spells; enchanted armour adds another 16 to the spell index to bring it
into the armour enchantment range. However, these items may also carry
generic enchantments, in which case the special properties map to spells
0-255 (and armour doesn't apply a special correction).
Wands don't hold their enchantments directly in the quantity field, since
they also need to store the number of charges remaining. Instead, they
link to a spell object which holds the enchantment; it seems here that
the "quality" field of the spell object determines the number of charges.
Other objects may also carry spells in this way.
4.2.2 Item Owner
Some items have a "... belongs to " description. The common
object properties (see chapter 6.2) determine if an object can have an
owner. The string printed is stored in the "owner" field and is an index
into string block 1; the string printed for the critter type is
"owner" - 1 + 370. When the field is 0, the object doesn't belong to anyone.
4.2.3 Mobile object extra info
The values stored in the NPC info area (19 bytes) contain infos for
critters unique to each object.
offsets type bits meaning
0008 0000 Int8 0-7 npc_hp
0009 0001
000a 0002 Int8 7
000b 0003 Int16 0-3 npc_goal
4-11 npc_gtarg
000d 0005 Int16 0-3 npc_level
4-11
8
13 npc_talkedto
14-15 npc_attitude
000f 0007 Int16 6- 12 npc height?
0011 0009
0012 000a
0013 000b Int8 7 single bit, unknown
0014 000c
0015 000d
0016 000e Int16 0-3 unknown
4-9 npc_yhome
10-15 npc_xhome
0018 0010 Int8 0-4: npc_heading?
5-7:
0019 0011 Int8 0-6: npc_hunger (?)
001a 0012 Int8 npc_whoami
The values are used for combat, AI and conversations.
4.3 Free lists
The free lists generally contain infos about the master object list usage
and are used for object slot allocation/deallocation.
Free list, mobile objects
-------------------------
This consists of an Int16 for each mobile object (critter) slot which is
not in use, giving the slot position in the master object list. Note that
there are only 254 entries in this table because object 0 is always the
null object (and hence is never allocated) and object 1 is always the
avatar (and can never be free - that's probably a metaphor for life, or
something). Of course, only the first (no. free mobile objects) entries
are valid.
Free list, static objects
-------------------------
This consists of an Int16 for each static object which is not in use, as
above. This table is 768 entries long (room for all possible static objects).
4.4 Texture mappings
The texture mapping table are used to map tile texture indices to the
actual texture used, since wall and floor textures are only encoded with
6 and 4 bits. The block of size 0x007a always look like this:
0000 48 x Int16 wall texture number (from w64.tr)
0060 10 x Int16 floor texture number (from f32.tr)
0074 6 x Int8 door texture number (from doors.gr)
007a
The last value from the floor texture number array is used as ceiling
texture.
"Look" descriptions come from block 000a, where wall textures use strings 0
to 255 and floor textures are described by strings 256 to 510, in reverse
order. Ceiling always uses string 511.
In uw2 the texture mapping is 134 (0x0086) bytes long and contains 64
Int16 entries that are indices into t64.tr. The first 16 entries are shared
by the floor and wall indices. Entries above 16 are wall-only textures.
Ceiling seems to be textured by entry 0x20. The last 6 bytes is the door
texture mapping, see above.
"Look" descriptions are almost the same as in uw1, but as textures can be
shared between walls and floors, there are two descriptions for every entry
in t64.tr. Floor descriptions start at string 255 without reversing order.
4.5 Animation infos
This block contains entries with length of 6 bytes with infos about
objects with animation overlay images from "animo.gr".
It always is 0x0180 bytes long which leads to 64 entries.
0000 Int16 link1
0002 Int16 unk2
0004 Int8 tile x coordinate
0005 Int8 tile y coordinate
link1's most significant 10 bits contain a link into the master object
list, to the object that should get an animation overlay.
4.6 Automap infos
Each block contains the "visited" bytes for each level. Each byte
describes a tile on the main map. The block size always is 0x1000.
rest not decoded yet
4.7 Terrain texture properties
The file "terrain.dat" in the data directory contains information on the
terrain types represented by the various wall and floor textures. There is
a 16-bit word per texture, up to a maximum of 256 walls and 256 floors.
Floor data therefore starts at file offset 0x200. Terrain types are:
0000 Normal (solid) wall or floor
0002 Ankh mural (shrines)
0003 Stairs up
0004 Stairs down
0005 Pipe
0006 Grating
0007 Drain
0008 Chained-up princess
0009 Window
000a Tapestry
000b Textured door (used for the lock to the Key of Infinity)
0010 Water (not waterfall)
0020 Lava (not lavafall)
0040 Waterfall - UW2
00C0 Ice wall - UW2
00D8
00E8 Ice walls (crumbling?)
0080 Lavafall - UW2
00F8 Ice - UW2
+--------------------------------------------------------------------------+
5 String resources
Game strings are stored in the file "strings.pak", and uses a Huffman
compression scheme to store its strings. The first 2 bytes of the file give
the number of nodes in the tree. Then follow the nodes themselves, 4 bytes
each:
0000 Int8 char symbol
0001 Int8 parent node
0002 Int8 left child
0003 Int8 right child
The last node stored in the file is the head of the tree. Following the
nodes is a 16-bit word giving the number of string blocks in the
file. Then follows the block directory, 6 bytes per block as follows:
0000 Int16 block number
0002 Int32 offset in file of start of block
Each block contains a variable number of strings. The block header is:
0000 Int16 no. of strings
0002 Int16 relative offset from end of block header to first string
0004 Int16 relative offset to second string
...
Strings are compressed using the Huffman tree in the usual way. Bits are
extracted big-endian i.e. rotated out of the top of each byte in turn.
Starting with the root node (last node), if a 1 bit is encountered the right
branch is taken, otherwise take the left. Repeat until a leaf (node with -1
for its children) is reached, at which point output the symbol for that node.
For the next bit we start again from the root. End of string is marked with a
`|' character. The remaining bits in the last byte are unused.
5.1 String block contents
block description
0001 general UI strings
0002 character creation strings, mantras (?)
0003 wall text/scroll/book/book title strings (*)
0004 object descriptions (*)
0005 object "look" descriptions, object quality states
0006 spell names
0007 conversation partner names, starting at string 17 for conv 1
0008 text on walls, signs
0009 text trap messages
000a wall/floor description text
0018 debugging strings (not used ingame)
0c00 intro cutscene text
0c01 ending cutscene text
0c02 tyball cutscene text
0c03 arial cuscene text (?)
0c18 dream cutscene 1 text "arrived"
0c19 dream cutscene 2 text "talismans"
0c1a-0c21 garamon cutscene texts
0e01-0f3a conversation strings
Block 0003 contains text/scroll etc. strings. The exact string to use for
books, scrolls or other text object is in the quantity field. It is
calculated as quantity - 0x0200. For each level, 32 string slots are available.
Block 0004 contains the object descriptions. The article (e.g. 'a' or
'an') is separated with an underscore '_'. When a '&' is in the string,
it separates the plural of the object's name. The complete text string is
"you see [named ]. can be
one of "mellow" or "upset".
In the uw_demo, the 0cXX blocks and many of the conversation string
blocks aren't available.
+--------------------------------------------------------------------------+
6 Objects and items
This chapter contains information about the objects in Ultima Underworld.
Objects (or items) are grouped by type. Here's a short overview of all
object groups:
0000-001f Weapons and missiles
0020-003f Armour and clothing
0040-007f Monsters
0080-008f Containers
0090-0097 Light sources
0098-009f Wands
00a0-00af Treasure
00b0-00bf Comestibles
00c0-00df Scenery and junk
00e0-00ff Runes and bits of the Key of Infinity
0100-010f Keys, lockpick, lock
0110-011f Quest items
0120-012f Inventory items, misc stuff
0130-013f Books and scrolls
0140-014f Doors
0150-015f Furniture
0160-016f Pillar, some decals, force field, special tmap obj
0170-017f Switches
0180-019f Traps
01a0-01bf Triggers
01c0-01cf Explosions/splats, fountain, silver tree, moving things
A description string for each object is stored in game strings block 0004
Object IDs are in the range 0x0000 to 0x01ff. The bits can be split up
for classification purposes of different objects:
bits
0..3 object number in subclass
4..5 object subclass
6..8 object class
The object class groups together 0x0040 (64) items each. Here's a list
of some classes:
class item_id description
1 0040 npc's
6 0180 traps/triggers
subclass 0/1: traps
subclass 2: triggers
7 01c0 sprites, animated objects
6.1 List of all objects and items
This chapter lists and describes all objects that are available in Ultima
Underworld. Descriptions for obvious items are omitted.
000f a_fist
this is not really an item, but is used for fist combat
002f a_pair of dragon skin boots
player doesn't get hurt when walking on lava
0040..0x007e NPC's
"sp_link" points to the inventory start
007f an_adventurer
the player as object; isn't used ingame
008f a_rune bag
looking at the bag shows the rune bag panel; available runes are
stored in the savegame
0098 a_wand (and other wands)
"sp_link" points to a_spell object
00c6 a_pile of bones
the "owner" field determines from whom the bones are; strings are
taken from block 4; a value of 63 means "an adventurer"
0100 a_key (and up to 010e)
the key ID that is needed to unlock a door with an associated lock
object is stored in the "owner" field.
010f a_lock
the lock object is associated with a door or portcullis and
determines the lock state and the lock ID. bit 9 of the flags
indicates if the lock is locked (1) or unlocked (0). the lower 6 bits
of the "link/special" field determines the lock ID. when unlocking a
door with a key, the lock ID must match the key ID on the key.
0110 a_picture of Tom
0114 a_book
the book explodes when the user tries to look at it; player takes
damage.
0120 a_spell
spell object used by wands, spell traps, magical items;
the "quality" field
013b a_map
the map is shown when looked at the map in inventory. The string at
block 1, string 151 is printed, too.
0140 a_door (and up to 014f)
if bit 1 of the "owner" field is set, the door is spiked. if the
"sp_link" field points to a_lock object, the door is locked.
0146 is a portcullis and 0147 is a secret door. doors from 0148
to 014f are the open versions of the closed doors.
the textures for the doors come from the 6 bytes at the end of the
texture mapping info from lev.ark.
0160 a_pillar
3d object. texture is determined by the lower 2 bits of the "flags"
field (?)
0161 a_lever
a clock-like lever with 8 positions; texture is determined by
the "flags" field (lower 3 bits) + 4, from "tmobj.gr".
0162 a_switch
almost the same as 0161, but starting with image 12 of "tmobj.gr".
not used in uw1
0164 a_bridge
bridge 3d object. texture to use comes from "flags" field. when the
value is < 2, the bridge texture is taken from "tmobj.gr", index
30 + flags. for this bridge, string block 0001, string 171 is printed
as "look" description.
when the "flags" value is > 2, flags-1 is used as index into the
floor texture mapping, effectively using a floor texture.
bridges could be use to alter the fixed ceiling height.
0165 a_gravestone
3d grave stone object (obj 0x13). text for gravestone is stored in
strings block 0008, indexed by "quantity" - 0x200. the index also
serves as offset into the file "grave.dat" which gives a unique grave
id. It is used for large gravestone images that are stored in
"cuts/cs401.n01", one frame each, indexed by the grave id. texture of
the gravestone is image "flags" + 28 from "tmobj.gr".
the text printed when looking at it is determined by the "flags"
field. gravestone/tombstone description text is from strings block
0008, string 352 + flags.
0166 some_writing
wall decal; text is determined through "quantity" field,
game strings block 0008. the texture used is determined from
"tmobj.gr", image "flags" + 20 (?).
the plaque description printed when looking at it is determined by
the "flags" field. text printed is from strings block 8, 368 + flags
0167 a_bed [uw2]
The "owner" field determines the colour of the sheets and pillow. The
sheets are colour (4*owner+5), the pillow colour (4*owner).
016e special tmap obj (tmap_c)
wall decal; texture used is determined from the "owner" field and is
an index into the texture mapping wall table.
016f special tmap obj (tmap_s)
same as 016e, with the difference that the wall is recognized in
collision detection, effectively providing thin walls that could
be removed by deleting the object.
017x a_button, etc.
buttons, switches, pull chains and levers, textured with images
from "tmflat.gr". special link usually points to a trigger.
01c9 a_fountain
01ca a_silver tree [uw1]
animated objects; "owner" field determines current animation overlay
01cf a_moving door
object that is used during opening a door
6.1.2 Traps and Triggers
Traps are set off by triggers, which are triggered by the player coming
near them. Traps can also be set off by switches or buttons, etc. All
triggers contain tilemap coords in "quality" and "owner" field called
trigger "target" coordinates. All triggers also have the "special link"
set that points to the trap(s) to set off. If a trap also has the "special
link" field set, then another trap is set off, allowing trap chaining.
0180 a_damage trap
player vitality is decreased; number of hit points are in "quality"
field; if the "owner" field is != 0, the hit points are added
instead. the trap is only set of when a random value [0..10] is >= 7.
0181 a_teleport trap
teleports the player to another level and tile; destination level is
given by "zpos" (0 means current level), tile x/y coordinates
are given in "quality" and "owner" fields.
0182 a_arrow trap
0183 a_do trap [uw1]
a multi-purpose trap. the "quality"-field determines action to
perform:
action description
02
03
05
18 bullfrog puzzle related; (special property is a link)
if not in level 5 (where the puzzle is), the trap just
prints "There is a pained whining sound.".
"owner" defines some subcode:
00/01: lowers/raises tiles
02: increases x coord (?) (0..7)
03: increases y coord (?) (0..7)
04: resets bullfrog puzzle; prints "reset activated"
28 (special property is a link)
2a starts conversation (uw1 starts a hard-wired conv., slot
0x19, the speaking "Door"
32
39 (not used in uw1)
3c..3e (not used in uw1)
3f ends game, shows end sequence
0183 a_hack trap [uw2]
0184 a_pit trap [uw1]
not implemented in uw1, but used on 3rd level, as target for a use
trigger that is associated with a check variable trap (map bug?)
this is probably a bottomless pit that drops the player through to
the next level.
0184 a_special effects trap [uw2]
this trap does 'visual' effects like earthquakes, or blurry vision.
0185 a_change terrain trap
the trap changes one or more tiles, according to the encoded info.
the trigger's target coordinates describe the starting tile.
TODO
0186 a_spelltrap
fields "quality" and "quantity" determine spell type.
0187 a_create object trap
creates a new object using the object referenced by the "quantity"
field as a template. the object is created only when a random number
between 0 and 3f is greater than the "quality" field value.
0188 a_door trap
opens or shuts a door when set off. the trigger "target" coords
determine the tile with the door to open/close. if the door
has an "a_lock" object associated, it is deleted. if there is no
lock, a new lock is created, using the template lock linked to by the
"sp_link" field. as the door points to a lock, there can't be another
trigger associated with it.
the "quality" value decides if a door trap only opens, closes or
toggles doors.
1: try open
2: try close
3: toggle door state
0189 a_ward trap
not used in uw1
018a a_tell trap [uw1]
not used in uw1, implemented the same way as a ward trap
018a a_skill trap [uw2]
018b a_delete object trap
deletes an object when set off. "owner" and "quality" of the trap
determines tile the object is to be found, "sp_link" points to the
object.
018c an_inventory trap
the trap searches for an item in the inventory; when it is found, the
sp_link'ed trigger is set off. the item_id is given by
("quality" << 5) | "owner". additionally, the zpos value must be != 0
to enable the trap.
018d a_set variable trap
sets a game variable; fields "quality", "owner" and "ypos" are
combined to form a "value" that is used as variable index later:
field bits in value
ypos 0..2
owner 3..7 (bit 5 of "owner" seems not to be used)
quality 8..13
the "zpos" field determines which variable to set. if zpos is 0, a
bit-field is modified and the index value indicates which bit to
modify. the "heading" field determines the operation to perform:
heading operation bit-field operation
0 add set bit
1 sub clear bit
2 set set bit
3 and set bit
4 or set bit
5 xor flip bit
6 shl set bit
values are modified and kept in range 0..63 (0x3f).
largest variable index in uw1 is 0x33, the only bit modified in uw1
is bit 7 of the bit field
018e a_check variable trap
the "value" from the set variable trap (018d) is also used here.
the trap checks a range of variables, starting from "zpos" and of
length "heading". if "xpos" is not 0, the variable values in range
are added; if it is 0, the lower 3 bits of every variable value are
shifted into the resulting value. here's some meta-C code to show
how the check works:
bool check_variable_trap(zpos,heading,value)
{
Int16 cmp = 0;
for(Int16 i=zpos; i - ", with the exception of group D
which is for armour items which are grammatically plural even if there is
only one of the object, e.g. "leather leggings".
6.3 Object class properties
Object properties specific to a range of objects are stored in the file
"objects.dat". The file contains several tables. Here is an overview:
pos size desc entries bytes per entry
0000 Int16 unknown, always 0x010f
0002 0x80 melee weapons table 16 8 bytes
0082 0x30 ranged weapons table 16 3 bytes
00b2 0x80 armour and wearables table 32 4 bytes
0132 0x0c00 critters table 64 48 bytes
0d32 0x30 containers table 16 3 bytes
0d62 0x20 light source table 16 2 bytes
0d82 0x20 unknown, maybe jewelry info table
0da2 0x40 animation object table 16 4 bytes
0de2 end
* Melee weapons table (0x0000-0x000f)
0000 Int8 damage modifier for Slash attack
0001 Int8 damage modifier for Bash attack
0002 Int8 damage modifier for Stab attack
0003 3 unknown
0006 Int8 skill type (3: sword, 4: axe, 5: mace, 6: unarmed)
0007 Int8 durability
* Ranged weapons table (0x0010-0x001f)
0000 Int16 unknown
bits 9-15: ammunition needed (+0x10)
0002 Int8 durability
* Armour and wearables table (0x0020-0x003f)
0000 Int8 protection
0001 Int8 durability
0002 Int8 unknown
0003 Int8 category:
00: shield
01: body armour
03: leggings
04: gloves
05: boots
08: hat
09: ring
* Critters table (0x0040-0x007f)
0000 Int8 unknown
0005 Int8 npc_power
* Containers table (0x0080-0x008f)
0000 Int8 capacity in 0.1 stones
0001 Int8 objects accepted; 0: runes, 1: arrows, 2: scrolls, 3: edibles, 0xFF: any
0002 Int8 number of slots available?; 2: , -1: any
* Light source table (0x0090-0x009f)
0000 Int8 light brightness (max. is 4; 0 means unlit)
0001 Int8 duration (00: doesn't go out, e.g. taper of sacrifice)
* Jewelry info table (?)
* Animation object table (0x01c0-0x01cf)
0000 Int8 unknown (0x00, 0x21 or 0x84)
0001 Int8 unknown (always 0x00)
0002 Int8 start frame (from animo.gr)
0003 Int8 number of frames
6.4 Object combining
In the Ultima Underworlds, when you `apply' certain objects to one another
in your inventory a new object is created. For example,
pole + strong thread = fishing pole. The mechanism for this is very simple
and is controlled by the file "cmb.dat" in the data/ directory.
This file contains a table of 3 16-bit words for each allowable
combination: `source1', `source2', `newobject' in that order. 3 zeros mark
the end of the table. The low 9 bits of each word is the object ID. If an
object of type source1 is applied to an object of type source2 (or vice
versa) an object of type newobject is created. The top bit of each of the
source words indicates whether that object is destroyed in the process: if
it is a 1, the object is destroyed. (It is always the case that at least
one of the source objects is destroyed: you can't create something from
nothing, at least not this way).
+--------------------------------------------------------------------------+
7 Conversations
Conversations in Ultima Underworld are controlled using an assembler-like
opcode-language that seemed to originate from Forth sourcecode. There are
256 conversation "slots" available that can contain code (but doesn't need
to). The field "npc_whoami" of the "Mobile object extra info" (see 4.2.3)
decides which slot to take. If it is 0, a generic NPC-conversation is used
instead.
To persist conversation info, there are several places to store variables.
* Quest Flags store infos needed by more than one NPC; they probably can be
modified by the game, e.g. when something happens (killing Tyball). Think
of them as global variables.
* Private Globals store infos saved after exiting conversations. Sizes of
this area are determined by the file "babglobals.dat".
* Local Variables store infos during conversation. They are lost when
conversation ends.
* NPC infos are stored along with the NPC's data in the Master Object List
and contain infos like npc_gtarg or npc_goal. See chapter 7.x for more.
* Game globals are variables that are global to the game, e.g. game time,
current dungeon level or player properties
The conversation code can call "intrinsic" functions to use functionality
built into the game, e.g. for bartering or player inventory access.
7.1 Conversation file format
Conversations are stored in the file "cnv.ark". Note that in Ultima
Underworld 2 .ark files are compressed. See Chapter 9.1 for details.
The File header looks like this:
0000 Int16 number of conversation slots in file
0002 Int32 offset to conversation slot #0
0006 Int32 offset to conversation slot #1
...
If an offset is 0, the conversation slot is empty and no conversation is
available. The name of the conversation partner is stored in string block
0007, string number = (conversation slot number - 0x0e00 + 16).
The conversation header looks like this:
0000 Int16 unknown, always seems to be 0x0828, or 28 08
0002 Int16 unknown, always 0x0000
0004 Int16 code size in number of instructions (16-bit words)
0006 Int16 unknown, always 0x0000
0008 Int16 unknown, always 0x0000
000A Int16 game strings block to use for conversation strings
000C Int16 number of memory slots reserved for variables (*)
000E Int16 number of imported globals (functions + variables)
0010 start of imported functions list
(*) This number includes all variables not belonging to stack, e.g. unnamed
globals, imported globals and private conversation globals.
An import record describes imported functions and game global variables
used by the conversation:
0000 Int16 length of function name
0002 n*char name of function
n+02 Int16 ID (imported func.) / memory address (variable)
n+04 Int16 unknown, always seems to be 1
n+06 Int16 import type (0x010F=variable, 0x0111=imported func.)
n+08 Int16 return type (0x0000=void, 0x0129=int, 0x012B=string)
After this table the code section follows.
7.2 Private global variables
Each conversation has a set of private global variables that are saved
across conversations. The initial size of the area is stored in the file
"babglobals.dat". When a game is saved, the globals are stored in the file
"bglobals.dat". The layout of both files is as follows:
0000 Int16 number of conversation slot
0002 Int16 size of private global data for that conv.(=n)
0004 n*Int16 all globals for that slot (omitted in "babglobals.dat")
... repeat until file end
On conversation start the game globals listed in the import table is
copied to the memory address in the private globals. At end of conversation
they are copied back to the game globals.
7.3 Memory layout
Conversation memory is set up at start like this:
0000 local (unnamed) variables (may be empty, size nglobals)
000n game globals (copied on start) (usually 0x001f long)
0020 private conversation globals (of size nprivglobals)
0020+n stack begin (ascends up)
There may be unnamed globals that start at memory location 0. The game
globals then start at a higher address (the exact positions of the game
globals are noted in the "import records list".
For the memory, a full range of 16-bit memory (64k) should be available,
which gives plenty of stack memory.
7.4 Assembler language opcodes
The conversation code is an assembler-like language with a set of opcodes.
It runs on a 16-bit virtual machine with a stack and a result register for
imported functions. The language set is described here:
Opcode no. immediate operands
| Name | no. stack operands
| | | | No. values saved to stack
| | | | | Action
| | | | | |
00 NOP 0 0 0 do nothing.
01 OPADD 0 2 1 push s[0] + s[1]
02 OPMUL 0 2 1 push s[0] * s[1]
03 OPSUB 0 2 1 push s[1] - s[0]
04 OPDIV 0 2 1 push s[1] / s[0]
05 OPMOD 0 2 1 push s[1] % s[0]
06 OPOR 0 2 1 logical OR of top two values.
07 OPAND 0 2 1 logical AND of top two values.
08 OPNOT 0 1 1 logical NOT of top value.
09 TSTGT 0 2 1 greater-than, nonzero if s[1] > s[0].
0A TSTGE 0 2 1 greater-than-or-equal.
0B TSTLT 0 2 1 less-than.
0C TSTLE 0 2 1 less-than-or-equal.
0D TSTEQ 0 2 1 equality. Nonzero if s[1] == s[0].
0E TSTNE 0 2 1 non-equal.
0F JMP 1 0 0 jump absolute. address is measured in words from the
start of the code.
10 BEQ 1 1 0 branch on equal. Pop a value, branch relative if zero.
11 BNE 1 1 0 branch on Not Equal. As BEQ but branch if the value
popped is non-zero.
12 BRA 1 0 0 branch. Always branch relative to the offset address.
13 CALL 1 0 1 call subroutine. Push the next instruction address and
jump to the absolute address (in words) given.
14 CALLI 1 0 0 call imported subroutine. Argument is the function ID.
15 RET 0 1 0 return from subroutine. Pop the return address off the
stack and jump to it.
16 PUSHI 1 0 1 push immediate value onto the stack.
17 PUSHI_EFF 1 0 1 push effective address onto the stack. The value
pushed is the current frame pointer address plus the
immediate operand. This allows local variables and
function parameters.
18 POP 0 1 0 pop a value from the stack (and throw it away).
19 SWAP 0 2 2 swap the top two stack values.
1A PUSHBP 0 0 1 push the current frame pointer onto the stack.
1B POPBP 0 1 0 pop the frame pointer from the stack
1C SPTOBP 0 0 0 new frame. Set the frame pointer to the stack pointer.
1D BPTOSP 0 0 0 exit frame. Set the stack pointer to the frame pointer.
1E ADDSP 0 1 * pop a value, add to the stack pointer. Used
to reserve stack space for variables.
1F FETCHM 0 1 1 pop address, push the value of the variable pointed to.
20 STO 0 2 0 store s[0] in the variable pointed to by s[1].
21 OFFSET 0 2 1 array offset. Add s[1] - 1 to the effective address in
s[0], push this as a new effective address.
22 START 0 0 0 start program.
23 SAVE_REG 0 1 0 pop a value from the stack and store it in the result
register.
24 PUSH_REG 0 0 1 push the value of the result register on the stack.
25 STRCMP ? ? ? string compare.
26 EXIT_OP 0 0 0 end program (?)
27 SAY_OP 0 1 0 NPC says something. Print a conversation string (from
the stack).
28 RESPOND_OP ? ? ? respond (?)
29 OPNEG 0 1 1 negate. s[0] -> -s[0].
(*) ADDSP, of course, doesn't actually push anything onto the stack, but its
effect on the stack pointer is of pushing as many values as its operand
specifies.
(?) I haven't yet encountered these in the wild, so don't know exactly what
they do.
7.5 Text substitutions
In text strings printed by SAY_OP or a imported function (like
"babl_menu") there may be strings like @SS1 or @GS8 that are substituted
with other text. The format of the "format string" is like this:
@XY
[]
X: source of variable to substitute, one of these: GSP
G: game global variable
S: stack variable
P: pointer variable
Y: type of variable, one of these: SI
S: value is a string number into current string block
I: value is an integer value
: decimal value
: format: C: use array index
For pointer variables, the num value determines the location of the
pointer relative to the stack. It usually refers to variables passed
to a function (since they were pushed onto the stack).
For stack variables, the num value determines which stack value is taken
from the current local variables. The value to take is basep +
For global variables, the value describes the globals position in memory,
at the front where the imported game globals and private globals are
stored.
Example:
@SS2 means: print string with string number found at basep + 2
@PI-3 means: print int value pointed to by pointer at basep - 3
@GS8 means: print string from global var #8
7.6 Intrinsic functions
Each imported function has an ID (the argument of the CALLI opcode), and
the "import table" (see above) usually imports all available functions,
even unused ones. All imported functions have at least one argument, and
the first one is the number of arguments additionally passed (some times
this rule seems to be violated, e.g. function "babl_menu", see (*)
below). First argument is always pushed last on stack. All values are
passed by reference (as pointer to the actual value or array).
Here is a quick overview of all builtin functions ("args" is the number
of arguments without the mandatory first one):
type args function name
int 1 (*) babl_menu
int 2 (*) babl_fmenu
void 1 print
int 0 babl_ask
int 2 compare
int 1 random
string ? plural
int 2 contains
string ? append
string ? copy
int ? find
int 1 length
int ? val
void ? say
void ? respond
int 1 get_quest
void 2 set_quest
string 2 sex
int 2 show_inv
int 2 give_to_npc
int 2 give_ptr_npc
int 1 take_from_npc
int 1 take_id_from_npc
int 4 identify_inv
int * do_offer
int 2 do_demand
int 1 do_inv_create
int 1 do_inv_delete
int 1 check_inv_quality
int 2 set_inv_quality
int 1 count_inv
void 0 setup_to_barter
void ? end_barter
void 0 do_judgement
void 0 do_decline
void ? pause
void 2 set_likes_dislikes
int 3 gronk_door
void 1/3 set_race_attitude
void 3 place_object
void 1 take_from_npc_inv
void ? add_to_npc_inv
void 0 remove_talker
void 2 set_attitude
int 2 x_skills
int 2 x_traps
void ? x_obj_pos
void 9 x_obj_stuff
int 2 find_inv
int 1 find_barter
int 4 find_barter_total
Here is a detailed description of every builtin function. arg0 is always
the first argument (the last value pushed on the stack) and specifies the
number of arguments passed.
id=0000 name="babl_menu" ret_type=int
parameters: arg1: array of string id's; ends with id = 0
description: shows a menu of further questions the user can select;
string id's are stored in list in arg1
return value: number of selected response (one-based index of arg1 list)
-------------------------------------------------------------------------
id=0001 name="babl_fmenu" ret_type=int
parameters: arg1: array of string id's; ends with id = 0
arg2: array with on/off flag values (1==on)
description: shows a menu with questions from list in arg1; the list in
arg2 indicates if the question is available (0 means not
available).
return value: number of selected response string (from the arg1 list)
-------------------------------------------------------------------------
id=0002 name="print" ret_type=void
parameters: arg1: string id
description: prints a string that is not spoken by anyone (e.g. scene
description).
-------------------------------------------------------------------------
id=0003 name="babl_ask" ret_type=int
parameters: none
description: lets the user type in a string; the string typed in is
stored in a newly allocated string slot. The string is not
stored after conversation ended.
return value: string id of allocated string
-------------------------------------------------------------------------
id=0004 name="compare" ret_type=int
parameters: arg1: string id
arg2: string id
description: compares strings for equality, case independent
return value: returns 1 when strings are equal, 0 when not
-------------------------------------------------------------------------
id=0005 name="random" ret_type=int
parameters: arg1: highest random value
description: generates a random number in the range of [1..arg1]
return value: the generated random number
-------------------------------------------------------------------------
id=0006 name="plural" ret_type=string
parameters: unknown
description: (not used in uw1)
return value: unknown
-------------------------------------------------------------------------
id=0007 name="contains" ret_type=int
parameters: arg1: pointer to first string id
arg2: pointer to second string id
description: checks if the first string contains the second string,
case-independent.
return value: returns 1 when the string was found, 0 when not
-------------------------------------------------------------------------
id=0008 name="append" ret_type=string
parameters: unknown
description: (not used in uw1)
return value: unknown
-------------------------------------------------------------------------
id=0009 name="copy" ret_type=string
parameters: unknown
description: (not used in uw1)
return value: unknown
-------------------------------------------------------------------------
id=000a name="find" ret_type=int
parameters: unknown
description: (not used in uw1)
return value: unknown
-------------------------------------------------------------------------
id=000b name="length" ret_type=int
parameters: arg1: string id
description: calculates length of string
return value: length of string
-------------------------------------------------------------------------
id=000c name="val" ret_type=int
parameters: unknown
description: (not used in uw1)
return value: unknown
-------------------------------------------------------------------------
id=000d name="say" ret_type=void
parameters: unknown
description: (not used in uw1)
return value: unknown
-------------------------------------------------------------------------
id=000e name="respond" ret_type=void
parameters: unknown
description: (not used in uw1)
return value: unknown
-------------------------------------------------------------------------
id=000f name="get_quest" ret_type=int
parameters: arg1: quest flag number
description: returns a quest flag value
return value: quest flag value
-------------------------------------------------------------------------
id=0010 name="set_quest" ret_type=void
parameters: arg1: new flag value
arg2: quest flag number
description: sets a quest flag value
return value: none
-------------------------------------------------------------------------
id=0011 name="sex" ret_type=string
parameters: arg1: pointer to first string id
arg2: pointer to second string id
description: decides on the gender of the avatar which string id to
return. for a male avatar, the second handle is taken,
otherwise the first handle is taken
return value: selected string id
-------------------------------------------------------------------------
id=0012 name="show_inv" ret_type=int
parameters: arg1: list with inventory item positions
arg2: list with object id's shown in player's barter area
description: the function copies the item positions and object id's of
all visible items in the barter area to the array in arg1
and arg2 (which needs at most 4 array values each)
return value: returns number of items stored in the arrays
-------------------------------------------------------------------------
id=0013 name="give_to_npc" ret_type=int
parameters: arg1: list of item inventory positions to give to npc
arg2: number of items in list in arg1
description: transfers a number of items from the player inventory to
the npc's inventory
return value: returns 0 if there were no items to give, and 1 if there
were some items
-------------------------------------------------------------------------
id=0014 name="give_ptr_npc" ret_type=int
parameters: arg1: quantity (?), or -1 for ignore
arg2: inventory object list pos
description: copies item from player inventory to npc inventory
return value: none
-------------------------------------------------------------------------
id=0015 name="take_from_npc" ret_type=int
parameters: arg1: item id (can also be an item category value, > 1000)
description: transfers an item from npc inventory to player inventory,
based on an item id. when the value is > 1000, all items of
a category are copied. category item start = (arg1-1000)*16
return value: 1: ok, 2: player has no space left
-------------------------------------------------------------------------
id=0016 name="take_id_from_npc" ret_type=int
parameters: arg1: inventory object list pos (from take_from_npc_inv)
description: transfers item to player, per id (?)
return value: 1: ok, 2: player has no space left
-------------------------------------------------------------------------
id=0017 name="identify_inv" ret_type=int
parameters: arg1:
arg2:
arg3:
arg4: inventory item position
description: unknown TODO
return value: unknown
-------------------------------------------------------------------------
id=0018 name="do_offer" ret_type=int
parameters: arg1 ... arg5: unknown
[arg6, arg7]: unknown
description: checks if the deal is acceptable for the npc, based on the
selected items in both bartering areas. the values in arg1
to arg5 are probably values of the items that are
acceptable for the npc.
the function is sometimes called with 7 args, but arg6 and
arg7 are always set to -1.
return value: 1 if the deal is acceptable, 0 if not
-------------------------------------------------------------------------
id=0019 name="do_demand" ret_type=int
parameters: arg1: string id with text to print if NPC is not willing
to give the item
arg2: string id with text if NPC gives the player the item
description: decides if the player can "persuade" the NPC to give away
the items in barter area, e.g. using karma.
return value: returns 1 when player persuaded the NPC, 0 else
-------------------------------------------------------------------------
id=001a name="do_inv_create" ret_type=int
parameters: arg1: item id
description: creates item in npc inventory
return value: inventory object list position
-------------------------------------------------------------------------
id=001b name="do_inv_delete" ret_type=int
parameters: arg1: item id
description: deletes item from npc inventory
return value: none
-------------------------------------------------------------------------
id=001c name="check_inv_quality" ret_type=int
parameters: arg1: inventory item position
description: returns "quality" field of npc? inventory item
return value: "quality" field
-------------------------------------------------------------------------
id=001d name="set_inv_quality" ret_type=int
parameters: arg1: quality value
arg2: inventory object list position
description: sets quality for an item in inventory
return value: none
-------------------------------------------------------------------------
id=001e name="count_inv" ret_type=int
parameters: unknown
description: counts number of items in inventory
return value: item number
-------------------------------------------------------------------------
id=001f name="setup_to_barter" ret_type=void
parameters: none
description: starts bartering; shows npc items in npc bartering area
-------------------------------------------------------------------------
id=0020 name="end_barter" ret_type=void
parameters: unknown
description: (not used in uw1), ends bartering, probably removing npc
bartering items
-------------------------------------------------------------------------
id=0021 name="do_judgement" ret_type=void
parameters: none
description: judges current trade (using the "appraise" skill) and
prints result
-------------------------------------------------------------------------
id=0022 name="do_decline" ret_type=void
parameters: none
description: declines trade offer (?)
-------------------------------------------------------------------------
id=0023 name="pause" ret_type=void
parameters: unknown
description: (not used in uw1)
-------------------------------------------------------------------------
id=0024 name="set_likes_dislikes" ret_type=void
parameters: arg1: pointer to list of things the npc likes to trade
arg2: pointer to list of things the npc dislikes to trade
description: sets list of items that a npc likes or dislikes to trade;
the list is terminated with a -1 (0xffff) entry
-------------------------------------------------------------------------
id=0025 name="gronk_door" ret_type=int
parameters: arg1: x tile coordinate with door to open
arg2: y tile coordinate
arg3: close/open flag (0 means open)
description: opens/closes door or portcullis
return value: unknown
-------------------------------------------------------------------------
id=0026 name="set_race_attitude" ret_type=void
parameters: unknown
description: sets attitude for a whole race (?)
-------------------------------------------------------------------------
id=0027 name="place_object" ret_type=void
parameters: arg1: x tile pos
arg2: y tile pos
arg3: inventory item slot number (from do_inv_create)
description: places a generated object in underworld
used in Judy's conversation, #23
-------------------------------------------------------------------------
id=0028 name="take_from_npc_inv" ret_type=void
parameters: arg1: unknown, always 1 in uw1
description: moves object from npc to player inventory, by npc inventory
index (only used in conv. #16, Ishtass)
return value: inventory object list position (used in take_id_from_npc)
-------------------------------------------------------------------------
id=0029 name="add_to_npc_inv" ret_type=void
parameters: unknown
description: (not used in uw1)
-------------------------------------------------------------------------
id=002a name="remove_talker" ret_type=void
parameters: none
description: removes npc the player is talking to (?)
-------------------------------------------------------------------------
id=002b name="set_attitude" ret_type=void
parameters: unknown
description: unknown
-------------------------------------------------------------------------
id=002c name="x_skills" ret_type=int
parameters: unknown
description: unknown
return value: unknown
-------------------------------------------------------------------------
id=002d name="x_traps" ret_type=int
parameters: unknown
description: unknown
return value: unknown
-------------------------------------------------------------------------
id=002e name="x_obj_pos" ret_type=void
parameters: unknown
description: (not used in uw1)
-------------------------------------------------------------------------
id=002f name="x_obj_stuff" ret_type=void
parameters: arg1: not used in uw1
arg2: not used in uw1
arg3: 0, (upper bit of quality field?)
arg4: quantity/special field, 115
arg5: not used in uw1
arg6: not used in uw1
arg7: quality?
arg8: identified flag?
arg9: position in inventory object list
description: sets object properties for object in inventory object list.
if a property shouldn't be set, -1 is passed for the
property value.
-------------------------------------------------------------------------
id=0030 name="find_inv" ret_type=int
parameters: arg1: 0: npc inventory; 1: player inventory
arg2: item id
description: searches item in npc or player inventory
return value: position in master object list, or 0 if not found
-------------------------------------------------------------------------
id=0031 name="find_barter" ret_type=int
parameters: arg1: item id to find
description: searches for item in barter area
return value: returns pos in inventory object list, or 0 if not found
-------------------------------------------------------------------------
id=0032 name="find_barter_total" ret_type=int
parameters: s[0]: ???
s[1]: pointer to number of found items
s[2]: pointer to
s[3]: pointer to
s[4]: pointer to item ID to find
description: searches for item in barter area
return value: 1 when found (?)
-------------------------------------------------------------------------
7.7 Imported variables
All imported variables have type int (except for "npc_name" and
"play_name", which are strings). Here's a list of all imported game
variables:
variable name description
play_hunger
play_health
play_arms
play_power
play_hp
play_mana
play_level
new_player_exp (not used in uw1)
play_name player name
play_poison (not used in uw1)
play_drawn is 1 when player has drawn his weapon (?)
play_sex (not used in uw1)
npc_xhome x coord of home tile
npc_yhome y coord of home tile
npc_whoami npc conversation slot number
npc_hunger
npc_health
npc_hp
npc_arms (not used in uw1)
npc_power
npc_goal goal that NPC has; 5:kill player 6:? 9:?
npc_attitude attitude; 0:hostile, 1:upset, 2:mellow, 3:friendly
npc_gtarg goal target; 1:player
npc_talkedto is 1 when player already talked to npc
npc_level
npc_name (not used in uw1)
dungeon_level (not used in uw1)
riddlecounter (not used in uw1)
game_time
game_days
game_mins
7.8 Quest flags
There are quest flags that are kept during gameplay to implement
interaction between NPC's. Flags can be get/set using get_quest() and
set_quest() or during gameplay, triggered by actions. Here's a list of
flags and their meanings (when no values for the flag are specified, 1
means yes or true, and 0 means no or false):
flag description
0 Dr. Owl's assistant Murgo freed
1 talked to Hagbard
2 met Dr. Owl?
3 permission to speak to king Ketchaval
4 Goldthirst's quest to kill the gazer (1: gazer killed)
5 Garamon, find talismans and throw into lava
6 friend of Lizardman folk
7 ?? (conv #24, Murgo)
8 book from Bronus for Morlock
9 "find Gurstang" quest
10 where to find Zak, for Delanrey
11 Rodrick killed
32 status of "Knight of the Crux" quest
0: no knight
1: seek out Dorna Ironfist
2: started quest to search the "writ of Lorne"
3: found writ
4: door to armoury opened
flags 0..31 are stored in a 32-bit integer bit field, flags 32..35 are
stored as Int8 values. See chapter 9.2. where the values are stored in the
savegame.
+--------------------------------------------------------------------------+
8 3d models
These are stored within the executable (bad! bad!). From Doug Church, on
the 3d models:
We'd take a 3ds model, convert it to an internal ASCII format, and then
run a "model builder" which would generate an inlined BSP tree for it.
This would be expressed in a byte code, or, really, as a set of simple
ASM like codes. Things like "Vtx 56.7, 435, 35.3" or "Color 153" or
"Norm 0.6, 0.8, 0.0, front04" or whatever. Then, we used the Macro
Assembler (well, Optasm, really) to generate a bunch of "db" statements
out of all this, and give it a label, and put it in the data segment. 3D
models were then drawn by calling a little "model interpreter" in the
game, which was given the address of this data block, which it then
interpreted. i.e. when it got to the byte for "Norm", it would fetch the
normal, dot it verse the eye vector, and jump or not based on the sign.
There is a table of 2-byte model offsets, with room for 64 models.
Positions of the table differ, as there are several builds of the uw.exe,
ultimau1.exe or uw2.exe.
game table start first bytes table base offset
uw1 0x0004e910 b6 4a 06 40 0x0004e99e
uw1 0x0004ccd0 b6 4a 06 40 0x0004cd5e same models, different place
uw1 0x0004e370 b6 4a 06 40 0x0004e3fe ditto (reported Gerd Bitzer)
demo 0x0004ec70 b6 4a 06 40 0x0004ecfe uw_demo
uw2 0x00054cf0 d4 64 aa 59 0x00054d8a
uw2 0x000550e0 d4 64 aa 59 0x0005517a another UW2 build
Models are:
index item_id description
00 -
01 014x door frame
02 0164 bridge
03 0150 bench
04 015f Lotus Turbo Esprit (no, really!)
05 0156 small boulder
06 0155? medium boulder
07 0154? large boulder
08 0151 arrow
09 0159 beam
0A 0160 pillar
0B 0157 shrine
0C
0D 0163 painting [uw2]
0E
0F
10 0161 texture map (8-way lever)
11 0162 texture map (8-way switch)
12 0166 texture map (writing)
13 0165 gravestone
14 016e? texture map
15 -
16 016f? ?texture map
17 015a moongate
18 0158 table
19 015d chest
1A 015e nightstand
1B 015b barrel
1C 015c chair
1D 0167 bed [uw2]
1E 0168 blackrock gem [uw2]
1F 0169 shelf [uw2]
Node list
---------
Actual model data is stored as a list with various "nodes" that do
different things. There also is a vertex array that is filled at start of
the model, and then faces are defined, using points from the vertex list.
The list is parsed every frame and tests are done if some faces have to be
rendered or not (depending if the player sees the polygon or not).
There are some data types that are used in the model node data. These are:
Int16 a simple 16 bit unsigned integer
Fixed fixed point number with 8 bits before the fraction point (8.8)
VertNo vertex number; lowest 3 bits are ignored
TexCo texture coordinate; contains a 16 bit fractional texture coord.
All data types use 16 bit values.
Every model has a header that looks like this:
0000 Int32 unknown
0004 Fixed extents X value
0006 Fixed extents Y value
0008 Fixed extents Z value
000a node entries follow
In general, node entries look as following:
0000 Int16 node id
0002 n custom node data (may be omitted)
8.1 List of all nodes
Here's an overview of all node types used:
node id description
0000 end node
0006 define sort node, arbitrary heading
000C define sort node, ZY plane
000E define sort node, XY plane
0010 define sort node, XZ plane
0014 ??? colour definition
002E ???
0040 ??? seems to do nothing but introduce a face definition
0044 ??? this one too
0058 define face plane, arbitrary heading
005E define face plane Z/Y
0060 define face plane X/Y
0062 define face plane X/Z
0064 define face plane X
0066 define face plane Z
0068 define face plane Y
0078 define model center
007A define initial vertex
007E define face vertices
0082 define initial vertices
0086 define vertex offset X
0088 define vertex offset Z
008A define vertex offset Y
008C define vertex variable height
0090 define vertex offset X,Z
0092 define vertex offset X,Y
0094 define vertex offset Y,Z
00A0 ??? shorthand face definition
00A8 define texture-mapped face
00B4 define face vertices with u,v information
00BC define face shade (6 bytes)
00BE ??? seems to define 2 shades
00CE ??? yet another texture-mapped face
00D2 ??? shorthand face definition
00D4 define dark vertex face (?)
00D6 define gouraud shading
Here's a detailed description of all nodes, sorted after functionality:
Misc. nodes
-----------
0000 end node
just ends current list; also ends sort node sublists
0078 define model center
0000 Int16 node id
0002 VertNo vertex list index to use as origin (?)
0004 Fixed origin X coordinate
0006 Fixed origin Y coordinate
0008 Fixed origin Z coordinate
000a Int16 ???
because of the limited resolution available for specifying object
positions, the origin for model coordinates will typically need to
be offset from the center of the collision volume. This parameter
gives that center, in model coords.
Vertex nodes
------------
Vertex node entries just set up the vertex list that is later used to take
points and draw faces (such as triangles, polygons, etc.)
007A define initial vertex
0002 Fixed vertex X coordinate
0004 Fixed vertex Y coordinate
0006 Fixed vertex Z coordinate
0008 VertNo vertex list index to store new vertex
0082 define initial vertices
0002 Int16 number of vertices (=nvert)
0004 Int16 first vertex no.?
0006 Fixed vertex X coordinate for first vertex
0008 Fixed vertex Y coordinate
000a Fixed vertex Z coordinate
000c Fixed vertex X coordinate for second vertex
000e and so on, for nvert vertices
0086 define vertex offset X
0002 VertNo reference vertex to modify
0004 Fixed offset to add to X coordinate
0006 VertNo new list index to store vertex
0088 define vertex offset Z
same as 0086, but for the Z coordinate
008A define vertex offset Y
same as 0086, but for the Y coordinate
0090 define vertex offset X,Z
0002 Fixed offset to add to X coordinate
0004 Fixed offset to add to X coordinate
0006 VertNo reference vertex to modify
0008 VertNo new list index to store vertex
0092 define vertex offset X,Y
same as 0090, but for the X and Y coordinate
0094 define vertex offset Y,Z
same as 0090, but for the X and Y coordinate
008C define vertex variable height
this is used for pillars and doorframes where the model must reach
up to the ceiling, wherever it be.
0002 VertNo reference vertex to modify
0004 Int16 ???
0006 VertNo new list index to store vertex
Face plane checks
-----------------
Face plane checks exist to check if a following face has to be drawn or
not. Usually a face normal vector is defined to verify. A length value
is given to know how many bytes to omit in the node list.
0058 define face plane, arbitrary heading (backface cull)
0002 Int16 number of bytes to skip when not visible
0004 Fixed normal vector X coordinate
0006 Fixed distance "model origin -> face" X value
0008 Fixed normal vector Y coordinate
000a Fixed distance "model origin -> face" Y value
000c Fixed normal vector Z coordinate
000e Fixed distance "model origin -> face" Z value
after this node usually face info nodes follow
0064 define face plane X
Y and Z coordinates of normal vector is 0 (YZ plane normal)
0002 Int16 number of bytes to skip when not visible
0004 Fixed normal vector X coordinate
0006 Fixed distance "model origin -> face" X value
after this node usually face info nodes follow
0066 define face plane Z
same as 0064, but with Z coordinate (XY plane normal)
0068 define face plane Y
same as 0064, but with Y coordinate (XZ plane normal)
005E define face plane Z/Y
X coordinate of normal vector is 0
0002 Int16 number of bytes to skip
0004 Fixed normal vector Z coordinate
0006 Fixed distance Z value
0008 Fixed normal vector Y coordinate
000a Fixed distance Y value
0060 define face plane X/Y
same as 005E, but with X and Y coordinates/values (in this order)
0062 define face plane X/Z
same as 005E, but with X and Z coordinates/values
Face info nodes
---------------
They just define a face (polygon) from some points of the vertex list.
007E define face vertices
0002 Int16 number of vertices (=nvert)
0004 VertNo vertex list index for first point
0006 VertNo vertex list index for second point
0008 and so on
00A8 define texture-mapped face
vertex u and v coordinates are stored as 0.16 fractional values.
just divide by 65535.0 to get coordinates in the range [0; 1]
0002 Int16 texture number (?)
0004 Int16 number of vertices (=nvert)
0006 VertNo vertex list index for first point
0008 TexCo first vertex u coordinate
000a TexCo first vertex v coordinate
000c VertNo vertex list index for second point
000e TexCo second vertex u coordinate
0010 and so on
00B4 define face vertices with u,v information
same structure as 00A8, but without the "texture number" field.
Sort nodes
----------
Sort nodes are there to sort faces of a whole node list and the renderer
can determine if the list has to be rendered at all.
0006 define sort node, arbitrary heading
0002 Fixed normal vector X coordinate
0004 Fixed distance X coordinate
0006 Fixed normal vector Y coordinate
0008 Fixed distance Y coordinate
000a Fixed normal vector Z coordinate
000c Fixed distance Z coordinate
000e Int16 left node offset (starting at 0010)
0010 Int16 right node offset (starting at 0012)
000C define sort node, ZY plane
the X coordinate values are assumed to be 0
0002 Fixed normal vector Z coordinate
0004 Fixed distance Z coordinate
0006 Fixed normal vector Y coordinate
0008 Fixed distance Y coordinate
000a Int16 left node offset (starting at 000c)
000c Int16 right node offset (starting at 000e)
000E define sort node, XY plane
same as 000C, but with X and Y coordinates (in this order)
0010 define sort node, XZ plane
same as 000C, but with X and Z coordinates (in this order)
Unknown nodes
-------------
0014 ??? colour definition
0002 VertNo vertex number
0004 Int8 c1 ???
0005 Int8 c2 ???
0006 VertNo vertex number
002E ???
0002 Int16 ???
0040 ??? seems to do nothing but introduce a face definition
0044 ??? this one too
no further node data
00BC define face shade
0002 Int16 colour (address of colour variable)
0004 Int16 shade (dark value)
00BE ??? seems to define 2 shades ?? refers to 2 model variables
0002 Int16 var 1
0004 Int16 var 2
00CE define texture-mapped face
same data as 00B4
00A0 shorthand texture-mapped face definition
seems to be a quick way to define a face with 4 vertices and
automatic texture coordinates; vertex list indices
0002 VertNo ???
0004 Int8 vertex list index 0
0005 Int8 vertex list index 1
0006 Int8 vertex list index 2
0007 Int8 vertex list index 3
00D2 define texture mapped face (shorthand)
same as 00A0
00D4 define vertex shading values
0002 Int16 number of vertices (=nvert)
0004 Int16 base colour for Gouraud shading (colour variable address)
0006 VertNo vertex list index for first point
0008 Int8 dark value for first point ???
0009 VertNo vertex list index for second point
000b Int8 dark value for second point ???
and so on; if nvert is odd, read another empty
byte to have 16 bit word align
00D6 introduce Gouraud shaded face
no other info; may switch on gouraud shading
+--------------------------------------------------------------------------+
9 Miscellaneous stuff
This section describes all miscellaneous data structures and things that
didn't fit elsewhere.
9.1 Ultima Underworld 2 compression scheme [uw2]
The second game uses a new compression scheme to crunch data in .ark files
together. The file header has a slightly different format than in uw1's
files:
0000 Int16 number of blocks in file (=nblocks)
0004 Int32 unknown (always 0)
Now follow 4 tables, each one is "nblocks" long and consists of Int32's.
Table 1: offset table
absolute file offset to block
an offset of 0 means the entry is not (yet) used
Table 2: flags for the datachunk
Bit 0: block should be compressed (always set in uw2)
Bit 1: actually is compressed
Bit 2: the chunk has extra space allocated in case the compression
isn't as effective next time. The "available space" value is
valid for such chunks.
Table 3: data size
this gives the actual space occupied by the chunks on disk, either
compressed or uncompressed data size, according to the flags.
Table 4: available space
This is valid for chunks with extra space (bit 2 in table 2 set). It
gives the total space available for the chunk in the archive file.
A compressed block always starts with an Int32 value that is to be ignored.
If a block is actually compressed, it can be divided into subblocks.
Each compressed subblock starts with an Int8 number; the bits from LSB to
MSB describe if the following byte is just transferred to the target buffer
(bit set) or if we have a copy record (bit cleared). After 8 bytes or copy
record, the next subblock begins with an Int8 again.
The copy record starts with two Int8's:
0000 Int8 0..7: position, bits 0..7
0001 Int8 0..3: copy count
4..7: position, bits 8..11
The copy count is 4 bits long and an offset of 3 is added to it. The
position has 12 bits (accessing the last 4k bytes) and an offset of 18 is
added. The sign bit is bit 11 and should be treated appropriate. As the
position field refers to a position in the current 4k segment, pointers
have to be adjusted, too. Then "copy count" bytes are copied from the
relative "position" to the current one.
9.2 Savegame format
Savegames are stored in folders called "SaveN", where N is the number
of the save game. Ultima Underworld only allows 4 save game slots. The
folder "Save0" is used to store the files "lev.ark" and "bglobals.dat"
during gameplay.
The file "player.dat" contains the main character data. The first 220 bytes
are encrypted with a rather simple xor algorithm. The first byte in the
file is the starting xor value. The next byte is xor'ed with xorvalue + 3,
and for each next byte the xorvalue is incremented by 3. A simple
implementation in C is here (it is assumed that xorvalue already contains
the first byte):
// descramble data
unsigned char incrnum = 3;
for(int i=0; i<220; i++)
{
if (i==80) incrnum = 3;
data[i] ^= (xorvalue+incrnum);
incrnum += 3;
}
After decryption, the file contains the following:
0000 14*char character name
...
001E Int8 Strength
001F Int8 Dexterity
0020 Int8 Intelligence
0021 Int8 Attack
0022 Int8 Defense
0023 Int8 Unarmed
0024 Int8 Sword
0025 Int8 Axe
0026 Int8 Mace
0027 Int8 Missile
0028 Int8 Mana
0029 Int8 Lore
002A Int8 Casting
002B Int8 Traps
002C Int8 Search
002D Int8 Track
002E Int8 Sneak
002F Int8 Repair
0030 Int8 Charm
0031 Int8 Picklock
0032 Int8 Acrobat
0033 Int8 Appraise
0034 Int8 Swimming
...
0036 Int8 max. vitality
0037 Int8 current mana, (play_mana)
0038 Int8 max. mana
0039 Int8 hunger, play_hunger
...
003D Int8 character level (play_level)
...
0044 3*8bits rune flags (*)
...
004C Int16 weight in 0.1 stones
004E Int32 experience in 0.1 points
...
0054 Int16 x-position in level
0056 Int16 y-position
0058 Int16 z-position
005A Int16 heading
005C Int16 dungeon level
...
005F Int8 bits 2..5: play_poison
...
00CE Int32 game time
...
00DC Int8 current vitality
(*) The rune field is a bitfield, where an 1 indicates an available
rune. Bits are seen from most to least significant. The 'A' rune is
stored in bit 7 of the first field.
how items are stored
9.3 Unknown files
This section lists files with unknown meaning.
skills.dat
----------
The file contains infos about the skills in character creation for every
class. First 0x20 bytes are unknown. There are 5 entries for each of the
8 character classes. The first Int8 byte contains the length of one entry.
Then follows as many Int8 bytes as the length tells. The bytes describe
the skill the user can select. If the length is 1, the only skill given is
auto-set, showing no selection.
shades.dat
----------
Contains 12 entries, 8 byte long each. When renamed to shadez.dat (or
deleted) uw runs in bright colors all the time without using candles and
such.
sounds.dat
----------
The file "sounds.dat" in the "sound" folder contains sound effect data.
The file starts with an Int8 value that is the number of data entries in
the file. Then 5 Int8 bytes for each entry in the file follow.
It is supposed that the data may be midi commands to play back sfx.
xfer.dat
----------
The Underworld games use the (colour-index) translation tables in
data/xfer.dat for the transparencies (each transparency index has a
table). [The numbers here are an attempt to back-convert from the
translation table to RGBA values using a hairy little program of my own
devising. They look "about right", so I'm inclined to leave them.]
(description from TSSHP CVS)
9.4 Error codes
Ultima Underworld has some error codes printed out when the game
unexpectedly quits. The format is Nxxx where N is an uppercase letter from
B to F and xxx is a decimal number. Error codes:
B: out of low memory (1000h)
C: out of EMS mem (2000h)
D: could not read (3000h)
E: could not write (4000h)
F: resource/internal problem
Here's a list of all possible error codes and their occurance
B001 couldn't alloc memory for "data/strings.pak"
D002 couldn't read "data/strings.pak"
D007 couldn't read "data/babglobs.dat"
E001 couldn't write "save0/bglobals.dat"
+--------------------------------------------------------------------------+
end of file