Разбор форматов: звук в некоторых играх на Unreal Engine

de13a88e1f3f407c8b3c16a95bc8c3d1.pngКультура модификации игр зародилась ещё в древние времена. Самое раннее, что я помню, это Wolfenstein 3D (1992 год). Если не ошибаюсь, можно было рисовать свои карты, а потом и новых врагов, заменять текстуры и звуки. Главным препятствием в моддинге является разбор неизвестных форматов данных. Оставим моральные аспекты этого явления для других ресурсов, и остановимся на технических сложностях, которые могут возникнуть в этом нелегком деле.

У меня накопилось довольно много историй такого рода, от самых простых, типа разбора простейшего архива, где в одном файле хранятся много тысяч файлов игры, до замены 3д-моделей, исследования и написания нестандартных кодеков звука. Расскажу одну из них, средней сложности.

Допустим, у вас появилось желание заменить определённые фразы в игре, или вообще замахнуться на полную озвучку на каком-нибудь языке, для которого у разработчиков не хватило сил или ресурсов. Казалось бы, надо только записать звук, найти где он находится в игре, и заменить нужные файлы. Но не всегда это бывает просто, например, в последних играх из серии Batman: Arkham используется звуковой движок wwise, который уже довольно давно интегрирован в Unreal Engine.

Я уже не раз сталкивался с UE, но, как известно, коммерческие разработчики имеют возможность полностью менять любую часть кода движка, поэтому почти все игры получаются уникальными с точки зрения структур данных, и это всегда интересно поисследовать.Для начала посмотрим звуковые файлы. Они как обычно лежат в папке audio и собраны в один большой пакет, с неожиданным расширением .WAD (привет DOOM). При желании даже можно извлечь из него все звуки, но это будет несколько тысяч безымянных файлов, и найти среди них что-то будет весьма проблематично, разве что «вручную» переслушивать их все. Надо сказать, что чаще всего бывает проще. Разработчики, для своего же удобства, оставляют где-нибудь файл со списком фраз. Но это не тот случай.

Логично предположить, что раз сама игра как-то находит нужные звуки и субтитры к ним, значит эта инфомация где-то содержится в файлах, надо только её найти. Нигде в папках для локализации тексты не обнаруживаются, значит они разбросаны по отдельным уровням игры, как это часто бывает. Возьмем для примера один из .upk файлов с названием, похожим на уровень, и распакуем его. Благо, инструменты для этого имеются, даже с исходными текстами.

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

3e970e31e409481fbd88b8238c1c246c.png

Имена файлов похожи на имена исходных звуков. Замечательно, теперь осталось только найти соответствие между ними и звуковыми файлами. Вот только тут и начинаются проблемы. В звуковом пакете конечно есть идентификаторы. Это 30-битный хеш, который всегда используются в wwise для звуков, но к сожалению нигде среди файлов диалога их найти не удаётся. Везде одни непонятные цифры, ничего похожего на ID звука нет, они сразу были бы заметны. С другой стороны, это и понятно, ведь движок не так прост, и нельзя просто так взять и проиграть звуковой файл в игре. Он содержится в аудио-банке, у него множество свойств, накладывающих различные эффекты и т.д.

И тут оказывается, что в каждой папке с диалогом есть файл .akbank — видимо это и есть аудио банк wwise.

b7245abd13774e4591105a122af7cad3.png

Вот у него внутри как раз очень много идентификаторов, перепробовав которые наугад, мы обнаруживаем, что один из них (выделен зеленым) имеется в звуковом пакете. Если мы извлечем оттуда данные по этому идентификатору, то получим некий сегмент из слепленных вместе нескольких звуков. Сконвертируем эти звуки из внутреннего формата wwise в обычные ogg. Да, действительно, в одном из них Бэтмен говорит: «I don’t have time for this», а в другом файле ему отвечают. И фразы как раз соответствуют текстам именно этого диалога.

