RIM (Resource Image)
RIM files operate as a radically leaner alternative to ERFs. They are used exclusively by the game engine for distributing absolutely essential or lightweight modules without the hefty structural metadata overhead of an ERF file. They provide rapid, self-contained loading for core engine environments.
At a Glance
| Property | Value |
|---|---|
| Extension(s) | .rim |
| Magic Signatures | RIM (version V1.0) |
| Type | Lightweight Archive |
| Rust Reference | View rakata_formats::Rim in Rustdocs |
File Layout
Three blocks: a fixed 120-byte header, a single key table, and the payload. The key table is found by keys_offset out of the header, and each entry carries its own payload offset, so nothing here depends on the blocks sitting next to each other.
| Block | Size | Located by |
|---|---|---|
| Header | 120 bytes | Always at 0x00 |
| Key table | 32 bytes per entry | keys_offset |
| Resource payload | remainder of the file | Each entry’s own data_offset |
Warning
Vanilla RIMs are padded, not tightly packed, and none of it is optional-looking Measured across every static RIM in a retail install, a shipped archive has structure between its payloads that a naive writer will not reproduce:
- Every
data_offsetis 4-byte aligned, without exception.- Consecutive resources follow
next_offset == round_up_4(prev_end) + 16in all but a handful of non-empty archives, a mandatory 16-byte gap on top of the alignment padding.- Interior gaps are zero-filled, never left as stale bytes.
- An 8-byte zero gap separates the key table from the first payload, widening to 72 bytes in two outliers.
- Every archive ends with exactly 10 zero bytes past the last payload.
A writer that packs tightly produces a structurally valid file the engine will read, and one that resembles no archive BioWare shipped. Since offsets are read from the table rather than inferred, the padding is not load-bearing for a reader. It is what a byte-comparison against vanilla trips on, and what tells you a file was generated rather than shipped.
The leanness compared to ERF is structural rather than cosmetic. An ERF splits its bookkeeping across two parallel tables, one saying what each resource is and another saying where its bytes are. A RIM has one table that does both jobs, which is why its entries are 32 bytes where an ERF’s are 24 plus 8. There is also no localized string block and no description, which is the metadata a RIM gives up in exchange.
Header (120 bytes)
| Offset | Field | Type | Notes |
|---|---|---|---|
0x00 | magic | fourcc | RIM , trailing space included. |
0x04 | version | fourcc | V1.0. |
0x08 | reserved | u32 | Preserved verbatim on round-trip. |
0x0C | entry_count | u32 | |
0x10 | keys_offset | u32 | |
0x14 | reserved | u32 | Preserved verbatim on round-trip. |
0x18 | reserved | 96 bytes | The dead zone described below. |
Key Entry (32 bytes each)
| Offset | Field | Type | Notes |
|---|---|---|---|
0x00 | resref | char[16] | Null-padded, not null-terminated. |
0x10 | type_id | u32 | Four bytes on disk holding one of the resource type codes. See the note below. |
0x14 | resource_id | u32 | The entry’s own index. Not the packed word KEY uses. See below. |
0x18 | data_offset | u32 | Absolute, from the start of the file. |
0x1C | data_size | u32 |
Note
resource_idis the entry’s index, not the KEY’s packed word The field shares a name with KEY’s and does not share its meaning. A KEY packs an archive number and an entry number into one word, because a KEY entry has to name which BIF it is talking about. A RIM answers only for itself, so there is nothing to pack: the value is the entry’s own position in the key table, matching in every entry of every RIM under a retail install’smodules/andrims/.It is therefore redundant with the array index a reader already has, and a writer produces it by counting. The high twelve bits, which are the archive half in a KEY, are zero throughout.
Note
type_idis four bytes wide holding a two-byte value Resource type codes are 16-bit everywhere else in the engine, including inKEYandERF, but a RIM key entry gives the field a full four bytes.The engine truncates.
CExoKeyTable::AddResourceImageContents(0x0040f990) copies all four bytes out of the entry unmasked, then passes the value toAddKey(0x0040e990) through an explicit two-byte cast. Everything after that point sees the truncated value: it is what gets hashed, what gets compared against existing entries, and what is stored into the live table’s two-byte type field. There is no bounds check anywhere on either side of the cast and no rejection path at all, so an out-of-range value does not fail. It silently aliases to whatever survives the truncation.No vanilla file exercises it. Across every key entry in every static RIM archive the upper half is zero, and every type value present is a real resource type code. That means the corpus is silent on the question rather than agreeing with the engine: it never produces a value the truncation would change. The population is a retail install’s
modules/andrims/; saves contain no RIMs and were not searched.Rakata’s reader rejects any value that does not fit a
u16. That remains our own strictness rather than a match for engine behaviour, and now in a specific direction: the engine would have accepted such a file and quietly mangled the type, where we refuse it. Keeping the rejection is the right call, but it is a choice to be stricter, not conformance.
Engine Audits & Decompilation
Read from CExoKeyTable::AddResourceImageContents at 0x0040f990 in swkotor.exe, with each subsection naming its own function below. Provenance: derived, not attested. The rows below have not been separately re-derived, so they sit on the reverse-engineering queue.
Resource Image Overrides (CExoKeyTable::AddResourceImageContents)
Mapped from 0x0040f990.
| Action | Engine Behavior |
|---|---|
| Signature Check | Explicitly validates the exact RIM magic and the V1.0 version string implicitly upon loading. |
| Header Evaluation | The engine physically reads the entry_count (offset 0x0C) and the keys_offset (offset 0x10) from the header to explicitly navigate the file structures. |
Tip
The 96-Byte “Dead Zone” Exactly like the
ERFdead zone, RIM files feature a massive 96 bytes of completely inert padding sitting physically between offsets0x18and0x77inside the 120-byte header. The engine blindly sweeps right past it during initialization. It is perfectly safe to zero out this region when generating new synthetic fixtures.