Серия «Геймдев»

11

Реверс-инжиниринг из детства - вытаскиваем данные из Carmageddon TDR2000 - часть 2

Серия Геймдев

Ну что, двигаем дальше?) В прошлый раз Реверс-инжиниринг из детства - вытаскиваем данные из Carmageddon TDR2000 удалось распаковать архив с данными, самое время эти данные визуализировать!

Итак, погнали. За что цепляется взгляд?
tga - очевидно, текстуры, до них дойдем в последний момент
dcol - в PAK локаций я видел scol, логично предположить, что это Динамический КОЛлайдер
mshs - судя по всему - меш (MESH)
hie - какой то текстовый файл, описание модели?

Как и обычно в таких случаях - начинаю с меша (как оказалось потом - зря:) )
Открываем хекс-редактором

Первое, что запоминаем, это порядок байт - Little endian.
Что мы можем найти?
целые числа - обычно четыре байта, можно узнать по нулям в правых байтах.
float - 4 непонятных байта :)), но в редакторе можно выделить - и в анализе данных будет вполне адекватное значение.
а есть читабельные флоаты, например 00 00 80 3F - это 1
еще есть такой маркер - если корень суммы квадратов 3-х чисел равен 1 - значит перед нами нормаль :)
В общем, просто накидал простой парсер, который бинарные данные дампит по такой логике в таблицу - так разобрать куда легче )

Опыт работы с такими данными подсказывает, что пачки флоатов в начале - это вершины. Иногда они идут вместе с UV. Но здесь есть закономерность именно по 3 числа - что как бы намекает, что перед нами список векторов. Предполагаем, что число 30 перед этим блоком - количество векторов. Умножаем на 12... Ну, почти совпадает :)
Опущу эксперименты с визуализацией, у меня получилась такая схема:

Дальше начинается интересное, если отбросить следующие 3 флоата (анализ которых говорит что это нормаль), дальше идут числа 0, 1, 2 - ну, собственно, вот они, номера вершин полигона. И такой паттерн повторяется каждые 132 байта. Значит, каждые такой блок - это полигон.
Смотрим, сколько таких блоков? оу, 38, прям как было в первом числе.
Итак, в начале имеем количество полигонов и количество вершин, далее - данные. Но файл на этом не заканчивается. Далее... снова знакомый паттерн. количество поликов - 0 - 1 - количество вершин. И так до конца файла.
Итог - mshs файл это некоторое количество мешей, каждый меш состоит из вершин и поликов. Вот что с этим делать? Как они позиционируются? Я слегка забуксовал, думая, что позиции мешей записаны в самом файле, но нет, ключ был в файле hie, с которого и стоило начинать анализ :)

Number of textures, number of materials - тут всё логично, есть материалы, материалы ссылаются на текстуры (те tga, что мы видели при распаковке, очевидно)
Что особенно интересно:

  • Matrices

  • Nodes

  • Meshes - а блин, количество как раз совпадает с количеством блоков, считанных в mshs )))

Взгляд цепляется за Nodes, как будто это те данные, которые выстраивают иерархию модели, вот только узлов как то многовато. Если данные развернуть в дерево, то получится довольно громоздкая структура.
Ответ в поле TYPE. каждый узел этого дерева иерархии отвечает за свой тип.
Ну, осталось только сопоставить где какой тип :)
Я заметил такую закономерность, для каждого типа узла есть свой диапазон значения в поле INDEX.
Что получаем?
1 - в пределах количества Matrices
2 - в пределах количества Textures
3 - в пределах количества Meshes
4 - в пределах количества Expressions (не упоминал ранее, но есть в HIE-файле)
5 - в пределах количества Materials
7 - в пределах количества Collision Meshes
Значит, алгоритм следующий.
Элементы типа 1 представляю собой собственно иерархию "костей", к каждой кости применяем матрицу преобразования, которая ей соответствует.
элемент типа 2 - устанавливает тектуру.
элемент типа 3 - читает меш из файла mshs
элемент типа 4 - применяет expression из самого файла hie (но, каюсь, не доразобрался зачем оно надо:) )
элемент типа 5 - устанавливает материал
элемент типа 7 - читает коллизию из файла *col (на данной стадии мне не особо это важно было, поэтому не парсил)
Пара скринов процесса :)

когда наконец-то понял, как позиционировать меши :)

когда наконец-то понял, как позиционировать меши :)

А вот с применением текстур ) рядом подвинул лоу-поли модель :)

А вот с применением текстур ) рядом подвинул лоу-поли модель :)

Повреждёная модель всё в том же файле :)

Повреждёная модель всё в том же файле :)

Ееее! Выцепил )

Ееее! Выцепил )

Применил логику ко всем машинам в карме - ну, не всё прям прочиталось отлично, где то проблемы с текстурами, но эксепшны не лезут - уже успех :))

Что дальше? Надо расковырять локации! А пока пока - вот вам скрин тачки из Carmageddon TDR2000 на трассе из Need for Speed 3 (которую я расковырял уже давно) :)

Надеюсь, до встречи следующей части! План - расковырять локации :)

Показать полностью 10
25

Реверс-инжиниринг из детства - вытаскиваем данные из Carmageddon TDR2000

Серия Геймдев

Оффтоп - у меня навязчивая идея сделать гонки, где можно было бы проехать на машинах из одной старой игры трассу из другой старой игры :) Уже поковырялся в NFS1-NFS5, Carmageddon1-2, и вот, дойдя до TDR2000 решил, что пора как-то уже делиться с людьми своими наработками :)

