Геймдев
2 поста
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 (которую я расковырял уже давно) :)
Надеюсь, до встречи следующей части! План - расковырять локации :)
Оффтоп - у меня навязчивая идея сделать гонки, где можно было бы проехать на машинах из одной старой игры трассу из другой старой игры :) Уже поковырялся в 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). Как результат - теперь получаем пачку файлов с данными машины. А вот об этом уже дальше, если вдруг это и правда интересно :)
Если есть желающие тоже в этом покопаться - черканите, оформлю в реп то, что уже накодил.
И, если есть ссылки на уже готовые решения - тоже пишите, мало ли, я провафлил этот момент и изобретаю велосипед :)
Я немного удивлён, что у меня теперь есть 2 подписчика, так что… почему бы не поразглагольствовать :))
В общем было дело 6 лет назад: вроде всего хватало по жизни, стабильная работа, доход, семья - ан нет, начало крыть что живу жизнь зря, и всё бессмысленно. Начало крыть настолько, что пристрастился к алкоголю. Пил почти год, пока знакомая не пригласила выступить на сцене. Она преподаватель по вокалу, и устраивала вечер, где пели её ученики. Ну и мне предложила, мол, слушай, ты же неплохо поёшь.
Согласился.
Выступил.
И всё - я понял, что вот там на сцене я чувствую себя живым.
Поставил себе цель - я хочу петь так круто, чтобы можно было выступать на постоянной основе.
Пошёл на уроки вокала. Первый год был тяжелый. Я пел, записывал себя, слушал, и… пил =)))
Но я пел каждый день.
И со временем я стал меньше плеваться от своего вокала.
А потом как то само собой закрутилось. Сначала пел по интернету (в Smule), потом пел на благотворительных мероприятиях для детей в детских домах. А потом попал в настоящий мюзикл! На главную злодейскую роль. Правда, мюзикл финансово не взлетел :( Но это был охрененный опыт - дополнительно прокачал актерский навык и завел знакомства с талантливыми ребятами.
Чтобы иметь какую-то реализацию в качестве вокалиста, решил - надо собрать кавер-группу, будем играть на репбазе, буду качать вокал, а заодно, может, дорастем до какой-нибудь небольшой сцены (где можно будет играть кавера).
Искать музыкантов это отдельная боль, поэтому гитару взял сам (благо на акустике уже лет 15 играл к тому моменту, а на электрухе давно хотел научиться).
А где то фоном зрела мысль, что нужны авторские песни.
Вообще у меня не было опыта написания собственных песен, и я был уверен что креатив - это не моё, но… но хотелось иметь собственный материал. В то время как раз бахнул ковид, и все сидели по домам, поэтому решил заняться изучением музыкальной теории. И вдруг оказалось что писать музыку самому - это вполне реально :))
Ну ладно, значит вопрос в тексте. Но что делать? стихи я не писал никогда (ну если только в рамках стёба зарифмовать что то, но ничего особо осмысленного). Окей, гугл, как писать стихи? :) Вот эта задача была гораздо сложней (спойлер: да и даже сейчас я трачу на тексты песен намного больше времени, чем на музыку). Но, в итоге, после нескольких неудач у меня состыковалась музыка с текстом, так появился на свет мой первый трек “Первый шаг”. Да, это трек про меня в том числе, но я всегда его посвящаю тем, кто решается сделать тот самый первый шаг, на который, как правило, нужно половину усилий - ведь дальше всё проще :)
Песня написана 3 года назад, с тех пор я успел собрать группу для выступлений, дать пару десятков концертов, даже организовать несколько своих фестов и сольник, записать в студии и выпустить 11 треков. Это были долгие 3 года, но, думаю, не стоит ими перегружать этот пост, но если будет интересно - расскажу в отдельном, каково это - поднимать группу с нуля :)
Оставлять прямые ссылки на паблики мне не позволяет скромность, но если кому интересно, группа называется “Одна Волна” - есть на всех основных музыкальных площадках :)
Основную работу, кстати, не бросил :) Даже как то меньше стал выгорать. Да и стабильный заработок нужен - музыка требует вложений: в оборудование, звукозапись и концерты.
А, к чему я это всё? Нет, я пока не чувствую, что добился чего то существенного. Но я хотя бы ощущаю себя на своём месте. И, мне бы хотелось своим примером вдохновить других. ТВОРИТЕ! это наполняет человека, придаёт жизни смысл.
Наверное накидают ща за воротник, но... пофиг! с Новым Годом, пикабушники! :))
Заказали доставку, были дома, но пропустили звонки от курьера, курьер не придумал ничего лучше, кроме как заблокировать дверь коробками. Спасибо, вы сделали наш день (даже фото прислал снаружи!) :))