File uw-formats.txt

From Ultima Codex
Jump to navigation Jump to search

Technical Details Warning

While the encyclopedic nature of the codex means that many articles will have information that could spoil some minor aspects of the game for newer players, this article is known to contain technical information about the game, game engine, data files, logic, etc. This information is not generally useful to the typical game players.

Continue reading at your own discretion.

This article is about the file uw-formats.txt, a technical document related to Ultima Underworld: The Stygian Abyss and Ultima Underworld II: Labyrinth of Worlds. Notice the title is "File uw-formats.txt" because the "File:" namespace only allows certain image format files.

Description[edit]

The file known as uw-formats.txt is widely regarded as the authoritative document on the internal formats and workings of the game engine for Ultima Underworld: The Stygian Abyss and Ultima Underworld II: Labyrinth of Worlds.

Most information known about Ultima Underworld's internal file formats comes from a document named uw-format.txt, itself evolved from a document named uw-specs.txt, which come out of The System Shock Hack Project (TSSHP), a project to reverse engineer System Shock, a game that used an engine similar (but newer and more advanced) to that of the Underworld games. Full story and credits can be found inside the document.

TODO More/better description; add history, credits, etc

Contents[edit]

The following in a snapshot of the file uw-formats.txt taken from the Underworld Adventures project on github by user vividos, last updated August 9th, 2023


 +--------------------------------------------------------------------------+
               Ultima Underworld 1 and 2 Formats Specification

                            Underworld Adventures
               https://vividos.github.io/UnderworldAdventures/
 +--------------------------------------------------------------------------+

Document version: 1.86, as of 2023-08-09


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.1  Critter animations [uw1]
   3.6.2  Critter animations [uw2]
   3.7.1  Cutscene animation [uw1]
   3.7.2  Cutscene animation [uw2]
   3.8    Weapon animations
   3.9    XFER.DAT Transparencies
   3.10   Lights/Shading
   3.10.1 List of all lights
   3.10.2 UW2 level light

   4      Level maps and object lists
   4.1    File format
   4.2    Level tilemap
   4.3    Object list
   4.3.1  Enchantments
   4.3.2  Item Owner
   4.3.3  Mobile object extra info
   4.4    Free lists
   4.5    Texture mappings
   4.6    Animation infos
   4.7    Automap infos
   4.8    Map notes
   4.9    Terrain texture properties

   5      String resources
   5.1    String block contents [uw1]
   5.2    String block contents [uw2]

   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.6.1  Ultima Underworld 1 intrinsic functions
   7.6.2  Ultima Underworld 2 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.1  Savegame format [uw1]
   9.2.2  Savegame Format [uw2]
   9.3    Unknown files
   9.4    Error codes

  10      Sound
  10.1    sounds.dat
  10.1.1  sounds.dat [uw2]
  10.1.2  sounds.dat [uw2]
  10.2    VOC files [uw2]
  10.3    XMI files

 +--------------------------------------------------------------------------+


