Автор Тема: Внедрение своего кода в оригинальный код игры  (Прочитано 572 раз)

Оффлайн Shagg_E

  • Опытный
  • **
  • Сообщений: 483
  • Репутация: +14/-0
  • Изобретательный Рукожопъ
    • Просмотр профиля
    • NewRockstar
Туториал для "внедрения" своего кода в оригинальный код игры.
Предупреждаю, что я "чайник", в связи с чем могу ошибаться, однако способ проверенно работает!
Спасибо 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++
  1. auto _GameShutDown = (void (_cdecl*)())0x4A49E0;
  2. auto _GameInitialise = (void (_cdecl*)())0x4A5C40;
  3. auto _GameShutDownForRestart = (void (_cdecl*)())0x4A47B0;
  4. BYTE *GameShutDownForRestartRestore     = (BYTE *)  0x4A47B0;

Создаем методом Sector-а функцию, которая будет подменять оригинальную функцию на нашу:
Код: C++
  1. void injectFunction (DWORD address, DWORD function)
  2. {
  3.         DWORD _old;
  4.         VirtualProtect((LPVOID)address,4,  PAGE_READWRITE, &_old);
  5.         BYTE * patch = (BYTE *)address;
  6.         *patch = 0xE9;    // JMP
  7.         *(DWORD *)(patch+1) = (function-(address+5));    
  8.         VirtualProtect((LPVOID)address,4, _old, &_old);
  9. }

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

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

Оффлайн Shagg_E

  • Опытный
  • **
  • Сообщений: 483
  • Репутация: +14/-0
  • Изобретательный Рукожопъ
    • Просмотр профиля
    • NewRockstar
P.S. Я знаю, что не изобрел ничего нового, однако сам не смог найти похожей статьи, поэтому долго с этим мучался и почувствовал себя "могучим кулхацкером", когда всё наконец заработало. Может быть, эта статья сэкономит кому-то нервы. Еще раз спасибо DK и Sector - без их помощи я бы не смог решить эту проблему с меню.
« Последнее редактирование: Июнь 13, 2016, 09:38:27 pm от Shaggy »

Оффлайн DK

  • Новичек
  • **
  • Сообщений: 213
  • Репутация: +311/-0
    • dk22pac
    • Просмотр профиля
Как-то всё запутано у тебя  :D
Если надо вызвать CGame::Shutdown и InitialiseGame перед CGame::ShutdownForRestart, очевидно же - нужно делать редирект на месте вызова CGame::ShutdownForRestart, в котором вызывать эти три функции - CGame::Shutdown,  InitialiseGame, CGame::ShutdownForRestart.
Код: C++
  1. void MyShutdownForRestart() {
  2.     CGame::Shutdown();
  3.     InitialiseGame();
  4.     CGame::ShutdownForRestart();
  5. }
  6.  
  7. patch::RedirectCall(0x600542 , MyShutdownForRestart);
« Последнее редактирование: Июнь 15, 2016, 06:50:01 pm от DK »
Plugin-SDK
gta_sa.idb
Skype-конференция по моддингу https://join.skype.com/jqGbs97avigh

Оффлайн Shagg_E

  • Опытный
  • **
  • Сообщений: 483
  • Репутация: +14/-0
  • Изобретательный Рукожопъ
    • Просмотр профиля
    • NewRockstar
Да у меня всегда так - всё от нехватки знаний ;D
К примеру - я понятия не имею, что такое редирект, а гугл тут не помогает...

UPD: А, всё - понял: типа, надо было пропатчить не само начало функции ShutdownForRestart, а место её вызова ;D
Надо попробовать!
UPD2: Странно: в случае подмены места вызова появляются баги, вроде отсутствия всех видов пешеходов кроме male1 и копов...
Видимо, мне пока рано пользоваться таким способом...

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

В общем, итоговый вариант кода выглядит так:
Объявление оригинальных функций и адреса вызова GameShutDownForRestart:
Код: C++
  1. auto _GameShutDown = (void (_cdecl*)())0x4A49E0;
  2. auto _GameInitialise = (void (_cdecl*)())0x4A5C40;
  3. auto _GameShutDownForRestart = (void (_cdecl*)())0x4A47B0;
  4. BYTE    *CallGameShutDownForRestart     = (BYTE *)  0x600542;
Функция инжекта почти та же - поменял E9 на E8, поскольку в вызове оригинальной функции стоит E8:
Код: C++
  1. void injectFunction (DWORD address, DWORD function)
  2. {
  3.         DWORD _old;
  4.         VirtualProtect((LPVOID)address,4,  PAGE_READWRITE, &_old);
  5.         BYTE * patch = (BYTE *)address;
  6.         *patch = 0xE8;    // JMP
  7.         *(DWORD *)(patch+1) = (function-(address+5));    
  8.         VirtualProtect((LPVOID)address,4, _old, &_old);
  9. }
Сама наша функция без объявления типа __stdcall и на этот раз с восстановлением куска кода вызова, а не самой функции GameShutDownForRestart(E8 69 42 EA FF):
Код: C++
  1. void LoadGameShutDown(char key)
  2. {
  3.         _GameShutDown();
  4.         _GameInitialise();
  5.         _GameShutDownForRestart();
  6.         ProtectBYTE(CallGameShutDownForRestart,0xE8);
  7.         ProtectBYTE(CallGameShutDownForRestart+1,0x69);
  8.         ProtectBYTE(CallGameShutDownForRestart+2,0x42);
  9.         ProtectBYTE(CallGameShutDownForRestart+3,0xEA);
  10.         ProtectBYTE(CallGameShutDownForRestart+4,0xFF);
  11. }
« Последнее редактирование: Июнь 16, 2016, 07:36:16 pm от Shaggy »