Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

MDL Format (Model Hierarchy)

The .mdl format serves as the overarching structural spine for 3D model geometry. Rather than storing literal vertex positions directly, it recursively structures a tree of generalized nodes (Bones, Trimeshes, Lights, Emitters) into a unified visual mesh. It delegates vertex geometry out, binds textures, links dynamic controllers (keyframe transformations), and maps bounding sphere matrices directly to the model’s rigid physical space.

At a Glance

PropertyValue
Extension(s).mdl
Magic SignatureText (filedependancy) or Binary (\0 byte header)
Type3D Hierarchical Mesh
Rust ReferenceView rakata_formats::Mdl in Rustdocs

Data Model Structure

Rakata maps the .mdl binary tree exactly into rakata_formats::Mdl.

Because a model intrinsically utilizes 11 distinct struct sub-types, Rakata resolves the pointer-based tree structure into a secure Rust Vec<MdlNode>. Native file pointer offsets which are normally resolved inside KOTOR via an explicit raw memory relocation dump are converted into safe recursive structures at parse time.

Node Sub-Types

The engine determines exact node allocations using a rigid bitflag header.

Sub-TypeDescription
BaseA pure structure node (Dummy) acting strictly as an invisible visual group or spatial pivot.
LightProjects localized dynamic lighting, lens flares, and shading priorities.
EmitterConfigures particle spawning systems (fountains, single-shots, lightning, explosions).
CameraAn empty node serving as a static viewport anchor for dialogue cinematics.
ReferenceAn anchor point explicitly linking an external 3D model asset to a point.
TriMeshA rigid standard triangle geometry boundary carrying static vertex arrays.
SkinMeshA procedural mesh utilizing skeleton bone-weights and vectors to calculate organic deformations.
AnimMeshA mesh carrying hardcoded, explicitly sampled vertex coordinate animation loops.
DanglyMeshA sub-mesh evaluated through swinging physics constraints (displacement, tightness, period).
AABBA strict spatial collision tree structurally defining an internal walkmesh barrier.
SaberAllocates dynamic 3D quad arrays utilized exclusively to generate stretching lightsaber swing trails.

Engine Audits & Decompilation

Deep Dive: For an exhaustive archive of the Ghidra decompilation notes detailing the exact byte-level layout of the binary MDL format and engine loading pipeline, refer to the MDL & MDX Deep Dive.

The following information documents the engine’s exact load sequence for genuine Binary MDL models. All behavior was mapped from natively analyzing swkotor.exe execution pipelines via Ghidra.

Loading and Wrapper Validation

Read initially via Input::Read (0x004a14b0).

Pipeline EventGhidra Provenance & Engine Behavior
Binary vs ASCII DetectionThe engine checks the exact first byte of the file. If it hits a \0 (NULL), it dispatches the asset entirely to the InputBinary track. If it hits text ("filedependancy" or "newmodel"), it loops into the FuncInterp ASCII parser track.
Wrapper MappingThe Binary format evaluates the initial 12 bytes as an abstract Wrapper block defining explicit sizes for the .MDL and the associated .MDX geometry.
In-Memory Heap DumpThe engine allocates the sizes noted in the wrapper, runs memcpy on both the .MDL and .MDX assets blindly into memory, and then runs the recursive Reset path to relocate spatial internal pointer offsets to absolute memory addresses.

Node Dispatch Architecture

Read initially via InputBinary::ResetMdlNode (0x004a0900). The engine recursively navigates downwards matching against a constant 16-bit node-type flag lookup spanning from 0x0001 (Base Node) to 0x0821 (Lightsaber).

Mapped PropertyEngine Behavior
Sub-node Allocation SizesNodes are dynamically allocated varying byte lengths strictly based on their type-mask. A root Base node only evaluates 80 contiguous bytes, but an Emitter allocates 304, and a Skin allocates 512.
Parent/Child Graph ResolutionEngine structures evaluate nodes continuously downward via embedded raw pointer arrays. These arrays branch a group of distinct sub-children implicitly off their master parent. At load time, the engine must safely rewrite all relative file offsets into absolute physical memory locations, otherwise the entire hierarchy will instantly detach.

Mapped Behavior Quirks

Mapped PropertyGhidra Provenance & Engine Behavior
LOD Suffix GenerationThe engine natively evaluates if the cullWithLOD property is set. If true, it explicitly triggers string concatenations for FindModel(name + "_x") and FindModel(name + "_z") sequentially to dynamically attach lower-quality auxiliary geometry instances based on viewport distance.
Animation Bone BindingWhen building the live hierarchy tree for a rendering sequence, the engine explicitly ignores the node’s textual string name. Instead, it rigidly evaluates physical pairings against a mapped node_id integer. If the bone isn’t properly sequenced to that numeric ID array, it detaches from the runtime arrays entirely.
Self-Describing KeyframesUnlike older properties that rely on rigid dictionaries, KOTOR determines how an animation was saved dynamically by reading the keyframe’s controller type integer. It applies a bitwise AND check against the type’s lowest hex digit (& 0x0F) to instantly dictate whether the loaded keyframe is a single float (like scaling), 3 floats (like an XYZ positional vector), or 4 floats (for a Slerp quaternion rotation).

Proposed Linter Rules (Rakata-Lint)

While rakata-lint currently only evaluates GFF formats and does not yet parse .mdl models dynamically, the engine behaviors above hint at some suggested lint diagnostics:

Planned Lint Diagnostics:

  1. Skeleton / Animation Tracing: Flags animation nodes where the internal skeletal node_number binding parameter implicitly equals 0, ensuring the mesh does not hard freeze via pointing to the rigid root spine.
  2. Controller Mask Encoding: Validates that generic Controller properties properly bit-mask against the Bezier indicator (0x10) rather than reading explicitly raw quaternion values (which causes cascading loop failures through the rest of the array block).
  3. Emitter Detonation Allocation: Flags interactive Emitter nodes attempting to bind the detonate key (Controller 502) while structurally mis-identifying as "Fountain". The engine native only maps controller 502 data to strict "Explosion" memory paths, resulting in an aggressive Access Violation engine crash otherwise.
  4. Name Graph Sanitization: Notifies developers if the node graph contains artificially un-referenced graph pointers mapped under the unified Name Table. (BioWare notoriously shipped identical shared name tables compiling .pwk and .wok models into .mdl nodes natively throughout the 2003 pipeline).