как писать моды на юнити

Нюансы разработки плагина под Unity

Недавно столкнулся с написанием плагинов под Unity. Опыта раньше не было, да и пользователем данной среды являюсь всего 2-3 месяца. За время разработки плагина накопилось очень много интересных моментов, о которых в интернете мало информации. Хочу все эти моменты описать подробнее для первопроходцев, чтобы они не попадали на те же самые грабли, на которые я сам наступал много и много раз.

Само создание плагина и код выкладывать не буду – пардон, коммерческая тайна, а на общих принципах остановлюсь. Для реализации видеодекодера взял кодеки vp8 и vp9, которые могут проигрывать открытый и не требующий лицензионных отчислений формат WebM. После декодирования видеокадра получаем данные в цветовой модели YUV. Затем каждый компонент пишем в отдельную текстуру. По сути, на этом работа плагина заканчивается. Дальше в самом Unity шейдер декодирует YUV в цветовую модель RGB, которую уже и применяем к объекту.

Вы спросите — почему шейдер? Хороший вопрос. Сначала пробовал конвертировать цветовую модель софтварно, на процессоре. Для десктопов это приемлемо, да и производительность особо не падает, а вот на мобильных платформах картина кардинально отличается. На iPad 2 в рабочей сцене софтовый конвертер давал 8-12 FPS. При цветовой конвертации в шейдере получали 25-30 FPS, что уже является нормальным играбельным показателем.

Перейдем собственно к нюансам разработки плагина.

Основные положения

Документация по написанию плагинов для Unity довольно скудная, все описывается в общих чертах (для iOS многие нюансы сам находил опытным путем). Ссылка на доку.

Что радует — есть примеры, собранные под актуальные студии и платформы (кроме iOS: наверное, Apple не доплатила разработчикам). Сами примеры обновляются с каждым обновлением Unity, но есть и ложка дегтя: часто меняются API, интерфейсы, переименовываются дефайны и константы. К примеру, взял свежий апдейт, откуда использовал новый хидер. Потом долго разбирался, почему плагин не работает на мобильных платформах, пока не заметил:

Наверное, единственный важный момент для всех платформ, который нужно сразу учесть, — цикл отрисовки. Рендеринг в Unity может выполняться в отдельном потоке. Это значит, в основном потоке работать с текстурами не получится. Для разрешения данной ситуации в скриптах есть функция IssuePluginEvent, которая в нужный момент дергает callback, где должна быть выполнена работа с ресурсами, нужными для отрисовки. При работе с текстурами (создание, апдейт, удаление) рекомендую использовать корутину, котороя будет дергать callback в конце кадра:

Что интересно, если пытаться работать с текстурами в основном потоке, то игра падает только на DX9 API, да и то не всегда.

Наверное, самая простая и беспроблемная платформа. Плагин собирается быстро, дебажить тоже легко. В xCode делаем attach to Process → Unity. Можно ставить бряки, смотреть callstack при падении и т. д.

Был всего один интересный момент. Недавно Unity обновился до версии 5.3.2. В редакторе основным графическим API стал OpenGL 4, в более старой версии был OpenGL 2.1, который сейчас deprecated. В обновленной версии редактор просто не проигрывал видео. Быстрый дебаг показал, что функция glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, width, height, GL_ALPHA, GL_UNSIGNED_BYTE, buffer) возвращает ошибку GL_INVALID_ENUM. Судя по документации OpenGL, на замену формату пикселей GL_ALPHA пришел GL_RED, который не работает с OpenGL 2.1… Пришлось подпереть костылем:

А самое загадочное то, что в конечном билде, собранном под OpenGL 4, все отлично работает с флагом GL_ALPHA. Записал этот нюанс в раздел магии, но все же сделал по-человечески.

Unity Editor можно запустить на более старой версии OGL. Для этого в консоли пишем:

image loader

Так я узнал что в Unity Editor используется 1326 текстур.

Windows

На этой платформе OpenGL версия плагина тоже собралась без каких-либо проблем. А вот на DirectX 9 остановлюсь поподробнее.

1. DirectX 9 обладает такой «фичей», как потеря устройства (lost device). OpenGL и DirectX (начиная с 10-й версии) лишены данного недостатка. Фактически происходит утрата контроля над графическими ресурсами (текстуры, шейдера, мэши в видеопамяти и т.д.). Получается, что мы должны обрабатывать эту ситуацию, и если она произошла, то обязаны загрузить или создать все текстуры заново. По моим наблюдениям во многих плагинах именно так и делают. Мне удалось немного схитрить: текстуры я создаю со скриптов Unity, а потом передаю их указатели в плагин. Таким образом весь менеджмент ресурсов я оставляю Unity, и он сам отлично справляется с ситуацией потери устройства.