1  Introduction

   This file tries to collect all file formats and data structures used in the
   game Ultima Underworld 1. It originated 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 http://bearcity.pwp.blueyonder.co.uk/)
   - Ulf Wohlers (Objects and 3D models)
   - Fabian Pache (about map details and 3D models, uw2 conversations uw2
     player save http://uw2rev.sourceforge.net)
   - Hank Morgan, from his UnderworldExporter project
     https://github.com/hankmorgan/UnderworldExporter
   - Marc A. Pelletier (coren), from the Labyrinth of Worlds project
     (http://low.sourceforge.net/)
   - Telemachos (http://www.peroxide.dk/), infos about weapon animation and
     miscellaneous bits about uw.
   - Murgo (https://web.archive.org/web/20090119003115/http://uwediting.freehostia.com:80/files/uwcspecs.txt),
     various infos

   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) 2014-2018 Hank Morgan
   Copyright (c) 2008,2009 Julien Langer
   Copyright (c) 2002,2008,2009 Fabian Pache
   Copyright (c) 2000,2001,2002,2003 Jim Cameron
   Copyright (c) 2002,2003,2004 Michael Fink
   Copyright (c) 2004 Kasper Fauerby
   Copyright (c) 2022 Mike Ratzlaff
   Copyright (c) 2008 Murgo


 +--------------------------------------------------------------------------+


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     character generation data?
   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 (uw1 palette #0, uw2 palette #?)
   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     light infos
   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 timbre library
   uw.ad          adlib timbre library
   *.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   cutscene 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 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        game bitmaps archive
   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        NPC schedules?
   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:
   cs000.n??      intro part 1: letter to the avatar
   cs001.n??      intro part 2: blackrock shell
   cs002.n??      outro
   cs004.n??      audio only cutscene ?
   cs005.n??      audio only cutscene ?
   cs006.n??      audio only cutscene ?
   cs007.n??      audio only cutscene ?
   cs011.n??      title screen, "ultima underworld 2, labyrinth of worlds"
   cs012.n??      credits
   cs030.n??      blue vision
   cs031.n??      blue vision
   cs032.n??      blue vision
   cs033.n??      blue vision
   cs034.n??      guardian on red
   cs035.n??      guardian on camo
   cs036.n??      guardian on blue
   cs037.n??      guardian on green
   cs040.n??      guardian on black
   cs403.n??      player death, bubbling skulls

   lback00?.byt   large background files for camera paning cutscenes (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         OPL timbre library
   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, Ethereal Void
   uwa11.xmi      Prison Tower
   uwa12.xmi      Death
   uwa13.xmi      Killorn Keep, Pits of Carnage
   uwa14.xmi      Ice Caverns
   uwa15.xmi      Scintillus Academy
   uwa16.xmi      Praecor Loth, Castle British
   uwa17.xmi      The Labyrinth of Worlds Theme (again)
   uwa30.xmi      Introduction
   uwa31.xmi      Guardian's Trap

   Some variations of the names come from some mp3 files available here:
   http://stygian.ttlg.mobi/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 in UW1 and
   11 in UW2. 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
   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

    [UW1]
    indices 16 through 23: lava fire effect
    These 8 indices are rotated for fire/lava effect, but are not all rotated
    together. The first 5 are rotated together, and the next 3 are rotated
    together. These rotations move "up", as in the colors "travel" up the
    palette from lower indices to higher.

    indices 48 through 63: water effect
    These 16 indices are rotated for water effects, however the group of 16
    colors are not all rotated together. They are broken up into 4 groups of 4,
    and those groups are each rotated individually. These rotations move colors
    "down" from higher indices to lower ones.

    [UW2]
    Uses the same rules stated above, but palette indices are different:
    indices 3 through 10: lava fire effect
    indices 224 through 239: water effect


   - Palette #2: game start screen

    indices 64 through 127: "Ultima Underworld" logo warping effect [uw1]
    indices 43 through 48, 49 through 51, 57 through 66: "Labyrinth of Worlds"
    logo blinking effect [uw2]


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

   Entries in .gr files can contain null records. There will be a place-holder
   entry in the offsets table, but it does not point to a valid graphics record
   for that entry. A record should be considered null if:
      The offset points past the end of file or
      The offset is the same as the next offset in the list
   If you are ripping graphics from .gr files and notice duplicate graphics,
   such as the blood splats and damage traps in objects.gr, then you have not
   properly accounted for null records.

   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.
                For bitmap type 08, this is the number of 4-bit nibbles, not
                bytes. Actual byte count is nibbles/2 rounded up.
                For bitmap types 04 & 0A, this is the actual byte count.

   The file "panels.gr" contains bitmaps that don't have a bitmap header, but
   immediately start with image data. There are 4 bitmaps of type 04 that have
   a hard-coded height and width.
   [UW1] The first three are 83 x 114 and the final one is 3 x 120
   [UW2] The first three are 79 x 112 and the final one is 3 x 112


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, type 06)

   Compressed bitmaps are stored with a run length encoding and a
   lookup table. The wordsize used for a certain bitmap is determined by the
   type.

      type 08: wordsize 04 bits
      type 06: wordsize 05 bits

   If the size of the compressed image is given in words, the number of bytes
   are calculated as

      bytecount = (wordcount * wordsize + 7) / 8

   The bytes of the compressed image are treated as a bit stream, starting
   at the most significant bit of byte 0.

   Data consists of repeat and run records.
       - Repeat records let the decoder repeat a single word a certain number
         of times.
       - Run record takes a certain number of next nibbles to be as
         uncompressed.

   The decompression engine keeps a counter of the number of repeat records
   that precede the next run record. This counter is initially 1. See
   special count interpretation for repeat records below.

   For every record, first there is a count to retrieve. A count is a single
   number made up from a variable number of words. The algorithm to determine
   a count is as follows:
   Get a word.

      c = w0

   If it is not 0, it is a count. Otherwise, get two more words,
   w1 and w2. The count is:

      c = (w1 << 4) | w2

   If the count is still zero, take another three words, and calculate the
   count:

      c = (((w3 << 4) | w4) << 4) | w5

   Note that even if the wordsize is 5, the words are only shifted by 4 bits,
   thus overlapping the words theoretically. In practice a count is at most 6
   words long, leading to the following possible patterns:

      1 word count : [w0]                          , with (w0) != 0
      2 word count :   0  [w1] [w2]                , with (w1 << 4 | w2) != 0
      3 word count :   0    0    0  [w3] [w4] [w5]

   A run record consists of a count and then follow 'count' words, that are
   the raw pixel data. A repeat record consists of a count and a single word,
   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 are cases where the decompression yields a greater number
   of words than would be necessary to display the image.

   The resulting words make up the atom map for a given image. This atom map
   is then translated, byte by byte, from their value range of 16 (4 bit) or
   32 (5 bit) to full 8 bit using the corresponding atom to fragment maps.
   These maps are also sometimes referred to a auxiliary palettes in this
   document.

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   graphic file format; always 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 floor 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.1  Critter animations [uw1]

   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.

   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, feral 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 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.6.2  Critter animations [uw2]

   The following files of the CRIT folder are involved in Underworld 2
   animations:

   pg.mp     contains a skiptable to find a frame in the crXX.YY files
   cr.an     animation sequences for all critter and all actions
   as.an     contains a mapping of critter IDs to crXX.YY files and palettes
   crXX.YY   the pixel information for critter XX, fragment YY

   XX and YY are using octal digits

   The "pg.mp" file has a map of all crXX.YY files and how many frames are
   found in each file.

   The "as.an" file is like the "assoc.anm" file from uw1, but without the
   animation name string table at the start; it is 128 bytes long. It maps
   from actual critter item IDs (0x0040-0x007f, 128) to 32 critter animations,
   and which auxiliary palette to use for each.

   The "cr.an" file specifies, for each of the 32 critter animation types, the
   frames needed to render for 8 different animations and 8 viewing
   directions.

3.6.2.1 Format and use of cr.an

   This file contains 32 chunks [C] of 512 bytes each.
   Each chunk contains the complete animation information for one character.

      x*512 : start character x animation definition [C]

   Each chunk has 8 subchunks of 64 bytes. A subchunk [SC] describes the
   animation frames to take for a certain action. The actions are

      [C]+0000 : [SC] standing
      [C]+0040 : [SC] walking
      [C]+0080 : [SC] in combat
      [C]+00c0 : [SC] attack
      [C]+0100 : [SC] attack
      [C]+0140 : [SC] attack
      [C]+0180 : [SC] attack
      [C]+01c0 : [SC] dying

   The use of the attack animations differs from critter to critter since not
   all critters have 4 different attacks. If a critter has magical and melee
   attacks, the melee attacks come first.

   Each action is split into 8 animation sequence [AS] definitions for the
   angle it is observed from. The blocks always contain valid values, even if
   there is no explicit frame for a certain animation e.g dying usually exists
   only from a frontal point of view, thus the animation for all angles is the
   same.

      [SC]+0000 : [AS] rear
      [SC]+0008 : [AS] rear    right
      [SC]+0010 : [AS]         right
      [SC]+0018 : [AS] front   right
      [SC]+0020 : [AS] front
      [SC]+0028 : [AS] front   left
      [SC]+0030 : [AS]         left
      [SC]+0038 : [AS] rear    left

   The layout of each animation sequences is as follows:
   The frame indices reference values in the offset table of crXX.*
   Use the values in pg.mp to find the right fragment, see 3.6.2.4

      [CB]+0 .. [CB]+6 : animation frame index
      [CB]+7           : number of valid entries starting at [CB]

   Invalid entries in the animation frame indices are marked with 0xFF


3.6.2.2 Format and use of as.an

   Multiple critters use the same pixel data, but with different palettes.
   This file contains two Int8 for each critter from 0 to 63. 0xFF
   marks unused indices (the last entry of as.an)

      2*n     Int8  critter file to use (XX of crXX.*)
      2*n+1   Int8  atom to fragment map index (=auxiliary palette index)


3.6.2.3 Format and use of crXX.YY

   Each unique critter is contained in its own file crXX. For various reasons
   each critter is split across multiple files. There are 32 critter pixel
   maps. XX in the filename is encoded in octal.

      0000..007F : atom to fragment mapping (4 auxiliary palettes)
      0080..027F : [int16] table of offsets into the file for frame data
      0280..eof  : frame data

   The actual color value to be used for displaying a single pixel is
   determined as follows:
      Decompressing the pixel map for a frame yields an atom map. The size of
         an atom for a critter is 5 bit.
      The atom is converted to a fragment using the atom to fragment map
         determined by the entity id and the index from as.an.
      The fragment is converted to a colorindex using the current light level
         and LIGHTS.DAT or MONO.DAT.
      Finally the colorindex is translated to a rgb color by applying PALS.DAT

   All fragments of one critter crXX.00, crYY.01, etc have the same
   header describing the pixel mapping from atom to fragment.

      0000..001F : atom to fragment mapping 0
      0020..003F : atom to fragment mapping 1
      0040..005F : atom to fragment mapping 2
      0060..007F : atom to fragment mapping 3

   Mapping 3 is never accessed from as.an. The trigger for this mapping is
   unknown.

   The offset table of the first file starts at index 0. The offset table of
   the next fragment continues with the index where the previous left off.
   (see also pg.mp).

      0080+frame*2 : [int16] offset into this for file [O]

   The frame data is defined in the same manner as in Underworld 1.

      [O] + 0000    [Int8]       width
      [O] + 0001    [Int8]       height
      [O] + 0002    [Int8]       hotspot x
      [O] + 0003    [Int8]       hotspot y
      [O] + 0004    [Int8]       compression type; (06: 5-bit word size)
      [O] + 0005    [Int16]      data length in number of words
      [O] + 0007                 start of rle-encoded image data (see 2.3)


3.6.2.4 Format and use of pg.mp

   The purpose of pg.mp is to find the file fragment YY of a specific critter
   XX, given a specific frame. The file contains 32 fragment id chunks. Each
   fragment id chunk has 8 bytes. Each byte corresponds to a fragment and
   contains the last animation frame contained in the fragment. Invalid
   combinations of XX and YY (== crXX.YY does not exist) are marked with 0xFF.

      8*xx+yy : [int8] last frame in fragment YY (file crXX.YY)


3.7.1  Cutscene animations [uw1]

   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
      ...
      0044   Int16   frame rate

   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);
               }
            }
         }
      }

   The .n00 files are special, as they are not following the .anm format, but
   contain animation control instructions.

   The file consists of variable length entries, starting with an Int16 frame
   number, an Int16 command and a variable number of command arguments,
   depending on the command. In uw1 there are 0x0f commands, and uw2 extends
   them to 0x1f commands.

   The following commands are used. The command names are not official and
   invented for this document.

     Int16  name      args  meaning
      0000  show-text  2    displays text arg[1] with color arg[0]
      0001  set-flag   0    sets some flag to 0
      0002  no-op      2    no-op, arguments are ignored
      0003  pause      1    pauses for arg[0] / 2 seconds

      0004  to-frame   2    plays up to frame arg[0]
      0005  ????       1    unknown, set frame for static cutscene?
      0006  end-cutsc  0    ends cutscene
      0007  rep-seg    1    repeat segment arg[0] times

      0008  open-file  2    opens a new file csXXX.nYY, with XXX from arg[0]
                            and YY from arg[1]; XXX and YY are formatted as
                            octal values
      0009  fade-out   1    fades out at rate arg[0] (higher is faster)
      000a  fade-in    1    fades in at rate arg[0] (higher is faster)
      000b  ????       1    unknown

      000c  ????       1    unknown
      000d  text-play  3    displays text arg[1] with color arg[0] and plays
                            audio arg[2]
      000e  ????       2    unknown
      000f  klang      0    plays 'klang' sound

   Commands 0000 and 000d output the text at arg[1]; when the value is
   0xffff, no text is output.

3.7.2  Cutscene animation [uw2]

   Ultima Underworld 2 extends the .n00 file commands with another set of
   commands:

     Int16  name      args  meaning
      0010  ????       *    unknown, not used in uw2
      0011  ????       *    unknown, not used in uw2
      0012  no-op2     4    does nothing, not used in uw2
      0013  ????       3    unknown

      0014  ????       3    unknown
      0015  ????       2    unknown
      0016  ????       3    unknown
      0017  ????       3    unknown

      0018  ????       1    unknown, usually at the start of cutscene
      0019  ????       1    unknown
      001a  no-op3     1    does nothing, not used in uw2
      001b  ????       1    unknown

   (*) Commands 0010 and 0011 have a variable number of arguments:

      0010: numArgs = arg[1] * 3 + 2
      0011: numArgs = arg[3] * 3 + 4

   The existing 0005 command is slightly changed:

     Int16  name      args  meaning
      0005  ????       0    unknown

   The existing 0008 "open-file" command is extended: When arg[0] == 0x03e4
   (=996) is passed, a random page is used, using the formula:
     rand() / 4 + 28

   The cutscenes can refer to background files lbackXXX.byt. The files are
   are simple index maps of 320 x 200 pixel. The pixel are ordered top to
   bottom and left to right. The palette is stored with the cutscene that
   references the background.

   The cutscene commands were figured out thanks to khedoros' code in his uw
   engine:
   https://github.com/khedoros/uw-engine/blob/master/csparse.cpp

