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)

BIFs are essentially giant, uncompressed data silos. Because they act as the raw storage tier of the KOTOR engine, they don’t waste bytes on complex metadata or internal filenames – they are simply pure, tightly packed continuous byte arrays for game resources. They are designed to be randomly accessed extremely quickly at runtime strictly via their companion KEY index file.

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

Data Model Structure

The rakata-formats crate handles raw Bif parsing for you by reading the internal offset tables. However, developers very rarely interact with a raw Bif file on its own.

  • Unified Access: Typically, you’ll use the KeyFile API (rakata_extract::keyfile::KeyFile), which automatically ties .key index files to their .bif data payloads so you don’t have to map them yourself.
  • Seek Performance: To prevent loading 100MB+ binary files completely into memory just to read a tiny script, Rakata parses the archive’s entry table once, caches it, then jumps straight to the coordinate of the single resource you asked for. models.bif alone is nearly a gigabyte, so this is the difference between a lookup costing a hash probe and it costing the whole file.

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

The following documents the engine’s exact load sequence and field requirements for .bif archive headers mapped from swkotor.exe.

(Decompilation logic for this section was entirely audited and verified via native Ghidra pipeline against swkotor.exe, explicitly pulling from CExoResFile::LoadHeader (0x0040d910) and CExoResFile::ReadResource (0x0040da20).)

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. No alignment or structural normalization is applied—the data is dumped entirely blindly.

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.