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

Resource System & Resolution

The Odyssey Engine’s resource resolution dictates exactly how the game searches for files when it needs to render a texture, load a module, or mount a script – including the exact precedence logic when multiple mods attempt to overwrite the same asset.


ResRef Validation

A ResRef is the engine’s primary resource identifier: a fixed 16-byte buffer used everywhere a resource needs to be named (KEY/BIF entries, RIM keys, GFF resref fields, save-game resource handles, network messages, etc.).

Engine Audits & Decompilation

The following documents how swkotor.exe constructs and stores CResRef instances.

(Decompilation logic for this section was audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from the constructor family CResRef::CResRef at 0x00405ed0, 0x00405ef0, 0x00406d60, 0x00406d80, 0x00406da0, and the network read path CSWMessage::ReadCResRef at 0x004d6180.)

Engine RuleRuntime Behavior
Verbatim Byte CopyEvery CResRef constructor performs a straight memcpy of up to 16 bytes from the source into the internal buffer. There is no character classification, no rejection of “invalid” bytes, and no separate ValidResRef helper anywhere in the binary.
No Character WhitelistThe engine recognizes no [A-Za-z0-9_-] style restriction. The binary contains no “invalid resref” or “bad resref” error strings. Real vanilla content depends on this freedom: chitin.key includes upgrade-modifier resrefs containing +, RIM key tables include !, and similar punctuation appears in script and texture names across the vanilla corpus.
16-Byte CapInputs longer than 16 bytes are silently truncated by the constructor (if (0xf < param_2) { param_2 = 0x10; }). The remaining buffer is zero-padded.
No UTF-8 AwarenessThe engine treats the buffer as raw bytes. There is no encoding-aware comparison. Resources stored under a non-ASCII byte sequence in the source data would be looked up by exact byte equality (after the engine’s case-insensitive comparison logic kicks in elsewhere).
Default ConstructionThe empty constructor (0x00405ed0) zeros all 16 bytes. A null-pointer source falls through to the same all-zero state.

How Rakata Models This

rakata_core::ResRef accepts any single ASCII byte (0..=0x7F) up to 16 bytes and lowercases ASCII letters for canonicalization (matching the engine’s case-insensitive lookup). Multi-byte UTF-8 input is rejected because Rust’s &str API forces inputs to be UTF-8 valid, and a multi-byte sequence in a &str cannot represent the same byte the engine would store under Windows-1252 conventions; round-tripping such input would silently corrupt the lookup key. Vanilla content is exclusively single-byte and unaffected.

The 16-byte cap and case-insensitive canonicalization match the engine. The ASCII-only restriction is a Rust-side safety net rather than an engine constraint.


TXI Sidecar Lookup

Texture Extensions (TXIs) are independent ascii text configurations used to override material instructions for specific graphics.

Engine Audits & Decompilation

The following documents the engine’s exact load sequence for TXI sidecar files mapped from swkotor.exe.

(Decompilation logic for this section was audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from AurResGet at 0x0044c740.)

Pipeline EventGhidra Provenance & Engine Behavior
Global CallbackWhen the game needs a TXI file, it always routes through a global helper calling AurResGet(name, ".txi", ..., true). Three different rendering systems use this exact same path to hunt for TXIs: CAurTextureBasic::Init, Gob::EnableRenderBumpedOut, and Material::Init.
Total IndependenceBecause AurResGet only checks the raw filename and the .txi extension, it performs a totally fresh, global search through the game’s file systems. It does not know or care where the parent texture actually came from (like a specific BIF archive).

Note

Because it is entirely independent from the parent texture handle, swkotor.exe supports pulling a TXI from the /override folder even if the parent texture was sourced natively from a KEY/BIF package. Rakata maintains this independent sidecar lookup model natively via the rakata_extract::resolver::TextureWithTxiResult logic to guarantee resolver parity.


Key/BIF Resolution Mapping

The engine has a strict hierarchical override order when hunting for identical overlapping resource identifiers across multiple virtual disk mounts.

Engine Audits & Decompilation

The following documents the engine’s exact resource directory search order mapped from swkotor.exe.

(Decompilation logic for this section was audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from CExoKeyTable::FindKey at 0x0040ec50.)

Pipeline EventGhidra Provenance & Engine Behavior
First-Match ExitWhen hunting for a file, the key table loops through standard folders in a hardcoded order. The second it finds a matching file name, FindKey returns success and completely ignores any duplicates hiding deeper in other archives.
Duplicate CheckingDuring startup, the engine’s AddKey function actually scans for duplicates. If it finds one, it ignores it, permanently locking in the file that had the higher resolution priority.

Tip

Resolution Priority:
resource_directory (Override folder) → ERF (Pass 1) → RIMERF (Pass 2) → Fixed / Archive


Module Loading Priorities

Modules orchestrate KOTOR’s area hubs. They are layered collections of ERF/RIM files functioning as a localized state.

Composition Loading Precedence

Because KOTOR modules are often fragmented into multiple discrete archive files (e.g., separating rigid layouts from variable area dialog), it uses the following concrete precedence when constructing a single “virtual” module (the order below lists the highest priority target first).

1. <root>_dlg.erf (K2 Dialog overrides)
2. <root>_s.rim (Supplemental properties)
3. <root>_a.rim (Base Area) if present, ELSE <root>_adx.rim (Extended Area) if present, ELSE <root>.rim (Main/Vanilla)
4. <root>.mod (Single-file Mod archive)

Tip

Rust Integration The rakata-extract crate natively replicates this exact priority order through the CompositeModule struct. When you pass a directory path to CompositeModule::load_from_directory, it automatically scans the folder and merges the _dlg, _s, _a / _adx, and base .mod files together using the engine’s strict precedence hierarchy.

Engine Audits & Decompilation

The following documents the engine’s exact load sequence for module assemblies mapped from swkotor.exe.

(Decompilation logic for this section was audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from CExoResMan::AsyncLoad at 0x004094a0.)

Pipeline EventGhidra Provenance & Engine Behavior
Primary MOD SearchThe game natively attempts to load the highest-level package by explicitly targeting the MODULES:<root>.mod path first.
RIM Fallback ChainsIf the .mod file doesn’t exist, the system catches the failure and immediately shifts to look for the <root>_s.rim fallback.
Area Extension ProbesThroughout the module loading process, the engine actively probes for the <root>_a.rim and <root>_adx.rim extension files to violently merge in the physical area geometry.