
Приступим
Вся работа будет проводиться в Visual Studio 2010. Для начала исследуем целевую библиотеку на предмет экспортируемых функций. Для этого воспользуемся утилитой dumpbin (в каталоге VS2010\VC\bin):
vcvars32
dumpbin /EXPORTS c:\windows\system32\ddraw.dll
В результате получим: ordinal hint RVA name
1 0 00002E69 AcquireDDThreadLock
2 1 000327FA CompleteCreateSysmemSurface
3 2 00032FAE D3DParseUnknownCommand
4 3 00033EEF DDGetAttachedSurfaceLcl
5 4 000325D7 DDInternalLock
6 5 0003258C DDInternalUnlock
7 6 000363FC DSoundHelp
8 7 0000859D DirectDrawCreate
9 8 00037851 DirectDrawCreateClipper
10 9 0000EBC6 DirectDrawCreateEx
11 A 000338C9 DirectDrawEnumerateA
12 B 00033368 DirectDrawEnumerateExA
13 C 00032CB2 DirectDrawEnumerateExW
14 D 0003333B DirectDrawEnumerateW
15 E 000387C1 DllCanUnloadNow
16 F 00038607 DllGetClassObject
17 10 00032675 GetDDSurfaceLocal
18 11 0003A5F9 GetOLEThunkData
19 12 0000E927 GetSurfaceFromDC
20 13 00027CC4 RegisterSpecialCase
21 14 00002EA8 ReleaseDDThreadLock
22 15 000421A6 SetAppCompatData
На основании этого мы уже можем создавать свою прокси-dll. Поскольку ничего кроме WinAPI нам не нужно, поэтому в свежем проекте win32-библиотеки первым делом отключим RTL, включив параметр
/NODEFAULTLIB
и указав точку входа DllMain
. Это позволит сильно выиграть в объёме. Далее в DEF файле укажем экспортируемые функции в следующем виде:
LIBRARY "ddraw"
EXPORTS
AcquireDDThreadLock = FakeAcquireDDThreadLock @1
CheckFullscreen = FakeCheckFullscreen @2
CompleteCreateSysmemSurface = FakeCompleteCreateSysmemSurface @3
D3DParseUnknownCommand = FakeD3DParseUnknownCommand @4
...
Поскольку нам необходимо добиться, чтобы наши «фейковые» функции просто передавали управление своим оригиналам, в C++ объявление каждой из них будет выглядеть так:
__declspec(naked) void FakeAcquireDDThreadLock()
{
_asm { jmp [ddraw.AcquireDDThreadLock] }
}
Используя __declspec(naked)
мы заставляем компилятор не генерировать код стандартного пролога и эпилога, которые вставляют данные в стек. Как результат — данные в стеке хранятся уже в подходящем виде, и мы можем просто передать управление оригинальной функции одной командой jmp
без повторной передачи параметров в стек.Адрес для перехода берётся из структуры ddraw, которая выглядит следующим образом:
struct ddraw_dll
{
HMODULE dll;
FARPROC AcquireDDThreadLock;
FARPROC CheckFullscreen;
FARPROC CompleteCreateSysmemSurface;
FARPROC D3DParseUnknownCommand;
// ... аналогично объявляются другие функции ...
} ddraw;
Эта структура заполняется в DllMain в момент загрузки нашей прокси-dll. Сперва мы загружаем оригинальную библиотеку, затем по очереди получаем адреса всех экспортируемых функций. Код будет выглядеть примерно так:BOOL APIENTRY DllMain(HMODULE hModule, DWORD ul_reason_for_call, LPVOID lpReserved)
{
char path[MAX_PATH];
switch (ul_reason_for_call)
{
case DLL_PROCESS_ATTACH:
CopyMemory(path+GetSystemDirectory(path,MAX_PATH-10), "\\ddraw.dll",11);
ddraw.dll = LoadLibrary(path);
if (ddraw.dll == false)
{
MessageBox(0, "Cannot load original ddraw.dll library", APP_NAME, MB_ICONERROR);
ExitProcess(0);
}
ddraw.AcquireDDThreadLock = GetProcAddress(ddraw.dll, "AcquireDDThreadLock");
ddraw.CheckFullscreen = GetProcAddress(ddraw.dll, "CheckFullscreen");
ddraw.CompleteCreateSysmemSurface = GetProcAddress(ddraw.dll, "CompleteCreateSysmemSurface");
ddraw.D3DParseUnknownCommand = GetProcAddress(ddraw.dll, "D3DParseUnknownCommand");
// ... аналогичные вызовы для других экспортируемых функций ...
break;
case DLL_PROCESS_DETACH:
FreeLibrary(ddraw.dll);
break;
}
return TRUE;
}
В результате мы получили компактную dll, которая просто пропускает через себя все вызовы, совершенно не вмешиваясь в процесс. Похожего результата можно было бы добиться с использованием автоматического решения, но код получился бы не таким красивым.
Оконный режим для DirectDraw игр
После того, как мы получили чистую прокси-dll, которая успешно работает с целевым приложением, мы можем приступить к необходимым модификациям. В момент загрузки нашей библиотеки мы можем пропатчить необходимые участки кода уже в памяти, установить хуки на вызовы из других библиотек, а также изменить логику работы тех функций, что мы проксируем в своей dll. В нашем случае наиболее логичным было бы изменить функцию
DirectDrawCreate
таким образом, чтобы она возвращала структуру IDirectDraw с подменёнными адресами методов, поведение которых мы хотели бы изменить для достижения конечной цели.Однако, это слишком большой объём работы для демонстрационной работы, и поэтому мы просто воспользуемся услугами библиотеки wndmode.dll из предыдущей статьи, которая уже реализует всё необходимое, достаточно лишь только подгрузить её в адресное пространство процесса.
Сделаем это прямо в
DllMain
, добавив в case DLL_PROCESS_ATTACH
одну строку:LoadLibrary("wndmode.dll");
Остальные изменения на ваш вкус :)Библиотека wndmode.dll
Эта библиотека занимается тем, что перехватывает все вызовы DirectDraw, изменяет параметры таким образом, чтобы программа работала в окне, и только после этого передаёт управление оригинальным функциям DirectDraw.
Сам по себе wndmode.dll является сильно модифицированной версией библиотеки d3dhook.dll, где реализовано:
- Полная независимость от программы D3D Windower
- Настройки загружаются из секции [WINDOWMODE] файла wndmode.ini
- Настройки по умолчанию заменены для совместимости с Genie
- Добавлен параметр Border, который включает/выключает рамку вокруг окна
- Если игровое разрешение равно системному, автоматически убирается рамка
В wndmode.ini могут содержаться следующие настройки:
[WINDOWMODE]
UseWindowMode=1
UseGDI=0
UseDirect3D=0
UseDirectInput=0
UseDirectDraw=1
UseDDrawColorEmulate=1
UseDDrawFlipBlt=0
UseDDrawColorConvert=1
UseDDrawPrimaryBlt=1
UseDDrawAutoBlt=0
UseDDrawEmulate=0
UseDDrawPrimaryLost=0
UseCursorMsg=0
UseCursorSet=0
UseCursorGet=0
UseSpeedHack=0
SpeedHackMultiple=10
UseBackgroundResize=0
UseForegroundControl=0
UseFGCGetActiveWindow=0
UseFGCGetForegroundWindow=0
UseFGCFixedWindowPosition=0
EnableExtraKey=0
ShowFps=0
UseCursorClip=0
UseBackgroundPriority=0
DDrawBltWait=-1
Border=1
Большинство опций совпадает с D3D Windower. Были удалены параметры Height и Width, которые фиксировали размеры окна, а вместо этого добавлен параметр Border (отображать или нет рамку окна) и реализовано автоматическое сокрытие рамки, если разрешение игры совпадает с системным разрешением. Подходящие настройки для каждой игры будут разные, их придётся подбирать вручную.
Скачать
Исходный код: на bitbucket.org
Бинарники: wndmode.zip (340 кб)
Демо: Age of Empires: The Rise of Rome
Копируем наш ddraw.dll, wndmode.dll и wndmode.ini в каталог с игрой, запускаем игру. Барабанная дробь…