3.8  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 palettes 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 palette used for the .gr files...


3.9   XFER.DAT Transparencies

   The following was observed in uw2 but should be valid for uw1 as well.
   The file XFER.DAT contains 5 color shifting palettes to provide for
   transparencies. Certain colors in critter color index tables do not
   overwrite the underlying pixel. Instead they trigger a lookup into
   XFER.DAT. Colorindices are

      0xf0   fade to red
      0xf4   fade to blue
      0xf8   fade to green
      ????   fade to white
      ????   fade to black
   <<TODO confirm / complete>>

   XFER.DAT layout

      0x0080      fade to red
      0x0100      fade to green
      0x0180      fade to blue
      0x0200      fade to white
      0x0280      fade to black

   XFER.DAT usage

      The pixel that would overwrite the existing pixel decides which fade
      block to choose from. The pixel that would be overwritten decides the
      index inside the the fade block. The value at that location replaces
      the original.

   XFER.DAT for modern systems

      When trying to recreate a the same effect on modern systems which are not
      palette based, one can either create a pixelshader which does the same
      translation XFER.DAT did for colorindices in RGB or derive the parameters
      for the more common transparency equation

         dst_color = overlay_color * alpha + (1-alpha) * src_color

      One way to do this is look for colorindeces that are mapped on
      themselves by XFER.DAT. These pixel, which are essentially unaffected by
      XFER.DAT have roughly the same color as the overlay and can subsequently
      be used to derive an average alpha value for the texture.


3.10 Lights/Shading

   Lights are stored in shades.dat which consist of 8 light entries with 12
   bytes each. (Up to 16 lights are supported).

   A light entry has the following format:

      Offset  Size   Description

      0000    Int8   Shading
      0001    Int8   Unknown (bit 7 turns off shading?)
      0002    Int8   Bit 0-3: Starting light level (from light.dat)
      0003    Int8   Unknown
      0004    Int16  Distance before start of shading (negative value)
      0006    Int16  Viewing Distance (Value 0-15* sets the distance in
                     nr of tiles)
      0008    Int16  Texturing Distance
      000A    Int16  Unknown

   * The maximum viewing distance is 15, but the game might crash if it's
     higher than 9-10 depending on how much stuff (walls/floors/objects
     etc.) there is in the level.

   (some info was found in "Increased brightness for Ultima Underworld 1 & 2")

3.10.1 List of all lights

   Nr Light        Enchanted item  Spell

   0  No light     Darkness
   1  Candle       Burning match
   2  Torch        Candlelight
   3  Taper [uw1]  Light           In Lor
   4  Lantern      Magic Lantern
   5               Night Vision    Quas Lor
   6  Light Sphere [uw2] Daylight  Vas In Lor
   7               Sunlight*

   * Sunlight is the same as daylight in both uw1/uw2.

3.10.2 UW2 level light

   The light used in uw2 levels (castle etc.) is stored in dl.dat which
   consists of 80 bytes (1 for each level).

   The value in the file represents light 0-9 which is activated by the
   "special light feature" flag.
   If the value is >=10 then the "special light feature" flag function is
   inverted.


 +--------------------------------------------------------------------------+


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/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/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 maps for uw2 are stored in these blocks:
   0..4:   Castle Britannia / Sewers (BR)
   8..15:  Prison tower (PT)
   16..17: Killorn Keep (KK)
   24..25: Ice Caverns (IC)
   32..33: Talorus (TA)
   40..47: Scintillus Academy (SA)
   48..51: Praecor Loth's Tomb (LT)
   56..58: Pits of Carnage (PC)
   64..67: Ethereal Void (EV)
   69:     Secret level

   Note that not all 8 map blocks are used on every world.

   This is an overview which face of the blackrock gem shows what world
   (excuse my ASCII art skills):

      +----------+
     /EV|  PT  |KK\
    +__  |    |  __+
    |  '-+----+-'  |
    | LT | BR | IC |
    |    |    |    |
    | __-+----+-__ |
    +'PC | SA | TA'+
     \  |      |  /
      +----------+

   The "level tilemap/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    00FE   active mobile object list
   7bfa    0006   unknown
   7c00    0002   nr. of active mobile objects
   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 object list that is the start of an object chain with
   objects in this tile.

   The first 0x4000 bytes of each "level tilemap/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)
                    Tiles with this bit set have a door, but not every tile
                    with a door has this bit set. Perhaps this bit tells if
                    an NPC can open the door?

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

   about the floor height (word 0000, bits 4-7):
      UW internally uses 7 bits for the map height (0..127). The full range of
      7 bits is stored in the object properties, but only 4 bits are stored in
      in the tilemap, so this value should be left shifted by 3 bits.

   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.3  Object list

   The 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 information, 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)
        OR
        9-15       texture number

        Note: some objects don't have flags and use the whole lower byte as a
        texture number (gravestone, picture, lever, switch, shelf, bridge, ..)

   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.3.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.3.2  Item Owner

   Some items have a "... belongs to <critter type>" 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.3.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.4  Free lists

   The free lists generally contain infos about the 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 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.5  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.6  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 object
   list, to the object that should get an animation overlay.


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

   The following was observed in Underworld 2.
   The automap contains one byte per tile, in the same order as the
   level tilemap. A valid value in the low nibble means the tile is displayed
   on the map. Valid values are the same as tile types:
            SOLID    0x0
            CLEAR    0x1
      Diagonal Tiles have a flat floor and are divided diagonally,
      the direction of the wall normals is given as suffix to 'DIAG_'
            DIAG_SE  0x2
            DIAG_SW  0x3
            DIAG_NE  0x4
            DIAG_NW  0x5
      Slope Tiles are treated like ordinary CLEAR tiles
            SLOPE_N  0x6
            SLOPE_S  0x7
            SLOPE_E  0x8
            SLOPE_W  0x9
   Other values are considered undiscovered

   The high nibble contains a modifier on how to display the tile:
            DEFAULT  0x0
            DOOR     0x1
            TELEPORT 0x3
            WATER    0x4
            BRIDGE   0x6
            LAVA     0xc

4.8  Map notes

   The following was observed in Underworld 2.
   The map labels are compressed with the same scheme as the tiles. Each
   label is stored in 54 (0x36)bytes, regardless of the actual length of the note.

   The number of labels has to be calculated by dividing the size of the
   uncompressed chunk by 54. Each label has the following format:

      0000 - 0031  zero-terminated string
      0032  int16  x-position
      0034  int16  y-position

   Like the tile map the y-position is inverted with 0 denoting the bottom
   of the screen and 200 (0xc8) the top of the screen.


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

   [uw1] The file is 0x0400 bytes long and has 256 entries, for 256 walls and
   256 floors, corresponding to the W16.tr/W64.tr and F16.tr/F32.tr files.
   Floor data starts at file offset 0x200, even though there are less than
   256 floor textures.

   [uw2] The file is 0x0200 bytes long and has 256 entries, corresponding to
   the textures in the T64.tr file.

   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)

   [uw2] introduces some more terrain types, mostly used for walls:

    0040    Waterfall (wall texture)
    0048    Swamp/poison?
    0050    Purple fluid, water?
    0058    Water
    0060    Swamp/poison?
    0080    Lavafall (wall texture)
    00C0    Ice wall
    00C8    Ice wall, with hole
    00D8    Ice wall, cracked
    00E8    Ice wall, smooth
    00F8    Ice floor, cracked

   So basically all with bit 0x0040 set ise water terrain, all with bit 0x0080
   set is lava terrain, and all entries with 0x00C0 set is ice.

 +--------------------------------------------------------------------------+


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 [uw1]

   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 cutscene 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 <article> <mood> <description> [named <npc-name>]. <mood> can be
   one of "mellow" or "upset".

   In the uw_demo, the 0cXX blocks and many of the conversation string
   blocks aren't available.

