Добавление широкоформатных разрешений в Grand Theft Auto

от автора

image
Глядя на серию постов о тридцати строчном javascript программировании, тоже захотелось что-нибудь написать, правда не в 30 строк, но потратив минимум времени, just for fun. Был скачан установщик классической Grand Theft Auto для Windows, а т.к. эта GTA работает только в стандартных разрешениях с соотношением сторон 4:3, я решил написать плагин, который бы запускал её в родном разрешении моего монитора(1920×1080).


Перед тем как приступить непосредственно к написанию плагина, нужно как-то загружать его в процесс игры. Для этого я использую универсальный ASI Loader. GTA 1 — игра довольно старая, а значит самым оптимальным вариантом будет использование ddraw.dll. Убедиться, что Grand Theft Auto.exe действительно использует эту библиотеку можно через хекс редактор:

Копирую содержимое архива в папку Grand Theft Auto\WINO(папка с исполняемым файлом), переименовываю dinput8.dll(ASI Loader) в ddraw.dll. Зная, что игра запустится в низком разрешении, создаю в этой же папке пустой файл wndmode.ini. Т.к. ASI Loader включает в себя wndmode.dll, о которой на хабре уже было упоминание, при наличии файла wndmode.ini GTA должна отобразиться в окне.

При первом запуске тестовый плагин рапортует, что все работает, и можно писать свой:
image

Открылось меню игры, в таком вот виде:

А еще оказалось, что игра вылетает при сворачивании, и от оконного режима пришлось отказаться, wndmode.ini был удален. Также была удалена папка scripts, за ненадобностью. Запускаю игру снова, теперь при сворачивании/разворачивании не вылетает, и выглядит так:

Естественно 1024х768 в 2013 году меня не устраивает, поэтому в Visual Studio создаю новый проект Win32, тип — DLL, а в свойствах выставляю:

  • Конфигурация — Release
  • Набор символов — Использовать многобайтовую кодировку
  • Библиотека времени выполнения — Многопоточная (/MT)
  • Конечное расширение — .asi
  • Выходной каталог — E:\Games\Rockstar Games\Grand Theft Auto Classics\Grand Theft Auto\WINO\

Основа плагина:

#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, пока не наткнулся на это:
image

Перед вызовом функции CreateWindowExA игра помещает в стэк значения nWidth и nHeight, которые находятся по адресам 0x787310 и 0x787314. Эти адреса есть в таблице Cheat Engine, так что начать подмену я решил именно с них. Для этого в IDA нажимаю «X» на nWidth, смотрю где происходит запись (Type — w):
image

В том же месте происходит запись в nHeight:
image

Возвращаюсь в студию, создаю функцию 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 приводит в порядок и меню.

В итоге, того результата, на который рассчитывал, я добился:
  

Без побочных эффектов конечно не обошлось, 2d-спрайты немного растянуты, меню не на всех разрешениях отображается корректно, но это в мои планы не входило, так что оставляю как есть.

Исходный код доступен на GitHub, готовый фикс там же. Установщик с игрой был найден на просторах интернета, т.к. официальный на Windows 8 не запускается, да и для скачивания в данный момент не доступен. Размер exe — 774 144 байта, с другими плагин может не работать.

ссылка на оригинал статьи http://habrahabr.ru/post/202692/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *