#include "Hooks_DirectInput8Create.h" #include #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; } }