THE UNOFFICIAL ULTIMA UNDERWORLD SPECIFICATIONS =============================================== 0.1 Table of contents 0.1 Table of contents (you're looking at it) 1.1 Summary of data files 2.1 Game strings 2.2 Level maps 2.2.1 Level map header 2.2.2 Level map 2.2.3 2.2.4 Level texture list 2.3 2.3.x Object IDs 2.4 Graphics files 2.4.1 Graphics file header 2.4.2 Bitmap format 2.5 Conversations 2.5.1 File header and offsets list 2.5.2 Conversation header 2.5.3 Imported functions table 2.5.4 The conversation code virtual machine 2.6 Fonts 2.7 3D object models 2.8 Class-specific object information 2.8.1 Header 2.8.2 Melee weapons table 2.8.3 Ranged weapons and missiles table 2.8.4 Armour and wearables table 2.8.5 Critters table 2.8.6 Containers table 2.8.7 Animation object table 2.9 Texture properties 0.2 CREDITS =========== This file written by Jim Cameron Additional information from: Alistair Brown (basic object format, via the Underworld II editor ) Ulf Wohlers (Objects and 3D models) Michael Fink (Conversations) 1.1 SUMMARY OF DATA FILES Filename Section Description data/3dwin.gr 2.4 data/animo.gr 2.4 data/armor_f.gr 2.4 Armour bitmaps (worn) (female) data/armor_m.gr 2.4 Armour bitmaps (worn) (male) data/babglobs.dat Initial conversation globals data/blnkmap.byt Blank map screen template data/bodies.gr 2.4 data/buttons.gr 2.4 Editor button caps data/chains.gr 2.4 data/chargen.byt Character generation screen background data/charhead.gr 2.4 data/chrbtns.gr 2.4 data/cnv.ark 2.5 Conversation scripts data/comobj.dat Common object properties data/compass.gr 2.4 Your compass (bottom of view screen) data/converse.gr 2.4 data/cursors.gr 2.4 data/doors.gr 2.4 data/dragons.gr 2.4 Those cute dragons below the viewscreen data/eyes.gr 2.4 data/f16.tr (UW) Low-res (16x16) floor/ceiling textures data/f32.tr (UW) High-res (32x32) floor/ceiling textures data/flasks.gr 2.4 data/font*.sys 2.6 Fonts data/gempt.gr (UW2) 2.4 data/genhead.gr 2.4 data/ghed.gr (UW2) 2.4 data/grave.dat Grave IDs data/heads.gr 2.4 data/inv.gr 2.4 data/lev.ark 2.2 Level map archive data/lfti.gr 2.4 data/main.byt Main game screen background data/objects.dat 2.8 Object data specific to an object class data/objects.gr 2.4 data/opbtn.gr 2.4 data/opscr.gr Opening splash screen background data/optb.gr 2.4 data/optbtns.gr 2.4 data/pals.dat Game palettes data/panels.gr 2.4 data/power.gr 2.4 data/pres1.byt "ORIGIN Presents" splash screen data/pres2.byt "A Blue Sky Productions Game" splash screen data/question.gr 2.4 data/scrledge.gr 2.4 data/spells.gr 2.4 Active spell icons data/strings.pak 2.1 Game strings data/tmflat.gr 2.4 data/tmobj.gr 2.4 data/views.gr 2.4 data/weap.gr (UW2) 2.4 data/weapons.gr (UW1) 2.4 1.2 GENERIC FILE FORMATS ======================== 1.2.1 Archive file (.ARK), Underworld II ---------------------------------------- The Underworld II archive format is more sophisticated than that used by the original Ultima Underworld, and allows compressed chunks. The basic format is a 6-byte header, followed by the chunk directory, followed by data. Chunk header: 0000 int16 No. directory entries 0002 int32 Unknown (always 0) The chunk directory consists of 4 arrays of 32-bit integer values. Each array has a file size of (4 * no. entries) bytes. The arrays are, in the order in which they appear in the file: 0 File offset. Each chunk has an entry giving the (absolute) offset within the file of the start of the chunk data, or zero if the chunk is not present. 1 Flags. This is a 32-bit value, but only the low 3 bits are used: bit 0: Should be compressed. bit 1: Actually is compressed. bit 2: Slop. 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. 2 File size. This gives the actual space occupied by the chunks on disc. 3 Available size. This is valid for chunks with extra slop. It gives the total space available for the chunk in the archive file. 2.1 GAME STRINGS, file data/strings.pak ======================================= This file uses a Huffman compression scheme to store its strings. The first 2 bytes of the file gives the number of nodes in the tree. Then follow the nodes themselves, 4 bytes each: 0000 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 ID 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 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. 2.2 LEVEL MAPS, file data/lev.ark 2.2.1 Level map header (Underworld) This contains the offsets to all the chunks in the level archive. There is a 2- byte value giving the number of chunk directory entries (0x0087 = 135 for 9x15 possible chunks) followed by a 4-byte offset for each chunk (measured from the start of the file). Chunks are arranged in blocks of 9, since there are 9 levels in Ultima Underworld. There is space in the header for 15 such blocks, though only 5 are actually used. 2.2.2 Level map The level map chunk for each level contains information about the level architecture (tile map) and the objects which live in it: Offset Size Description 0000 4000 Tile map (64x64x4bytes) 4000 1b00 Mobile object information (objects 000-0ff, 256x27bytes) 5b00 1800 Static object information (objects 100-3ff, 768x8bytes) 7300 01fc Free list for mobile objects (objects 002-0ff, 254x2bytes) 74fc 0600 Free list for static objects (objects 100-3ff, 768x2bytes) 7afc 0104 Unknown (260 bytes) 7c00 0002 7c02 0002 No. entries in mobile free list minus 1 7c04 0002 No. entries in static free list minus 1 7c06 0002 0x7775 ('uw') The first 16384 (0x4000) bytes of the level map chunk contain information about the tiles in the level map. There is a single 32-bit word for each tile of a 64x64 grid, 4096 in all. The tile description word contains the following bitfields: 0- 3 Tile type 0-9 4- 7 Floor height 10-13 Floor texture 16-21 Wall texture 22-31 First object in tile (index into object list) 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 Object information, normal object The first 8 bytes of all object definitions (which is all of the definition for a static object) consists of 4 16-bit ints: 00 objid / flags: 0- 8 Object ID (see below) 12 Enchantment flag (enchantable objects only) 13 Direction flag (doors) 14 Invisible flag (don't draw this object) 15 Link flag (link field is quantity/special) 02 position: 0- 6 Object Z position (0-127) 7- 9 Heading (*45 deg) 10-12 Object Y position (0-7) 13-15 Object X position (0-7) 04 quality / chain: 0- 5 Quality 6-15 Index of next object in chain 06 link / special: 0- 5 Owner / special 6-15 Quantity / link / special property Note: I got this from examining the dump from Alistair Brown's rather excellent UW2 editor. Get it at http:// The link field can be used for various things. If the link flag is 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). If the link flag is set, this field is a quantity or special property. Quantity is straightforward; if the value is less than 512 it gives the number of a stackable item that are present in the stack, so identical items may be combined into one object definition. Quantities >= 512 are special properties. The value is the link field minus 512 (unsurprisingly), the meaning depends on object type and flags. 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 the LOWER spell range (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. The `owner/special' field for a bed (Underworld II only) determines the colour of the sheets and pillow. The sheets are colour (4*special+5), the pillow colour (4*special). Free list, mobile objects This consists of an int16 for each mobile object (critter) slot which is not in use, giving the slot position in the master object list. Note that there are only 254 entries in this table because object 0 is always the null object (and hence is never allocated) and object 1 is always the avatar (and can never be free - that's probably a metaphor for life, or something). Of course, only the first (no. free mobile objects) entries are valid. Free list, static objects This consists of an int16 for each static object which is not in use, as above. This table is 768 entries long (room for all possible static objects). 2.2.3 2.2.4 Level texture list For Underworld I this contains 48 16-bit words for the wall textures (64x64 texture ID in data/w64.tr), followed by 10 16-bit words for the floor textures (32x32 texture ID in data/f32.tr), followed by 6 bytes whose meaning I haven't yet deciphered. For Underworld II this simply consists of 64 16-bit words giving the main (64x64) texture ID, in data/t64.tr, to use for each possible map texture. 2.3 2.3.x Object IDs 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 (?) 0170-017F Switches 0180-019F Traps 01A0-01BF Triggers 01C0-01CF Explosions/splats, fountain, silver tree, moving things 2.4 GRAPHICS FILES, data/*.gr 2.4.1 Graphics file header 0000 int8 Graphic file format: 01 .gr 02 .tr 03 .cr 04 .sr 05 .ar 0001 int16 no. bitmaps 0003 - xxxx int32 offset to bitmap 2.4.1 Bitmap format 00 int8 type : 04 8-bit uncompressed 08 4-bit run-length 0A 4-bit uncompressed 01 int8 width 02 int8 height For the 4-bit formats (08 and 0A) there follows a byte indicating which of the auxiliary palettes in data/allpals.dat to use. This file is simply a set of 16-byte tables containing, for each possible nybble, the index in the main palette it represents in this bitmap. Then follows, for all formats, a 16-bit word giving the size of the data stored in the file. NOTE however that this depends on the word length; for 8-bit formats it is the number of bytes but for 4-bit formats it is the number of nybbles. After that follows the bitmap data itself. Palette index 0 is transparent. UNCOMPRESSED BITMAPS (type 04) These are straightforward and should require no additional explanation 8-) 4-BIT RUN-LENGTH COMPRESSED BITMAPS (type 08) Type 8 (4-bit run-length) bitmaps are a little more interesting. The word length is 4 (nybbles); we take the high nybble first if we only need one from a byte. (in general for LG files, if we only need part of a byte take the high bits first and save the low for later). A _count_ is obtained as follows: take a nybble, call it c. If c == 0, take the next 2, then c = (n1 << wordsize) + n2. [wordsize is 4 in this case] If c == 0 still, take the next 3 nybbles, then c = (n1 << 2*wordsize) + (n2 << wordsize) + n3. I haven't encountered any case where more than this is needed. A count is therefore between 1 and 6 nybbles long. There are 2 types of record: run of bytes and repeated byte. (This should come as no surprise). A run record consists of a count followed by that number of nybbles; for each of these the byte output is the palette index in the auxiliary palette corresponding to that nybble. A repeat record consists of a count followed by a single nybble; the corresponding palette index is written (count) times. We start off with a repeat record and (normally) alternate between repeats and runs. However, as there is no point in repeating a nybble fewer than 3 times, counts 1 and 2 in a repeat record are special. 1 Skip this record. No repeat is performed, the next run follows immediately. This is used only at the very beginning of the compressed data if it should start with a run rather than a repeat. 2 Multiple repeats. Get another count, then extract that number of repeat records before the next run. 3+ Normal repeat record, this is the repeat count. It also looks as if a run record containing a single zero byte (10) marks the end of the compressed data, but this is not always present. NOTE that there also exists a 5-bit compressed format which is exactly the same as the above except that the word length is 5 bits instead of 4. This is used for critter animation frames in the crit/ subdirectory. The auxiliary palette contains 32 entries and is stored with the animation. 4-BIT UNCOMPRESSED BITMAPS (type 0A) These are simple enough: for each nybble in the file, the colour index in the bitmap proper is the corresponding index in the auxiliary palette. 2.5 CONVERSATION SCRIPTS, data/cnv.ark This file controls the conversations with the various NPCs in the game. 2.5.1 File header and offsets list The header is very simple, and consists of the number of available conversation slots followed by the file offset to each conversation. 0000 int16 number of conversation slots (not all need be used) 0002 .. int32 File offsets to conversations The name of the NPC involved in conversation n is in string (n+16) in chunk 6 of strings.pak . If a conversation is absent its offset will appear as zero in the file header, otherwise is measured from the start of the file. 2.5.2 Conversation header Each conversation has a header of its own as follows (16 bytes): 0000 int32 ?? 0004 int32 Length of code in 16-bit words 0008 int16 Unused? Always 0 000A int16 Seems to be conversation slot no. + 0x0E00. NPC name? 000C int16 No. words of private global variable space 000E int16 No. of import definitions in the imports table Following on from the conversation header comes the imported functions table. 2.5.3 Imported functions table The imported functions table contains the game builtins called from the bytecode using the CALLI opcode, and also imported game-global variables referenced by it. This table maps the function and variable names to 16-bit IDs used in the conversation code. Import definitions are variable length, depending on the length of the function name: 0000 int16 Length of name (=n) 0002 n*char Function/variable name. The builtin is recognised by this n+02 int16 Function/variable ID. n+04 int16 n+06 int16 Type. 0111=builtin, 010F=global n+08 int16 Return/variable type. 0000=void, 0129=int, 012b=string The ID spaces for builtins and variables are separate, so the IDs will overlap. The game global variables referenced in the table are loaded into the conversation's global memory (each conversation has its own memory block) when then conversation is started, and updated from conversation memory on exit. Following the imported functions list is the code itself. 2.5.4 The conversation code virtual machine Conversation code is run on a 16-bit stack-based virtual machine. Operands for most operations are taken from the stack, and the results pushed onto it. Each conversation has its own private global variable space, which persists between calls to the conversation script. The virtual machine has the following 16-bit "registers": Program counter (IP): Address of next opcode to be interpreted. Stack pointer (SP): Local variables, parameters and function return pointers are stored on the stack. Frame pointer (BP): Each function has a stack frame: the frame pointer is used as the base for locals and parameters. (it is called BP because that register is typically used as a frame pointer on the x86) Result register (REG): Used to store the result of a function. Accessed by pushing it onto the stack with a PUSH_REG opcode. Follows a summary of the opcodes and their effects. Here s[0] refers to the value on the top of the stack, s[1] the next and so on. 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. 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 (subtract) 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. 2.6 FONTS, data/font*.sys Characters are stored individually as 1-bit bitmaps, high bit to the left. 2.6.1 Font file header This is 12 bytes long: 0000 int16 Always 1. Might be size of character width field 0002 int16 Size of a single character bitmap in bytes 0004 int16 Width of a blank (space) character 0006 int16 Character height in pixels 0008 int16 Width of a character row in bytes 000A int16 Maximum width of a character in pixels 2.6.2 Font bitmaps Each character has its own bitmap. Bitmap size is constant. Each character row is stored in turn. Character bitmaps are followed by a single byte giving the character width in pixels. 2.7 3D OBJECT MODELS These are stored within the executable (bad! bad!) There is a table of 2-byte model offsets, with room for 64 models. Models are 00 - 01 door frame 02 bridge 03 bench 04 Lotus Turbo Esprit (no, really!) 05 small boulder 06 medium boulder 07 large boulder 08 arrow 09 beam 0A pillar 0B shrine 0D painting 10 texture map (8-way lever) 11 texture map (8-way switch) 12 texture map (writing) 13 gravestone 14 texture map 15 - 16 ?texture map 17 moongate 18 table 19 chest 1A nightstand 1B barrel 1C chair 1D bed (UWII) 1E blackrock gem (UWII) 1F shelf (UWII) They are command-based like System Shock models. Following are the currently known commands: 0000 end node. This terminates the model definition if no child nodes remain 0006 define sort node 0000 command = 0x0006 0002 sort plane definition (3 comps) 000E offset to first child 0010 offset to second child 000C define sort node 0000 command = 0x000C 0002 sort plane definition (2 comps) 000A offset to first child 000C offset to second child 0010 define sort node 0000 command = 0x0010 0002 sort plane definition (2 comps) 000A offset to first child 000C offset to second child 0014 ??? 0040 ??? This seems to occur only before a face vertex list and consists only of the command itself 0058 backface cull, arbitrary heading 0000 command = 0x0058 0002 length of face information 0004 normal vector X component 0006 face->origin X component 0008 normal vector Z component 000A face->origin Z component 000C normal vector Y component 000E face->origin Y component 0010 ... face information 005E define face plane Z/Y 0000 command = 0x005E 0002 length of face information 0004 normal vector Z component 0006 face->origin Z component 0008 normal vector Y component 000A face->origin Y component 000C ... face information 0060 define face plane X/Y 0000 command = 0x0060 0002 length of face information 0004 plane definition (as 0x005E but components are X, Y) 000C ... face information 0062 define face plane X/Z 0000 command = 0x0062 0002 length of face information 0004 plane definition (as 0x005E but components are X, Z) 000C ... face information 0064 define face plane X. This is a shorthand for faces parallel to the X=0 plane i.e. whose normal vector Y and Z components are zero 0000 command = 0x0064 0002 length of face information 0004 normal vector X component 0006 distance model origin -> face 0008 ... face information 0066 define face plane Z. This is similar to command 0064 but the face is parallel to the Z=0 plane and only the normal vector Z component is given 0068 define face plane Y (similarly) 0078 define model centre 0000 command = 0x0078 0002 vertex no. to use as centre (??) 0004 X 0006 Y 0008 Z 000A ?? Because of the limited resolution available for specifying object positions, the origin for model coordinates will typically need to be offset from the centre of the collision volume. This parameter gives that centre, in model coords. 007A define initial vertex 0000 command = 0x007A 0002 vertex X coord 0004 vertex Y coord 0006 vertex Z coord 0008 vertex no. 007E define face vertices 0000 command = 0x007E 0002 no. vertices 0004 ... vertex numbers 0082 define initial vertices 0000 command = 0x0082 0002 no. vertices 0004 first vertex no.? 0006 vertex 1 X coord 0008 vertex 1 Y coord 000A vertex 1 Z coord 000C vertex 2 X coord (...) 0086 define vertex offset X 0000 command = 0x0086 0002 base vertex 0004 offset from base in X direction 0006 number of new vertex 0088 define vertex offset Z (as above, but offset is in Z direction) 008A define vertex offset Y (similarly) 008C define vertex variable height. This is used for the pillar and doorframe where the model must reach up to the ceiling, wherever it be. 0090 define vertex offset X,Z 0000 command = 0x0090 0002 offset from base in X direction 0004 offset from base in Z direction 0006 base vertex 0008 number of new vertex 0092 define vertex offset X,Y (as above, but offsets are X and Y) 0094 define vertex offset Y,Z (similarly) 00A0 shorthand texture-mapped face 0000 command = 0x00A0 0002 ??? 0004 vertex 1 (single byte, not multiplied by 8) 0005 vertex 2 0006 vertex 3 0007 vertex 4 00A8 define texture-mapped face 0000 command = 0x00A8 0002 ?? texture no. 0004 no. vertices 0006 vertex 1 no. vertex 1 u coord vertex 1 v coord ... 00B4 define face vertices with u,v information 0000 command = 0x00B4 0002 no. vertices 0004 vertex 1 no. 0006 vertex 1 u coord 0008 vertex 1 v coord ... 00BC define face shade (6 bytes) 0000 command = 0x00BC 0002 colour (address of colour variable) 0004 shade (dark value) 00BE ?? refers to 2 model variables 0000 command = 0x00BE 0002 var 1 0004 var 2 00CE define texture-mapped face This command seems to be identical to command 00B4 00D2 define texture mapped face (shorthand) 0000 command = 0x00D2 0002 vertex 1 (single byte, not multiplied by 8) 0003 vertex 2 0004 vertex 3 0005 vertex 4 00D4 define vertex shading values 0000 command = 0x00D4 0002 no. Gouraud vertices 0004 base colour for Gouraud shading (colour variable address) 0006 ... 00D6 introduce Gouraud shaded face 0000 command = 0x00D6 2.8 CLASS-SPECIFIC OBJECT DATA, data/objects.dat This file contains various tables giving information about objects in the various classes. 2.8.1 Header, 2 bytes: 0000 0F 01 Not sure what this is, might be a file ID 2.8.2 Melee weapons table This contains 8 bytes per melee weapon. There are 16 entries in this table, for objects with ID 0x000-0x00F. It begins at file offset 0x0002. Each entry has the form 0000 ?? damage modifier for Slash attack 0001 ?? damage modifier for Bash attack 0002 ?? damage modifier for Stab attack 0006 skill type (3 sword, 4 axe, 5 mace, 6 unarmed) 0007 durability 2.8.3 Ranged weapons and missiles table This contains 3 bytes per ranged weapon / missile. There are 16 entries in this table, for objects with ID 0x010-0x01F. It begins at file offset 0x0082. Each entry has the form 0002 durability 2.8.4 Armour and wearables table This contains 4 bytes per armour item / wearable thing. There are 32 entries in this table, for objects with ID 0x020-0x03F. It begins at file offset 0x00B2. Each entry has the form 0000 protection 0001 durability 0002 ?? 0003 category 00 shield 01 body armour 03 leggings 04 gloves 05 boots 08 hat 09 ring 2.8.5 Critters table This contains 48 bytes per critter. There are 64 entries in this table, for objects with ID 0x040-0x07F. It begins at file offset 0x0132. 2.8.6 Containers table This contains 3 bytes per container object. There are 16 entries in this table. It begins at file offset 0x0D32. Each entry has the form 0000 capacity 0001 objects accepted (0xFF any) I suspect that there is a lighting and a foodstuff information table after here in the file, but have not investigated. 2.8.7 Animation object table This contains 4 bytes per animation object. There are 16 entries in this table. It begins at file offset 0x0DA2. Each entry has the form 0002 start frame (in animo.gr) 0003 no. frames 2.9 TEXTURE PROPERTIES, data/terrain.dat The file terrain.dat in the data directory contains information on the terrain types represented by the various wall and floor textures. There is a 16-bit word per texture, up to a maximum of 256 walls and 256 floors. Floor data therefore starts at file offset 0x200. Terrain types are: 0000 Normal (solid) wall or floor 0002 Ankh mural (shrines) 0003 Stairs up 0004 Stairs down 0005 Pipe 0006 Grating 0007 Drain 0008 Chained-up princess 0009 Window 000a Tapestry 000b Textured door (used for the lock to the Key of Infinity) 0010 Water (not waterfall) 0020 Lava (not lavafall) 3.1 Common object properties These are stored in the file "comobj.dat". The number of object properties is determined by (filelen-2) / 11 (each entry is 11 bytes long). In fact, there are always 512 entries, one for each possible object (not all objects exist). The first two bytes contain unknown information. My guess is that it is a file ID. Each entry has the following format: 0000 Int8 Height 0001 Int16 b0-2 radius b4-11 mass in 0.1 kg 0003 Int8 flags (lower 4 bits only) 0/2: object in range [336, 368[ seem to be 3d objects 4: decal object 0004 Int16 value (?) 0009 Int8 unknown2 000A Int8 lower 4 bits are quality type 0-f Each possible value of "quality type" maps onto a group of 6 strings in chunk 4 from lowest to highest quality. Items which have a quality are always described as "a/an ", with the exception of group D which is for armour items which are gramatically plural even if there is only one of the object, e.g. "leather leggings". 3.x TRAPS Traps have various effects on the game world. Traps have object IDs from 0180 (384) to 0190 (400) 0180 Damage trap 0181 Teleport trap 0182 Arrow trap 0183 "Do" trap 0184 Pit trap 0185 Change terrain trap 0186 Spell trap 0187 Create object trap 0188 Door trap 0189 Ward trap 018A "Tell" trap 018B Delete object trap 018C Inventory trap 018D Set variable trap 018E Check variable trap 018F Combination trap 0190 Text string trap 3.x.2 Teleport trap (object 0181 385) This trap teleports the player. The destination level is given by the trap z position (0 ==> remain on the current level). The destination tile coordinates are given by the "owner" and "quality" fields. 3.x.5 Pit trap (object 0184 388) This is probably a bottomless pit that drops the player through to the next level. 3.x.8 Create object trap (object 0187 391) This trap creates an object when it is set off. The quantity/link field points at the object to create. 3.x.9 Door trap (object ID 0188 392) When set off, this trap opens or shuts a door. The quantity/link field points at the lock, if the door has one. 3.x.17 Text string trap (object 0190 400) This trap causes the player to get a text message when it is set off. The "Unk2" field of the object definition contains the string number per level, in game string block 8. The actual string number printed is (64*level + unk2).