Итак, заходим в папку игры, что мы имеем? папку Assets и папку Saves. Очевидно, данные в первой :)

А там уже есть и папка Tracks (локации) и папка Cars (тачки)!

Ну, ввиду того, что цель стоит в том, чтобы разобраться как всё устроено - начнём с Cars, там обычно меньше данных :))

Ткнём, ну, в electric_blue

файл *.h - это описание каких то нод, пока запомним про его наличие :)

*descriptor.txt - а вот это, похоже, важный файл, есть упоминания файлов, входящих в описание модели. Думаю, обязательно к нему вернёмся :)

*texturedescriptor.txt - наверняка описание использования текстур. к нему если и вернёмся, то на стадии текстурирования модели :)

Вот что и правда интересно, и не поддается анализу через супер-утилиту Блокнот (ахахах) - так это файл dir и файл pak.

Логично предположить (исходя из размера и названия) - dir это индекс, pak - это сами упакованные данные.

Вообще обычно в старых играх индекс и сами данные лежат в одном файле. А тут - отдельно. Ну, мне же проще. Открываем DIR в Hex-редакторе...

ан нет, не проще :))

Глаз цепляется за то, что каждый нечётный байт как будто бы содержит контент, а чётный - видимо какие то флаги.

Вообще, при расшифровке таких архивов важно получить следующие данные:

- Название файла
- Смещение начала даннах
- Размер данных

Вернёмся же к индексу.

В чётных байтах как будто записаны названия файлов.

Но названия (String) должны либо иметь длину в начале, либо заканчиваться нулём.

Здесь ни то ни то, но в байте флагов в конце строчки - всегда 08h!

Запомним-с. а лучше - запишем-с

А что идёт после названия? наверняка нужные нам указатели смещения и размера.

C9 7F 16 00 - хм. какое то большое значение для смещения первого элемента.

B6 02 00 00 - а вот это для размера норм. Но, как бы там ни было, смещение укладывается в размер файла PAK (1474505 внутри файла 1 475 199 байт) - и даже тот факт, что так близко к верхней границе, говорит о том, что мы близко к истине :) просто первый элемент в индексе не является первым в самом архиве.

Пора писать скрипт чтения архива!

Вот здесь я реально немного задолбался писать свои копания в архиве, скажу кратко - в DIR файле не просто индекс, он еще и "заархивирован" так, чтобы повторяющиеся части путей не писать повторно. В итоге файл делится на блоки - ноды.

вся суть в байте флагов (про который я говорил ранее)

исследование привело к следующим выводам:

- 08h - завершение строки. после него сразу идут смещение и размер данных в файле PAK

- 40h - у текущего символа есть "вложения" - нужно дальше искать ноды и присобачить текущий преффикс к ним перед тем, как возвращать имя этих записей

- 80h - ничего не делаем, просто читаем дальше имя записи.

Итак, вот у нас есть список имён файлов, их смещения и размеры внутри PAK.

Пробуем читать.

На первый взгляд всё ок - смещения, а так же смещения+размер - все укладываются в размер файла PAK.

Но, что то не так.

Файлы, которые называются .txt ведут на данные, которые не очень то похожи на текстовые.

Запакованы?

В те времена часто использовался zlib для упаковки, который можно проверить по наличию байтов компрессии (с просторов интернета):

78 01 - No Compression/low
78 5E - Fast Compression
78 9C - Default Compression
78 DA - Best Compression

И да, 78 DA частенькой находится в файле PAK

Единственное, что смущает - он не в начале данных конкретной записи файла идёт... значит, пишем алгоритм, который пропускает всё, что идёт до этих данных. А остальное скармливаем ZLib'у (использую Ionic.zlib). Как результат - теперь получаем пачку файлов с данными машины. А вот об этом уже дальше, если вдруг это и правда интересно :)

На CSV не смотрите, это следующий шаг анализа dcol-файлов :)

На CSV не смотрите, это следующий шаг анализа dcol-файлов :)

Если есть желающие тоже в этом покопаться - черканите, оформлю в реп то, что уже накодил.

И, если есть ссылки на уже готовые решения - тоже пишите, мало ли, я провафлил этот момент и изобретаю велосипед :)

Показать полностью 4
Отличная работа, все прочитано!

Темы

Политика

Теги

Популярные авторы

Сообщества

18+

Теги

Популярные авторы

Сообщества

Игры

Теги

Популярные авторы

Сообщества

Юмор

Теги

Популярные авторы

Сообщества

Отношения

Теги

Популярные авторы

Сообщества

Здоровье

Теги

Популярные авторы

Сообщества

Путешествия

Теги

Популярные авторы

Сообщества

Спорт

Теги

Популярные авторы

Сообщества

Хобби

Теги

Популярные авторы

Сообщества

Сервис

Теги

Популярные авторы

Сообщества

Природа

Теги

Популярные авторы

Сообщества

Бизнес

Теги

Популярные авторы

Сообщества

Транспорт

Теги

Популярные авторы

Сообщества

Общение

Теги

Популярные авторы

Сообщества

Юриспруденция

Теги

Популярные авторы

Сообщества

Наука

Теги

Популярные авторы

Сообщества

IT

Теги

Популярные авторы

Сообщества

Животные

Теги

Популярные авторы

Сообщества

Кино и сериалы

Теги

Популярные авторы

Сообщества

Экономика

Теги

Популярные авторы

Сообщества

Кулинария

Теги

Популярные авторы

Сообщества

История

Теги

Популярные авторы

Сообщества

Недвижимость и ремонт

Теги

Популярные авторы

Сообщества