Глядя на серию постов о тридцати строчном javascript программировании, тоже захотелось что-нибудь написать, правда не в 30 строк, но потратив минимум времени, just for fun. Был скачан установщик классической Grand Theft Auto для Windows, а т.к. эта GTA работает только в стандартных разрешениях с соотношением сторон 4:3, я решил написать плагин, который бы запускал её в родном разрешении моего монитора(1920x1080).
Перед тем как приступить непосредственно к написанию плагина, нужно как-то загружать его в процесс игры. Для этого я использую универсальный ASI Loader. GTA 1 — игра довольно старая, а значит самым оптимальным вариантом будет использование ddraw.dll. Убедиться, что Grand Theft Auto.exe действительно использует эту библиотеку можно через хекс редактор:
Копирую содержимое архива в папку Grand Theft AutoWINO(папка с исполняемым файлом), переименовываю dinput8.dll(ASI Loader) в ddraw.dll. Зная, что игра запустится в низком разрешении, создаю в этой же папке пустой файл wndmode.ini. Т.к. ASI Loader включает в себя wndmode.dll, о которой на хабре уже было упоминание, при наличии файла wndmode.ini GTA должна отобразиться в окне.
При первом запуске тестовый плагин рапортует, что все работает, и можно писать свой:
Открылось меню игры, в таком вот виде:
А еще оказалось, что игра вылетает при сворачивании, и от оконного режима пришлось отказаться, wndmode.ini был удален. Также была удалена папка scripts, за ненадобностью. Запускаю игру снова, теперь при сворачивании/разворачивании не вылетает, и выглядит так:
Естественно 1024х768 в 2013 году меня не устраивает, поэтому в Visual Studio создаю новый проект Win32, тип — DLL, а в свойствах выставляю:
- Конфигурация — Release
- Набор символов — Использовать многобайтовую кодировку
- Библиотека времени выполнения — Многопоточная (/MT)
- Конечное расширение — .asi
- Выходной каталог — E:GamesRockstar GamesGrand Theft Auto ClassicsGrand Theft AutoWINO
Основа плагина:
#include "stdafx.h"
#include "CPatch.h"
DWORD WINAPI Thread(LPVOID param)
{
return 0;
}
BOOL APIENTRY DllMain(HMODULE hModule, DWORD reason, LPVOID lpReserved)
{
if (reason == DLL_PROCESS_ATTACH)
{
HANDLE HndThread = CreateThread(0, 0, (LPTHREAD_START_ROUTINE)&Thread, NULL, 0, NULL);
}
return TRUE;
}
Теперь нужно узнать, какие адреса памяти хранят в себе текущее значение разрешения экрана. С их помощью можно будет найти функции, в которых производится запись этого значения и заменить эту запись на свою. Запускаю игру, нажимаю клавишу F11, она позволяет менять разрешениe экрана:
По умолчанию было выставлено 1024х768, открываю Cheat Engine, в нем выбираю процесс Grand Theft Auto.exe, в параметрах поиска выставляю следующие настройки:
После нажатия кнопки First Scan было найдено около двадцати тысяч адресов, чтобы отсеять лишние, я несколько раз менял разрешение в игре и искал новые значения через кнопку Next Scan. Тоже самое было сделано и для высоты, в итоге я получил около двух десятков адресов:
Простоe изменение этих адресов не дает результата, так как они перезаписываются актуальным значением сразу после разворачивания игры.
Совсем не в полной уверенности что это сработает, я решил, что стоит попробовать заменить команды записи оригинального разрешения на своё собственное. Дизассемблировав Grand Theft Auto.exe в IDA, первым делом начал смотреть параметры вызовов стандартных функций CreateWindowExA, SetWindowPos, ShowWindow, пока не наткнулся на это:
Перед вызовом функции CreateWindowExA игра помещает в стэк значения nWidth и nHeight, которые находятся по адресам 0x787310 и 0x787314. Эти адреса есть в таблице Cheat Engine, так что начать подмену я решил именно с них. Для этого в IDA нажимаю «X» на nWidth, смотрю где происходит запись (Type — w):
В том же месте происходит запись в nHeight:
Возвращаюсь в студию, создаю функцию patch_res() и делаю прыжок на неё по адресу 0x491E4C:
CPatch::RedirectJump(0x491E4C, patch_res);
~~~
void __declspec(naked)patch_res()
{
_asm
{
mov eax, 1920
MOV DWORD PTR DS : [0x787310], EAX
MOV DWORD PTR DS : [0x787370], EAX
MOV EAX, DWORD PTR DS : [EBX + 1B4h]
INC EAX
TEST ECX, ECX
mov eax, 1080
MOV DWORD PTR DS : [0x787314], EAX
MOV DWORD PTR DS : [0x787388], EAX
mov jmpAddress, 0x491E69
jmp jmpAddress
}
}
Оригинальный asm-код был скопирован из OllyDbg, т.к. код из Cheat Engine или IDA студия интерпретирует не всегда правильно, приходится исправлять. Компилирую, запускаю игру и вижу знакомую картину:
Из таблицы Cheat Engine переписал часть адресов, отсеяв лишние:
CPatch::RedirectJump(0x491E4C, patch_res);
CPatch::RedirectJump(0x414FF7, patch_res_x1);
CPatch::RedirectJump(0x43B7CF, patch_res_x2);
CPatch::RedirectJump(0x46453B, patch_res_x3);
CPatch::RedirectJump(0x46452C, patch_res_x4);
CPatch::RedirectJump(0x486848, patch_res_x5);
CPatch::RedirectJump(0x486852, patch_res_x6);
CPatch::RedirectJump(0x48C137, patch_res_x7);
CPatch::RedirectJump(0x48C276, patch_res_x8);
CPatch::RedirectJump(0x48C159, patch_res_x9);
CPatch::RedirectJump(0x49168B, patch_res_x10);
CPatch::RedirectJump(0x415008, patch_res_y1);
CPatch::RedirectJump(0x43B7D8, patch_res_y2);
CPatch::RedirectJump(0x464532, patch_res_y3);
CPatch::RedirectJump(0x48683A, patch_res_y4);
CPatch::RedirectJump(0x48C13D, patch_res_y5);
CPatch::RedirectJump(0x48C2B0, patch_res_y6);
//CPatch::RedirectJump(0x, patch_res_y7);
Создал соответствующие функции:
void __declspec(naked)patch_res_x1()
{
_asm
{
mov eax, res_x
MOV DWORD PTR DS : [0x504CC0], EAX
mov jmpAddress, 0x414FFC
jmp jmpAddress
}
}
void __declspec(naked)patch_res_x2()
{
_asm
{
mov edx, res_x
MOV DWORD PTR DS : [0x5C0C00], EDX
mov jmpAddress, 0x43B7D5
jmp jmpAddress
}
}
~~~~~~~
void __declspec(naked)patch_res_y6()
{
_asm
{
mov eax, res_y
MOV DWORD PTR DS : [0x787AF0], EAX
mov jmpAddress, 0x48C2B5
jmp jmpAddress
}
}
void __declspec(naked)patch_res_y7()
{
_asm
{
mov edx, res_y
MOV DWORD PTR DS : [0x4B48C0], EDX
mov jmpAddress, 0x48AE8B
jmp jmpAddress
}
}
res_x и res_y установил в 1920 и 1080, да вот результат не очень порадовал:
Хотя полдела сделано, игра работает в 1920х1080. Поначалу я решил, что рендер происходит некорректно из-за того, что не все значения в таблице Cheat Engine изменены на 1920 и 1080. Но все их отловить не реально, так что я попробовал убрать редиректы на некоторые мои функции. Методом научного тыка было обнаружено, что patch_res_x4, 5 и 6 вызывают подобное поведение, а без них все работает нормально, кроме меню. Отключение x7-x10 приводит в порядок и меню.
В итоге, того результата, на который рассчитывал, я добился: