Need help with DirectInput hooking

c++ / delphi package - dll injection and api hooking
Post Reply
Coffein
Posts: 3
Joined: Sat Oct 23, 2004 3:22 pm

Need help with DirectInput hooking

Post by Coffein »

Hi there,
I've read the several other posts about DirectInput hooking, and they cleared
it up a bit already.
I'm hooking a game which uses dinput8.dll and therefore uses DirectInput8Create, which I have successfully hooked and it gets executed when the game starts.
However, from this point I'm completely lost, I want to catch keyboard events and mouse clicks, and also send them.
Thanks for your help
madshi
Site Admin
Posts: 10766
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

Well, I'm no expert with DirectInput stuff. So hopefully those will help out, which have already done this kind of thing.
Sirmabus
Posts: 89
Joined: Fri May 28, 2004 6:20 pm

Post by Sirmabus »

I did this using madCodeHook with DirectInput 7, it's the same for 8.

Anyhow, if you look up "GetDeviceState()" in the DirectX docs you will see that it reads in up to the entire array of 256 byte key codes at the time.

So all you need to do is fake a particular key code by setting it's flag so that the key is down, then in a future read (might need to fake it down for a few polling cycles) fake the key up.

Follow the examples posted elsewhere and add the "GetDeviceState()" method to it.

Now it gets a bit more complicated if the application uses DirectInput for both keyboard and mouse inputs.
In your "CreateDeviceEx()" you will want to track the the decives (GUID_SysKeyboard, GUID_SysMouse, etc.) as they are created, because later you will need to compare the interface pointer to the THIS pointer to determin what the "GetDeviceState()" (and other data related calls too) is being polled for. That's the way I did it anyhow.
Possibly you can just observe if cbData == 256 then you know it's intended for a keyboard poll.

Depending on what you are doing, if you need a more general hooking system then you will probably need a fair amount of logic to figure out how the application is using the inputs at run time.
If you can get away with a more specific hook then you can do a bit more reversing of the application and code something more simple.

Hope that helps.
madshi
Site Admin
Posts: 10766
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

Thanks for the comment, Sirmabus. Is there any chance that you could create a very little DirectInput hooking demo? It seems that quite a lot of people try to do DirectInput hooking recently. They'd surely benefit quite greatly. Of course you don't need to. Just if you like. No problem if you don't.
Sirmabus
Posts: 89
Joined: Fri May 28, 2004 6:20 pm

Post by Sirmabus »

Ok, I'll clean it up a little and post it here :D
madshi
Site Admin
Posts: 10766
Joined: Sun Mar 21, 2004 5:25 pm

Post by madshi »

Great! :D
Sirmabus
Posts: 89
Joined: Fri May 28, 2004 6:20 pm

Post by Sirmabus »

Ok here is what I have.

When working with DirectInput hooks I realized an ideal setup would be a real robust hook system that could hook up to IDirectInput8 (current as of DirectX 9).
Hooking LoadLibrary()/LoadLibraryEx() and looking at the “DirectInputCreate()”, “dwVersion” parameter to determine what version the application is using.
Then track “SetEventNotification()”, etc., to determine if the application is using polled or event driven input, etc.
Would be nice, but would take some time to develop!
This is just a start and I tested the keyboard hooking a little but just needed to fake left mouse clicks for now.

Note: my C++ code here is not heavily object oriented but I like to use a lot of enums, namespace, etc., to organize and keep things clean.

First a typical “Trace” function I use to output to a debug console from a DLL.
Pick up “Debugview” from http://www.sysinternals.com/ntw2k/freew ... view.shtml

Code: Select all

	   

// ****************************************************************************
// Func: Trace()
// Desc: Output text to debugger console
//
// ****************************************************************************
void Trace(const char *format, ...)
{
	if(format)
	{
		// Format string
		va_list vl;
		char	str[256];

		va_start(vl, format);
		_vsntprintf(str, sizeof(str), format, vl);
		va_end(vl);

		// Output it to debugger
		OutputDebugString(str);
	}
}



Header to add the DirectInput hook support:

Code: Select all

	   
// ****************************************************************************
// File: DirectInput7Hook.h
// Desc: DirectX DirectInput capture module
//
// ****************************************************************************

namespace DINPUT7HOOK
{
	BOOL InjectHook(void);
	void FakeLeftClick(void);
};



DirectInput7Hook.cpp:

Code: Select all

	   
// ****************************************************************************
// File: DirectInputCapture.cpp
// Desc: DirectX DirectInput capture module using MadCodeHook
//       Currently only supports "IID_IDirectInput7A" and standard input
//       interfaces: "keyboard" and "mouse".
//
// ****************************************************************************

// In "stdafx.h" include <dinput.h> for direct input defines

#include "stdafx.h"
#include "DirectInput7Hook.h"


