enderalse/source/fs.dll/skse64/skse64/Hooks_DirectInput8Create.cpp

597 lines
16 KiB
C++

#include "Hooks_DirectInput8Create.h"
#include <queue>
#include "PapyrusForm.h"
#include "skse64_common/SafeWrite.h"
enum
{
kDeviceType_Keyboard = 1,
kDeviceType_Mouse
};
static const GUID GUID_SysMouse = { 0x6F1D2B60, 0xD5A0, 0x11CF, { 0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00} };
static const GUID GUID_SysKeyboard = { 0x6F1D2B61, 0xD5A0, 0x11CF, { 0xBF,0xC7,0x44,0x45,0x53,0x54,0x00,0x00} };
typedef HRESULT (_stdcall * CreateDInputProc)(HINSTANCE, DWORD, REFIID, LPVOID, LPUNKNOWN);
DIHookControl g_diHookData;
FramerateTracker g_framerateTracker;
static CreateDInputProc DICreate_RealFunc;
class FakeDirectInputDevice : public IDirectInputDevice8
{
public:
FakeDirectInputDevice(IDirectInputDevice8 * device, DWORD type)
:m_device(device), m_deviceType(type), m_refs(1)
{
//
}
HRESULT _stdcall QueryInterface (REFIID riid, LPVOID * ppvObj)
{
return m_device->QueryInterface(riid,ppvObj);
}
ULONG _stdcall AddRef(void)
{
m_refs++;
return m_refs;
}
ULONG _stdcall Release(void)
{
m_refs--;
if(!m_refs)
{
m_device->Release();
delete this;
return 0;
}
else
{
return m_refs;
}
}
// IDirectInputDevice8A
HRESULT _stdcall GetCapabilities(LPDIDEVCAPS a) { return m_device->GetCapabilities(a); }
HRESULT _stdcall EnumObjects(LPDIENUMDEVICEOBJECTSCALLBACKA a,LPVOID b,DWORD c) { return m_device->EnumObjects(a,b,c); }
HRESULT _stdcall GetProperty(REFGUID a,DIPROPHEADER* b) { return m_device->GetProperty(a,b); }
HRESULT _stdcall SetProperty(REFGUID a,const DIPROPHEADER* b) { return m_device->SetProperty(a,b); }
HRESULT _stdcall Acquire(void) { return m_device->Acquire(); }
HRESULT _stdcall Unacquire(void) { return m_device->Unacquire(); }
HRESULT _stdcall GetDeviceState(DWORD outDataLen, LPVOID outData)
{
if(m_deviceType == kDeviceType_Keyboard)
{
// keyboard
// get raw data
UInt8 rawData[InputMap::kMaxMacros];
HRESULT hr = m_device->GetDeviceState(256, rawData);
if(hr != DI_OK) return hr;
DIHookControl::GetSingleton().ProcessKeyboardData(rawData);
memcpy(outData, rawData, outDataLen < 256 ? outDataLen : 256);
return hr;
}
else
{
// mouse
ASSERT(outDataLen == sizeof(DIMOUSESTATE2));
g_framerateTracker.Update();
DIMOUSESTATE2 * mouseState = (DIMOUSESTATE2 *)outData;
// get raw data
HRESULT hr = m_device->GetDeviceState(sizeof(DIMOUSESTATE2), mouseState);
if(hr != DI_OK) return hr;
DIHookControl::GetSingleton().ProcessMouseData(mouseState);
return hr;
}
}
// oblivion and on use this for menus and the console
HRESULT _stdcall GetDeviceData(DWORD dataSize, DIDEVICEOBJECTDATA * outData, DWORD * outDataLen, DWORD flags)
{
// ### begin hack land
if(m_deviceType == kDeviceType_Keyboard)
{
UInt8 rawData[InputMap::kMaxMacros];
HRESULT hr = m_device->GetDeviceState(256, rawData);
if(hr == DI_OK)
{
DIHookControl::GetSingleton().ProcessKeyboardData(rawData);
}
}
return DIHookControl::GetSingleton().ProcessBufferedData(m_device, dataSize, outData, outDataLen, flags);
}
HRESULT _stdcall SetDataFormat(const DIDATAFORMAT* a) { return m_device->SetDataFormat(a); }
HRESULT _stdcall SetEventNotification(HANDLE a) { return m_device->SetEventNotification(a); }
// in debug builds, force cooperative level so input isn't locked out
HRESULT _stdcall SetCooperativeLevel(HWND a,DWORD b)
{
#if defined(_DEBUG) || 0
b = DISCL_BACKGROUND | DISCL_NONEXCLUSIVE;
#endif
return m_device->SetCooperativeLevel(a,b);
}
HRESULT _stdcall GetObjectInfo(LPDIDEVICEOBJECTINSTANCEA a,DWORD b,DWORD c) { return m_device->GetObjectInfo(a,b,c); }
HRESULT _stdcall GetDeviceInfo(LPDIDEVICEINSTANCEA a) { return m_device->GetDeviceInfo(a); }
HRESULT _stdcall RunControlPanel(HWND a,DWORD b) { return m_device->RunControlPanel(a,b); }
HRESULT _stdcall Initialize(HINSTANCE a,DWORD b,REFGUID c) { return m_device->Initialize(a,b,c); }
HRESULT _stdcall CreateEffect(REFGUID a,LPCDIEFFECT b,LPDIRECTINPUTEFFECT *c,LPUNKNOWN d) { return m_device->CreateEffect(a,b,c,d); }
HRESULT _stdcall EnumEffects(LPDIENUMEFFECTSCALLBACKA a,LPVOID b,DWORD c) { return m_device->EnumEffects(a,b,c); }
HRESULT _stdcall GetEffectInfo(LPDIEFFECTINFOA a,REFGUID b) { return m_device->GetEffectInfo(a,b); }
HRESULT _stdcall GetForceFeedbackState(LPDWORD a) { return m_device->GetForceFeedbackState(a); }
HRESULT _stdcall SendForceFeedbackCommand(DWORD a) { return m_device->SendForceFeedbackCommand(a); }
HRESULT _stdcall EnumCreatedEffectObjects(LPDIENUMCREATEDEFFECTOBJECTSCALLBACK a,LPVOID b,DWORD c) { return m_device->EnumCreatedEffectObjects(a,b,c); }
HRESULT _stdcall Escape(LPDIEFFESCAPE a) { return m_device->Escape(a); }
HRESULT _stdcall Poll(void) { return m_device->Poll(); }
HRESULT _stdcall SendDeviceData(DWORD a,LPCDIDEVICEOBJECTDATA b,LPDWORD c,DWORD d) { return m_device->SendDeviceData(a,b,c,d); }
HRESULT _stdcall EnumEffectsInFile(LPCSTR a,LPDIENUMEFFECTSINFILECALLBACK b,LPVOID c,DWORD d) { return m_device->EnumEffectsInFile(a,b,c,d); }
HRESULT _stdcall WriteEffectToFile(LPCSTR a,DWORD b,LPDIFILEEFFECT c,DWORD d) { return m_device->WriteEffectToFile(a,b,c,d); }
HRESULT _stdcall BuildActionMap(LPDIACTIONFORMATA a,LPCSTR b,DWORD c) { return m_device->BuildActionMap(a,b,c); }
HRESULT _stdcall SetActionMap(LPDIACTIONFORMATA a,LPCSTR b,DWORD c) { return m_device->SetActionMap(a,b,c); }
HRESULT _stdcall GetImageInfo(LPDIDEVICEIMAGEINFOHEADERA a) { return m_device->GetImageInfo(a); }
private:
IDirectInputDevice8 * m_device;
DWORD m_deviceType;
ULONG m_refs;
};
class FakeDirectInput : public IDirectInput8A {
public:
/*** Constructor ***/
FakeDirectInput(IDirectInput8 * obj)
:m_realDInput(obj), m_refs(1) { }
/*** IUnknown methods ***/
HRESULT _stdcall QueryInterface (REFIID riid, LPVOID* ppvObj) { return m_realDInput->QueryInterface(riid, ppvObj); }
ULONG _stdcall AddRef(void)
{
m_refs++;
return m_refs;
}
ULONG _stdcall Release(void)
{
m_refs--;
if(!m_refs)
{
m_realDInput->Release();
delete this;
return 0;
}
return m_refs;
}
/*** IDirectInput8A methods ***/
HRESULT _stdcall CreateDevice(REFGUID typeGuid, IDirectInputDevice8A ** device, IUnknown * unused)
{
#if 0
_MESSAGE("IDirectInput8A::CreateDevice: %08X-%04X-%04X-%02X%02X%02X%02X%02X%02X%02X%02X",
typeGuid.Data1, typeGuid.Data2, typeGuid.Data3,
typeGuid.Data4[0], typeGuid.Data4[1], typeGuid.Data4[2], typeGuid.Data4[3],
typeGuid.Data4[4], typeGuid.Data4[5], typeGuid.Data4[6], typeGuid.Data4[7]);
#endif
if(typeGuid != GUID_SysKeyboard && typeGuid != GUID_SysMouse)
{
return m_realDInput->CreateDevice(typeGuid, device, unused);
}
else
{
IDirectInputDevice8A * dev;
HRESULT hr = m_realDInput->CreateDevice(typeGuid, &dev, unused);
if(hr != DI_OK) return hr;
*device = new FakeDirectInputDevice(dev, (typeGuid == GUID_SysKeyboard) ? kDeviceType_Keyboard : kDeviceType_Mouse);
return hr;
}
}
HRESULT _stdcall EnumDevices(DWORD a,LPDIENUMDEVICESCALLBACKA b,void* c,DWORD d) { return m_realDInput->EnumDevices(a,b,c,d); }
HRESULT _stdcall GetDeviceStatus(REFGUID r) { return m_realDInput->GetDeviceStatus(r); }
HRESULT _stdcall RunControlPanel(HWND a,DWORD b) { return m_realDInput->RunControlPanel(a,b); }
HRESULT _stdcall Initialize(HINSTANCE a,DWORD b) { return m_realDInput->Initialize(a,b); }
HRESULT _stdcall FindDevice(REFGUID a,LPCSTR b,LPGUID c) { return m_realDInput->FindDevice(a,b,c); }
HRESULT _stdcall EnumDevicesBySemantics(LPCSTR a,LPDIACTIONFORMATA b,LPDIENUMDEVICESBYSEMANTICSCBA c,void* d,DWORD e) { return m_realDInput->EnumDevicesBySemantics(a,b,c,d,e); }
HRESULT _stdcall ConfigureDevices(LPDICONFIGUREDEVICESCALLBACK a,LPDICONFIGUREDEVICESPARAMSA b,DWORD c,void* d) { return m_realDInput->ConfigureDevices(a,b,c,d); }
private:
IDirectInput8 * m_realDInput;
ULONG m_refs;
};
static HRESULT _stdcall Hook_DirectInput8Create_Execute(HINSTANCE instance, DWORD version, REFIID iid, void * out, IUnknown * outer)
{
IDirectInput8A * dinput;
HRESULT hr = DICreate_RealFunc(instance, version, iid, &dinput, outer);
if(hr != DI_OK) return hr;
*((IDirectInput8A**)out) = new FakeDirectInput(dinput);
_MESSAGE("hooked dinput");
return DI_OK;
}
void Hooks_DirectInput_Commit(void)
{
uintptr_t thunkAddress = (uintptr_t)GetIATAddr((UInt8 *)GetModuleHandle(NULL), "dinput8.dll", "DirectInput8Create");
DICreate_RealFunc = (CreateDInputProc)*(uintptr_t *)thunkAddress;
SafeWrite64(thunkAddress, (uintptr_t)Hook_DirectInput8Create_Execute);
}
DIHookControl::DIHookControl()
{
memset(&m_keys, 0, sizeof(m_keys));
}
bool DIHookControl::_IsKeyPressed(KeyInfo* info, UInt32 flags)
{
bool result = false;
//bool isMouseButton = keycode >= kMacro_MouseButtonOffset;
// data sources
if(flags & kFlag_GameState) result |= info->gameState;
if(flags & kFlag_RawState) result |= info->rawState;
if(flags & kFlag_InsertedState) result |= info->insertedState;
// modifiers
bool disable = false;
if((flags & kFlag_IgnoreDisabled_User) && info->userDisable)
disable = true;
if((flags & kFlag_IgnoreDisabled_Script) && info->scriptDisable)
disable = true;
if(disable) result = false;
return result;
}
bool DIHookControl::IsKeyPressed(UInt32 keycode, UInt32 flags)
{
if(keycode >= InputMap::kMaxMacros) return false;
// default mode
if(!flags) flags = kFlag_DefaultBackCompat;
KeyInfo * info = &m_keys[keycode];
return _IsKeyPressed(info, flags);
}
UInt32 DIHookControl::GetNumKeysPressed()
{
UInt32 keysPressed = 0;
for (UInt32 keycode = 0; keycode < InputMap::kMaxMacros; keycode++)
{
KeyInfo* info = &m_keys[keycode];
if (_IsKeyPressed(info, kFlag_DefaultBackCompat))
keysPressed++;
}
return keysPressed;
}
SInt32 DIHookControl::GetNthKeyPressed(UInt32 n)
{
UInt32 index = 0;
for (UInt32 keycode = 0; keycode < InputMap::kMaxMacros; keycode++) {
KeyInfo* info = &m_keys[keycode];
if (_IsKeyPressed(info, kFlag_DefaultBackCompat)) {
if (index == n) {
return keycode;
} else
index++;
}
}
return -1;
}
bool DIHookControl::IsKeyDisabled(UInt32 keycode)
{
if(keycode >= InputMap::kMaxMacros) return false;
KeyInfo * info = &m_keys[keycode];
return info->userDisable || info->scriptDisable;
}
bool DIHookControl::IsKeyHeld(UInt32 keycode)
{
if(keycode >= InputMap::kMaxMacros) return false;
return m_keys[keycode].hold;
}
bool DIHookControl::IsKeyTapped(UInt32 keycode)
{
if(keycode >= InputMap::kMaxMacros) return false;
return m_keys[keycode].tap;
}
void DIHookControl::SetKeyDisableState(UInt32 keycode, bool bDisable, UInt32 mask)
{
if(!mask) mask = kDisable_All; // default mask value
if(keycode < InputMap::kMaxMacros)
{
KeyInfo * info = &m_keys[keycode];
if(mask & kDisable_User) info->userDisable = bDisable;
if(mask & kDisable_Script) info->scriptDisable = bDisable;
}
}
void DIHookControl::SetKeyHeldState(UInt32 keycode, bool bHold)
{
if(keycode < InputMap::kMaxMacros)
m_keys[keycode].hold = bHold;
}
void DIHookControl::TapKey(UInt32 keycode)
{
if(keycode < InputMap::kMaxMacros)
m_keys[keycode].tap = true;
}
void DIHookControl::BufferedKeyTap(UInt32 key)
{
DIDEVICEOBJECTDATA data;
data.uAppData = -1;
data.dwTimeStamp = GetTickCount();
data.dwSequence = 0; // engine doesn't appear to use this and we can't fake it easily
data.dwOfs = key;
data.dwData = 0x80;
IScopedCriticalSection lock(&m_bufferedPressesLock);
// key down
m_bufferedPresses.push(data);
// key up
data.dwData = 0x00;
m_bufferedPresses.push(data);
}
void DIHookControl::BufferedKeyPress(UInt32 key)
{
DIDEVICEOBJECTDATA data;
data.uAppData = -1;
data.dwTimeStamp = GetTickCount();
data.dwSequence = 0;
data.dwOfs = key;
data.dwData = 0x80;
IScopedCriticalSection lock(&m_bufferedPressesLock);
m_bufferedPresses.push(data);
}
void DIHookControl::BufferedKeyRelease(UInt32 key)
{
DIDEVICEOBJECTDATA data;
data.uAppData = -1;
data.dwTimeStamp = GetTickCount();
data.dwSequence = 0;
data.dwOfs = key;
data.dwData = 0x00;
IScopedCriticalSection lock(&m_bufferedPressesLock);
m_bufferedPresses.push(data);
}
void DIHookControl::ProcessKeyboardData(UInt8 * data)
{
// process keys
for(UInt32 idx = 0; idx < 256; idx++)
{
bool keyDown = data[idx] != 0;
keyDown = m_keys[idx].Process(keyDown, idx);
data[idx] = keyDown ? 0x80 : 0x00;
}
}
void DIHookControl::ProcessMouseData(DIMOUSESTATE2 * data)
{
STATIC_ASSERT(sizeof(data->rgbButtons) == InputMap::kMacro_NumMouseButtons);
// process buttons
for(UInt32 idx = 0; idx < InputMap::kMacro_NumMouseButtons; idx++)
{
UInt32 macroIdx = InputMap::kMacro_MouseButtonOffset + idx;
bool keyDown = data->rgbButtons[idx] != 0;
keyDown = m_keys[macroIdx].Process(keyDown, macroIdx);
data->rgbButtons[idx] = keyDown ? 0x80 : 0x00;
}
// process mouse wheel
UInt8 wheelState[InputMap::kMacro_MouseWheelDirections]; // 0 = +, 1 = -
wheelState[0] = data->lZ > 0 ? 0x80 : 0x00;
wheelState[1] = data->lZ < 0 ? 0x80 : 0x00;
for(UInt32 idx = 0; idx < InputMap::kMacro_MouseWheelDirections; idx++)
{
UInt32 macroIdx = InputMap::kMacro_MouseWheelOffset + idx;
bool keyDown = wheelState[idx] != 0;
keyDown = m_keys[macroIdx].Process(keyDown, macroIdx);
wheelState[idx] = keyDown ? 0x80 : 0x00;
}
// wheel state not transferred back
}
HRESULT DIHookControl::ProcessBufferedData(IDirectInputDevice8 * device, DWORD dataSize, DIDEVICEOBJECTDATA * outData, DWORD * outDataLen, DWORD flags)
{
ASSERT(dataSize == sizeof(DIDEVICEOBJECTDATA));
IScopedCriticalSection lock(&m_bufferedPressesLock);
// if we have nothing to inject, pass through
if(m_bufferedPresses.empty())
return device->GetDeviceData(dataSize, outData, outDataLen, flags);
UInt32 eventsRequested = *outDataLen;
// pass down to the device
UInt32 numRealEvents = eventsRequested;
HRESULT hr = device->GetDeviceData(dataSize, outData, &numRealEvents, flags);
if((hr != DI_OK) && (hr != DI_BUFFEROVERFLOW))
{
*outDataLen = numRealEvents;
return hr;
}
// move pointer down and update count
DIDEVICEOBJECTDATA * virtualOutData = (DIDEVICEOBJECTDATA *)(((UInt8 *)outData) + (dataSize * numRealEvents));
UInt32 virtualEventsRequested = eventsRequested - numRealEvents;
UInt32 numVirtualEvents = 0;
if(flags & DIGDD_PEEK)
{
if(outData)
{
// todo: switch from queue to list so we can handle this
HALT("DIHookControl::ProcessBufferedData: can't handle non-NULL data in peek mode");
}
else
{
UInt32 virtualEventsAvail = m_bufferedPresses.size();
if(virtualEventsAvail > virtualEventsRequested)
numVirtualEvents = virtualEventsRequested;
else
numVirtualEvents = virtualEventsAvail;
}
}
else
{
for(UInt32 i = 0; i < virtualEventsRequested; i++)
{
if(m_bufferedPresses.empty()) break;
if(outData)
{
*virtualOutData = m_bufferedPresses.front();
virtualOutData = (DIDEVICEOBJECTDATA *)(((UInt8 *)outData) + dataSize);
}
m_bufferedPresses.pop();
numVirtualEvents++;
}
}
*outDataLen = numRealEvents + numVirtualEvents;
return hr;
}
bool DIHookControl::KeyInfo::Process(bool keyDown, UInt32 idx)
{
insertedState = false;
rawState = keyDown;
// only process whitelisted keys
if(userDisable)
keyDown = false;
if(!scriptDisable)
{
if(hold)
insertedState = true;
if(tap)
{
insertedState = true;
tap = false;
}
if(insertedState)
keyDown = true;
}
gameState = keyDown;
return keyDown;
}
// this code doesn't belong here
FramerateTracker::FramerateTracker()
:m_lastTime(0), m_lastFrameLength(0),
m_frameTimeHistoryIdx(0), m_frameTimeHistoryPrimed(false),
m_averageFrameTime(0)
{
for(UInt32 i = 0; i < kFrameTimeHistoryLength; i++)
m_frameTimeHistory[i] = 0;
}
void FramerateTracker::Update(void)
{
DWORD time = GetTickCount();
// calculate current frame time
m_lastFrameLength = (float)(time - m_lastTime) / 1000.0f;
m_lastTime = time;
// store in ring buffer
m_frameTimeHistory[m_frameTimeHistoryIdx % kFrameTimeHistoryLength] = m_lastFrameLength;
m_frameTimeHistoryIdx++;
// filled ring buffer? flag it
if(m_frameTimeHistoryIdx >= kFrameTimeHistoryLength)
m_frameTimeHistoryPrimed = true;
// history full?
if(m_frameTimeHistoryPrimed)
{
// calculate and store the average
float total = 0;
for(UInt32 i = 0; i < kFrameTimeHistoryLength; i++)
total += m_frameTimeHistory[i];
m_averageFrameTime = total / kFrameTimeHistoryLength;
}
else
{
// report 0 frametime until primed
m_averageFrameTime = 0;
}
}