5.2  String block contents [uw2]

   The layout of strings.pak has not changed from uw1 to uw2. Common
   strings are followed by cutscenes and conversations. As before not every
   ID has been used. Block 3 (ID 0004) contains strings with special markup
   characters. The underscore '_' is replaced by the according item condition
   string taken from block 4 (ID 0005). An ampersand '&' marks strings with
   different singular/plural forms. A plural form never contains a condition.

   index      ID    size
       0    0001     512    technical stuff, number text conversion
       1    0002     512    character generation
       2    0003     512    books, scrolls. Readable items
       3    0004     512    item names
       4    0005     512    item condition
       5    0006     512    light levels, spell friendly names, spell effects
       6    0007     512    barter quality, npc names, monster internal names
       7    0008     512    wall labels, tombs. Readable stuff in the world
       8    0009     512    game state messages: vision, door state
       9    000a     512    descriptions of textures/decals
      10    0018     512    debug/console strings

   Cutscenes include the intro and outro as well as the various taunts by
   the guardian which occur during conversations or dreams.

   index      ID    size
      11    0c00      13
      ..      ..      ..
      28    0c2b       1

   Conversation strings used by the conversation engine. Contains both the
   phrases by the npc the player is talking to, as well as the answers by the
   player. The npc name for each conversation is stored in block 6 (ID 0007).
   The name of the npc for a given conversation is (ID - 0x0e00 + 16), e.g.
   ID 0x0e2e contains the conversation with character 0x3e (62), Jerry the Rat

   index      ID    size
      29    0e00     113
      ..      ..      ..
     135    0ea8      95


 +--------------------------------------------------------------------------+


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:

   item_id    description
   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.
         Bit 10 of "flags" is for "use once". "Zpos" is lock quality.

   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

   0130  a_book (and up to 0133)
         "Quantity" is an index in string block 2.

   0134  a_scroll (and up to 0137)
         "Quantity" is an index in string block 2.

   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.
         The "doordir" bit determines in which direction the door swings open.
         Doors are rendered using two 3D models, one for the frame and one for
         the door. It appears that the frame uses the wall texture of the tile
         in which the door is located. The frame also seems to change the color
         of the "border" between the two sides according to the object.

   0150  a_bench
         3D object (see section about 3D models)

   0154  a_large boulder
         3D object (see section about 3D models)

   0157  a_shrine
         3D object (see section about 3D models)

   0158  a_table
         3D object (see section about 3D models). Texture is 32 from tmobj.gr.

   015a  a_moongate
         3D object (see section about 3D models)

   015c  a_barrel
         3D object (see section about 3D models)

   015c  a_chair
         3D object (see section about 3D models). Texture is 38 from tmobj.gr.

   015d  a_chest
         3D object (see section about 3D models)

   015e  a_nightstand
         3D object (see section about 3D models)

   0160  a_pillar
         3D object. texture is determined by the lower byte of the "flags"
         field from "tmobj.gr"

   0161  a_lever
         3D object. A clock-like lever with 8 positions; texture is determined
         by the "flags" field (lower 3 bits) + 4, from "tmobj.gr".

   0162  a_switch
         3D object. Almost the same as 0161, but starting with image 12 of
         "tmobj.gr". Not used in uw1

   0163  a_picture [uw2]
         3D object. Texture is lower byte of flags + 42 from "tmobj.gr"
         "Flags" are used to select painting.

         ??? [uw1]
         uw1 has an item with this id but it's currently unknown what it is

   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-2 is used as index into the
         floor texture mapping (shared floor+wall for UW2), effectively using a
         floor texture.

         bridges could be used 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]
         3D object. The "owner" field determines the colour of the sheets and
         pillow. The sheets are colour (4*owner+5), the pillow colour
         (4*owner).

   0169  a_shelf [uw2]
         3D object. Texture is lower byte of flags + 36 from "tmobj.gr"

   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.
         3D object. buttons, switches, pull chains and levers, textured with
         images from "tmflat.gr". The texture index into tmflat.gr is the
         lowest byte of the item id (subclass id).
         Special link usually points to a trigger.
         Note that after pressing the button the itemid increases by 8, thus
         using the texture. of the closed button. I don't know if this
         behaviour is hardcoded or done via the trigger.

   0180-01ac traps and triggers; see chapter 6.1.2 below

   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 off when a random value [0..10] is >= 7.
         [uw2] Setting "owner" field = poison

   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
         Throws an object.
         Object ID: bit 0-4 = "owner" field
                    bit 5-9 = "quality" field

         "Heading" sets direction, "zpos" sets height, "xpos"
         sets x-position, "ypos" sets y-position.

   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]
         Used for different things.
         "quality" is a command? and "owner" is a value.

   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.
         "quality" sets effect type.

   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.
         Tile type: bit 0 = "quality" bit 0
                    bit 1-3 = "heading"

         "quality" bit 1-4 sets floor texture, "owner" sets wall
         texture, "zpos" sets height, "xpos" = nr of tiles in
         x-direction, "ypos" = nr of tiles in y-direction.

   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<zpos+heading; i++)
            {
               if (xpos != 0)
                  cmp += game_vars[i];
               else
               {
                  cmp <<= 3;
                  cmp |= game_vars[i] & 7;
               }
            }

            return cmp != value
         }

         The trigger associated with the trap is set off when the resulting
         value is not equal the "value".

   018f  a_combination trap [uw1]
         not implemented in uw1

   018f  a_null trap [uw2]

   0190  a_text string trap
         causes the player to get a text message when it is set off. The
         "owner" field specifies string number per level, in game strings
         block 0009. The actual string number printed is (64*level + "owner")

   0191  an_experience trap [uw2]
         Increases or decreases experience points.
         "Quality" and "owner" determines the value.

   0192  a_jump trap [uw2]
         causes the player to get an upward velocity according to the quality
         field; where '24' would be the velocity one would get from a standing
         jump.

   0193  a_change from trap [uw2]
   0194  a_change to trap [uw2]
   0195  an_oscillator trap [uw2]
         "Quality" is lower height, "owner" is upper height.

   0196  a_proximity trap [uw2]
   0197  a_pit trap [uw2]
         Changes height and floor texture of a tile.
         Floor texture: bit 0-2 = "xpos"
                        bit 3 = "ypos" bit 1
         "Owner" changes height.

   0198  a_bridge trap [uw2]
         Creates a bridge at trigger target position.
         "Quality" sets length of bridge, "heading" sets
         direction, "owner" sets texture, "zpos" sets height.

   019e  a_flam rune [uw2]
   019f  a_tym rune [uw2]

   01a0  a_move trigger
         triggers the associated trap when player moves near the trigger

   01a1  a_pick up trigger

   01a2  a_use trigger
         triggers a trap when the player uses the object that points to the
         use trigger

   01a3  a_look trigger
         triggers a "look at" action; "special" contains some string id?

   01a4  a_step on trigger [uw1]
         not used in uw1

   01a4  a_pressure trigger [uw2]

   01a5  an_open trigger [uw1]
   01a5  a_pressure release trigger [uw2]

   01a6  an_unlock trigger
         not used in uw1

   01a6  an_enter trigger [uw2]
   01a7  an_exit trigger [uw2]
   01a8  an_unlock trigger [uw2]
   01a9  a_timer trigger [uw2]
   01aa  an_open trigger [uw2]
   01ab  a_close trigger [uw2]
   01ac  a_scheduled trigger [uw2]

   [uw2]: the triggers 01a0..01ac are repeated at 01b0..01bc (same object
          class, different object sub-class)


