Skip to content
Tech News
← Back to articles

Reverse-engineering Codemasters' BIGF archive format in Ruby

read original more articles

When you say “I’m going to reverse-engineer a binary file format,” people picture C, or Python with struct , or Kaitai. Nobody pictures Ruby. Ruby is for web apps and DSLs and being pleasant; it is not, in the popular imagination, for byte-banging floats out of a 2003 racing game.

That popular imagination is wrong. The reader for Codemasters’ BIGF archive format — the container that holds the AI data in TOCA Race Driver — is pure, dependency-free Ruby, and it reads four different games’ archives. I should be upfront about how it came to be: this was reverse engineering done with an AI the whole way — me steering, deciding what to trust and verifying every claim against the bytes; the model drafting code, recalling the corners of the standard library, and proposing hypotheses I then tested. What follows is the part of Ruby that made that collaboration genuinely pleasant: Ruby strings are byte buffers, and String#unpack is a tiny, fast binary parser hiding in plain sight.

Strings are bytes

The first thing to internalise is that a Ruby String is not “text.” It’s a sequence of bytes with an encoding label attached. Read a file in binary mode and you get the raw bytes, indexable and sliceable like any string:

data = File . binread ( "aib.big" ) # the whole file as an ASCII-8BIT String data [ 0 , 4 ] # => "BIGF" — the first four bytes data . bytesize # => 3448832

File.binread is the key: it reads the file as binary ( ASCII-8BIT / BINARY encoding), so no UTF-8 interpretation mangles your 0x80 + bytes. From there, data[offset, length] carves out byte ranges, and data.index(needle, from) finds a magic number or a marker anywhere in the file. That’s most of a parser already.

unpack : the binary decoder you already have

The workhorse is String#unpack (and its single-value sibling unpack1 ). You hand it a format string of directives and it decodes the bytes. The two directives that did 90% of the work here:

V — an unsigned 32-bit integer, little-endian . Every count, block index, offset and size in BIGF is a V .

— an unsigned 32-bit integer, . Every count, block index, offset and size in BIGF is a . e — a little-endian single-precision float (32-bit). The AI data is arrays of these: the racing-line coordinates, the control values, the padding.

... continue reading