Crusader Kings III — отличная игра. Замечательна она не только своим официальным контентом, но и мощными инструментами моддинга. Ещё до её выпуска меня привлекли обещания разработчиков о расширении возможностей моддинга.
Хотя игра позволяет игроку реформировать средневековые культуры, привив им терпимость к однополым парам, в CK3 версии 1.3.1 пока нет возможности заключения однополых браков. Однако они должны быть приемлемы; ведь для этого и нужны моды!
Проблема моддинга
Довольно большой объём кода CK3 реализован на собственном скриптовом языке Paradox Interactive (он немного напоминает LISP без скобок). Также в игре есть удобные средства для создания, упаковки и распространения игроками собственных скриптов, например, через Мастерскую Steam.
Сделать так, чтобы два персонажа поженились (без каких-то сложных условностей), довольно просто. Достаточно такого скрипта:
do_ye_a_marriage = {
category = interaction_category_friendly
auto_accept = yes
is_shown = { always = yes }
on_auto_accept = {
scope:actor = {
marry = scope:recipient
}
}
}
Он добавляет опцию в меню, открываемое при нажатии правой клавишей мыши на персонаже:
При её выборе герцогиня Тосканская без лишних вопросов выйдет за того персонажа, которым вы играете.
Следовательно, создать мод для женитьбы персонажей одного пола должно быть очень просто. Король Швеции не женат, поэтому мы можем применить опцию к нему и…
… ничего не случится? Ну, по крайней мере, в игре. CK3 ведёт довольно подробные логи ошибок, которые в Windows обычно хранятся в папке
C:\Users\<Username>\Documents\Paradox Interactive\Crusader Kings III\logs\error.log
. В них мы увидим следующее:[jomini_script_system.cpp:169]: Script system error!
Error: marry effect [ Svend Estrid of k_denmark (Internal ID: 15228 - Historical ID 101515) is not allowed to marry Erik Stenkiling of k_sweden (Internal ID: 17969 - Historical ID 100525) ]
К сожалению, оказалось, что логика женитьбы двух персонажей находится не в файлах скриптов, а в двоичном файле игры, а игровой движок в одностороннем порядке запрещает однополые браки. Из-за этого попытка поженить двух однополых персонажей Cruisader Kings 3 становится… недопустимой операцией.
Эту особенность довольно быстро выявили моддеры после выпуска игры. Я создал тему, ставшую основным тредом на официальных форумах Paradox для просьб о внесении изменений. С течением времени патчи умудрялись только ухудшать ситуацию, и из сообщений на форумах и вне их стало понятно, что устранение проблемы будет сложным и не является основным приоритетом.
Моддеры игр, привыкшие самостоятельно браться за решение проблем с геймплеем, могут при наличии достаточного количества времени исправить что угодно. Вдохновившись недавним исправлением GTA Online, я решил потратить немного времени на поверхностный реверс-инжиниринг движка игры. Благодаря нему мне удалось создать неофициальный 390-байтный патч, обходящий встроенные запреты на однополые браки, а также реализующий другие эффекты, необходимые однополым партнёрам, чтобы брать приёмных детей. После публикации патча на официальных форумах последовала такая реакция:
Не совсем то, на что я надеялся… Беспокоило также то, что в дополнение к бану и удалению треда с патчем удалялись любые посты с упоминанием патча, в том числе целые страницы из треда с просьбами игроков.
Я мог бы попробовать найти какой-нибудь другой сайт, который бы согласился выложить патч, но у этого решения были свои недостатки. Созданный мной патч подходил только для текущей Steam-версии CK3 в моей ОС и с моей архитектурой процессора. Но я могу дать вам нечто более универсальное: знания!
Итак, давайте без лишних предисловий изучим пошаговую инструкцию, позволяющую любому, обладающему начальными техническими знаниями, выполнить реверс-инжиниринг игры наподобие Crusader Kings 3.
(Если у вас не так много технических знаний или вы просто нетерпеливый моддер вроде меня, то можете сразу переходить к разделу «Быстрый патч» ближе к концу статьи)
Подготавливаем инструменты
Ghidra — это пакет инструментов для реверс-инжиниринга, созданный Агентством национальной безопасности правительства США,
Не буду писать руководство по установке, оно есть на официальном веб-сайте. Запустив программу и прощёлкав туториал, вы увидите такое окно:
Для начала нужно будет выполнить формальности создания нового проекта. Эта операция выполняется через меню
File -> New Project...
. Укажите название проекта и место его хранения, после чего можно будет импортировать файл игры.Для этого нужно будет найти, где находится
ck3.exe
. Мой расположен в версии Steam, установленной в D:\SteamLibrary\steamapps\common\Crusader Kings III\binaries
, но вам, возможно, придётся поискать его самостоятельно.Найдя файл
ck3.exe
, можно импортировать его через интерфейс File -> Import File...
. Это будет выглядеть примерно так:После нажатия на OK и загрузки двоичного файла вы увидите отчёт, в котором можно просто нажать OK, и новый файл в панели Active Project. Дважды щёлкните на этом файле, чтобы начать веселье.
Интерфейс Ghidra поначалу пугает, но после освоения он кажется вполне простым. Помните, что если вы случайно выйдете из панели навигации, то её обычно можно вернуть через меню Windows в верхней панели.
При первом открытии двоичного файла вам предложат начать его анализ:
Ghidra может анализировать большой объём информации файла. Если вы пока не уверены, что ищете конкретно, то проще всего оставить стандартные настройки и нажать на Analyze. Для двоичного файла размера
ck3.exe
анализ может занять много времени. Для анализа своего файла я оставил программу работать на ночь. После завершения анализа можно нажать на кнопку сохранения в левом верхнем углу, чтобы анализ сохранился при повторном открытии файла.Идём по следу
И здесь нам открывается целый мир. Иногда самое сложное в реверс-инжиниринге конкретной функции — разобраться, с чего начинать. К счастью, у нас есть зацепка: то самое сообщение об ошибке, с которого всё началось. Часто используемые тексты обычно хранятся как последовательность в файле программы. Текст типа
Svend Estrid of k_denmark
, вероятно, генерируется динамически, однако текст is not allowed to marry
, скорее всего, хранится в самом файле.В панели меню выберите
Search -> For Strings...
и нажмите на Search, не меняя стандартных опций. Здесь вы сможете искать строку текста при помощи поля Filter. После инициализации программа будет (медленно) обыскивать файл игры в поисках строк, соответствующих фильтру.Нам повезло! Дважды щёлкнув на эту строку, мы перейдём к строке текста в панели Listing (в другом окне; привыкайте к переключению окон).
Здесь отображается информация о строке текста и её расположении в памяти. Пока сама строка даёт нам не так много сведений. Нам больше интересен текст, использующий эту строку. Нажав правой клавишей мыши на выделенной строке, можно выбрать
References -> Show References to Address
, чтобы найти такие тексты.Похоже, на строку ссылаются два участка памяти. Можно начать с первого. Дважды щёлкнем на первой строке, чтобы перейти к её местоположению. В основном окне местоположение при должно открыться в панелях Listing и Decompile (если у вас нет панели Decompile, то её можно открыть через меню
Window -> Decompile
).Изучаем внутренности
На панель Decompile выводится много тарабарщины. Игры наподобие CK3 обычно пишутся на чём-то вроде C++, а затем разработчики обычно удаляют из файлов игры имена функций и переменных, чтобы уменьшить их размер. Чтобы компенсировать это, Ghidra даёт большинству функций и переменных произвольные имена.
При реверс-инжиниринге двоичного файла любопытно то, что часто и не нужно знать, что значит основная часть этой тарабарщины. В нашем случае можно просто прокрутить окно вниз, пока мы не увидим строчку с нашей текстовой строкой.
Мы можем изучить вызываемую функцию, нажав на
FUN_14034cb90
(спойлер: по большей части она просто выводит ошибку), но нам интереснее сделать так, чтобы она просто не работала. Поднявшись к началу блока if, в котором находится эта функция, мы видим следующее:Итак,
cVar2
является результатом функции FUN_140a1b9c0
, и если этот результат равен нулю, срабатывает код обработки ошибок. Учитывая то, что ошибка связана с невозможностью женитьбы двух персонажей, вполне можно предположить, что lVar7
и lVar3
как раз и являются этими проверяемыми персонажами. Если мы дважды щёлкнем на FUN_140a1b9c0
, то можем посмотреть, что у функции внутри.И здесь нам очень поможет знание программирования на C. Мы ищем место, где сравниваются друг с другом входящие параметры и возвращается ноль. Ближе к началу функции я нашёл такой блок:
Он получает наши входящие данные (которые, как предполагается, представляют собой некие структуры персонажей) и сравнивает байт по смещению
0x124
в каждом из них. Если байты равны, то вся функция возвращает ноль. Похоже, что она может сравнивать пол персонажей, поскольку пол персонажа можно уместить в один байт (а от тех, кто играет с Cheat Engine я знаю, что это единственный бит, 0 — мужчина, 1 — женщина).Начинается колдовство с битами
Лучше всего проверить нашу догадку о конструкции if, попробовав её обойти. При нажатии на «if» в панели Decompiler панель Listing перейдёт к соответствующей ассемблерной команде.
Это самый нижний уровень, на который мы можем опуститься при исследовании двоичного файла.
JZ
— это сокращение от «Jump if Zero». Команда берёт результат предыдущего сравнения (в данном случае это два пола) и переходит к новой позиции в памяти, если это сравнение приводит к нулю (что бывает, когда оба пола равны). Если мы проследуем по чёрной линии в левой части команд, то она приведёт нас к коду, который заставляет функцию возвращать ноль.Один из способов удаления такой команды — изменение места, в которое она осуществляет переход. Для этого нажмём правой кнопкой мыши на строке в панели Listing и выберем
Patch Instruction
.Обратите внимание, что теперь место перехода выглядит очень похожим на место в памяти. Если мы хотим, чтобы наша команда
JZ
никогда не выполняла переход, то мы можем изменить её место перехода на адрес следующей строки, то есть 140a1ba00
. В результате этого мы будем оказываться там вне зависимости от равенства значений полов персонажей.Обратите внимание, что стрелка слева сменилась на новое место перехода. После того, как Ghidra завершит повторную декомпиляцию функции, отредактированной нами конструкции if там уже больше не будет.
Тестируем изменения
Все изменения мы пока выполняли только в проекте Ghidra. Если мы хотим протестировать изменения, нужно вернуть их в двоичный файл. Известно, что Ghidra имеет проблемы с экспортом файлов в используемом нами виде, поэтому придётся воспользоваться свободным скриптом, чтобы компенсировать это.
Официальные инструкции, написанные по ссылке, у меня не сработали, поэтому я поместил файл
SavePatch.py
в папку <папка установки Ghidra>\Ghidra\Features\Python\ghidra_scripts\
. После этого нужно выделить перетаскиванием ассемблерной строки в панели Listing, чтобы вся она стала зелёной. А затем нажать на кнопку Script Manager рядом с верхней частью интерфейса:Введите «SavePatch» в поле Filter, отобразится скачанный скрипт.
Дважды нажмите на найденную строку, после чего нужно будет указать путь к файлу
ck3.exe
, который мы изначально импортировали, и выбрать его (возможно, стоит сначала скопировать его как резервную копию). Если всё пройдёт правильно, то ваша игра на диске будет пропатчена модифицированной командой перехода.Если мы снова запустим игру и попробуем применить к королю Швеции
do_ye_a_marriage
, то…… всё равно ничего не получим. Но в логах ошибок появилось новое сообщение:
[pdx_assert.cpp:568]: Assertion failed: Can only marry characters of different gender.
Раунд второй
Похоже, у разработчиков Paradox есть какие-то дополнительные проверки для собственного удобства. Однако мы можем ещё раз воспользоваться той же методикой.
Search -> For Strings...
, фильтр по «Can only marry characters of different gender», затем на найденной строке References -> Show References to Address
.На этот раз только одна находка. Теперь, когда мы представляем, что искать, можно с лёгкостью найти в этой функции соответствующий код.
Эта конструкция похожа на первую. Она сравнивает два параметра по смещению
0x124
и проверяет их равенство. После выбора «if» соответствующий ассемблерный код выглядит немного иначе:Наша подсвеченная команда — это
JNZ
, или команда «Jump if Not Zero», а стрелка указывает на место после строки «Can only marry». Поэтому нам нужно, чтобы эта команда всегда выполняла переход. Для этого снова нажмём правой клавишей мыши на выделенной строке и выберем Patch Instruction
. Затем заменим JNZ
на JMP
, то есть на команду безусловного перехода.Чтобы применить это изменение к игре, повторим тот же процесс: выделение, Script Manager, выбор SavePatch.py.
После этого можно запустить игру, и…
… наконец-то после всего пережитого добиться личной унии Дании и Швеции, о которой мы всегда мечтали.
Патч, который я опубликовал изначально, содержал это и несколько других изменений для возможности совместно усыновлять детей однополым парам. Впрочем, в них всех использовались те же ассемблерные трюки.
Король и я, тоже король
Но что делают все эти изменения? В CK3 удалённые нами проверки дублируются в файлах скриптов, поэтому базовая игра не должна вообще видеть никаких изменений. Я уже довольно много играл с простыми модами, реализующими гей-браки, и пока самым заметным багом был такой:
Оказалось, что эффект
marry
в CK3 не имеет проверки на женитьбу на себе. Некоторые проверки, например set_father
, явным образом препятствуют тому, чтобы персонаж становился сам себе отцом, но любой персонаж всегда имеет с самим собой один пол, поэтому запрет гей-браков служит и запретом на брак с собой. Вероятно, моддерам стоит ввести явную проверку этого в своих модах.Впрочем, игра, несмотря на это, была довольно стабильной. Отдаю должное разработчикам CK3, движок получился очень надёжным.
(Если это случайно прочтёт кто-то из разработчиков CK3, то я бы рекомендовал всё-таки добавить специальную проверку женитьбы на самом себе.)
Быстрый патч
Но что если вы не хотите начинать карьеру в сфере реверс-инжиниринга ПО? Ну, если у вас Windows 10 и Steam-версия CK3 1.3.1, то вы можете открыть файл
ck3.exe
в любом шестнадцатеричном редакторе и внести следующие изменения:Результат будет таким же, только меньше мороки. Однако при создании модов учитывайте проблему «женитьбы на себе» (впрочем, если вы нарцисс, то не берите в голову).