Уже неплохо! В принципе, на этом можно было бы и остановиться: все диалоги разложены по папкам, для каждого из них есть банк со ссылкой на звуковой сегмент. Мы конечно не знаем, где какой файл, но разрезать сегмент на части, послушать и расставить по местам несколько фраз (а их в диалогах обычно бывает всего 3–4 штуки) можно и вручную.

Но мы не ищем легких путей. Разбираться, так до конца. Проверим на всякий случай, вдруг звуки идут прямо по порядку? Конечно же нет, они перепутаны. Как ни крути, где-то должна быть информация о связи звуков в сегменте с текстом диалога. Я довольно долго копался в разных файлах, в надежде что-то обнаружить, но всё бесполезно. Хорошо. Раз такое дело, распакуем все пакеты игры. Это несколько гигабайт, ну ничего, первый раз что-ли? Вот только полный поиск по всем данным игры также ничего не дал. Единственное место, где есть идентификаторы звуков, это аудио банк. Выходит, связь идёт только через него. Ничего не поделаешь, придется лезть внутрь и разбираться, как он устроен.

Теперь, для верности, найдём в игре какой-нибудь диалог, который можно быстро проверять. После эффектного вступления с очаровательной девушкой-репортёршей и маски-шоу, Бэтмена захватывает Hugo Strange. Он говорит пару фраз, начинающихся с «I feel I should thank you», потом уходит, и начинается игра. Именно здесь происходит первое сохранение. Этот момент нам подойдёт.

7901682c28de4bfabfc4bbb564bc419d.jpg

Найдём фразу злодея в файлах. Она оказывается в пакете OW_E8_Ch1z_Anim. Так сразу и не догадаешься. Внутри всего один диалог, в котором содержится всё начало игры. Это целых 24 фразы, но возможно это даже хорошо, в мешанине кодов легче найти число 24, чем 1 или 2. Итак, мы собирались изучить содержимое .akBank

Формат банков wwise оказывается уже частично исследован. Будем надеяться что этой информации хватит для нашей цели. Судя по началу файла .akbank, в нем находятся сразу 5 аудио банков для 5 языков, первым идёт банк INT (английский) — его мы и посмотрим.

f65eaeb3121b4069a768fdfcc57d04d9.png

Сначала там имеется непонятная таблица после заголовка ВКРК, потом довольно много нулей (это видно на прошлой картинке), потом сегмент BKHD, и потом сегмент HIRC, в котором, по всей видимости, находится описание всех аудио-объектов. В данном случае у нас их 79 штук (0×4F выделено зеленым). Как утверждает описание, объекты в сегменте идут один за другим, для каждого указан тип (1 байт), потом 32-битная длина, и ID. Длина и содержимое объекта отличается в зависимости от типа.

b7803e21719c4556bc8f573af14adca4.png

Объекты тип 2 — это собственно звуки. Тип выделен красным, длина — желтым. У каждого из них указан ID самого объекта (зеленым) и ID звукового файла (фиолетовым), где он содержится. Ниже видно начало следующего объекта такого же типа.

f122ff25a257411aa6a346fce844986b.png

Объекты 3 — звуковые действия, похоже каждое из них это «проиграть звук», с какими-то неизвестными нам параметрами, но в каждом из них есть свой ID (серым) и ID звука, который собственно нужно проиграть (зеленым).

Объекты 4 — звуковые события. Очень короткие записи, в которых только и есть, что ID события (голубым), а также указано, что оно содержит только одно действие, и ID этого самого действия (серым).

Ну вот, похоже у нас имеется 24 цепочки событий следующего вида:

событие —> действие —> звук

