GTA Vice City > Справочная информация

Внедрение своего кода в оригинальный код игры

(1/1)

Shagg_E:
Туториал для "внедрения" своего кода в оригинальный код игры.
Предупреждаю, что я "чайник", в связи с чем могу ошибаться, однако способ проверенно работает!
Спасибо 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], переходим к делу.
Объявляем оригинальные функции, которые мы будем потом вызывать, а также адрес подменяемой функции:

--- Код: C++ ---auto _GameShutDown = (void (_cdecl*)())0x4A49E0;auto _GameInitialise = (void (_cdecl*)())0x4A5C40;auto _GameShutDownForRestart = (void (_cdecl*)())0x4A47B0;BYTE *GameShutDownForRestartRestore     = (BYTE *)  0x4A47B0;
Создаем методом Sector-а функцию, которая будет подменять оригинальную функцию на нашу:

--- Код: C++ ---void injectFunction (DWORD address, DWORD function) {        DWORD _old;        VirtualProtect((LPVOID)address,4,  PAGE_READWRITE, &_old);         BYTE * patch = (BYTE *)address;        *patch = 0xE9;    // JMP        *(DWORD *)(patch+1) = (function-(address+5));            VirtualProtect((LPVOID)address,4, _old, &_old); }
Пишем нашу функцию:

--- Код: C++ ---void __stdcall LoadGameShutDown(char key) {        _GameShutDown();          _GameInitialise();          ProtectBYTE(GameShutDownForRestartRestore,0x53);         ProtectBYTE(GameShutDownForRestartRestore+1,0x55);        ProtectBYTE(GameShutDownForRestartRestore+2,0x6A);        ProtectBYTE(GameShutDownForRestartRestore+3,0x00);        ProtectBYTE(GameShutDownForRestartRestore+4,0xE8);        _GameShutDownForRestart();}Её суть:
Сначала происходит вызов оригинальных функций Shutdown[3] и InitialiseGame[4]. Затем, прежде чем перейти обратно на ShutDownForRestart[1], побайтово восстанавливаются 5 байт оригинальной функции:

Почему именно 5 байт? Дело в том, что injectFunction заменяет именно первые 5 байт на "прыжок" к нашей функции LoadGameShutDown, поэтому нам нужно "подчистить" за собой эти изменения.
После восстановления оригинальной функции и перехода на неё, игра исполняет остальной код в обычном режиме, т.е. после ShutDownForRestart[1] исполняется InitialiseWhenRestarting[2] и так далее.
Ах да - о применении этого(момент самого внедрения): когда потребуется, в вашем коде пропишите

--- Код: C++ ---injectFunction((DWORD)GameShutDownForRestartRestore, (DWORD)LoadGameShutDown);, где GameShutDownForRestartRestore - адрес функции, перед которой нужно внедрить ваш код, а LoadGameShutDown - ваша функция.

Shagg_E:
P.S. Я знаю, что не изобрел ничего нового, однако сам не смог найти похожей статьи, поэтому долго с этим мучался и почувствовал себя "могучим кулхацкером", когда всё наконец заработало. Может быть, эта статья сэкономит кому-то нервы. Еще раз спасибо DK и Sector - без их помощи я бы не смог решить эту проблему с меню.

DK:
Как-то всё запутано у тебя  :D
Если надо вызвать CGame::Shutdown и InitialiseGame перед CGame::ShutdownForRestart, очевидно же - нужно делать редирект на месте вызова CGame::ShutdownForRestart, в котором вызывать эти три функции - CGame::Shutdown,  InitialiseGame, CGame::ShutdownForRestart.

--- Код: C++ ---void MyShutdownForRestart() {    CGame::Shutdown();    InitialiseGame();    CGame::ShutdownForRestart();} patch::RedirectCall(0x600542 , MyShutdownForRestart);

Shagg_E:
Да у меня всегда так - всё от нехватки знаний ;D
К примеру - я понятия не имею, что такое редирект, а гугл тут не помогает...

UPD: А, всё - понял: типа, надо было пропатчить не само начало функции ShutdownForRestart, а место её вызова ;D
Надо попробовать!
UPD2: Странно: в случае подмены места вызова появляются баги, вроде отсутствия всех видов пешеходов кроме male1 и копов...
Видимо, мне пока рано пользоваться таким способом...
UPD3: Блин! Sector же в уроке четко расписал, что объявлять свою функцию, как "__stdcall" нужно лишь в случае, если оригинальная функция очищает стек после себя(или как-то так). Короче, убрав объявление функции, я решил проблемы со стриммингом.
Правда, мне всё равно пришлось в конце моей функции побайтово восстанавливать старый кусок кода, чтобы в дальнейшем игра при загрузке сохранений использовала "быструю" загрузку.

В общем, итоговый вариант кода выглядит так:
Объявление оригинальных функций и адреса вызова GameShutDownForRestart:

--- Код: C++ ---auto _GameShutDown = (void (_cdecl*)())0x4A49E0;auto _GameInitialise = (void (_cdecl*)())0x4A5C40;auto _GameShutDownForRestart = (void (_cdecl*)())0x4A47B0;BYTE    *CallGameShutDownForRestart     = (BYTE *)  0x600542;Функция инжекта почти та же - поменял E9 на E8, поскольку в вызове оригинальной функции стоит E8:

--- Код: C++ ---void injectFunction (DWORD address, DWORD function) {        DWORD _old;        VirtualProtect((LPVOID)address,4,  PAGE_READWRITE, &_old);         BYTE * patch = (BYTE *)address;        *patch = 0xE8;    // JMP        *(DWORD *)(patch+1) = (function-(address+5));            VirtualProtect((LPVOID)address,4, _old, &_old); }Сама наша функция без объявления типа __stdcall и на этот раз с восстановлением куска кода вызова, а не самой функции GameShutDownForRestart(E8 69 42 EA FF):

--- Код: C++ ---void LoadGameShutDown(char key){        _GameShutDown();        _GameInitialise();        _GameShutDownForRestart();        ProtectBYTE(CallGameShutDownForRestart,0xE8);        ProtectBYTE(CallGameShutDownForRestart+1,0x69);        ProtectBYTE(CallGameShutDownForRestart+2,0x42);        ProtectBYTE(CallGameShutDownForRestart+3,0xEA);        ProtectBYTE(CallGameShutDownForRestart+4,0xFF);}

Навигация

[0] Главная страница сообщений

Перейти к полной версии