2. Когда, казалось, уже все было готово, обнаружилась неожиданная проблема. Иногда и только на некоторых видео картинка выводилась со смещением, как показано на скриншоте:

image loader

Судя по виду изображения, ошибка могла присутствовать в алгоритме копирования данных в текстуру, в текстурных координатах или была связана с wrap-ом текстур. Документация подсказала, что DirectX для оптимизации может выравнивать размеры текстуры, добавляя дополнительные байты. Эта информация хранится в структуре:

Pitch — количество байт в одном ряду текстуры с учетом выравнивания.
Немного подправив алгоритм копирования, получил нужный результат (добавочные пиксели заполнил нулями):

Для отладки OpenGL поможет утилита gDEBugger, которая по функционалу схожа с OpenGL Profiler для OSX:

image loader

К сожалению, для DX9 подобных утилит я не нашел. Наличие такой тулзы помогло бы в поиске ошибки с копированием данных в текстуру.

Примера проекта под эту платформу в семплах не было. В документации мало полезной информации и в основном только о доступе к функциям с плагина.

Остановлюсь на важных аспектах:

1. В xCode создаем обычный iOS проект с типом StaticLib. Подключаем OpenGL фреймворки — и можно собирать плагин.

2. Имя конечного файла плагина не имеет значения. В Unity функции импортируются со всех плагинов, которые находятся в папке iOS:

3. Важный момент — если у вас в другом плагине есть функция с одинаковым именем, то собрать билд не получится. Линковщик Unity будет материться на двойную имплементацию. Совет — именуйте так, чтобы до такого названия никто не додумался.

4. UnityPluginLoad(IUnityInterfaces* unityInterfaces), которая должна вызываться при загрузке плагина, не вызывается! Чтобы узнать, когда все же плагин стартанул и получить информацию о текущем рендер устройстве, нужно создавать свой контроллер, унаследованный от UnityAppController и в нем зарегистрировать вызов функций для старта плагина и RenderEvent. Созданный файл следует поместить в папку с плагинами для iOS. Пример реализации контроллера для регистрации функций:

5. Если в плагине используется несколько разных архитектур, то их для удобства можно объединить в одну статическую библиотеку:

7. Был замечен серьезный баг. Если собрать проект под iOS с включенным Script Debugging, то при креше в игре падает и xCode. В результате ни логов, ни callstack…

Дебажить плагин под iOS платформу одно удовольствие — в xCode всегда есть callstack при падении. В консоли можно почитать логи как скриптов, так и плагина, а если в проект добавить *.CPP файлы плагина, то можно ставить бряки и пользоваться полным функционалом lldb дебагера! А вот со скриптами все намного хуже, так что логгирование в помощь.

Android

Сборка под Android требует больше всего тулзов:

— нормальный IDE для редактирования С++ кода. Я использовал xCode. Да и вообще на маке под Android собирать как-то проще;
— NDK для сборки С кода в статическую библиотеку;
— Android Studio со всеми его потребностями вроде Java и т. д. Студия нужна для удобного логгирования происходящего в приложении.

Пройдемся по интересным моментам:

1. Ситуация с отладкой плагинов в Android довольно печальная, так что рекомендую сразу подумать о записи логов в файл. Можно, конечно, заморочиться и попробовать настроить удаленную отладку, но у меня для этого не было времени и пришлось пойти более простым путем, просматривая логи через Android Studio. Для этого в android/log.h есть функция __android_log_vprint, которая работает аналогично printf. Для удобства обернул в кроссплатформенный вид:

Советую не обходить стороной asserts. В случае их срабатывания Android Studio позволяет просмотреть полный стек вызовов.

2. На этой платформе особая специфика именования плагинов — libMyPluginName.so. Например, префикс lib является обязательным (более подробно можно почитать в документации Unity).

3. В Android-приложении все ресурсы хранятся в одном бандле, который является jar или zip файлом. Мы не можем просто так открыть стрим и начать читать данные, как в остальных платформах. Кроме пути к видео, необходим Application.dataPath, который содержит путь к Android apk, только таким образом можем получить и открыть нужный ассет. Отсюда получаем длину файла и его смещение относительно начала бандла:

