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

KEY (Global Index)

Think of the KEY file as the absolute master table of contents governing the entire game directory. Because uncompressed BIF archives are completely blind payloads that contain no internal filenames, the KEY file acts as the singular, authoritative index that tells the engine exactly which BIF holds which file, and precisely where to seek inside that BIF to find it.

At a Glance

PropertyValue
Extension(s).key
Magic SignaturesKEY (version V1 )
TypeArchive Global Index
Rust ReferenceView rakata_formats::Key in Rustdocs

File Layout

Four blocks, and only three of them are found the usual way. The header sits at 0x00, the BIF file table and the key table are each located by their own header offset, and the BIF filenames are not a located block at all: every file entry carries its own offset and length pointing into the filename region, so the strings are addressed individually rather than as a table.

BlockSizeLocated by
Header64 bytesAlways at 0x00
BIF file table12 bytes per BIFfile_table_offset
BIF filenamesvariableEach file entry’s own filename_offset and filename_size
Key table22 bytes per keykey_table_offset

Header (64 bytes)

OffsetFieldTypeNotes
0x00magicfourccKEY , trailing space included.
0x04versionfourccV1 , with two trailing spaces. See the version note below.
0x08bif_countu32
0x0Ckey_countu32
0x10file_table_offsetu32
0x14key_table_offsetu32
0x18build_yearu32Years since 1900.
0x1Cbuild_dayu32Day of the year.
0x20reserved32 bytesPreserved verbatim on round-trip.

BIF File Entry (12 bytes each)

OffsetFieldTypeNotes
0x00file_sizeu32Size of the BIF this entry names.
0x04filename_offsetu32Absolute, from the start of the KEY.
0x08filename_sizeu16Includes the terminating NUL: the count is the string length plus one, in every entry of a retail chitin.key.
0x0Adrivesu16A bitfield naming which install medium holds the BIF. Nothing in the audits below traces what swkotor.exe does with it, so treat it as unexamined rather than inert.

Key Entry (22 bytes each)

OffsetFieldType
0x00resrefchar[16], null-padded
0x10type_idu16 (see the resource type codes)
0x12resource_idu32

The resource_id bitmask

This is the whole point of the format, and it is what the engine’s payload mapping below is tearing apart. A resource_id is not an index into anything on its own. It is two numbers packed into one word:

BitsMeaning
31..20Index into the BIF file table
19..0Index of the resource within that BIF

So resolving a resource is: find the key entry by resref and type, split its resource_id, use the high twelve bits to pick a BIF from the file table, and the low twenty bits to pick an entry inside it.

Warning

The format allocates twenty bits to the index and the loader reads fourteen, so a BIF holds 16,384 resources at most Both halves are confirmed and they do not conflict. The twenty-bit split above is what the field carries: across every key entry in a full install, the low twenty bits equal the resource’s index within its BIF and the high twelve equal that BIF’s position in the file table, without a single exception. The loader is narrower. The BIF audit records CExoResFile::ReadResource masking with 0x3fff before scaling by the 16-byte entry stride, which is fourteen bits, and nothing on that path reads or tests bits 14 through 31 at all.

Bits 14 to 19 are therefore allocated by the format and thrown away by the reader, and the consequence lands on a writer rather than a reader. A BIF carrying more than 16,384 resources yields a KEY the engine resolves to the wrong entry, quietly, because index 16,384 masks down to zero. The bounds check on that path does not help: it compares the masked index against variable_count, so an aliased index is in range and reads as a legitimate hit. Rakata implements the full twenty bits and is thus more permissive than the engine. Treat 16,384 as the ceiling.

Nothing shipped comes near it. The largest vanilla archive, models.bif, holds well under half that many resources, so no vanilla lookup can reach the divergence and a tool that only ever reads retail data will never see it.

Note

The version field has two trailing spaces It is V1 , not V1.0. A V1.1 constant exists in our reader and is accepted only in compatibility mode; the canonical K1 mode takes V1 alone, matching the engine. As the audit below records, there is no V1.1 branch anywhere in vanilla K1 and it is not established that such a file exists at all.

Engine Audits & Decompilation

Read from CExoKeyTable::AddKeyTableContents at 0x0040fb80 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.

Key Table Registration (CExoKeyTable::AddKeyTableContents)

Mapped from 0x0040fb80.

ActionEngine Behavior
Signature CheckValidates exactly for the KEY magic and the explicit V1 version signature.
Version BranchingThere is absolutely zero logic handling any speculative V1.1 version branch in vanilla K1. It is currently unknown if a V1.1 KEY format actually exists in the wild, but the engine certainly wouldn’t load it.
Payload MappingExtrapolates the file location natively by tearing apart the ResourceId bitmask to locate both the target BIF file index and the internal struct array offset.

Note

The engine handles KEY table loading extremely early in the application lifecycle during CExoBase::InitObject. If a global KEY fails to mount due to malformed headers, the engine immediately aborts execution.