- Messages
- 10
- Country
I couldn't find much discussion on this on Google, so I figured I'd share my code which I finally got working. I'm using this to hook into FSX's Direct3D9 instance, to draw bitmap overlays (custom in-game menus) which work both in windowed and full-screen mode.
However, I also played around with drawing in 3D, and I think it could be used for that, too.
First, you need to make a DLL add-on. It won't work in an EXE add-on because you need to be in the same process as FSX to get its Direct3D instance.
The idea is your add-on overwrites the d3d9.dll "Present" function used by FSX, to insert an assembly jump instruction to your own add-on's "Present" function. You then add your draw code, then your add-on calls the real Present function. This is a well-known "hooking" technique (and is the same technique is used by FRAPS, among other programs).
This is the C code I'm using. I'm calling the "HookIntoD3D" function on the Sim (start) event, but you could do it on DLLStart if you want.
The main part is in the Present function -- here I'm calling g_GUI.OnFSXPresent() which is my main GUI app. That's where you put whatever you want to draw.
There are a couple important parts which frustrated me for a long time while debugging. One is the function definitions for the hooked function need to be defined with _stdcall. The other is the "dummy" Direct3D9 object created in HookIntoD3D() needs to be created with the D3DCREATE_FPU_PRESERVE flag, otherwise it messes up object positions in FSX (which are high precision, not the default low-precision). So don't modify those.
Now, how to overlay your bitmaps: there a couple ways to do it, either create a 3D object ("textured quad"), or get a pointer to the back buffer and overlay your bitmap. I experimented with the former, and got it to work "sometimes," but it was easier to just copy the bitmap into the back buffer. So the idea is to load your bitmap into a IDirect3DSurface9 object, and when your Present function is called, you get the pointer to the back buffer from the passed-in IDirect3DDevice9 pointer, and use its StretchRect method to copy your surface in.
Another thing to remember is FSX creates multiple devices, so the IDirect3DDevice9 pointer in the Present function may be different. When you create a IDirect3DSurface9 object to hold your bitmap, it only works for the device it was created for. So you need to keep track of the IDirect3DDevice9 that is passed in. If your bitmap was created for a different device, you need to reload it for the new device and create a new IDirect3DSurface9 for that device. FSX creates one device object for windowed mode, and another for full-screen mode. And if there are multiple monitors, FSX creates a separate device object for each monitor, when in full-screen mode. Your code needs to keep track of that and hold different surfaces for each device object.
For 3D objects: in my hooked function I experimented with showing the bitmap as a textured quad. It worked "sometimes" because FSX would call Present in different render states (for example, when the menu bar was shown, or mouse button was down). If you want to insert 3D objects, I think you'd need to save the entire render state (all 30-40 variables), set your own, then restore the state. But you have the rotation and projection matrices available, so I'd think you should be able to insert your objects in 3D. I didn't test it much, but I think it's possible.
Hope this helps! My open-source project is at vpc.codeplex.net if you want to see further code (it's an FSX client to connect to the Vatsim network).
However, I also played around with drawing in 3D, and I think it could be used for that, too.
First, you need to make a DLL add-on. It won't work in an EXE add-on because you need to be in the same process as FSX to get its Direct3D instance.
The idea is your add-on overwrites the d3d9.dll "Present" function used by FSX, to insert an assembly jump instruction to your own add-on's "Present" function. You then add your draw code, then your add-on calls the real Present function. This is a well-known "hooking" technique (and is the same technique is used by FRAPS, among other programs).
This is the C code I'm using. I'm calling the "HookIntoD3D" function on the Sim (start) event, but you could do it on DLLStart if you want.
Code:
#include <d3d9.h>
#pragma comment(lib, "d3d9.lib")
//Direct3D hooking functions and data
unsigned char g_OrigCode[5]; //Original code at start of D3D's Present function
unsigned char g_PatchCode[5]; //The patched code we overlay
unsigned char* g_pPatchAddr = NULL; //Address of the patch (start of the real Present function)
void HookIntoD3D();
void ApplyPatch();
void RemovePatch();
typedef HRESULT (_stdcall *RealPresentFuncType)(void*, const RECT*, const RECT*, HWND, const RGNDATA* );
RealPresentFuncType RealPresent;
HRESULT _stdcall Present(void *pThis, const RECT* pSourceRect,const RECT* pDestRect, HWND hDestWindowOverride, const RGNDATA* pDirtyRegion);
//This function finds the address to the d3d9.dll Present function, which we know FSX has already
//loaded into this process, and patches the code with a JMP to our Present function. In our Present
//function we first call g_GUI.OnFSXPresent(), then we undo the patch with the original code, call
//the real d3d Present function, and reinstate the patch. Note this needs to be in the same process
//as FSX (to get the same d3d9.dll instance FSX is using), which is why this is a DLL addon and not
//an EXE.
void HookIntoD3D()
{
//Create a temporary direct 3D object and get its IDirect3DDevice9 interface. This is a different
//"instance," but the virtual table will point to the function within d3d9.dll also used by FSX's
//instance.
LPDIRECT3D9 p = Direct3DCreate9(D3D_SDK_VERSION);
if (!p)
return;
D3DPRESENT_PARAMETERS presParams;
ZeroMemory(&presParams,sizeof(presParams));
presParams.Windowed = TRUE;
presParams.SwapEffect = D3DSWAPEFFECT_DISCARD;
IDirect3DDevice9 *pI = NULL;
HRESULT hr;
hr = p->CreateDevice( D3DADAPTER_DEFAULT, D3DDEVTYPE_NULLREF, g_hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING | D3DCREATE_FPU_PRESERVE, &presParams, &pI);
if (!pI)
{
p->Release();
return;
}
//Get the pointer to the virtual table for IDirect3DDevice9 (pI is a pointer to a pointer to the Vtable)
PVOID* pVTable = (PVOID*)*((DWORD*)pI);
//Set permissions so we can read it (117 = size of whole table)
DWORD dwOldProtect;
VirtualProtect(pVTable, sizeof(void *) * 117, PAGE_READWRITE, &dwOldProtect);
//Get the address to the (real) Present function (17th function listed, starting at 0 -- see definition
//of IDirect3DDevice9).
RealPresent = (RealPresentFuncType)pVTable[17];
//Calculate the offset from the real function to our hooked function. Constant 5 is because JMP
//offset is from start of next instruction (5 bytes later)
DWORD offset =(DWORD)Present - (DWORD)RealPresent - 5;
//Create the patch code: JMP assembly instruction (0xE9) followed by relative offset
g_PatchCode[0] = 0xE9;
*((DWORD*) &g_PatchCode[1]) = offset;
g_pPatchAddr = (unsigned char*)RealPresent;
//Set permission to allow reading/write/execute
VirtualProtect(g_pPatchAddr, 8, PAGE_EXECUTE_READWRITE, &dwOldProtect);
//Save out the original bytes
for (int i = 0; i < 5; i++)
g_OrigCode[i] = *(g_pPatchAddr + i);
//Copy in our patch code
ApplyPatch();
//Delete dummy D3D objects
pI->Release();
p->Release();
return;
}
//Our hooked function. FSX calls this thinking it's calling IDirect3DDevice9::Present
HRESULT _stdcall Present(void *pThis, const RECT* pSourceRect,const RECT* pDestRect, HWND hDestWindowOverride, const RGNDATA* pDirtyRegion)
{
IDirect3DDevice9 *pI = (IDirect3DDevice9 *)pThis;
//Call our main class
g_GUI.OnFSXPresent(pI);
//Call the real "Present"
RemovePatch();
HRESULT hr = RealPresent(pThis, pSourceRect, pDestRect, hDestWindowOverride, pDirtyRegion);
ApplyPatch();
return hr;
}
//Modify d3d9.dll Present function to call ours instead
void ApplyPatch()
{
for (int i = 0; i < 5; i++)
*(g_pPatchAddr + i) = g_PatchCode[i];
//FlushInstructionCache(GetCurrentProcess(), g_pPatchAddr, 5); //apparently not necessary on x86 and x64 CPU's?
return;
}
//Restore d3d9.dll's Present function to its original code
void RemovePatch()
{
for (int i = 0; i < 5; i++)
*(g_pPatchAddr + i) = g_OrigCode[i];
//FlushInstructionCache(GetCurrentProcess(), g_pPatchAddr, 5);
return;
}
The main part is in the Present function -- here I'm calling g_GUI.OnFSXPresent() which is my main GUI app. That's where you put whatever you want to draw.
There are a couple important parts which frustrated me for a long time while debugging. One is the function definitions for the hooked function need to be defined with _stdcall. The other is the "dummy" Direct3D9 object created in HookIntoD3D() needs to be created with the D3DCREATE_FPU_PRESERVE flag, otherwise it messes up object positions in FSX (which are high precision, not the default low-precision). So don't modify those.
Now, how to overlay your bitmaps: there a couple ways to do it, either create a 3D object ("textured quad"), or get a pointer to the back buffer and overlay your bitmap. I experimented with the former, and got it to work "sometimes," but it was easier to just copy the bitmap into the back buffer. So the idea is to load your bitmap into a IDirect3DSurface9 object, and when your Present function is called, you get the pointer to the back buffer from the passed-in IDirect3DDevice9 pointer, and use its StretchRect method to copy your surface in.
Another thing to remember is FSX creates multiple devices, so the IDirect3DDevice9 pointer in the Present function may be different. When you create a IDirect3DSurface9 object to hold your bitmap, it only works for the device it was created for. So you need to keep track of the IDirect3DDevice9 that is passed in. If your bitmap was created for a different device, you need to reload it for the new device and create a new IDirect3DSurface9 for that device. FSX creates one device object for windowed mode, and another for full-screen mode. And if there are multiple monitors, FSX creates a separate device object for each monitor, when in full-screen mode. Your code needs to keep track of that and hold different surfaces for each device object.
For 3D objects: in my hooked function I experimented with showing the bitmap as a textured quad. It worked "sometimes" because FSX would call Present in different render states (for example, when the menu bar was shown, or mouse button was down). If you want to insert 3D objects, I think you'd need to save the entire render state (all 30-40 variables), set your own, then restore the state. But you have the rotation and projection matrices available, so I'd think you should be able to insert your objects in 3D. I didn't test it much, but I think it's possible.
Hope this helps! My open-source project is at vpc.codeplex.net if you want to see further code (it's an FSX client to connect to the Vatsim network).
Last edited: