Пишем прокси-dll для исправления Silent Storm

Решил я на выходных поностальгировать в Silent Storm. Установил, запустил и как-то не пошло… Разрешение, то ладно, можно и в 4:3 поиграть. Но, отсутствие теней, эффектов и дикий FPS мешали погрузиться в игру.

В сообществе стима уже были варианты исправлений, но использовать чужую dll-ину не хотелось. Да и не все мои хотелки она правила. Автор патча Novik65 в одном из обсуждений рассказал, что именно надо поправить и, даже, дал ссылку на исходники. Которые, к сожалению, за давностью лет потерялись.

Вот и появился повод прокачать скилл программинга 😊 Итак, порядок действий был таков:

  • Написать "чистый" прокси для библиотеки d3d9.dll и проверить работоспособность экспортируемых функций.
  • Реализовать свои интерфейсы IDirect3D9 и IDirect3DDevice9, которые будут вызывать методы настоящих.
  • Поправить функцию Direct3DCreate9, так чтобы она создавала фальшивый IDirect3D9.
  • Поправить метод IDirect3D9::CreateDevice, так чтобы он создавал фальшивый IDirect3DDevice9. Здесь же принудительно включается VSync.
  • Поправить метод Direct3DDevice9::GetAvailableTextureMem. Дело в том, что игра кушает результат в int и не переваривает размер видеопамяти современных видюх. Надо урезать результат до 2-х гигов, чтобы все работало правильно.

Пишем чистый прокси.

Я в этом вопросе плохо разбираюсь, поэтому нашел статью грамотного человека:

Она рассказывает про прокси для ddraw.dll, но принцип тот же: получаем полный перечень всех экспортируемых функций, загружаем оригинальную библиотеку и получаем их адреса. А дальше работает такой код для всех "чистых" функций:

extern "C" __declspec(naked) void Fake_D3DPERF_BeginEvent() { _asm { jmp[d3d9dll.D3DPERF_BeginEvent] } }

Его прелесть заключается в том, что для вызова "чистой" функции достаточно знать только ее адрес. Параметры и возвращаемое значение лежат тихонько в стеке и нашей прокси-функцией не портятся.

На этом все! Собрал и подкинул фейковую d3d9.dll в папку с игрой. Игра запустилась и заработала без проблем. Значит можно продолжать.

Реализовываем свои IDirect3D9 и IDirect3DDevice9.

Открываем заголовочный файл d3d9.h и охреневаем 🤔

Интерфейс IDirect3DDevice9. И каждую строчку нужно реализовать.
Интерфейс IDirect3DDevice9. И каждую строчку нужно реализовать.

В этот момент, мне расхотелось играть в Silent Storm 😊 Ладно, копаем дальше, должны же быть извращенцы помимо меня. И я нашел аналогичный прокси для Pac-Man Battle Royale.

Я не скажу, что весь код этого автора мне понравился. Пришлось его рихтовать под себя. Но самое главное, прокси-методы мне не пришлось писать самому и огромный пласт нудных работ я просто скопировал себе:

void FakeDirect3DDevice9::SetCursorPosition(int X, int Y, DWORD Flags) { return(m_pIDirect3DDevice9->SetCursorPosition(X, Y, Flags)); }

Подмена интерфейсов и правка багов.

Вот тут начинается самое интересное. Мне нужно было переписать некоторые функции, чтобы они возвращали мои липовые интерфейсы. И, конечно, исправляли недочеты игры. Первым делом переписал Direct3DCreate9, возвращающую интерфейс на объект Direct3D9.

extern "C" IDirect3D9* WINAPI Fake_Direct3DCreate9(UINT SDKVersion) { typedef IDirect3D9* (WINAPI * DIRECT3DCREATEPROC)(UINT SDKVersion); DIRECT3DCREATEPROC Direct3DCreate9Proc = (DIRECT3DCREATEPROC)d3d9dll.Direct3DCreate9; IDirect3D9* pFakeDirect3D9 = Direct3DCreate9Proc(SDKVersion); return new FakeDirect3D9(pFakeDirect3D9); }

Идея всех подобных функций проста: сначала я создаю настоящий Direct3D9 объект (первые три строчки в теле функции), а потом создаю свой прокси-объект, которому скармливаю настоящий (последняя строка). В дальнейшем все методы прокси либо будут передавать управление настоящему объекту, либо вносить правки и уже потом передавать управление. Наружу из такой функции, естественно возвращается интерфейс на поддельный объект.

Теперь очередь за IDirect3D9::CreateDevice.

HRESULT FakeDirect3D9::CreateDevice(UINT Adapter,D3DDEVTYPE DeviceType,HWND hFocusWindow,DWORD BehaviorFlags,D3DPRESENT_PARAMETERS* pPresentationParameters,IDirect3DDevice9** ppReturnedDeviceInterface) { pPresentationParameters->PresentationInterval = D3DPRESENT_INTERVAL_ONE; // force VSYNC HRESULT hres = m_pIDirect3D9->CreateDevice(Adapter, DeviceType, hFocusWindow, BehaviorFlags, pPresentationParameters, ppReturnedDeviceInterface); *ppReturnedDeviceInterface = new FakeDirect3DDevice9(*ppReturnedDeviceInterface); return(hres); }

Тут, тоже самое. За исключением первой строчки в теле метода. Именно она отвечает за включение вертикальной синхронизации. Никаких дополнительных проверок я делать не стал - ребята из Nival дичи не творили, все параметры передают хорошие.

Ну и последний метод, исправляющий баг с тенями и эффектами:

UINT FakeDirect3DDevice9::GetAvailableTextureMem(void) { UINT ret = m_pIDirect3DDevice9->GetAvailableTextureMem(); if (ret > (UINT)INT_MAX) ret = (UINT)INT_MAX; return(ret); }

Исправляем то, о чем я писал выше. Игра внутри себя хранит результат в int (максимум 2'147'483'647), а надо хранить в unsigned int (максимум 4'294'967'296). Ну т.е. на видюхе с более чем 2 гига оперативки, int захлебывается и уходит в отрицательные значения, что и приводит к неверному авто-определению настроек. Данный метод, просто обрубает настоящий результат по границе в 2 гига.

Итог.

Update: Добавил фикс для разрешения 1920х1080. Мне не нравится результат - многие надписи обрезаются по вертикали. В комментариях есть скриншоты с примерами. Чтобы перейти в FullHD, в игре нужно установить разрешение 800х600.

Тут лежат исходники и собранная dll. Так как я играю на рабочем компьютере и не сильно заморачивался со сборкой, то возможны зависимости от 2019-й студии. Ну да это уже совсем другая история 😊

269269
81 комментарий

Вот это тот самый контент, ради которого я пришёл на ДТФ! Больше!

77
Ответить

Это контент раздела реверс инженеринг на хабре. И если на хабре такой контент норм заходит, то тут это на любителя.
Это, все таки, не гейм дев.

5
Ответить

Ну это совсем другого уровня работа. Я просто собрал в кучку то, что уже делали до меня.

4
Ответить

немножко другого уровня работа. Тут пара ифов и подстановка значений, дело в основном в технике DLL-Вызовов. Что не умаляет усилий автора. Но по циве работа более скрупулезная, не стоит сравнивать.

2
Ответить

Комментарий недоступен

Ответить