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);}
Навигация
Перейти к полной версии