// IDirectInput7 interface method indexes
enum eIDI7METHOD
{
	eIDI7M_QueryInterface,
	eIDI7M_AddRef,
	eIDI7M_Release,

	eIDI7M_CreateDevice,     
	eIDI7M_EnumDevices,
	eIDI7M_GetDeviceStatus,
	eIDI7M_RunControlPanel,
	eIDI7M_Initialize,
	eIDI7M_FindDevice,

	eIDI7M_CreateDeviceEx   
};



Also, in your “stdafx.h” add the necessary includes in there:

Code: Select all

	   
#include <dinput.h>

namespace MADCODEHOOK
{
	#include "..\..\madCodeHook\madCHook.h"
};



Made enums to the interfaces from “dinput.h”. Easier to read and less error prone then using hard coded numbers for them.

Code: Select all

	   
// Method hook indexes
enum eIDI7MHOOK
{
	eMH_CreateDeviceEx,

           // ** Add more method ID’s here, and fill in aMethodHook[] down below **  

	eMH_COUNT
};

// IDirectInput7 method hook container
struct tIDI7METHODHOOK
{
	eIDI7METHOD eMethod;
	PVOID       pHook;
	PVOID       pNext;
};

// DirectInput device interface methods
// Note: The interface functions/addresses are the same for both keyboard and mouse
enum eDIDMETHOD
{
	eDIDM_QueryInterface,
    eDIDM_AddRef,
    eDIDM_Release,
   
    eDIDM_GetCapabilities, 
    eDIDM_EnumObjects,
    eDIDM_GetProperty,
    eDIDM_SetProperty,
    eDIDM_Acquire,
    eDIDM_Unacquire,
    eDIDM_GetDeviceState,
    eDIDM_GetDeviceData,
    eDIDM_SetDataFormat,
    eDIDM_SetEventNotification,
    eDIDM_SetCooperativeLevel,
    eDIDM_GetObjectInfo,
    eDIDM_GetDeviceInfo,
    eDIDM_RunControlPanel,
    eDIDM_Initialize,

	eDIDM_COUNT
};

// Direct input devices
enum eDEVICE
{
	eDID_KEYBOARD,
	eDID_MOUSE,

	eDID_COUNT
};

// Method hook indexes
enum eDIDMHOOK
{
	//eDIDMH_SetCooperativeLevel,
    //eDIDMH_Acquire,
	eDIDMH_GetDeviceState,

	eDIDMH_COUNT
};


// DirectInput device interface hook container
struct tDIDMETHODHOOK
{
	eDIDMETHOD eMethod;
	PVOID      pHook;
	PVOID      pNext;
};	 
  

// **** Function prototypes ****
// IDirectInput7 method hooks
HRESULT WINAPI CreateDevice_Hook(LPVOID pSelf, REFGUID rguid, LPDIRECTINPUTDEVICE *lplpDirectInputDevice, LPUNKNOWN pUnkOuter);     
HRESULT WINAPI CreateDeviceEx_Hook(LPVOID pSelf, REFGUID rguid, REFIID riid, LPVOID *pvOut, LPUNKNOWN pUnkOuter);
// Device method hooks
HRESULT WINAPI SetCooperativeLevel_Hook(LPVOID pSelf, HWND hwnd, DWORD dwFlags);
HRESULT WINAPI Acquire_Hook(LPVOID pSelf);
HRESULT WINAPI GetDeviceState_Hook(LPVOID pSelf, DWORD cbData, LPVOID lpvData);


// **** Data ****
HRESULT (WINAPI *DirectInputCreateEx_Next)(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID *ppvOut, LPUNKNOWN punkOuter) = NULL;
LPDIRECTINPUTDEVICE7 apDeviceInterface[eDID_COUNT] = {NULL};

// IDirectInput7 method hooks
tIDI7METHODHOOK aMethodHook[eMH_COUNT] =
{
	{eIDI7M_CreateDeviceEx, CreateDeviceEx_Hook, NULL},
};
// Device method hooks. 
tDIDMETHODHOOK aDeviceMethodHook[eDIDMH_COUNT] =
{
	//{eDIDM_SetCooperativeLevel, SetCooperativeLevel_Hook, NULL},
	//{eDIDM_Acquire,             Acquire_Hook,             NULL},
	{eDIDM_GetDeviceState,      GetDeviceState_Hook,      NULL}
};


// Get interface method for index
#define GETINTERFACEMETHOD(pInterface, uIndex) ((PVOID) ((DWORD *) *((DWORD * ) (pInterface)))[uIndex])	 


The access function “InjectHook()” just needs to hook “DirectInputCreateEx()” so we can grab the method “CreateDeviceEx()”. Again a more robust system needs also DirectInputCreate() and more methods like “CreateDevice()”.
It turns out my “test” application just uses these..

Code: Select all

	   

