Туториал для "внедрения" своего кода в оригинальный код игры.
Предупреждаю, что я "чайник", в связи с чем могу ошибаться, однако способ проверенно работает!Спасибо
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-а функцию, которая будет подменять оригинальную функцию на нашу:
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);
}
Пишем нашу функцию:
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] и так далее.
Ах да - о применении этого(момент самого внедрения): когда потребуется, в вашем коде пропишите
injectFunction((DWORD)GameShutDownForRestartRestore, (DWORD)LoadGameShutDown);
, где
GameShutDownForRestartRestore - адрес функции, перед которой нужно внедрить ваш код, а
LoadGameShutDown - ваша функция.