Официальных инструментов у первого Frostpunk просто не существует. Разработчики сделали игру, выпустили её и спокойно ушли делать вторую часть. А мы, люди с идеями, остались стоять перед закрытой дверью.
Раз дверь не открывается, будем снимать её с петель
Ликбез, без которого дальше будет больно
Прежде чем лезть в игру, две минуты теории. Иначе дальше вы будете смотреть на буквы и цифры как я в первый день
Любой файл на пк - это длинная цепочка байтов
Байт - маленькое число от 0 до 255. Всё. Текст, картинки, музыка, сохранения, ваши фотки с отпуска, дикпики - всё это в итоге длинная колбаса из таких чисел
Показывать их принято не обычными цифрами, а двумя символами от 00 до FF:
00 - это ноль
0A - это десять
FF - это 255
Буквы A-F здесь просто работают цифрами от 10 до 15. Непривычно, но так любой байт помещается ровно в два символа.
И главный подвох, который попортит нам кровь позже.
Большие числа записаны задом наперёд...
Число 900 в файле выглядит вот так:
Читать надо с конца: 00 00 03 84. Это 3 × 256 + 132 = 900 (базовый курс информатики, да?)
Почему задом наперёд? Потому что так устроены процессоры в наших компьютерах. Не фростпанк виноват. Но когда ты впервые видишь 84 03 и пытаешься понять, где тут девятьсот, хочется вырубить пк и уйти в лес
Склад без подписей
Жду, что увижу аккуратные папочки: "здания", "законы", "картинки"
Вместо этого там лежат несколько здоровенных файлов парами: .dat и .idx.
Работает это как склад с журналом учёта:
.dat - это сам склад. Коробки стоят вплотную, без подписей и промежутков. Просто километровый ряд коробок
.idx - журнал учёта. В нём записано: коробка такая-то начинается вот тут, весит вот столько, запечатана или нет
Без журнала склад — это просто гора хлама
common - настройки, экономика, законы, здания, технологии, события, погода
localizations - языки
textures-s3 - картинки
templates - шаблоны зданий и мест на карте мира
scenes - сцены
Шаг первый. Журнал, в котором нет имён
Тут сразу надо отдать должное
Как устроен журнал .idx, до меня уже выяснил автор утилиты frostract Mystik738 на гитхабе, за что ему огромное спасибо. Переписал её под себя и встроил в модкит. Но формат вскрыл он. Низкий поклон, добрый человек
00 02 01 - "подпись". Игра смотрит на эти три байта и понимает: да, это моё
[8 байт] - сколько коробок на складе дальше по 29 байт на каждую коробку:
4 байта - отпечаток имени файла
8 байт - сколько весит в сжатом виде
8 байт - сколько весит после распаковки
8 байт - где на складе начинается
1 байт - упакована или нет
Смотрю на отпечаток имени.
Я ожидал увидеть там что-нибудь вроде configs/frostpunkeconomyconfig.bin
Это называется хэш. Имя файла прогоняют через специальную формулу (здесь это MurmurHash2), и на выходе получается короткое число - отпечаток
Фокус в том, что формула работает только в одну сторону
Из имени отпечаток получить легко: подставил - готово
А из отпечатка имя не получить НИКАК. Формула перемешивает буквы так, что обратно их не собрать
Представьте телефонную книгу, где вместо фамилий записаны номера паспортов. Хочешь найти Иванова? Сначала узнай его номер паспорта. Не знаешь? Ну тогда любуйся столбиком цифр
Выход нашёлся через подглядывание
Когда игра загружается, она сама запрашивает свои файлы по нормальным именам: "дай-ка мне файл экономики". Если в этот момент тихонько стоять рядом и записывать, получится список имён
Дальше просто: берём каждое имя, прогоняем через ту же формулу. Отпечаток совпал с записью в журнале? Поздравляю, вы опознали коробку
Коробки с отрезанным хвостом
Большинство коробок на складе упакованы, как zip, чтобы меньше места занимать. Формат обычный - gzip
Беру коробку. Распаковываю стандартной программой
Оказалось, у каждой упакованной коробки кто-то заботливо отрезал последние восемь байт. А там должна лежать контрольная сумма - проверочный код "ничего не испорчено" - и исходный размер
Стандартная программа видит обрубок и паникует (сука такая)
Пришлось распаковывать в обход проверки. А при упаковке обратно так же аккуратно отрезать этот хвост. Иначе игра смотрит на свой же собственный склад и говорит - Это не моё. Я это не ела
Шаг второй. Тексты, или минутка нежности
После такого начала я ждал, что тексты окажутся филиалом ада на земле
А они оказались лапочками
Реально. Единственное место во всей игре, где разработчики как будто подумали: "а вдруг кто-то захочет это прочитать"
Файл языка (russian.lang, english.lang и остальные) устроен честно и просто:
[4 байта] - размер файла
[4 байта] - сколько в нём строк
дальше строки одна за другой:
[2 байта] - длина ключа
ключ латиницей
[2 байта] - длина текста
сам текст
Ключ - это адрес строки. По нему игра её ищет. Например Blueprints/CoalMine
Текст - то, что видит игрок
Вот настоящая строка из русского файла:
13 00 - длина ключа, 19 символов
42 6C 75 65 70 72 69 6E 74 73... - "Blueprints/CoalMine", одна буква = один байт
05 00 - длина текста, 5 символов
23 04 33 04 3E 04 3B 04 4C 04 - "Уголь"
Внимательный читатель спросит: какого хера пять букв заняли десять байт?
А вот такого. Русские буквы записаны в кодировке UTF-16, и каждая весит два байта. И да, они тоже задом наперёд. А ты чего ждал?
У - номер 0423, в файле лежит как 23 04
г - номер 0433, лежит как 33 04
о - номер 043E, лежит как 3E 04
л - номер 043B, лежит как 3B 04
ь - номер 044C, лежит как 4C 04
Номер буквы - это её место в огромной мировой таблице символов Юникоад. Русский алфавит там живёт где-то с номера 0410. Не спрашивайте почему, так исторически сложилось, как и всё хорошее в этом мире
Итог: любой текст можно заменить на любой другой, любой длины. Добавить свои строки.
Собрать файл обратно
Все 13 языков после "разобрал - собрал" получаются точно такими же, какими были
Я откинулся на спинку кресла и подумал: "да это же легко. Чего все ноют?"
Запомните этот момент. Это последний раз, когда я так думал
Шаг третий. Мозг игры
В складе common лежит 51 файл настроек
Это мозг фростпанка. Сколько стоит здание. Сколько угля жрёт генератор. Что делает закон.
Когда придёт буря и сколько народу она выкосит
Открываю файл экономики. Нахожу кусок про уголь:
01 43 6F 61 6C 00
0B CF 0B 94 B9 43 37 85 9C 44 BC DF 67 F9 BE E1
96 00 00 00
84 03 00 00
00 00 00 00
01 40 52 65 73 6F 75 72 63 65 73 2F 4E 61 6D 65 73 2F 43 6F 61 6C 00
Смотрю на это
Оно смотрит на меня
Никто не моргает
Проходит минута
Я моргнул первым
Забегая вперёд, покажу, что тут написано. Узнал я это сильно позже и ценой нескольких лет жизни и остатков психики:
01 43 6F 61 6C 00 - внутреннее имя. 01 значит "имя есть", дальше буквы C-o-a-l, в конце 00 значит "всё, кончилось". Получается Coal
0B CF ... BE E1 (16 байт) - уникальный номер угля. GUID. По нему на уголь ссылаются все остальные файлы игры
96 00 00 00 - стартовый запас. 150
84 03 00 00 - вместимость склада на старте. 900
00 00 00 00 - условия, при которых ресурс показывается. Список из нуля штук, то есть условий нет
01 40 52 ... 6C 00 - адрес названия в файле текстов: @Resources/Names/Coal
Выглядит просто?
А теперь главная подлянка
В файле нет ни одной подписи. Вообще. Ни одной
Слова "стартовый запас" там нет. Слова "склад" нет. Есть 96 00 00 00, а следом 84 03 00 00. И иди нахуй
Откуда мне знать, что 150 - это запас, а 900 - склад? А не наоборот? И что это вообще два числа по четыре байта? Может, одно по восемь? Или четыре по два? Или это вообще не числа, а чья-то дата рождения?
Это как анкета, у которой оторвали левую колонку с вопросами
Остались одни ответы:
"32"
"Да"
"Иванов"
"17"
Где тут возраст, а где номер квартиры? А где количество судимостей?
Хер его знает
А порядок вопросов знает только одна сущность во вселенной - сам Frostpunk.exe
Значит, лезем в него
Frostpunk.exe - скомпилированная программа
Когда-то разработчики написали её на нормальном человеческом языке программирования. С названиями, с комментариями, с понятной структурой. Возможно даже с шутками в комментариях. Мы этого уже никогда не узнаем
Потом специальная программа, компилятор, перевела всё это в машинный код - длинную цепочку команд для процессора
При этом всё человеческое выбрасывается к хуям. Остаются голые команды: "возьми число отсюда, прибавь четыре, прыгни туда"
Это как если бы от рецепта борща остался только чек из пятёрочки. Свёкла, капуста, сметана.
Удачи
Чтобы хоть что-то понять, берут декомпилятор. Он превращает машинный код обратно в подобие программы
Я взял Ghidra - бесплатный инструмент, который, внимание, сделало и выложило в открытый доступ АНБ США. Да, те самые ребята, которые читают вашу переписку. Спасибо им, в кои-то веки они сделали что-то полезное и для меня
Подобие программы получается без имён
Вместо "ПрочитатьНастройки" там написано FUN_140e42c60
Вместо "цена" - param_2
Вместо "количество" - param_3
И сидишь, как археолог над черепком: "так. Эта функция берёт что-то, прибавляет четыре и вызывает другую функцию. А та берёт что-то ещё и тоже вызывает функцию. Что же ты делаешь, сука?"
В какой-то момент я поймал себя на том, что разговариваю с FUN_140e42c60 вслух. Ласково. По имени
Но однажды нашлось главное - как игра читает свои настройки
Устроено оно так:
В игре много видов объектов. Классы: "ресурс", "закон", "здание", "уровень холода", "эффект события"
При запуске игры каждый класс регистрирует свои поля в строгом порядке. Для ресурса: сначала имя, потом номер, потом запас, потом склад, потом условия показа...
Когда игра читает файл, она идёт по этому списку и для каждого поля вызывает свою читалку. Для числа - "прочитай 4 байта". Для строки - "читай, пока не встретишь 00". И так далее
И тут до меня дошло!!
Оторванная колонка с вопросами никуда не делась. Она лежит внутри игры. В функциях регистрации полей
Все ответы были у меня перед носом. Нужно было просто вытащить их из exe. Всего-то
"Всего-то" заняло столько, что я перестал считать дни
Функция за функцией. Класс за классом. Поле за полем
Пока не собралась полная схема: какие поля у каждого класса, какого они типа и в каком порядке
Получилось около трёх с половиной тысяч описаний классов
Три. С половиной. Тысячи
В какой-то момент мне начали сниться GUID'ы
Параллельно я подглядывал за игрой прямо во время работы. Для этого есть Frida - инструмент, который встраивается в запущенную программу и перехватывает её функции
По сути я подсадил игре жучка: "каждый раз, когда читаешь поле - стучи мне, какое"
Ghidra говорила, как должно быть
Frida показывала, как оно на самом деле
И когда они расходились, начиналось самое весёлое. В кавычках
Хорошая новость: кирпичиков оказалось немного
число. 4 байта. Количество, время, цена. Бывает целое (900) и дробное (0,75)
флажок. 1 байт. "Да" или "нет", 01 или 00
GUID. 16 байт. Уникальный номер объекта, как паспорт
строка. Байт "строка есть", потом буквы, потом 00 в конце
список. 4 байта "сколько штук", потом сами штуки
список с сюрпризом. Про него отдельно, там пиздец
Дробные числа хранятся так, будто их шифровали специально, чтобы я страдал
Единица выглядит как 00 00 80 3F
Ноль семьдесят пять - как 00 00 40 3F
Вот эти четыре настройки экономики, которые идут в файле подряд, читаются так:
00 00 00 40 - 2.0, как часто списывается содержание зданий
00 00 80 3F - 1.0, возврат за отменённую стройку, 100%
00 00 40 3F - 0.75, возврат за снос, 75%
00 00 00 3F - 0.5, сколько дают за разбор завалов, 50%
GUID - это кровеносная система игры
Закон не пишет "разрешаю построить паб". Он пишет шестнадцатибайтный номер паба
Технология не пишет "стоит 20 угля". Она пишет номер угля - тот самый 0B CF 0B 94... из примера - и рядом число 20
Весь фростпанк - огромная паутина объектов, связанных номерами. Потянешь за одну ниточку - дрогнет полигры. Потянешь не за ту - игра молча упадёт на рабочий стол и даже не извинится
А теперь список с сюрпризом
У события в игре есть список эффектов. И каждый эффект может быть чем угодно: "дать ресурс", "начать цель", "показать окно", "закончить игру нахуй". У каждого вида свои поля, свой размер, свои тараканы
Поэтому перед каждым эффектом в файле прямо буквами записано имя его класса
Прочитал имя - понял, по какой схеме читать дальше
Ошибся хоть в одном классе - всё, что идёт после, превращается в кашу. Причём не сразу.
Сначала читается нормально, потом чуть криво, а через сто байт у тебя уголь стоит минус восемьсот миллионов, а буря приходит в 1904 году
Их было много. Если собрать все, получится отличный забор. Покажу три самых обидных
Грабли первые. Прабабушки
Классы в игре наследуются друг от друга, как в семье. "Эффект - дать ресурс" - ребёнок "эффекта". А "эффект" - ребёнок "объекта игры". И так далее, вглубь веков
Поля предков читаются раньше собственных
Я честно читал поля класса и его родителя
Про бабушку забыл
Бабушка обиделась
Часть файлов поехала
Пришлось разбираться, где движок проходит всю родословную до седьмого колена, а где останавливается на полпути.
Грабли вторые. Имена-шифровки
У части полей тип хитрый: словарь, массив ровно из десяти штук, массив с ярлыками. Как такое читать, записано только в названии типа внутри игры
Выглядит это название вот так:
.?AV?$RTTIAutoProperty@V$EnumArray@UUITextureTile@@W4eSurvivorsGroupPopulationType@@$02@@@@
Нет, это не пароль от моего вайфая
Это так называемое "искажённое имя" из плюсов. Компилятор упаковывает в него всю информацию о типе, но в таком виде, что читать это может только машина. Ну и я. Спустя время.
Пришлось написать отдельный переводчик с этого языка на человеческий: "сначала 4 байта - количество, потом столько-то пар ресурс + число"
Больше всего бесит, что вся нужная информация там была с самого начала. Просто записанная так, будто её прятали от меня лично
Грабли третьи. Оборотень
Один и тот же класс в разных местах записан по-разному. Где-то с дополнительными полями. Где-то без
Одни файлы читаются идеально
Другие ломаются
НА ТОМ ЖЕ САМОМ МЕСТЕ
Я смотрел на это час
Потом ещё час
Потом пошёл за чаем, вернулся и снова смотрел
Помогло только разделить этого оборотня на два варианта в зависимости от того, где он встречается. Днём он один, ночью другой. Как и я, в общем-то
Как понять, что ты не бредишь
Самое опасное в таком деле - получить что-то похожее на правду
Числа выглядят разумно. Названия читаются. Вроде всё работает. Ты уже почти готов идти хвастаться
А на деле ты ошибся в размере одного поля. И всё после него прочитано мусором.
Правдоподобным таким, аккуратным мусором
Поэтому я поставил себе жёсткое правило:
Прочитал файл. Собрал его обратно из того, что понял. Получился тот же самый файл. Байт в байт
Не "примерно такой же". Не "почти". Байт. В байт
Это как разобрать часы и собрать обратно. Лишних винтиков не осталось, часы идут - значит ты понял, как они устроены
Остался хоть один винтик - ты где-то наебался. Даже если стрелки бодро крутятся и тикают
И вот настал момент!
Запускаю проверку на всех файлах
Жду
Первый - совпал
Второй - совпал
Третий...
Сижу, не дышу
...
Пятьдесят первый
Совпал
Все 51 файл настроек разбираются и собираются обратно байт в байт
Программа больше не угадывает. Она понимает каждый байт в каждом файле
Я сидел и совершенно искренне улыбался. Как дурак. В пустой комнате
Модкит не копирует файл кусками, а собирает его заново из понятной структуры. Все служебные числа - "сколько штук в списке", "какой длины строка" - он пересчитывает сам
А значит, можно не только поменять цену паба. Можно:
добавить свой закон, свою технологию, своё событие
удалить ненужное
написать любой текст любой длины
"Я могу поправить одну цифру" превратилось в "я могу сделать свой сценарий"
На этом фундаменте стоит весь модкит
Правда, тогда я ещё не знал, что первый же настоящий мод сломает игре главное меню
А закон, который я сделаю, просто исчезнет из книги законов
Из-за русских букв
Сука
Но это уже совсем другая история
P.S. Ещё раз спасибо Mystik738 за frostract. Без его работы я бы до сих пор стоял перед складом без журнала и плакал
Я добился сильно большего, и разработки до полноценного модкита рабочего модкита осталось совсем немного. Я начал серию постов "дневника разработчика" с целью заранее раскрутить популярность модкита. Он будет бесплатный и доступным каждому. Сейчас 3 моих тестировщика (жена друг и кот) активно собирают свои моды и сами же в них пытаются играть.
Косяков много, попутно исправляются.
Из последнего - я смог засунуть в игру кастомные объекты (постройки) с кастомным функционалом.
Мод, который я поставил себе в качестве точки которой стоит достигнуть тянет на полноценное ДЛС, и в разработке он будет довольно простой любому другому человеку. Он сможет заглянуть в него и понять как и что делалось.
Дальше будет документация
Если тебе интересна игра, интересен моддинг этой игры, следи за серией.
В первую очередь релиз выйдет на пикабу (а че нет?), потом на другие площадки. К тому же, в первую очередь читать предложения и замечания буду тут (нахер гитхаб, правильно)