// The DirectInputCreateEx() API hook
static HRESULT WINAPI DirectInputCreateEx_Hook(HINSTANCE hinst, DWORD dwVersion, REFIID riidltf, LPVOID *ppvOut, LPUNKNOWN punkOuter)
{
	HRESULT hResult = DirectInputCreateEx_Next(hinst, dwVersion, riidltf, ppvOut, punkOuter); 

	if(hResult == DI_OK)
	{
		// Hook methods
		for(int i = 0; i < eMH_COUNT; i++)
		{
			if(!aMethodHook[i].pNext)
			{
				//Trace("DINPUT7HOOK: ADDR[%d]: 0x%X\n", i, GETINTERFACEMETHOD(*ppvOut, i));

				if(!MADCODEHOOK::HookCode(GETINTERFACEMETHOD(*ppvOut, aMethodHook[i].eMethod), aMethodHook[i].pHook, &aMethodHook[i].pNext))
				{
					Trace("DINPUT7HOOK: HookCode() failed for method: %d!\n", aMethodHook[i].eMethod);
				}
			}
		}
	}

	Trace("DINPUT7HOOK: DirectInputCreateEx() Result: 0x%X\n", hResult);

	return(hResult);
}



// ****************************************************************************
// Func: InjectHook()
// Desc: Inject DirectInput hook into current application
//
// ****************************************************************************
BOOL DINPUT7HOOK::InjectHook(void)
{
	BOOL bResult = MADCODEHOOK::HookAPI("DINPUT.DLL", "DirectInputCreateEx", DirectInputCreateEx_Hook, (PVOID *) &DirectInputCreateEx_Next);

	Trace("DINPUT7HOOK: InjectHook() Result: %d\n", (bResult & 1));

	return(bResult);
}
	 
Just a function to help output the action:

Code: Select all

	   

// ****************************************************************************
// Func: GetInputDeviceName()
// Desc: Return a string name for a input device GUID
//
// ****************************************************************************
const char *GetInputDeviceName(REFGUID rguid)
{
	struct tGUID_NAME
	{
		const GUID *pGuid;
		const char *szName;

	} aInputGuids[] =
	{
		{&GUID_SysKeyboard,    "GUID_SysKeyboard"},
		{&GUID_SysMouse,       "GUID_SysMouse"},
		{&GUID_Joystick,       "GUID_Joystick"},
		{&GUID_SysMouseEm,     "GUID_SysMouseEm"},
		{&GUID_SysMouseEm2,    "GUID_SysMouseEm2"},
		{&GUID_SysKeyboardEm,  "GUID_SysKeyboardEm"},
		{&GUID_SysKeyboardEm2, "GUID_SysKeyboardEm2"}
	};

	for(int i = 0; i < (sizeof(aInputGuids) / sizeof(tGUID_NAME)); i++)
	{
		if(memcmp(&rguid, aInputGuids[i].pGuid, sizeof(GUID)) == 0)
		{
			return(aInputGuids[i].szName);
		}
	}

	return("Unknown");
}
	 
Here we’ve hooked “CreateDeviceEx()” to intercept the interfaces for the basic mouse and keyboard.

Code: Select all

	   

// ====== Method Hooks ======

HRESULT WINAPI CreateDeviceEx_Hook(LPVOID pSelf, REFGUID rguid, REFIID riid, LPVOID *pvOut, LPUNKNOWN pUnkOuter)
{
	HRESULT hResult = ((HRESULT (WINAPI *)(LPVOID, REFGUID, REFIID, LPVOID *, LPUNKNOWN)) aMethodHook[eMH_CreateDeviceEx].pNext)(pSelf, rguid, riid, pvOut, pUnkOuter);

	if(hResult == DI_OK)
	{
		// We only support the 7A interface
		if(memcmp(&riid, &IID_IDirectInputDevice7, sizeof(GUID)) == 0)
		{
			Trace("DINPUT7HOOK: CreateDeviceEx() D: %s, IA: 0x%X\n", GetInputDeviceName(rguid), *pvOut);


			// Keyboard
			if(memcmp(&rguid, &GUID_SysKeyboard, sizeof(GUID)) == 0)
			{
				if(!apDeviceInterface[eDID_KEYBOARD])
				{
					apDeviceInterface[eDID_KEYBOARD] = (LPDIRECTINPUTDEVICE7) *pvOut;

					if(!apDeviceInterface[eDID_MOUSE])
					{
						for(int i = 0; i < eDIDMH_COUNT; i++)
						{
							if(!aDeviceMethodHook[i].pNext)
							{
								Trace("DK: 0x%X\n", GETINTERFACEMETHOD(*pvOut, aDeviceMethodHook[i].eMethod));

								if(!MADCODEHOOK::HookCode(GETINTERFACEMETHOD(*pvOut, aDeviceMethodHook[i].eMethod), aDeviceMethodHook[i].pHook, &aDeviceMethodHook[i].pNext))
								{
									Trace("DINPUT7HOOK: CreateDeviceEx() failed for method: %d!\n", aDeviceMethodHook[i].eMethod);
								}
							}
						}
					}
				}
			}


			// Mouse? 
			if(memcmp(&rguid, &GUID_SysMouse, sizeof(GUID)) == 0)
			{
				if(!apDeviceInterface[eDID_MOUSE])
				{
					apDeviceInterface[eDID_MOUSE] = (LPDIRECTINPUTDEVICE7) *pvOut;

					if(!apDeviceInterface[eDID_KEYBOARD])					
					{
						for(int i = 0; i < eDIDMH_COUNT; i++)
						{
							if(!aDeviceMethodHook[i].pNext)
							{
								Trace("DK: 0x%X\n", GETINTERFACEMETHOD(*pvOut, aDeviceMethodHook[i].eMethod));

								if(!MADCODEHOOK::HookCode(GETINTERFACEMETHOD(*pvOut, aDeviceMethodHook[i].eMethod), aDeviceMethodHook[i].pHook, &aDeviceMethodHook[i].pNext))
								{
									Trace("DINPUT7HOOK: CreateDeviceEx() failed for method: %d!\n", aDeviceMethodHook[i].eMethod);
								}
							}
						}
					}
				}
			}

		}
		else
			Trace("DINPUT7HOOK: CreateDeviceEx() unsupported interface!\n");
	}

	return(hResult);
}	 