6.2  Common object properties

   Object properties common to all items are stored in the file "comobj.dat".
   The number of object properties is determined by (filelen-2) / 11   (each
   entry is 11 bytes long).

   The first two bytes of the file contain unknown information (always 01 0E
   in both uw1 and uw2). Then entries for all items follow. Each entry has the
   following format:
   0000  Int8    height
                 [observed in UW1] height seems to be about half the pixel
                 height of the object. Double this to get the height in pixels.
                 Seems that non-colliding objects have a height of 0.
   0001  Int16   mass/stuff:
                 bits 0-2: radius
                 [observed in UW1] This is 4 for bridge and anvil, 3 on doors
                 and a few other things, 2 for all critters. All other objects
                 are 1 or 2. and some 0. Not sure how to get a width in pixels
                 from this.
                 bit 3: 1 for npc's, 3d objects and misc. items (animated flg?)
                 [observed in UW1] all NPCs, 3d models excepts doors, and a few
                 animations: blood, mist, damage
                 bits 4-15: mass in 0.1 stones
   0003  Int8    flags TODO several unknown
                 0: [observed in UW1] always 0
                 1: [observed in UW1] is set to 1 for most 3d model objects.
                 Exceptions are doors, missiles, pillar, gravestone.
                 2: [observed in UW1] set to 1 for all switches, levers, etc
                 3: magic object (?)
                 [observed in UW1] set to 1 on some projectiles and animations.
                 This seems to set for items and effects that when spawned
                 spontaneously are deleted when they are finished.
                 4: decal object (always 0 in uw1)
                 5: is set when object can be picked up
                 [oserved in UW1] Almost but not exactly. Some items set to 1
                 cannot be picked up: urn, orb, campfire, fountain, cauldron
                 6: TODO unknown
                 [observed in UW1] this is set to 1 for weapons (not missiles),
                 equipment (even shields), all critters, containers (except
                 cauldron), and 3d models (except missiles) and all triggers
                 and traps.
                 7: is set when object is a container
                 [observed in UW1] except cauldron
   0004  Int16   monetary value
   0006  Int8    bits 0-1: TODO unknown
                 bit 0: [observed in UW1] set to 1 for almost every object.
                 Exceptions: Void monsters, zanium, all triggers and traps,
                 all animations except silver tree.
                 bit 1: [observed in UW1] set to 1 for some switches and
                 missiles.
                 bits 2-3: quality class (this value*6+quality gives index
                            into string block 5
                 bits 4-7: TODO unknown
                 bit 4: [observed in UW1] Set to 1 for all projectiles.
   0007  Int8    bit 0: TODO unknown
                 bit 0: [observed in UW1] set to 1 mostly for stones:
                   sling stone, stone, rings except iron, gems, amulet,
                   resilient sphere, spike, glowing rock, small boulder
                 bits 1-4: type? a=talisman, 9=magic, 3..5=ammunition
                 bits 5-6: TODO unknown
                 bit 6: [observed in UW1] similar to byte 3 bit 5, seems to
                 tell if an object can be picked up. Only exception seems to be
                 the missile 3d models and silver tree.
                 bit 7: if 1, item can have owner ("belongs to ...")
   0008  Int8    scale value (?) TODO
   0009  Int8    TODO unknown
                 bit 0: [observed in UW1] set to 1 for NPCs and flat texture
                 map objects
                 bits 6-7: [observed in UW1] always 00
   000A  Int8    bits 0-3: quality type 0-f
                 bit 4: printable "look at" description when 1
                 bits 5-7: TODO unknown
                 [observed in UW1] always 011, even for null objects

   Each possible value of "quality type" maps onto a group of 6 strings in
   block 4 from lowest to highest quality. Items which have a quality are
   always described as "a/an <quality> <item>", 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 bytes 0x01 0x0f (or 0x0f01 as word)
   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  0x10     food nutrition table        16         1 byte
   0d92  0x10     jewelry table               16         1 byte
   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   Int8   attack charge calculation: minimum charge?
   0004   Int8   attack speed - how quickly an attack charge builds up
   0005   Int8   attack charge calculation: maximum charge?
   0006   Int8   skill type (3: sword, 4: axe, 5: mace, 6: unarmed)
   0007   Int8   durability (FF means undestroyable)

   * Ranged weapons table (0x0010-0x001f)

   0000   Int16  unknown TODO
                 bits 9-15: ammunition needed (+0x10)
   0002   Int8   durability

   * Armour and wearables table (0x0020-0x003f)

   0000   Int8   protection
   0001   Int8   durability
   0002   Int8   unknown, values 0, 4, 5, 6, 7
   0003   Int8   category / paperdoll position:
                 00: none/not on paperdoll (only used by shields)
                 01: body armour
                 03: leggings
                 04: gloves
                 05: boots
                 08: hat
                 09: ring

   * Critters table  (0x0040-0x007f)

   0000   Int8   npc_level: level of the critter

   0001-3 3*Int8 armor: amount of damage subtracted from attacks; either one
                 value and 3x FF is used, or 4 (often) different values are,
                 used, for body, ??, ?? and head
   0004   Int8   vitality
   0005   Int8   npc_power; base damage calculation
   0006   Int8   dexterity; attack score calculation
   0007   Int8   intelligence; magic spell attacks?
   0008   Int8   combination of remains after death and the type of blood
                 splatters this critter produces:
                 bits 0-3: blood splatter type;  0: dust, 8: red blood
                 bits 4-7: remains type; 0: nothing, 2: rotworm corpse,
                     4: rubble, 6: wood chips, 8: bones, 10: green blood pool,
                     12: red blood pool, 14: red blood pool giant spider
   0009   Int8   generic name; index into game strings block 1, value + 370
   000a   Int8   passiveness; 0xff: will never attack
   000b   Int8   magic related; extra/specific spells?
   000c   Int8   movement speed; 0: immobile, 12: maximum, e.g. vampire bat
   000f   Int8   poison damage; starting value for poison damage in the first
                 minute, reduced by 1 for every following minute
   0010   Int8   critter category:
                 0x00: ethereal (like ghosts, wisps, and shadow beasts)
                 0x01: humanoid (humanlike non-thinking forms like lizardmen,
                       trolls, ghouls, and mages)
                 0x02: flying (like bats and imps)
                 0x03: swimming (like lurkers),
                 0x04: creeping (like rats and spiders)
                 0x05: crawling (like slugs, worms, reapers, and fire
                       elementals)
                 0x11: golem (only used for the earth golem)
                 0x51: human (humanlike thinking forms like goblins,
                       skeletons, mountainmen, fighters, outcasts, and stone
                       and metal golems)
   0011   Int8   attack power (equipment damage?)
   0012   Int8   defense power
   0013   Int8   weapon
   0014   Int8   damage
   001c   Int8   eyes: number of feet (really?)
   0028   Int16  experience when killed
   002f   In8t   unknown; always 0x65

   Non-listed values in between the described ones are unknown.

   Parts of this table were found by comparing with this bestiary table:
   https://gamefaqs.gamespot.com/pc/564592-ultima-underworld-ii-labyrinth-of-worlds/faqs/74915
   The tables are from the Ultima Underworld II Clue Book.

   * Containers table (0x0080-0x008f)

   0000   Int8   capacity in 0.1 stones
   0001   Int8   objects accepted; 0: runes, 1: arrows, 2: scrolls,
                 3: edibles, 4: keys [uw2], 0xFF: any
   0002   Int8   number of slots available
                 2: only 2 items, 0xFF: any

   * Light source table (0x0090-0x009f)

   0000   Int8   duration (0: doesn't go out, e.g. taper of sacrifice)
   0001   Int8   light brightness (max. is 4; 0 means unlit)

   * Food nutrition table (0x00b0-0x00bf)

   0000   Int8   nutrition value of food (alcoholic beverages +0xF0)

   * Jewelry table (0x00a0-0x00af)

   0000   Int8   jewelry value

   * Animation object table (0x01c0-0x01cf)

   0000   Int8   unknown (0x00, 0x21 or 0x84) TODO
   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).
   [uw2] Empty slots are marked with three 0xffff words.


 +--------------------------------------------------------------------------+


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 "babglobs.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 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
   "babglobs.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 "babglobs.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 3 dedicated registers:
      BP: the base pointer for the current function. this is an index into
          the main memory array. BP+n are local variables on the stack.
          BP-n are parameters passed into the function.
      SP: the current stack pointer. this is an index into the main memory
          array of the VM. Most operations reference the stack pointer.
      RV: result value for imported function returns. Can also be used as a
          general purpose register.

   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<num>[<extension>]

   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
   <num>: decimal value
   <extension>: format: C<number>: use array index <number>

   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 + <num>

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


7.6.1 Ultima Underworld 1 intrinsic functions

   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:   arg1: when >= 0 and <= 30, sets skill to this value
                       when 10001, increase skill by 1?
                       when 9999, return skill value?
                 arg2: skill to use; one of:
                    0 attack, 1 defense, 2 unarmed, 3 swords
                    6 missile, 7 mana, 8 lore, 9 casting,
                    10 disarm, 11 searching, 14 repair,
                    15 charisma, 16 lockpick, 18 evaluation, 19 swimming
                    (maps to the savegame skill values, starting at 0021h,
                    see chapter 9.2.1)
   description:  returns or sets skill value
   return value: skill value/newly set skill value
   -------------------------------------------------------------------------

   id=002d name="x_traps" ret_type=int
   parameters:   arg1: value >= 0 and <= 0x3f
                 arg2: index into savegame array at 0070h
   description:  unknown; gets or sets some value
   return value: current value, or newly set value (same as arg1 when in range)
   -------------------------------------------------------------------------

   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.
                 [uw2] sewer key is arg6=28, arg8=1
   -------------------------------------------------------------------------

   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 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.6.2 Ultima Underworld 2 intrinsic functions

   Each function that is ever imported in a conversation is listed here. not
   all functions are always imported. The following complete list is sorted by
   function id.
   Underworld 2 knows 63 functions, 12 more than uw1. The additions are marked
   with [x] below and described later.

   0x00 babl_menu
   0x01 babl_fmenu
   0x02 print
   0x03 babl_ask
   0x04 compare
   0x05 random
   0x06 plural
   0x07 contains
   0x08 append
   0x09 copy
   0x0a find
   0x0b length
   0x0c val
   0x0d say
   0x0e respond
   0x0f babl_hack             [x]
   0x10 give_all_stuff        [x]
   0x11 do_input_wait         [x]
   0x12 get_quest
   0x13 set_quest
   0x14 sex
   0x15 show_inv
   0x16 give_to_npc
   0x17 give_ptr_npc
   0x18 take_from_npc
   0x19 take_id_from_npc
   0x1a identify_inv
   0x1b do_offer
   0x1c do_demand
   0x1d do_inv_create
   0x1e do_inv_delete
   0x1f check_inv_quality
   0x20 set_inv_quality
   0x21 count_inv
   0x22 setup_to_barter
   0x23 end_barter
   0x24 do_judgement
   0x25 do_decline
   0x26 pause
   0x27 set_likes_dislikes
   0x28 gronk_door
   0x29 gronk_trigger         [x]
   0x2a set_race_attitude
   0x2b place_object
   0x2c take_from_npc_inv
   0x2d add_to_npc_inv
   0x2e transform_talker      [x]
   0x2f remove_talker
   0x30 set_attitude
   0x31 x_skills
   0x32 x_traps
   0x33 x_clock               [x]
   0x34 x_exp                 [x]
   0x35 teleport_player       [x]
   0x36 add_event             [x]
   0x37 x_obj_pos
   0x38 x_obj_stuff
   0x39 find_inv
   0x3a find_barter
   0x3b find_barter_total     [x]
   0x3c teleport_talker       [x]
   0x3d switch_pic            [x]
   0x3e set_sequence          [x]


<<TODO>> ... encounter, observe, etc

   0x0f babl_hack             [x]
   0x10 give_all_stuff        [x]
   0x11 do_input_wait         [x]
   0x29 gronk_trigger         [x]
   0x2e transform_talker      [x]
   0x33 x_clock               [x]
   0x34 x_exp                 [x]
   0x35 teleport_player       [x]
   0x36 add_event             [x]

   List of UW2 extra functions:

   Type     Args  Function Name

   void     3     set_sequence
   void     1     switch_pic
   int      2     teleport_talker
   void     ?     add_event
   int      3     teleport_player
   int      1     x_exp
   int      2     x_clock
   void     4     transform_talker
   int      ?     gronk_trigger
   void     0     do_input_wait
   void     0     give_all_stuff
   void     1     babl_hack

   0x3b find_barter_total     [unconfirmed]
         return int
         argc: 4
         *(argv[0]): unknown
         *(argv[0]): unknown
         *(argv[0]): item match mask (?)
         *(argv[0]): item match template (?)
      find_barter_total looks for an item in the players inventory. The mask
      determines which bits of the template are compared to the items.
      return number of matches.
      Example: conversation 1, request for kitchen delivery voucher


   0x3c teleport_talker       [x]

   0x3d switch_pic
         return: void
         argc: 1
         *(argv[0]): character id
      switch_pic changes the image of the character the player is talking to.
      Usally used to give the impression that somebody else has joined a
      conversation. character id + 16 can usually be used to determine the name
      of the new talker. This does not result in a conversation slot change.
      Example: conversation 142 (Lord British)

   0x3e set_sequence          [x]
         return: void
         argc: 3

   -------------------------------------------------------------------------

   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=000f name="babl_hack" ret_type=?
   parameters:   arg0: ???
                   9 = merzan deal evaluation (b=525, then 0)
                 arg1: ???
   description:  ???
   return value: ???
   -------------------------------------------------------------------------

   name="give_all_stuff"

   parameters:    unknown
   description:   unknown
   return value:  unknown
   -------------------------------------------------------------------------

   name="do_input_wait"

   parameters:    unknown
   description:   unknown
   return value:  unknown
   -------------------------------------------------------------------------

   name="gronk_trigger"

   parameters:    unknown
   description:   unknown
   return value:  unknown
   -------------------------------------------------------------------------

   name="transform_talker"

   parameters: arg1: ?
               arg2: ?
               arg3: npc_whoami
               arg4: Type (0-63)
   description:  Transform Talker
   return value: unknown

   -------------------------------------------------------------------------
   name="x_clock"

   parameters:    unknown
   description:   unknown
   return value:  unknown
   -------------------------------------------------------------------------

   name="x_exp"

   parameters:    unknown
   description:   unknown
   return value:  unknown
   -------------------------------------------------------------------------

   name="teleport_player"

   parameters:    arg1: Level+1 (0=Current Level)
                  arg2: Y-pos
                  arg3: X-pos
   description:   Teleport Player
   return value:  Y-Pos?
   -------------------------------------------------------------------------

   name="add_event"

   parameters:    unknown
   description:   unknown
   return value:  unknown
   -------------------------------------------------------------------------

   name="teleport_talker"

   parameters:    arg1: Y-Pos
                  arg2: X-Pos
   description:   Teleport Talker
   return value:  unknown
   -------------------------------------------------------------------------

   name="switch_pic"

   parameters:    arg1: Pic Nr
   description:   Switch Pic
   return value:  unknown
   -------------------------------------------------------------------------

   name="set_sequence"

   parameters:    unknown
   description:   unknown
   return value:  unknown
   -------------------------------------------------------------------------


   List of function ID:

   Function            ID:     UW1  UW2
   babl_menu                    0    0
   babl_fmenu                   1    1
   print                        2    2
   babl_ask                     3    3
   compare                      4    4
   random                       5    5
   plural                       6    6
   contains                     7    7
   append                       8    8
   copy                         9    9
   find                         A    A
   length                       B    B
   val                          C    C
   say                          D    D
   respond                      E    E
   give_all_stuff               -   10
   do_input_wait                -   11
   get_quest                    F   12
   babl_hack                    -    F
   set_quest                   10   13
   sex                         11   14
   show_inv                    12   15
   give_to_npc                 13   16
   give_ptr_npc                14   17
   take_from_npc               15   18
   take_id_from_npc            16   19
   identify_inv                17   1A
   do_offer                    18   1B
   do_demand                   19   1C
   do_inv_create               1A   1D
   do_inv_delete               1B   1E
   check_inv_quality           1C   1F
   set_inv_quality             1D   20
   count_inv                   1E   21
   setup_to_barter             1F   22
   end_barter                  20   23
   do_judgement                21   24
   do_decline                  22   25
   pause                       23   26
   set_likes_dislikes          24   27
   gronk_door                  25   28
   gronk_trigger               --   29
   set_race_attitude           26   2A
   place_object                27   2B
   take_from_npc_inv           28   2C
   add_to_npc_inv              29   2D
   transform_talker            --   2E
   remove_talker               2A   2F
   set_attitude                2B   20
   x_skills                    2C   31
   x_traps                     2D   32
   x_clock                     --   33
   x_exp                       --   34
   teleport_player             --   35
   add_event                   --   36
   x_obj_pos                   2E   37
   x_obj_stuff                 2F   38
   find_inv                    30   39
   find_barter                 31   3A
   find_barter_total           32   3B
   teleport_talker             --   3C
   switch_pic                  --   3D
   set_sequence                --   3E


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         vitality?
    npc_hp             vitality?
    npc_arms           (not used in uw1)
    npc_power          attack power
    npc_goal           goal that NPC has; see below
    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 level
    npc_name           (not used in uw1)
    dungeon_level      (not used in uw1)
    riddlecounter      (not used in uw1)
    game_time
    game_days
    game_mins

   The npc_goal has the following possible values:
     5: attack
     6: flee from goal target
     9: attack


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

7.8.1 UW1 quest flags

   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.

7.8.2 UW2 quest flags

   flag   description

    109   Lord British gave his startgame speech
    110   Mors Gotha is invading the castle
    112   The Avatar is persona non grata (?)
    130   A bit field with available worlds (blackrock gem faces, map gem)

7.9 Ultima Underworld 2 specialities

Ultima Underworld 1 conversations always have some stock function in the code,
starting from 0012 up to 029c, even when unused. Conversations always start
at 029d. In Ultima Underworld 2, the "linker" removed all unused functions and
conversations may start at a lower position, saving on space in the cnv.ark
file.

It seems that one conversation has some CALL 0xFFFF opcodes; I guess this call
is just a no-op, or it would crash the conversation virtual machine.



 +--------------------------------------------------------------------------+


8  3D models

   These are stored within the executable (bad! bad!), in a segment of the
   overlaid executable. The renderer most probably is written in assembly
   language for optimization. There are different versions of uw.exe, uw2.exe
   and uwdemo.exe executables, which makes it a bit harder to find the models.

   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

   From Abysmal project:
   There's also a model information table stored in the .exe file.

   [uw1] It starts at 0x0005f7e5 (in my uw.exe, md5sum:
   c64cc46fd8162c626135a6a19315d21e) and contains 4 bytes for each model
   (a total of 32*2 bytes).
   The layout is as follows:

     0000   Int8*3 Color table. These values are indexed by the "color" values
                   in the model, and are itself indexes into game palette 0.
                   Note that this table is NULL-terminated, so the table can
                   actually have the size 0-3 (this plays a role when indexing
                   into this table from the model)
     0003   Int8   Flags (unknown)


   [uw2] The model information table for underworld 2 starts at 0x0006908A
   in a UW2.EXE with base 0x00054cf0. Each table entry has 5 bytes

      0000   int8   high nibble unknown
                    low nibble contains number of valid color entries
      0001   int8*3 color table as before
      0004   int8


   Models are:
   (All models from 0x140 to 0x17F are drawn using 3D models)

   index   item_id   description
    00               - (empty)
    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     0153/0154 large boulder
    08     0151      arrow
    09     0159      beam
    0A     0160      pillar
    0B     0157      shrine
    0C               ??? unknown
    0D     0163      painting [uw2]
    0E     014x      door (with submodel)
    0F     014x      secret door (with submodel)
    10     0161,017x texture map (8-way lever and buttons)
    11     0162      texture map (8-way switch)
    12     0166      texture map (writing)
    13     0165      gravestone
    14     016e      texture map
    15               - (empty)
    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
   ---------
   The 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.
     Color   A color value. This is an index into the 3 byte "auxpal" in the
             model information table. The correct
             index into the 3-byte auxpal is obtained by the following
             calculation:

             for UW1:
             (color - 0x2920) / 2 % c
             for UW2:
             (color - 0x2680) / 2 % c

             where c is the number of entries in the auxpal, which may be less
             then three.

   All data types use 16 bit values.

   Every model has a header that looks like this:

     0000   Int32   unknown (does not seem to change anything)
     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
     0012     ???
     0014     ??? colour definition
     0016     ???
     002E     ??? seems to influence shading for the next face [uw2]
     0040     ??? seems to do nothing but introduce a face definition
     0044     ??? this one too
     004A     define translation XZY
     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     define texture mapped face (shorthand)
     00A8     define texture-mapped face
     00B2     ??? potential texid for following 00B4 calls
     00B4     define face vertices with u,v information
     00BA     render door submodel
     00BC     define flat face shade
     00BE     ??? seems to define 2 shades
     00CE     define texture-mapped face, same as 00B4
     00D2     define texture mapped face (shorthand) (similar to 00A0)
     00D4     define vertex Gouraud shading values
     00D6     turn on on Gouraud shading

   The same list as above, sorted by node ID and including the number of
   arguments each node takes (=argc); var means a variable number of
   arguments.

       ID  argc
     0000     0      end node
     0006     8      define sort node, arbitrary heading
     000C     6      define sort node, ZY plane
     000E     6      define sort node, XY plane
     0010     6      define sort node, XZ plane
     0012     1      ???
     0014     3      ??? colour definition
     0016     3      ???
     002E     1      ??? seems to influence shading for the next face [uw2]
     0040     0      ??? seems to do nothing but introduce a face definition
     0044     0      ??? this one too
     004A     3      define translation XZY
     0058     7      define face plane, arbitrary heading
     005E     5      define face plane Z/Y
     0060     5      define face plane X/Y
     0062     5      define face plane X/Z
     0064     3      define face plane X
     0066     3      define face plane Z
     0068     3      define face plane Y
     0078     5      define model center
     007A     4      define initial vertex
     007E     var    define face vertices
     0082     var    define initial vertices
     0086     3      define vertex offset X
     0088     3      define vertex offset Z
     008A     3      define vertex offset Y
     008C     3      define vertex variable height
     0090     4      define vertex offset X,Z
     0092     4      define vertex offset X,Y
     0094     4      define vertex offset Y,Z
     00A0     5      shorthand textured face definition
     00A8     var    define texture-mapped face
     00B2     1      ??? potential texid for following 00B4 calls
     00B4     var    define face vertices with u,v information
     00BA     3      render door submodel
     00BC     2      define face shade (6 bytes)
     00BE     2      ??? seems to define 2 shades
     00CE     var    same as 007E (?)
     00D2     4      shorthand textured face definition (similar to 00A0)
     00D4     var    define vertex Gouraud shading values
     00D6     0      turn on on 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 and submodels

     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     unknown, always 0

            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 number
              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 Z 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 height.
              0002   VertNo    reference vertex to modify
              0004   Int16     unknown, always 0x0800
              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 culling)
              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, nvert times

     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, always 6
              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, nvert times

     00B4   define face vertices with u,v information
            same structure as 00A8, but without the "texture number" field.

     00CE   define texture-mapped face
            same data as 00B4

     00A0   define texture mapped face (shorthand)
            seems to be a quick way to define a face with 4 vertices and
            automatic texture coordinates; vertex list indices
              0002   Int16     unknown, always 6
              0004   Int8      vertex list index 0
              0005   Int8      vertex list index 1
              0006   Int8      vertex list index 2
              0007   Int8      vertex list index 3

            The automatic texture coordinates for the 4 vertices are
            (normalised to the range [0; 1]):
            0.0, 0.0
            1.0, 0.0
            1.0, 1.0
            0.0, 1.0

     00D2   define texture mapped face (shorthand)
            same as 00A0 but missing the first Int16 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)


   Shading/Coloring nodes
   ----------------------
     00D4   define vertex Gouraud shading values
            Build a table of shading information for each vertex.
            Note that the list generated by this command doesn't
            always have the same size as the vertex table.
            Also note that each model should only define this table
            once.

              0002   Int16     number of vertices (=nvert)
              0004   Color     base colour for Gouraud shading
              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   turn on on Gouraud shading
            switches on Gouraud shading. The following faces use
            the color definitions set with the "define vertex shading values"
            (00D4) command

     00BC   define flat face shade
            Defines a flat shade for the next (or all following?) faces.
            The values given here are the same as in the shading table (00D4)
            so the same calculations and palette indexing rules apply here
              0002   Color     colour offset
              0004   Int16     shade (dark value)

   Unknown nodes
   -------------
     0012   ???
              0002   Int16     ???

     0014   ??? colour definition
              0002   VertNo    vertex number
              0004   Int8      c1 ???
              0005   Int8      c2 ???
              0006   VertNo    vertex number

     0016   ???
              0002   VertNo    ??
              0004   Color     ??
              0006   Int16     ??

     002E   ??? seems to influence shading for the next face [uw2]
            This node influences how the next face is drawn. I'm not sure what
            it does exactly, but I'm sure that it switches Gouraud shading off
            (and possibly some other mode on)
            It's used quite a lot in the Lotus Turbo Esprit model and only
            once in the table model (with data=0). Other than that it's not
            used.
              0002   Int16     ???

     0040   ??? seems to do nothing but introduce a face definition
     0044   ??? this one too
            no further node data

     00B2   ??? potential texid for following 00B4 calls
              0002   Int16     ???

     00BE   ??? seems to define 2 shades ?? refers to 2 model variables
              0002   Int16     var 1
              0004   Int16     var 2

   Door nodes
   ----------
   Doors appear to have a special set of nodes only used for door models.
   Portcullis might be determined by the first parameter of the 00BA command.

     004A   define translation
              0002   Fixed     vertex x position
              0004   Fixed     vertex z position
              0006   Fixed     vertex y position

          This defines a translation to apply to coming nodes. Note that z
          and y seem to be swapped.

     00BA   render door submodel
              0002   Int16     Pointer to some initial set up data for the
                               door submodel rendering. Not sure what this
                               actually does. Possibly related to door angle
                               and visibility.
              0004   Int16     Pointer to first model node of submodel to
                               render. Offset is relative to the location of
                               the current node (00BA).

 +--------------------------------------------------------------------------+


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 (=numBlocks)
     0004  Int32  unknown (always 0)

   Now follow 4 tables, each one is "numBlocks" 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, except for
              scd.ark)
       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.1  Savegame format [uw1]

   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 || i==160) incrnum = 3;
         data[i] ^= (xorvalue+incrnum);
         incrnum += 3;
      }

   Note that the initial "player.dat" file in the "data" folder isn't
   encrypted at all and doesn't contain the first xor byte.

   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
   0035   Int8    current vitality
   0036   Int8    max. vitality
   0037   Int8    current mana (play_mana)
   0038   Int8    max. mana
   0039   Int8    hunger (play_hunger)
   003A   Int8    fatigue (or swapped with 0039?)
   003B   Int8    unknown
   003C   Int8    unknown
   003D   Int8    character level (play_level)
   003E   Int16   active spell 1
   0040   Int16   active spell 2
   0042   Int16   active spell 3
   0044   3*8bits rune flags (*)
   0047   Int8    selected rune 1 (0x24 == none)
   0048   Int8    selected rune 2
   0049   Int8    selected rune 3
   004A   Int16   current weight carried (in 0.1 stones)
   004C   Int16   maximum weight in 0.1 stones.
                  Value is always 300 + 13 * [001E] (strength)
   004E   Int32   experience in 0.1 points
   0052   Int8    Number of skill points available
   0053   Int8    same as 0052?
   ...
   0055   Int16   x-position in level
   0057   Int16   y-position
   0059   Int16   z-position
   005B   Int8    heading (0..7?)
   005C   Int8    dungeon level
   005D   Int8    unknown
   005E   Int8    moon stone/silver tree
                  bits 0..3: level of moon stone
                  bits 4..7: level of silver tree
   005F   Int8    misc. bits
                  bits 0..1: incense dream bits
                  bits 2..5: play_poison
                  bits 6..7: no. of active spells
   0060   Int8    various plot flags
   0061   Int8    unknown
   0062   Int8    MiscQuestVars? drunkenness level in lower hex digit (?)
   0063   Int8    bits 4..7: light level
   0064   Int8    character class details
                  bit 0: hand left/right
                  bit 1: gender, 0: male, 1: female
                  bit 2..4: body (0-4)
                  bit 5..7: class
                     0: fighter, 1: mage, 2: bard, 3: tinker
                     4: druid, 5: paladin, 6: ranger, 7: shepard
   0065   Int32   Quest flags 0-31 (bits 0..31)
   0069   Int8    Quest flag 32
   006A   Int8    Quest flag 33
   006B   Int8    Quest flag 34
   006C   Int8    Quest flag 35
   006D   Int8    Quest flag 36 and above (number of talismans remaining)
   006E   Int8    Garamon dream related
   006F   Int8    unknown
   0070-00A4      conv. variables? 0-52
   ...
   00B4   Int8    difficulty (0=normal, 1=easy)
   00B5   Int8    game options
   ...
   00CE   Int16   game_time; lower 32 bits
   00D0   Int16   game_time; upper 32 bits
   ...
   00DC   Int8    current vitality (duplicate of 0035)?
   ...
   00F7-011C Int16  all these fields are indices into the inventory list
                    that starts at offset 311 or 0x0137
   00F7   Int16   Helm
   00F9   Int16   Chest
   00FB   Int16   Gloves
   00FD   Int16   Leggings
   00FF   Int16   Boots
   0101   Int16   Top right shoulder
   0103   Int16   Top left shoulder
   0105   Int16   Right hand
   0107   Int16   Left hand
   0109   Int16   Right ring
   010B   Int16   Left ring
   010D   Int16   Backpack 0
   ...
   011B   Int16   Backpack 7
   ...
   0137   ???     Inventory

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

   <TODO> how items are stored


