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 Rule | Runtime Behavior |
|---|---|
| Verbatim Byte Copy | Every 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 Whitelist | The 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 Cap | Inputs 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 Awareness | The 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 Construction | The 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 Event | Ghidra Provenance & Engine Behavior |
|---|---|
| Global Callback | When 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 Independence | Because 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.exesupports pulling a TXI from the/overridefolder even if the parent texture was sourced natively from aKEY/BIFpackage. Rakata maintains this independent sidecar lookup model natively via therakata_extract::resolver::TextureWithTxiResultlogic 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 Event | Ghidra Provenance & Engine Behavior |
|---|---|
| First-Match Exit | When 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 Checking | During 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) →RIM→ERF(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-extractcrate natively replicates this exact priority order through theCompositeModulestruct. When you pass a directory path toCompositeModule::load_from_directory, it automatically scans the folder and merges the_dlg,_s,_a/_adx, and base.modfiles 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 Event | Ghidra Provenance & Engine Behavior |
|---|---|
| Primary MOD Search | The game natively attempts to load the highest-level package by explicitly targeting the MODULES:<root>.mod path first. |
| RIM Fallback Chains | If the .mod file doesn’t exist, the system catches the failure and immediately shifts to look for the <root>_s.rim fallback. |
| Area Extension Probes | Throughout 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. |