Обратная инженерия двоичного формата BIGF компании Codemasters на Ruby

Обратная инженерия двоичного формата BIGF компании Codemasters на Ruby

Статья демонстрирует, как на чистом Ruby без внешних библиотек можно реверс-инжиниринг двоичного формата архивов BIGF, используемого в гоночной игре TOCA Race Driver компании Codemasters (2003). Автор и его команда провели весь процесс с помощью AI: человек управлял направлением, принимал решения и проверял каждое утверждение по байтам, а модель писала код, вспоминала детали стандартной библиотеки и предлагала гипотезы для тестирования.

Преимущество Ruby для этой задачи, строки (String) работают как буферы сырых байтов. Метод File.binread читает файл в двоичном режиме (ASCII-8BIT), а data[offset, length] извлекает диапазоны байтов безопасно. Основная рабочая лошадка, String#unpack с форматными директивами: V (32-битное беззнаковое целое, little-endian) и e (32-битный float, little-endian) сделали 90% работы по разбору.

В статье разобраны три ключевые части формата: проверка заголовка (magic BIGF), два варианта таблицы каталога (плоская с фиксированными 24-байтовыми записями и переменной длины с маркерами), и декодирование внутренних записей с классификацией по битовому паттерну (padding-значения, нулевые данные, скалярные значения, координаты пути и прочее). Для обхода файла используется String#index, чтобы сканировать маркеры; getbyte читает один байт целым числом без выделения памяти.

Автор подчёркивает, что Ruby стоит против стереотипа (язык для веб-приложений и DSL), но его String-буферы, unpack, и интеграция с REPL делают его исключительно удобным для исследовательской работы: код одновременно служит документацией формата, зависимостей нет (важно для инструмента, который должен работать пять лет), и скорость C-реализации unpack позволяет обрабатывать сотни тысяч чисел с плавающей точкой без «налога интерпретатора».

Полный открытый исходник находится на GitHub (github.com/davidslv/bigf, MIT-лицензия) с парсером контейнера и декодером записей, протестированными на четырёх играх разных платформ (включая Xbox 360 big-endian).

Ключевые факты

  • Ruby String как буфер сырых байтов + метод unpack образуют полноценный двоичный парсер стандартной библиотеки без зависимостей
  • Формат BIGF (архив AI-данных из гоночной игры 2003) включает заголовок, каталог в одной из двух схем и секцию данных; все смещения и размеры, 32-битные little-endian целые
  • Классификация 16-байтовых записей по битовым паттернам (sentinel-значения 0x3f3f3f3f, нулевые блоки, скалярные пары, координаты пути) требует работы с NaN и денормализованными числами
  • AI помогал всю дорогу: руководил тестированием гипотез, вспоминал детали Ruby и стандартной библиотеки, но все решения проверялись вручную по байтам исходного файла
  • Код, читаемый как спецификация формата, главное преимущество; на irb File.binread и одной строкой unpack можно проверить любое смещение быстрее, чем это выговорить

Почему это важно

Стереотип видит Ruby лишь для веб-разработки и DSL. На деле язык отлично подходит для двоичного разбора и обратной инженерии: строки, это буферы байтов, unpack, полнофункциональный и быстрый парсер (реализован на C внутри интерпретатора), а REPL позволяет итерировать в реальном времени. Для исследовательского инструмента это критично: код не завязан на внешние gem, поэтому будет работать через пять лет; одновременно он служит документацией формата.

Кому это важно

Обратным инженерам двоичных форматов, мейнтейнерам наследуемого софта (старые игры, проприетарные системы), разработчикам парсеров и конвертеров, а также тем, кто рассматривает Ruby только сквозь призму веб-экосистемы. AI-специалистам интересен опыт: AI предлагал гипотезы, но человек проверял каждую против фактических байтов, это эффективнее, чем доверять модели.

Как это применить

В своём проекте изучи String#unpack и его форматные директивы (V для u32 LE, N для u32 BE, e для float32 LE, g для float32 BE). Используй File.binread для чтения в двоичном режиме, data[offset, length] для нарезки, String#index для поиска маркеров. Для NaN и денормализованных чисел проверяй с помощью Float#nan? и Float#abs. В REPL быстро тестируй гипотезы. Открытый исходник на GitHub (lib/bigf/archive.rb и lib/bigf/toca/profile.rb), конкретный пример двух схем каталога и классификации записей.

Можно ли доверять

Статья основана на реальном проекте (github.com/davidslv/bigf) и протестирована на четырёх играх (включая Xbox 360 с другой endianness); описанные методы, стандартная Ruby (File.binread, String#unpack, String#index, Float#nan?). Автор честно раскрывает, что AI помогала, но всё проверялось вручную. Методика unpack и примеры директив легко воспроизводятся и верны.

Риски и подводные камни

Главный риск, забыть binread и прочитать текстом (UTF-8), тогда байты выше 0x7F искажаются. Endianness в директиве (V vs N, e vs g), критично, ошибка даст полный мусор. NaN в float-записях требует специальной обработки (x == x всегда false для NaN). Слайсинг в Ruby безопасен (нет краша за границей), но вернёт nil или короткую строку, нужно проверять длину. Код без зависимостей хорош, но если формат сложнее, специализированные парсеры (Kaitai, struct в Python) могут быть полезнее для других платформ.