Открываем файлстрим по пути Application.dataPath стандартными средствами (fopen или тем, что вам больше нравится), и начинаем читать файл со смещением offset — это и есть наше видео. Длина нужна, чтобы знать, когда закончится видео файл и остановить дальнейшее чтение.

s_DeviceType всегда содержит kUnityGfxRendererNull. Судя по форумам, это ошибка Unity. Обернул Android часть кода в дефайн, где по умолчанию определил:

При разработке под Android надо быть готовым к постоянному копанию в консоли и регулярной пересборке либ. Если изначально корректно настроить Android.mk и Application.mk, то проблем со сборкой не должно возникнуть.

Ну вот, вроде, и все. Я старался остановиться на всех важных моментах, которые не были очевидны изначально. Теперь, имея эти знания, можно заранее разработать нормальную архитектуру плагина, и не придется переписывать код по несколько раз.

В качестве заключения

По моим предварительным подсчетам, эта работа должна была занять 2-3 недели, но ушло 2 месяца. Большинство времени пришлось потратить на выяснение описанных выше моментов. Самый нудный и долгий этап – это Android. Процесс пересборки статических библиотек и проекта занимал около 15 минут, а отладка происходила с помощью добавления новых логов. Так что запаситесь кофе и наберитесь терпения. А еще не забываем про частые падения и зависания Unity.

Надеюсь, данный материал будет полезен и поможет сэкономить драгоценное время. Критика, вопросы – приветствуются!

Источник

Как разрабатываются моды для Unity-игр. Часть 1: внедряемся в код игры

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

Я довольно много играю в Beat Saber с модами, и в какой-то момент появилась необходимость написать свои. Beat Saber сделан на Unity, так что я покопался в его коде, в коде существующих модов, разобрался, как там всё устроено, и написал об этом статью. Получилось довольно много материала, так что я разбил его на две части. В этой части мы рассмотрим, какие методы используется для того, чтобы игра запускала сторонние моды: модификации библиотек игры и самого движка, подмена адресов в таблице импорта dll и вмешательство в загрузку Mono Runtime.

qz btwwnew2hj7pup0dp el33g0

О Beat Saber

Beat Saber является одной из самых популярных игр для VR-шлемов. Если у вас есть такой шлем, то, скорее всего, вы уже знаете, что такое Beat Saber. Если нет, то, возможно, вы видели хотя бы одно видео из игры в рекомендациях Youtube:

Единственное, что Beat Saber поддерживает из модов — это пользовательские уровни. В игре есть официальный редактор, но нет никакого сервиса, чтобы как-либо распространять свои творения, и, по моему мнению, вряд ли появится. В основе практически любого уровня лежит какая-то песня. Разрешить выкладывать такие файлы в своем официальном сервисе — значит привлечь к себе внимание правообладателей с их постоянными запросами что-то удалить или вообще с требованиями закрыть весь сервис.

Поэтому не удивительно, что вокруг Beat Saber сформировалось огромное сообщество: Beat Saber Modding Group (BSMG). Именно сообщество ответственно за большую часть того, что есть в игре.

Все это поддерживается сообществом. Большинство модов и даже сами сайты — это проекты с открытым исходным кодом, их можно найти на GitHub, чем я и воспользовался при подготовке к этой статье. Примеры кода, которые я здесь привожу, сильно упрощены, чтобы показать только суть и не увеличивать объем текста. Для тех, кто хочет посмотреть полный код, я привожу ссылки на файлы с оригинальным кодом на GitHub.

Общая информация о Unity

Mono — это одна из реализаций спецификации общеязыковой инфраструктуры (Common Language Infrastructure, CLI) с открытым исходным кодом. Mono работает на Windows, Linux, macOS, мобильных устройствах и игровых консолях. Разрабатывается с 2001 года компанией Ximian, которую в 2003 купила компания Novell, которую в 2011 купила Attachmate. Attachmate сократила почти всех, но разработчики Mono хотели и дальше разрабатывать Mono, поэтому организовали в том же году отдельную компанию Xamarin, которую в 2016 купили Microsoft.

Это стандартная практика в разработке игр, когда высокопроизводительная часть движка написана на C или C++, а игровая логика пишется на скриптовом языке, например Lua. Преимущество C# в качестве скриптового языка в том, что он достаточно “высокоуровневый”, чтобы упростить и ускорить разработку игры, но при этом позволяет использовать JIT-компиляцию в нативный код. Получается такая архитектура игрового движка:

