19 November 2013

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

C++AssemblerGame development
image
Глядя на серию постов о тридцати строчном 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 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 байта, с другими плагин может не работать.
Tags:reverse engineeringреверс-инжинирингигрыстарые игрыhigh resolutiongtarockstar games
Hubs: C++ Assembler Game development
+97
38.2k 88
Comments 20