9.2.2 Savegame Format [uw2]

   The player.dat of uw2 is encoded with a different scheme than the one of
   uw1. The encryption algorithm uses a block of magic bytes in combination
   with previous ciphertext and plaintext.

   File layout

   0000   int8    magic seed
   0001 - 037D    encrypted character data
   037E - eof     inventory data in plaintext

   Inventory Data:
   037E   Int8      Number of items in inventory + 1

   0383   Int8      Bit 5: Seems to be set when avatar fist picks up an item in
                           the world and stays set forever

   0386   Int8      Bit 6: Seems to be set when avatar has any items in the
                           inventory and unset when it is empty

   03A3   19*Int16  Inventory slots in the following order:
                    head to feet, left shoulder, right shoulder, left hand,
                    right hand, left ring, right ring, 8 general slots
                    ("left" means screen's left, ot paperdoll's left)
                    Bits 6-15 contains the item's index

   03E3   ?         Inventory Items. Same format as static objects from section
                    4.2 though bytes 2 and 3 (position and heading) are ignroed.
                    The first item is index 1, and the others increase normally.
                    Notice that all objects form through their "next" and
                    "sp_link" fields a single tree, wih it's root in object
                    index 1, even though they may be in different inventory
                    slots. This is consistent with the view, that the Avatar
                    acts as a container for all items in the inventory.

   How to set up the magic bytes:
   The magic bytes are always 80 (0x50) bytes. They are filled in 4 rounds
   with differing stride using and changing the magic seed (MS from now on).
   The algorithm assumes the MS to be stored in an unsigned 8 bit variable
   and will overflow on MS multiple times.

   Magic Array (MA) fill algorithm:

      MS += 7;
      for (i = 0; i<80; ++i)
      {
         MS += 6;
         MA[i] = MS;
      }
      for (i = 0; i<16; ++i)
      {
         MS += 7;
         MA[i*5] = MS;
      }
      for (i = 0; i<4; ++i)
      {
         MS += 0x29;
         MA[i*12] = MS;
      }
      for (i = 0; i<11; ++i)
      {
         MS += 0x49;
         MA[i*7] = MS;
      }
   Notes on filling the MA bytes:
     - not resetting MS between loops is intentional.
      - unlike the first two rounds, the last two rounds are cut short.
      - the algorithm to fill MA was derived from an assembly trace of
        Robin Wöhlers uw2edit.exe. He uses a round 0 before the first round
        which is subsequently overwritten. This first round only results in
        increasing MS by 7, thus the first line of the algorithm outlined here.

   Example:
   A magic seed of 0x98 results in the following MA:
       0 | dc ab b1 b7 bd 8d c9 25 d5 db 94 e7 41 f3 6e 9b
      10 | 05 0b 11 17 a2 b7 29 2f 6a a9 41 47 00 53 b0 5f
      20 | 65 6b 71 49 93 83 89 8f be 9b 92 a7 ad c5 b9 bf
      30 | c5 db cc d7 dd e3 e9 d3 24 fb 01 07 da 13 19 6d
      40 | 25 e1 31 37 3d 43 b6 4f 55 5b 61 ef 6d ff 79 7f

   Decrypting PLAYER.DAT:
   After setting up the MA, the ciphertext of the character (C) can be
   decrypted to the plaintext of the character (P). The ciphertext of
   0x037D bytes is split in blocks of 0x50 bytes, with the last block
   being cut short.

   Each block is decoded as follows, with ^ denoting a XOR operation:
     P[0] = C[0] ^ M[0]
     for (i=1; i<0x50;++i)
     {
        P[i] = C[i] ^ (P[i-1] + C[i-1] + M[i])
     }
   Notes on decrypting:
     - remember to account for the shorter last block
     - in terms of cryptography, this algorithm sucks. don't reuse
     - the algorithm is fully symmetric and can also be used for encryption
     - the algorithm was derived from an assembly trace of Underworld 2.

   Encrypting:
   As mentioned before, the same algorithm can be used to encrypt and decrypt.
   The trick is figuring out MS and MA. If an existing file is modified, MS
   and MA can be reused if the first letter of the character name is not
   changed. Otherwise a new MS and MA have to be calculated.

   Apparently the the MS can be calculated as
       0xAA ^ (first letter of character name), thanks Al_B from TTLG forums


   Character plaintext layout:
   <<TODO: basically the same a UW1 but with more information for quests>>

   Inventory layout:
     * seems like some kind of compression is being used
     * character = empty container?
   <<TODO>>


9.3  Unknown files

   This section lists files with unknown meaning.

   chrgen.dat
   ----------
   Character generation data?

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

   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 occurence

   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"


 +--------------------------------------------------------------------------+

10     Sound [uw2]

10.1   sounds.dat

   The file "sounds.dat" in the "sound" folder contains sound effect data.

10.1.1   sounds.dat [uw2]

   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.

10.1.2   sounds.dat [uw2]

   The file starts with an Int8 value that is the number of data entries in
   the file. Then 8 Int8 bytes for each entry in the file follow.
   It is supposed that the data may be midi commands to play back sfx.

      0000      int8: number of entries = 49 (0x31)
      0001+n*8  start of entry n
   <<TODO entries format>>

10.2   VOC files [uw2]

   VOC files contain sound samples in soundblaster voc format.
      BSPxx.VOC    cutscene speech
      SPxx.VOC     sound effect
      UWxx.VOC     guardian laughter

   There are numerous discussions of VOC files freely available on the net.
   For a complete discussion of the format google it, look at
   http://www.wotsit.org/ or whatever. Here is the short version as used by
   Underworld 2:

   VOC file format

      0x0014        uint16   [O] offset to type 1 block
      [O]+ 0x0000   uint8    type, always 1
      [O]+ 0x0001   uint24   [S] sample size in bytes
      [O]+ 0x0004...         sample data

   sample format

      0x0000        uint8    [R] sample rate
      0x0001        uint8    [C] compression
      0x0002..[S]            pcm data

   the sample rate [R] can be converted into Hz by the formula

      Hz = (-1000000) / ([R] - 256);

   All samples of uw2 are mono samples. The samples I checked were uncompressed

10.3   XMI files

   XMI files contain the rules to generate random music.
   <<TODO tear exult engine apart>>

end of file

External Links[edit]

This file has been forked and maintaine by several groups. The mst common ones are listed below. The latest and greatest is apparently the one on Underworld Adventures on github, but the one from Abysmal Engine on sourceforge does seem have a few notes that the UA one is lacking.

Project Link File Link Notes
Underworld Adventures sourceforge uw-formats.txt obsolete
Underworld Adventures github uw-formats.txt
Abysmal Engine sourceforge uw-formats.txt Last update was in 2009, project seems abandoned
UWReverseEngineering github uw-formats (Abysmal).txt

uw-formats (Underworld Adventures).txt

Both files here seem to be older versions of the ones above
UnderworldGodot UnderworldGodot does not host it's own uw-formats.txt but is based on UWReverseEngineering above, same author