ib4hwuej6z3eulggd0bbezpog9i

Подробнее об этом можно прочитать в документации Mono.

BSIPA

ghxe twuper7zmt7qohwmeawmcs

BSIPA (Beat Saber Illusion Plugin Architecture) — это набор библиотек, которые модифицируют файлы Beat Saber так, чтобы игра могла загружать сторонние моды. Иногда такие библиотеки называют менеджерами плагинов.

BSIPA написана на C# (исходный код) и является форком IPA. Т.е. моддеры взяли уже существующий менеджер плагинов для Unity и улучшили его специально под нужды Beat Saber. В исходном коде BSIPA я выделяю три основных модуля: IPA, IPA.Loader и IPA.Injector. На самом деле, их больше, но остальные не так важны.

IPA.exe

Обычно игроки используют ModAssistant и его аналоги, чтобы устанавливать моды, но мы рассмотрим, как это делается вручную. Для этого нужно скачать последний релиз BSIPA, распаковать все файлы в папку с игрой и запустить IPA.exe. Это исполняемый файл, который копирует файлы из только что распакованного архива в те папки, где они должны находиться. И это, в общем-то, все, что он делает: просто копирует файлы, и если какой-то из файлов уже существует в игре, то он делает его резервную копию. Вот список копируемых файлов:

Мы видим здесь еще два модуля BSIPA, которые я упомянул ранее: IPA.Loader.dll и IPA.Injector.dll. Остальные библиотеки нужны, чтобы работали эти две. Некоторые из них мы еще рассмотрим подробнее в этой статье и во второй части.

IPA.Loader.dll

Как и следует из названия этого модуля, он отвечает за загрузку и управление плагинами. Здесь определены классы PluginLoader, PluginManager и PluginComponent. PluginLoader — это класс, в котором определена логика загрузки плагинов из папки Plugins. В оригинальной IPA плагины загружаются в алфавитном порядке, что иногда приводит к проблемам. В BSIPA логика загрузки была переписана: Loader сначала загружает метаданные плагинов, анализирует зависимости, находит конфликты и строит порядок загрузки на основе этой информации.

Класс PluginManager отвечает за управление плагинами: поиск, активацию и деактивацию с учетом их зависимостей.

Класс PluginComponent — это компонент Unity, отвечающий за старт загрузки модов, их хранение и передачу им событий из игры. Например, если в основной игре сменилась активная сцена (мы запустили какой-то уровень или, наоборот, вернулись в главное меню), то PluginComponent итерирует по всем модам и сообщает им об этом. Подробнее об этом будет во второй части.

IPA.Injector.dll

Название Injector намекает, что этот модуль отвечает за внедрение в оригинальную игру. Начнем с забавного факта: BSIPA добавляет в игру антипиратскую защиту.

Защита примитивная, она просто проверяет, есть ли в папке с игрой файлы, которые явно указывают на взлом: SmartSteamEmu.ini, BSteam crack.dll, HUHUVR_steam_api64.dll и другие.

Скорее всего, BSIPA это нужно из практических интересов. Если игра пиратская, то это значит, что она была взломана и часть ее файлов изменены. BSIPA тоже изменяет файлы игры, а значит, совместимость с пиратскими библиотеками не гарантируется.

IPA.Injector использует Mono.Cecil, чтобы модифицировать библиотеку UnityEngine.CoreModule.dll. Она написана на C# (иначе бы мы не смогли ее редактировать) и содержит базовые сущности Unity. Например, там определены классы GameObject (все объекты в игре, которые располагаются на игровых сценах, являются объектами этого класса) и класс MonoBehaviour (базовый класс для компонентов с игровой логикой, в том числе PluginComponent). IPA.Injector находит в библиотеке UnityEngine.CoreModule.dll класс UnityEngine.Application и модифицирует его статический конструктор (или создает, если его нет), добавляя туда создание бутстраппера:

По крайней мере, в Unity 2019.3.0f3 у Application нет статического конструктора, а значит, эта модификация не удаляет существующий важный код и относительно безопасна. Если статический конструктор уже существует и Injector заменяет его, то это только в том случае, если мы повторно модифицируем библиотеку.

После модификации в классе Application появляется новый статический конструктор (код получен с помощью декомпиляции модифицированной UnityEngine.CoreModule.dll):

Метод CreateBootstrapper создает новый объект и добавляет в него компонент Bootstrapper:

Бутстраппер представляет собой интересное техническое решение:

