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

BIF (Binary Information File)

A BIF is bulk uncompressed storage: tightly packed resource bytes with no metadata and no filenames. It is built to be read at a known offset, which is what the companion KEY index supplies.

At a Glance

PropertyValue
Extension(s).bif, .bzf (compressed; mobile ports only)
Magic SignaturesBIFF (version V1 ) for both
TypeArchive Blob Payload
Rust ReferenceView rakata_formats::Bif in Rustdocs

File Layout

A fixed 20-byte header, one or two resource tables, and the payload. The variable table is found by variable_table_offset; the fixed table, when a file has one, follows it directly. Payload bytes are addressed per entry.

BlockSizeLocated by
Header20 bytesAlways at 0x00
Variable resource table16 bytes per entryvariable_table_offset
Fixed resource table20 bytes per entryFollows the variable table
Resource payloadremainder of the fileEach entry’s own data_offset

There are no names anywhere in here. A BIF entry knows its own id, type, offset and size, and nothing else. The resref lives in the KEY, which reaches in by position. That is what makes a BIF unreadable on its own and why the two formats are always discussed together.

Header (20 bytes)

OffsetFieldTypeNotes
0x00magicfourccBIFF. No trailing space on this one.
0x04versionfourccV1 , two trailing spaces.
0x08variable_countu32
0x0Cfixed_countu32Zero in every shipped archive. See below.
0x10variable_table_offsetu32

Variable Resource Entry (16 bytes each)

OffsetFieldTypeNotes
0x00resource_idu32The KEY’s packed word for this resource, repeated on the BIF side. Derivable from position. See below.
0x04data_offsetu32Absolute, from the start of the file.
0x08data_sizeu32For a .bzf, the uncompressed length.
0x0Ctype_idu32Four bytes holding one of the two-byte resource type codes, as in RIM. Rakata rejects anything that will not fit in a u16; that is our strictness, not traced engine behaviour.

Fixed Resource Entry (20 bytes each)

OffsetFieldType
0x00resource_idu32
0x04data_offsetu32
0x08part_countu32
0x0Cdata_sizeu32
0x10type_idu32

Note

resource_id is positional, and it is the KEY’s word repeated A variable entry’s resource_id is the same packed value the KEY carries for that resource: the entry’s own index within this archive in the low twenty bits, this archive’s index in the KEY file table in the high twelve. Both halves hold in every variable entry of every archive a retail install’s chitin.key names.

The two files agree exactly. Every BIF entry is named by one key, no key points at an entry that is not there, and no two keys carry the same value, so the correspondence is one-to-one in both directions rather than merely consistent where it was checked.

That makes the field carry nothing a reader does not already have from position. A writer, though, has to compute it rather than copy it, because the high half is not a property of the BIF at all: it depends on where the archive lands in the KEY’s file table, which the BIF cannot see.

The fixed table’s resource_id is untested, since nothing shipped populates that table at all.

Note

The fixed table is a format feature nothing uses fixed_count is 0 in all 26 archives of a full vanilla install, without exception. The engine reads the scalar and then ignores it entirely, as the audit below records: files declaring fixed entries are accepted and those entries are never mapped. Rakata parses the table when one is present, so a file carrying it round-trips, but nothing in the game will ever look at what it holds.

The Compressed Variant (.bzf)

Compression did not exist in the original 2003 PC release. Aspyr added it for the modern mobile ports to save storage, and it is the one place the BIF format gets genuinely confusing, because nothing inside the file says it is compressed.

PropertyUncompressedCompressed
SignatureBIFF / V1 BIFF / V1 (identical)
Header and tablesas documented abovebyte-for-byte the same shape
data_size in the tablethe resource’s lengththe uncompressed length
How the KEY names itdata\2da.bifdata\2da.bif (still .bif)
On-disk filename2da.bif2da.bzf

The extension is the only discriminator that exists in shipping data. Everything else matches. That has a direct consequence for tooling: compression has to be stated, not sniffed. Rakata reads it from the path when opening a file, and requires it as an argument when reading from a window that has no filename attached.

Payload Layout

Each resource is stored as its own LZMA-alone stream:

0x00        properties byte     packs (pb * 5 + lp) * 9 + lc
0x01..0x05  dictionary size     u32, little endian
0x05..      compressed data     terminated by an end-of-stream marker

There is no length field in the stream, because the uncompressed length already lives in the entry table. And there is no packed length recorded anywhere: an entry’s compressed extent runs from its offset to wherever the next entry begins, with the last one running to the end of the file. Trailing alignment inside that span is harmless, since the decoder stops at the end-of-stream marker.

Note

Ground truth and its limits. The facts above are read from a shipping Android bundle (com.aspyr.swkotor), whose 26 compressed archives every one carry the BIFF signature. Rakata decodes all 26, each entry to its exact declared length. iOS and Switch are unverified and simply assumed to match.

Two things worth flagging as inference rather than fact. Community references describe a BZF signature; no file in the bundle uses it, so Rakata does not look for one. And the engine presumably swaps the extension when resolving a KEY entry on mobile, since the KEY says .bif and the disk says .bzf, but that path has not been traced in a mobile binary. The Ghidra project carries the Android builds, so it is auditable whenever someone wants to.

Engine Audits & Decompilation

Read from CExoResFile::LoadHeader (0x0040d910) and CExoResFile::ReadResource (0x0040da20) in swkotor.exe. Provenance: derived, not attested. The rows below have not been separately re-derived, so they sit on the reverse-engineering queue.

Archive Initialization (CExoResFile::LoadHeader)

Mapped from 0x0040d910.

Pipeline EventEngine Behavior & Result
Signature CheckThe engine strictly validates both the BIFF magic and the exact V1 version. It does not actively process any files that deviate from this signature pair.
Variable Table LoadingThe system extracts the variable_count value from the header and physically reads variable_count * 16 bytes from the variable_table_offset to map the resource keys.
Fixed Table BypassThe fixed_count header scalar is entirely decorative. It is not part of the active runtime read path (files with nonzero values are accepted but never mapped).
Direct Asset ExtractionWhen reading a physical asset out of the .bif, the engine isolates the entry_index using (resource_id & 0x3fff) * 0x10. It then calls a direct C fseek(SEEK_SET) strictly matching the raw data_offset extracted from the 16-byte variable table entry. The index is bounds-checked against variable_count before use, so an out-of-range resource_id is rejected rather than read past the table. What is not applied is any alignment or structural normalization to the data_offset itself, which goes to fseek exactly as stored.

Caution

Because the engine passes the internal data_offset integer directly into a raw C fseek(SEEK_SET), any custom BIF files must meticulously guarantee byte-perfect offset tables. If the offset is even slightly misaligned, the engine will read garbage data into the stream, inevitably crashing the game.