• Which the release of FS2020 we see an explosition of activity on the forun and of course we are very happy to see this. But having all questions about FS2020 in one forum becomes a bit messy. So therefore we would like to ask you all to use the following guidelines when posting your questions:

    • Tag FS2020 specific questions with the MSFS2020 tag.
    • Questions about making 3D assets can be posted in the 3D asset design forum. Either post them in the subforum of the modelling tool you use or in the general forum if they are general.
    • Questions about aircraft design can be posted in the Aircraft design forum
    • Questions about airport design can be posted in the FS2020 airport design forum. Once airport development tools have been updated for FS2020 you can post tool speciifc questions in the subforums of those tools as well of course.
    • Questions about terrain design can be posted in the FS2020 terrain design forum.
    • Questions about SimConnect can be posted in the SimConnect forum.

    Any other question that is not specific to an aspect of development or tool can be posted in the General chat forum.

    By following these guidelines we make sure that the forums remain easy to read for everybody and also that the right people can find your post to answer it.

FSX Hooking into FSX's Direct3D and overlaying bitmaps, or inserting 3D objects

Messages
10
Country
us-maryland
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.

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:

ollyau

Resource contributor
Messages
1,026
Country
us-california
Thanks for sharing! I'll keep this in mind for future reference. :)
 

scruffyduck

Administrator
Staff member
FSDevConf team
Resource contributor
Messages
34,853
Country
unitedkingdom
Thanks for this - very interesting! :)
 

JB3DG

Resource contributor
Messages
1,325
Country
southafrica
Man...this is my dream!! Any chance you can help me with drawing directly to gauge or VC textures using DX?
 
Messages
10
Country
us-maryland
Man...this is my dream!! Any chance you can help me with drawing directly to gauge or VC textures using DX?
I assume you mean drawing over gauges that aren't your own? Hmmm, well, if it's an XML gauge I don't know how you could intercept that. If it's a C gauge (.gau) maybe load it in an object viewer to see what functions and variables it's exporting (you might have to rename it to .dll first). Then in your DLL addon maybe use LoadLibrary to get a pointer to the (hopefully already-loaded) gauge DLL, and GetProcAddress to get the exported function pointer you want to intercept (or variable?).

I haven't done gauge programming, just skimmed through the sample code, but at first glance it looks like a C gauge uses macros to dll-export the GDI bitmap the gauge creates. If you could get that pointer with GetProcAddress (and cast it to a pointer to that object??), maybe in the intercepted Present function in my code example, you could instead use that pointer to overlay on the bitmap using regular GDI+.

This is just a wild guess. Like I said, I haven't worked with gauges, but maybe it's an idea.
 
Last edited:

JB3DG

Resource contributor
Messages
1,325
Country
southafrica
No not quite. I make my own gauges all the time. What i mean is being able to either render directly to the $VC texture (or any texture in the model) or (probably a bit more difficult) find the GDI bitmap within my own gauge that is sent to the panel system to be drawn on the $VC texture and clear it with the bytes of a DX bitmap or buffer. Basically i want to replace the GDI/GDI+ drawing system with a more efficient D3D9/D2D(DX11 yes but doesnt matter as long as i can get a 24bit image format out) system, ultimately with a Differential Scan Conversion algorithm to speed things up even further by only drawing on the areas of the gauge that need updating.
 
Last edited:
Messages
10
Country
us-maryland
No not quite. I make my own gauges all the time. What i mean is being able to either render directly to the $VC texture (or any texture in the model) or (probably a bit more difficult) find the GDI bitmap within my own gauge that is sent to the panel system to be drawn on the $VC texture and clear it with the bytes of a DX bitmap or buffer. Basically i want to replace the GDI/GDI+ drawing system with a more efficient D3D9/D2D(DX11 yes but doesnt matter as long as i can get a 24bit image format out) system, ultimately with a Differential Scan Conversion algorithm to speed things up even further by only drawing on the areas of the gauge that need updating.

Can't you already do that? In your IGaugeCDrawable implementation that FSX is calling back on, in your GetFlags method can't you pass back the TAKES_PIMAGE flag (and maybe the other possible flags) and when FSX calls your Draw() method you should get a pointer to an IMAGE structure you fill in with your raw bitmap data. You can draw that however you want (Direct3D, GDI, or even OpenGL I suppose) as long as the format is one of the supported IMAGE_FORMAT enumerations. (All of these are defined in gauges.h).

Although if you use Direct3D, you'll want to use the same instance that FSX is using because I don't think multiple instances cooperate too well in the same process. So you can use the above code to get the IDirect3DDevice9 pointer, and then call its GetDirect3D method to get FSX's IDirect3D9 object. Maybe then use QueryInterface on that to see if it also has the DX11 or D2D interfaces you want.
 
Last edited:

JB3DG

Resource contributor
Messages
1,325
Country
southafrica
Aha thanks a bunch on the info of the BitBlt() function. That solves my problems nicely :D
 
Messages
19
Country
poland
Hello.

I would like to continue on the subject of FSX creating multiple devices. Is it just a small number of them ? like 8 - 10 ? or does it keep creating new ones every time you switch between windowed and full screen mode ?
 
Top