These work, but are commented out for now. One might want to hook “SetCooperativeLevel()” to force a more “cooperative” mode, etc.

Code: Select all

	   
#if 0
HRESULT WINAPI SetCooperativeLevel_Hook(LPVOID pSelf, HWND hwnd, DWORD dwFlags)
{
	HRESULT hResult = ((HRESULT (WINAPI *)(LPVOID, HWND, DWORD)) aDeviceMethodHook[eDIDMH_SetCooperativeLevel].pNext)(pSelf, hwnd, dwFlags);

	Trace("DINPUT7HOOK: SetCooperativeLevel() F: 0x%X, SA: 0x%X\n", dwFlags, pSelf);

	return(hResult);
}


HRESULT WINAPI Acquire_Hook(LPVOID pSelf)
{
	HRESULT hResult = ((HRESULT (WINAPI *)(LPVOID)) aDeviceMethodHook[eDIDMH_Acquire].pNext)(pSelf);

	Trace("DINPUT7HOOK: Acquire() SA: 0x%X\n", pSelf);

	return(hResult);
}
#endif



A quick hack to fake left mouse clicks:

Code: Select all

	   

int iFakeLeftClicks = 0;
void DINPUT7HOOK::FakeLeftClick(void)
{
	++iFakeLeftClicks;
}
	 
In the particular application I was “testing” on, it was polling the inputs.
Most games appear to poll the inputs easy enough.
This hook, “GetDeviceState()”, is where it all really happens, the previous stuff was mostly setup.
Now the game is polling inputs every game loop cycle or so, and this hook will fire off regularly.


Code: Select all

	   

HRESULT WINAPI GetDeviceState_Hook(LPVOID pSelf, DWORD cbData, LPVOID lpvData)
{
	HRESULT hResult = ((HRESULT (WINAPI *)(LPVOID, DWORD, LPVOID)) aDeviceMethodHook[eDIDMH_GetDeviceState].pNext)(pSelf, cbData, lpvData);

	if(pSelf == apDeviceInterface[eDID_MOUSE])
	{
		//Trace("DINPUT7HOOK: GetDeviceState() MOUSE tSize: %d\n", cbData);

		if(iFakeLeftClicks > 0)
		{
			Trace("DINPUT7HOOK: GetDeviceState() MOUSE fake left click\n");
			((LPDIMOUSESTATE) lpvData)->rgbButtons[0] = 0x80;
			--iFakeLeftClicks;
		}
	}
	else
	if(pSelf == apDeviceInterface[eDID_KEYBOARD])
	{ 
		//Trace("DINPUT7HOOK: GetDeviceState() KEYBOARD Buffer: 0x%X, Count: %d\n", lpvData, cbData);
	}
	else
		Trace("DINPUT7HOOK: GetDeviceState() unknown! SA: 0x%X\n", pSelf);

	return(hResult);
}
	 
To add filtering of key presses; by looking at the “IDirectInputDevice7::GetDeviceState()” documents you can see all you need to do is for a given key in the array to 0 (clearing the high bit).

To fake a key press set the high bit of the key index for as long as you need it down.

I.E:

Code: Select all

	   
((BYTE *) lpvData)[DIK_A] = 0x80;  // Fake press the ‘A’ key	 


Hope this helps.

EDIT: Link with "dxguid.lib" for the GUID defines.
Post Reply