Метод Start похож на метод Awake, но Awake вызывается в первый кадр, когда создается компонент, а Start вызывается в первый кадр после активации компонента (компоненты могут создаваться неактивными). Каждый из методов вызывается ровно один раз за жизненный цикл компонента и может использоваться для разных этапов инициализации компонента. Компонент Bootstrapper в своем методе Start вызывает уничтожение объекта, к которому он прикреплен. В методе OnDestroy вызывается колбэк, установленный в CreateBootstrapper:

Колбэк дожидается асинхронной загрузки плагинов и создает компонент PluginComponent. Если честно, я не знаю, зачем нужен этот механизм с самоуничтожением бутстраппера и созданием PluginComponent в OnDestroy. Мое предположение: это нужно для того, чтобы сделать загрузку отложенной — метод Start вызывается, когда компонент становится активным, а он не может стать активным, пока не загрузится игровая сцена. Буду рад, если более опытные Unity-разработчики поправят меня в комментариях.

Помимо этого IPA.Injector редактирует еще один файл — MainAssembly.dll. Эта библиотека содержит код Beat Saber. С помощью все того же Mono.Cecil IPA.Injector убирает у всех классов sealed и делает все методы публичными и виртуальными. Теперь любой класс в любом плагине может наследоваться от любого класса в оригинальной игре и переопределять поведение его методов. Эта модификация уже не относится к загрузке плагинов, но она очень упрощает разработку модов.

Если бы у нас использовалась не BSIPA, а оригинальная IPA, то на этом можно было бы остановиться. В оригинальной версии IPA.exe запускает Injector, модифицирует UnityEngine.CoreModule.dll, и в игре появляются моды. У такого подхода есть один минус — каждый раз, когда оригинальная игра обновляется, нужно заново запускать IPA.exe и патчить игру. BSIPA решает эту проблему, но, как я уже писал выше, IPA.exe в BSIPA просто копирует файлы и не делает больше ничего. За запуск Injector в этом случае отвечает другая библиотека.

Unity Doorstop

fkpz7

За внедрение в код Unity отвечает библиотека UnityDoorstop-BSIPA. Она лежит среди файлов BSIPA и написана на чистом C. UnityDoorstop-BSIPA (исходный код) — это тоже форк, оригинальный проект можно найти здесь. Далее для простоты буду вместо UnityDoorstop-BSIPA писать Doorstop. Лозунгом Doorstop является фраза “Run managed code before Unity does!”, что в примерном переводе звучит как “Запускай управляемый код до того, как Unity сможет это сделать”. Напомню, что “управляемый код” — это в нашем случае код C#. Выше мы уже выяснили, что ядро движка Unity написано на C++, а пользовательские скрипты для игровой логики и некоторые части самого Unity — на C#. Значит, Doorstop каким-то образом позволяет нам вмешаться в логику, когда ядро Unity уже загрузилось, а C#-скрипты — еще нет.

Когда мы запускаем игру на Unity (например, Beat Saber.exe), то в память одной из первых загружается библиотека UnityPlayer.dll. Она прилагается ко всем Unity-играм и отвечает за запуск и выполнение самой игры. У этой библиотеки есть таблица импорта, в которой говорится, что UnityPlayer использует функцию GetProcAddress из библиотеки kernel32.dll. GetProcAddress — это функция WinAPI, которая возвращает адрес функции из определенной библиотеки по ее названию. Я не видел исходного кода Unity, но судя по тому, что я видел в BSIPA и Doorstop, в UnityPlayer должно быть что-то вроде такого:

mono_jit_init_version — это функция, которая отвечает за инициализацию и запуск Mono. Подробнее можно прочитать здесь. Doorstop вмешивается в этот процесс. Делается это в два шага.

Шаг 1. Проксируем GetProcAddress

Этот код находит в памяти уже загруженную библиотеку UnityPlayer.dll, берет ее таблицу импорта (Import Address Table, IAT), находит в ней GetProcAddress из kernel32.dll и заменяет ее на нашу функцию hookGetProcAddress из Doorstop.dll. hookGetProcAddress в упрощенном виде выглядит так:

