[ПРОГРАММИРОВАНИЕ] Внедрение своего кода в оригинальный код игры - Modding и всё, что с этим связано - Grand Theft Auto - Каталог статей - NewRockstar Games
[ПРОГРАММИРОВАНИЕ] Внедрение своего кода в оригинальный код игры
Туториал для "внедрения" своего кода в оригинальный код игры. Предупреждаю, что я "чайник", в связи с чем могу ошибаться, однако способ проверенно работает! Спасибо DK за помощь с IDA и Sector за помощь с подменой функции!
Итак, суть проблемы: Когда Вайс(далее - игра) запущен и загружен, загрузка сохранений происходит без перезагрузки всех ресурсов. Это вызвало проблему в Main Menu Scene, когда мне потребовалось загружать сохранения оригинальной игры, когда по-факту загружены игровые ресурсы "меню". Я решил, что необходимо найти способ перезагрузить ресурсы игры во время загрузки сохранений, подобно тому, как это происходит при старте новой игры. Копаясь в IDA и проведя несколько часов, тестируя разные догадки, в псевдокоде я обнаружил такой момент: Когда игра уже запущена и загружает сохранение, она переходит к функциям ShutDownForRestart[1] и InitialiseWhenRestarting[2], которые перезагружают избранные моменты игры("легкая перезагрузка"), но не все ресурсы. В случае, если игра еще не загружена, игра просто загружает все ресурсы при помощи InitialiseGame[4], но потом всё равно переходит на "быструю перезагрузку"[1]. В случае, если игра загружена, но игрок выбрал старт новой игры - та, в свою очередь, полностью выгружает ресурсы при помощи Shutdown[3] и потом снова их загружает[4]. Таким образом, мне требовалось сделать так, чтобы перед "легкой перезагрузкой" происходила полная перезагрузка ресурсов игры, т.е. "внедрить" [3] и [4] перед [1]. Я решил подменить адрес функции [1] на свою функцию, в которой происходит вызов функций [3] и [4], а потом идет переброс обратно на функцию [1]. Чтобы не произошла рекурсия(т.к. функцию [1] я уже подменил на свою), было решено восстановить побайтово старый заголовок функции [1] и только потом делать переброс туда. Короче, вначале необходимо найти адрес функции: жмем в режиме псевдокода на функцию ShutDownForRestart[1], потом переносимся в IDA View, нажав TAB: Там дважды жмем ЛКМ на вызове нашей функции, т.е. на _ZN5CGame18ShutDownForRestartEv[5], и попадаем на начало самой функции ShutDownForRestart[6] по адресу 4A47B0: Таким образом, зная адрес начала функции, мы можем пошалить с ней, а именно - подменить заголовок на переброс к нашей собственной функции. Узнав аналогичным способом адреса функций [3] и [4], переходим к делу. Объявляем оригинальные функции, которые мы будем потом вызывать, а также адрес подменяемой функции:
Код
auto _GameShutDown = (void (_cdecl*)())0x4A49E0; auto _GameInitialise = (void (_cdecl*)())0x4A5C40; auto _GameShutDownForRestart = (void (_cdecl*)())0x4A47B0; BYTE *GameShutDownForRestartRestore = (BYTE *) 0x4A47B0;
Создаем методом Sector-а функцию, которая будет подменять оригинальную функцию на нашу:
Её суть: Сначала происходит вызов оригинальных функций Shutdown[3] и InitialiseGame[4]. Затем, прежде чем перейти обратно на ShutDownForRestart[1], побайтово восстанавливаются 5 байт оригинальной функции: Почему именно 5 байт? Дело в том, что injectFunction заменяет именно первые 5 байт на "прыжок" к нашей функции LoadGameShutDown, поэтому нам нужно "подчистить" за собой эти изменения. После восстановления оригинальной функции и перехода на неё, игра исполняет остальной код в обычном режиме, т.е. после ShutDownForRestart[1] исполняется InitialiseWhenRestarting[2] и так далее. Ах да - о применении этого(момент самого внедрения): когда потребуется, в вашем коде пропишите
ВТОРОЙ ВАРИАНТ(более грамотный): пропатчить не начало функции ShutDownForRestart, а место её вызова[5], т.е. 0x600542. Также, следует заметить, что Sectorв уроке четко расписал, что объявлять свою функцию, как "__stdcall" нужно лишь в случае, если оригинальная функция очищает стек после себя(или как-то так). Короче, в нашем случае - этого делать не нужно.
Объявление оригинальных функций и адреса вызоваGameShutDownForRestart:
Код
auto _GameShutDown = (void (_cdecl*)())0x4A49E0; auto _GameInitialise = (void (_cdecl*)())0x4A5C40; auto _GameShutDownForRestart = (void (_cdecl*)())0x4A47B0; BYTE *CallGameShutDownForRestart = (BYTE *) 0x600542;
Функция инжекта почти та же - поменял E9 на E8, поскольку в вызове оригинальной функции стоит E8:
Сама наша функция без объявления типа __stdcall и на этот раз с восстановлением куска кода вызова, а не самой функции GameShutDownForRestart(E8 69 42 EA FF):