Они связаны идентификаторами, и в итоге заканчиваются ссылками на звуковые файлы. Как же найти нужные файлы? Поискав эти коды, мы обнаруживаем их как раз в той самой таблице в начале банка. Видимо это таблица, в которой записано, где внутри звукового сегмента находятся отдельные звуки. И действительно, в ней как раз 24 элемента, и для каждого файла указан тот самый ID, который у нас был в звуковом объекте, смещение относительно начала, и длина. Поздравляем! Теперь у нас полностью прослеживается связь от аудио-событий в банках до отдельных звуковых файлов:

60b5248e08d94740b881403accf84496.pngTo есть как исходные данные у нас есть ID нескольких событий, по одному для каждой фразы диалога, и для каждого из них мы можем найти звуковой файл. Но как связать их теперь с самим диалогом?

Попробуем поискать где-нибудь эти идентификаторы. В файлах диалогов их опять нет. В папке есть еще какие-то очень короткие файлы .akevent — их тоже 24 штуки. Очевидно, это файлы аудио-событий. Внутри какие-то небольшие числа, у всех одинаковые, от них никакого толку. Единственное, что там есть разного, это как раз id аудио-событий, которые мы нашли в банке.

736726436c52456cb127df41e7703ec6.png

Опять тупик: есть идентификаторы для всех событий, но связи между ними и текстом диалога нет! На всякий случай сделаем тест: поменяем в нужном файле ID и запустим игру. Да, действительно, Хьюго открывает рот, но ничего не говорит. Значит это именно те данные, по которым игра находит нужный звук. Заодно отмечаем, что субтитр всё равно показывается. Значит тексты диалогов в нашем случае первичны, а от них уже идёт звук.

И тут я вспоминаю, что у движка UE3 есть привычка ссылаться на объекты пакета через их порядковый номер внутри пакета, то есть прямо как они упакованы внутри него. Посмотрим файл экспорта, который образуется у нас при распаковке пакетов:

4c911ef9b5ad4c16b0dc83b018e51423.png

Номера здесь десятичные, и начинаются с нуля, в игре же они начинаются с 1, поэтому получается, что файлы событий в экспорте идут под номерами 0×35–0×4С. Посмотрим, нет ли их где-то среди диалогов. Начинаем смотреть — и надо же, прямо в начале файла есть этот номер!

4aac57f03adf44bbbbdd4c0a3c71c6a6.png

Вот и последнее недостающее звено. Заодно рядом обнаруживаем 0×2С — это номер файла банка. В случае если в папке вдруг будет несколько диалогов, их тоже можно будет отличить. Теперь мы полностью знаем, как по тексту диалога найти соответствующий звук.

afca381af48046219f6b0d600fd6a0bb.png

Такая вот получилась довольно сложная схема взаимодействия. Похоже разработчики решили не заботиться об удобстве, и просто положились на внутренние механизмы движка, что и привело к такому результату в данном случае. А случаи, как я уже сказал, бывают самые разные. Структура файлов и связи между ними могут быть совершенно другие. Здесь у нас от текста диалога шла ссылка на звук. А бывает наоборот, первичным является звук, а к нему по идентификатору находится текст. Или первично событие скрипта игры, а от него идут ссылки и на звук, и на текст. Бывает, что файлы находятся не по имени, а по хешу. Но в любом случае, каким-то образом они все связаны, остаётся только найти эту связь.

В качестве последнего штриха попробуем проверить наши результаты. Найдём файл диалога именно от нужной нам фразы «I feel I should thank you» и заменим в нём 4B на 4C. Запускаем игру, и наш друг Хьюго вместо этой фразы многозначительно произносит: «It will be my legacy, a monument to your failure and if you try to stop me, I guarantee everyone will know your secret.»

Оставим на этом Бэтмена, исследование можно считать законченным. В письменном виде процесс выглядит быстро, но на самом деле каждый этап может сопровождаться долгим созерцанием 16-ричных цифр, без всякой надежды на то, что в какой-то момент они сложатся в осмысленные цепочки, и вы поймёте, что они значат. Но иногда это всё-таки происходит.

© Habrahabr.ru