Подробнее про IAT Hooking можно прочитать здесь. Получается, что hookGetProcAddress проксирует все вызовы GetProcAddress. Прокси-функция смотрит название функции, которую у нее запрашивают. Если это НЕ mono_jit_init_version, то hookGetProcAddress просто вызывает настоящую GetProcAddress и возвращает то, что у нее попросили, тем самым не мешая нормальной работе. Если у нее запрашивают mono_jit_init_version, то тогда она возвращает переопределенную функцию ownMonoJitInitVersion. Пользуясь случаем, прокси-функция получает указатель на библиотеку, в котором ищется mono_jit_init_version, и берет из нее настоящие функции Mono с помощью GetProcAddress (внутри init(module); ):

Шаг 2. Переопределеяем mono_jit_init_version

ownMonoJitInitVersion сначала вызывает настоящую mono_jit_init_version, чтобы создать Mono. Затем она с помощью Mono загружает сборку IPA.Injector.dll и запускает из нее статический метод Main. В (очень) упрощенном виде код ownMonoJitInitVersion выглядит так:

Мы уже рассмотрели выше, что IPA.Injector содержит код, который внедряет плагины в Beat Saber. После того, как IPA.Injector завершает свою работу, ownMonoJitInitVersion отдает Mono в Unity. Unity даже не в состоянии понять, что что-то было не так. Если б он вызвал настоящую mono_jit_init_version, то он бы получил Mono и начал бы дальше с ним работать. Если Unity запускает переопределенную ownMonoJitInitVersion, то он тоже получает Mono — он просто не в курсе, что этим Mono успели воспользоваться для чего-то еще.

winhttp.dll

Остался один нерешенный вопрос. Во-первых, в описании Doorstop я писал про библиотеку Doorstop.dll. Давайте опять взглянем на файлы, которые IPA.exe устанавливает в игру:

Как вы можете видеть, Doorstop.dll здесь нет. Во-вторых, даже если бы Doorstop.dll здесь бы, то почему Beat Saber или Unity должны его загружать в память, если его нет в таблице импорта? Решение: выдать Doorstop.dll за другую библиотеку, которую загружает Unity, и сделать так, чтобы она загрузилась раньше настоящей. Такой библиотекой в нашем случае является winhttp.dll — это системная библиотека Windows для http-запросов (хранится в C:/Windows/System32). У Unity в одной из библиотек в таблице импорта указано, что ей нужна библиотека winhttp.dll, поэтому во время загрузки Unity Windows сначала загружает winhttp.dll в память, и только после этого Unity начинает работать.

Doorstop собирается в библиотеку под тем же названием: winhttp.dll. Там содержится код Doorstop, отвечающий за все манипуляции с GetProcAddress и mono_jit_init_version, а также туда добавляется таблица экспорта (Export Address Table) со всеми теми же функциями, что в оригинальной winhttp.dll. Загрузка библиотек в Windows устроена так, что Windows сначала проверяет, есть ли нужные библиотеки в папке с программой, и только потом, если ничего не нашел, идет в System32. Поэтому при запуске игры Windows в первую очередь находит наш файл. Поддельная библиотека загружает настоящую winhttp динамически (с помощью LoadLibrary) и просто перенаправляет все вызовы из своей таблицы экспорта на адреса настоящих функций (с помощью GetProcAddress). Можно сравнить размеры: поддельная библиотека весит 16кб, а настоящая — 960кб.

В репозиториях IPA и BSIPA есть скрипты для генерации файла proxy.c, который содержит таблицу экспорта, совпадающую с оригинальной библиотекой, и загружает все методы через GetProcAddress. В IPA такой скрипт написан на PowerShell, а в BSIPA — на Python. Сгенерированный файл выглядит так: proxy.c.

Подробнее про DLL Search Order Hijacking можно прочитать тут.

Перечисление действий в хронологическом порядке

Повторяем все шаги в хронологическом порядке.

Про часть 2

На этом первая часть завершена. Мы разобрались, как с помощью манипуляций с библиотеками Windows и Unity можно добавить моды в Beat Saber. Несмотря на то, что BSIPA — это форк, сделанный специально для этой игры, мы в этой статье нигде не делали предположений о внутренней структуре игры и не использовали ее код, а значит можно сказать, что эти методы применимы ко всем Unity-играм.

В следующей части мы напишем свой собственный мод для Beat Saber: посмотрим, как моды обмениваются информацией с игрой, как модифицировать поведение оригинальной игры, а также воспользуемся Harmony — библиотекой для модификации C#-кода, которая используется моддерами в RimWorld, BATTLETECH, Cities: Skylines, Kerbal Space Program, Oxygen Not Included, Stardew Valley, Subnautica и многих других.

Источник

Общеобразовательный справочник
Adblock
detector