📜 ⬆️ ⬇️

Creating a proxy dll to run DirectDraw games in the window

In continuation of the topic of extending the functionality of ready-made programs, I would like to talk about another way of changing the logic of the already compiled program, which does not require making changes in the executable file itself. This can come in handy when distributing your modification in the United States, where direct interference with the executable file is strongly condemned. It will be about creating a tiny proxy dll (just ≈4 kilobytes) to replace the library used by the application using the example ddraw.dll.

Let's get started


All work will be done in Visual Studio 2010. To begin, examine the target library for exported functions. To do this, we use the dumpbin utility (in the VS2010 \ VC \ bin directory):
vcvars32 dumpbin /EXPORTS c:\windows\system32\ddraw.dll 
As a result, we get:
  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 

Based on this, we can already create our own proxy dll. Since we do not need anything other than WinAPI, therefore, in the fresh project of the win32 library, first of all, disable RTL by turning on the /NODEFAULTLIB parameter and specifying the DllMain entry DllMain . This will greatly benefit in volume. Further in the DEF file we indicate the exported functions in the following form:
 LIBRARY "ddraw" EXPORTS AcquireDDThreadLock = FakeAcquireDDThreadLock @1 CheckFullscreen = FakeCheckFullscreen @2 CompleteCreateSysmemSurface = FakeCompleteCreateSysmemSurface @3 D3DParseUnknownCommand = FakeD3DParseUnknownCommand @4 ... 

Since we need to ensure that our “fake” functions simply transfer control to their originals, in C ++, each of them will look like this:
 __declspec(naked) void FakeAcquireDDThreadLock() { _asm { jmp [ddraw.AcquireDDThreadLock] } } 
Using __declspec(naked) we force the compiler to not generate the standard prologue and epilogue code that insert data onto the stack. As a result, the data in the stack is already stored in a suitable form, and we can simply transfer control of the original function with one jmp command without jmp parameters to the stack.

The transition address is taken from the ddraw structure, which looks like this:
 struct ddraw_dll { HMODULE dll; FARPROC AcquireDDThreadLock; FARPROC CheckFullscreen; FARPROC CompleteCreateSysmemSurface; FARPROC D3DParseUnknownCommand; // ...     ... } ddraw; 
This structure is filled in DllMain at the time of loading our proxy dll. First we load the original library, then in turn we get the addresses of all the exported functions. The code will look something like this:
 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; } 

As a result, we received a compact dll, which simply passes all calls through, without interfering in the process at all. A similar result could have been achieved using an automatic solution , but the code would not have been so beautiful.

Window mode for DirectDraw games


Once we have a clean proxy dll that works successfully with the target application, we can proceed to the necessary modifications. At the time of loading our library, we can patch the necessary parts of the code already in memory, install hooks on calls from other libraries, and also change the logic of the functions that we proxied in our dll. In our case, the most logical would be to change the DirectDrawCreate function so that it returns the IDirectDraw structure with the replaced addresses of methods whose behavior we would like to change to achieve the final goal.
')
However, this is too much work for the demonstration work, and therefore we will simply use the services of the wndmode.dll library from the previous article, which already implements everything you need, you just need to load it into the address space of the process.

We do this right in DllMain by adding one line to the case DLL_PROCESS_ATTACH :
 LoadLibrary("wndmode.dll"); 
The rest of the changes to your taste :)

Wndmode.dll library


This library is engaged in intercepting all calls to DirectDraw, modifying the parameters so that the program works in the window, and only after that transfers control to the original DirectDraw functions.

By itself, wndmode.dll is a strongly modified version of the d3dhook.dll library, where it is implemented:
That is, with the help of wndmode.dll, we can try to make almost any DirectDraw game windowed.

The wndmode.ini can contain the following settings:
 [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 

Most options are the same as D3D Windower . The Height and Width parameters, which fixed the window size, were removed, but instead the Border parameter was added (to show the window frame or not) and automatic frame concealment was implemented if the game resolution matches the system resolution. The appropriate settings for each game will be different, they will have to pick up manually.

Download


Source code: on bitbucket.org
Binary: wndmode.zip (340 kb)

Demo: Age of Empires: The Rise of Rome


Copy our ddraw.dll, wndmode.dll and wndmode.ini into the directory with the game, run the game. Drumroll…

image

Source: https://habr.com/ru/post/133956/


All Articles