Oblivion XML Reference
This page shall serve as a comprehensive guide to editing menus in Oblivion.
General concepts
Oblivion's UI is actually a 3D scene rendered overtop any gameplay or other displays. Every UI element has 3D geometry constructed for it at run-time; this geometry typically consists of numerous flat shapes layered overtop each other. As such, concepts related to 3D rendering — UV mapping, texture filtering, and so on — are occasionally relevant.
The user interface is defined using XML files. These files can define tiles — objects in a menu — and specify their traits — different aspects of their configuration, such as their size, position, or color. Traits can be specified as constant values, or as operators that can compute values dynamically.
Trait operators can be used to create interactive behaviors within menus, but this use is very specialized and very rare. It's far more common for behaviors to be provided by the game engine itself. Every menu consists of custom code in the engine, which interacts with specific tiles within the XML. These tiles are identified by their id trait values.
Some basic UI code would look like this:
<rect name="container"> <x>0</x> <y>0</y> <width>500</width> <height> <!-- Enforce a 16:9 aspect ratio --> <copy src="me()" trait="width" /> <div>16</div> <mult>9</mult> <!-- The value is computed at run-time. This code means: copy my width, divide it by 16, and multiply it by 9. --> </height> <image name="icon"> <filename>some_picture.dds</filename> <!-- Set the image to use its "natural" size: --> <width> <copy src="me()" trait="filewidth" /></width> <height><copy src="me()" trait="fileheight" /></height> <!-- Position this image on the bottom-right corner of its container. --> <x> <copy src="parent()" trait="width" /> <sub src="me()" trait="width" /> </x> <y> <copy src="parent()" trait="height" /> <sub src="me()" trait="height" /> </y> </image> </rect>
File formats
DDS
The DirectDraw Surface format is used for textures. It supports multiple compression modes. DXT1 (DirectX Texture 1) compression is used for textures with no alpha transparency or one-bit alpha transparency (i.e. every pixel is either fully opaque or fully transparent). The DXT3 and DXT5 compression algorithms are used for textures with full alpha translucency, but they produce files that are twice as large as DXT1. DXT3 is theoretically better for textures with steep alpha changes, while DXT5 is theoretically better for textures with smooth alpha gradients.
Note that Oblivion has three texture folders for menus: Menus, Menus50, and Menus80. The game apparently decides which textures to use based on the user’s screen aspect ratio – specifically, the ratio of the true screen height in pixels to the UI-normalized screen height (i.e. 0.5, 0.8). The game will default to using textures from the Menus folder when the Menus50 and Menus80 files are missing.
The user’s texture quality setting (iTexMipMapSkip in Oblivion.ini) also affects menu textures. Be warned: some popular DDS exporters (particularly GIMP-DDS) do not save non-mipmapped textures properly; they write a mipmap count of 1 when it should be 0. This causes the textures to display corrupted pixels if the player’s texture quality isn’t at the maximum setting. If you really must use non-mipmapped textures exported through GIMP, then you can fix this by hex editing your exported DDS files: change the four-byte value at offset 0x1C to zero.
Oblivion attempts to use bilinear interpolation on all menu textures. However, texture filtering is broken for an unknown reason. Code analysis has indicated that the problem occurs whenever the menu cursor is receiving texture filtering (which, in the vanilla game, is always). The effect of this is that you may see odd seams or dithering on some textures, and high-resolution textures scaled down may look badly blurred or even blurred and pixelated.
NIF
Games that use Gamebryo (formerly NetImmerse) rendering tech rely on the NetImmerse Format for 3D models and scenes. The file format has several variations, as data structures are changed or added to accommodate evolving technologies and the needs of specific games; this means that NIFs made for one game generally can’t be used in another without being converted somehow.
As mentioned earlier, Oblivion's entire UI is rendered in 3D, and geometry is constructed for UI elements at run-time. However, the NIF tile type can be used to insert custom-made 3D geometry into the UI directly. This is commonly used to create animating elements.
XML
eXtensible Markup Language is a text syntax used to represent arbitrary data. A simplified XML dialect is used to define the content of Oblivion’s menus.
Oblivion's XML parser takes a number of shortcuts:
- CDATA is not supported.
- XML headers/declarations and doctypes are not supported.
- Namespaces are not supported.
- Standard XML and HTML entities are not supported.
- Attribute values must be placed in double-quotes, not single-quotes.
- Everything is case-insensitive.
- Leading and trailing whitespace is ignored in all elements.
- The only supported attribute for tile elements is "name." This attribute is required; if it is missing, then parsing will fail and the game will crash.
- Attributes not explicitly specified in Oblivion's XML grammar should be avoided. It is unlikely that the parser will handle them properly.
Moreover, Oblivion's XML parser sometimes fails to distinguish between numeric and string values within XML elements. A value is considered "numeric" if it only contains digits, dashes, and periods. This means that the following values are all read as the number zero:
- 0
- 0.0
- -0.0
- .
- ...
- -.-.-.-.-.-
- ---
Advanced information
Parser overview
Oblivion uses a "two-and-a-half"-stage parser. The first stage examines a document's markup and generates a series of very basic, very generic tokens. The first-and-a-half stage examines those tokens and combines them when possible. The second stage converts tokens into Tile
objects in memory. Notably, templates only go through first- and first-and-a-half-stage parsing, and the resulting tokens are stored on each Menu
object in memory, keyed to the template name.
References between tiles (by way of operators using the src
attribute) are resolved at parse-time, and are not live-updated. That is to say: you cannot refer to a tile that doesn't exist yet (e.g. because it's generated from a template) and have that reference suddenly start working when the tile is generated. It is impossible for non-templated content to refer to templated content.
All relevant XML data — tile types, traits, operator types, and parse-time tokens — is identified using a common numeric ID space. Underscore-prefixed traits have IDs generated at runtime, starting from 10000. Code analysis suggests that these IDs are refcounted, and possibly recycled when such a trait is no longer in use.