Главная Форум Файлы Статьи
Меню
Категории
Modding и всё, что с этим связано [11]
Статьи про моддинг
Описание, прохождение, секреты и приколы [0]
Описание игр серии гта, прохождение, различные секреты и приколы
Other [0]
Прочее, не вошедшее не в одну категорию
Чат
VC Modding











Главная | Регистрация | Вход
[ПРОГРАММИРОВАНИЕ] Внедрение своего кода в оригинальный код игры - Modding и всё, что с этим связано - Grand Theft Auto - Каталог статей - NewRockstar Games

Главная » Статьи » Grand Theft Auto » Modding и всё, что с этим связано

[ПРОГРАММИРОВАНИЕ] Внедрение своего кода в оригинальный код игры
Туториал для "внедрения" своего кода в оригинальный код игры.
Предупреждаю, что я "чайник", в связи с чем могу ошибаться, однако способ проверенно работает!
Спасибо 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 - ваша функция.
Категория: Modding и всё, что с этим связано | Добавил: Shagg_E (13.06.2016)
Просмотров: 806 | Комментарии: 1 | Рейтинг: 0.0/0
Всего комментариев: 1
avatar
0
1 Shagg_E • 19:36, 16.06.2016
ВТОРОЙ ВАРИАНТ(более грамотный): пропатчить не начало функции 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:
Код
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):
Код
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);
}
avatar

 


Профиль
Гости
Google Translate
ENGLISH!!!

ENGLISH!!!

Статистика
Онлайн всего: 1
Гостей: 1
Пользователей: 0

[Кто нас сегодня посетил]

Поиск