diff --git a/platform/windows/detect.py b/platform/windows/detect.py index 69aa992051d..19b6d98ee5d 100644 --- a/platform/windows/detect.py +++ b/platform/windows/detect.py @@ -404,6 +404,7 @@ def configure_msvc(env: "SConsEnvironment"): "dwrite", "wbemuuid", "ntdll", + "hid", ] if env.debug_features: @@ -777,6 +778,7 @@ def configure_mingw(env: "SConsEnvironment"): "dwrite", "wbemuuid", "ntdll", + "hid", ] ) diff --git a/platform/windows/os_windows.cpp b/platform/windows/os_windows.cpp index a24488014c4..781c7a4eb41 100644 --- a/platform/windows/os_windows.cpp +++ b/platform/windows/os_windows.cpp @@ -55,6 +55,7 @@ #include #include #include +#include #include #include #include @@ -62,6 +63,7 @@ #include #include #include +#include #if defined(RD_ENABLED) #include "servers/rendering/rendering_device.h" @@ -2740,9 +2742,108 @@ bool OS_Windows::_test_create_rendering_device_and_gl(const String &p_display_dr } #endif +using GetProcAddressType = FARPROC(__stdcall *)(HMODULE, LPCSTR); +GetProcAddressType Original_GetProcAddress = nullptr; + +using HidD_GetProductStringType = BOOLEAN(__stdcall *)(HANDLE, void *, ULONG); +HidD_GetProductStringType Original_HidD_GetProductString = nullptr; + +#ifndef HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER +#define HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER 0x08 +#endif + +bool _hid_is_controller(HANDLE p_hid_handle) { + PHIDP_PREPARSED_DATA hid_preparsed = nullptr; + BOOLEAN preparsed_res = HidD_GetPreparsedData(p_hid_handle, &hid_preparsed); + if (!preparsed_res) { + return false; + } + + HIDP_CAPS hid_caps = {}; + NTSTATUS caps_res = HidP_GetCaps(hid_preparsed, &hid_caps); + HidD_FreePreparsedData(hid_preparsed); + if (caps_res != HIDP_STATUS_SUCCESS) { + return false; + } + + if (hid_caps.UsagePage != HID_USAGE_PAGE_GENERIC) { + return false; + } + + if (hid_caps.Usage == HID_USAGE_GENERIC_JOYSTICK || hid_caps.Usage == HID_USAGE_GENERIC_GAMEPAD || hid_caps.Usage == HID_USAGE_GENERIC_MULTI_AXIS_CONTROLLER) { + return true; + } + + return false; +} + +BOOLEAN __stdcall Hook_HidD_GetProductString(HANDLE p_object, void *p_buffer, ULONG p_buffer_length) { + constexpr const wchar_t unknown_product_string[] = L"Unknown HID Device"; + constexpr size_t unknown_product_length = sizeof(unknown_product_string); + + if (_hid_is_controller(p_object)) { + return HidD_GetProductString(p_object, p_buffer, p_buffer_length); + } + + // The HID is (probably) not a controller, so we don't care about returning its actual product string. + // This avoids stalls on `EnumDevices` because DirectInput attempts to enumerate all HIDs, including some DACs + // and other devices which take too long to respond to those requests, added to the lack of a shorter timeout. + if (p_buffer_length >= unknown_product_length) { + memcpy(p_buffer, unknown_product_string, unknown_product_length); + return TRUE; + } + return FALSE; +} + +FARPROC __stdcall Hook_GetProcAddress(HMODULE p_module, LPCSTR p_name) { + if (String(p_name) == "HidD_GetProductString") { + return (FARPROC)(LPVOID)Hook_HidD_GetProductString; + } + if (Original_GetProcAddress) { + return Original_GetProcAddress(p_module, p_name); + } + return nullptr; +} + +LPVOID install_iat_hook(const String &p_target, const String &p_module, const String &p_symbol, LPVOID p_hook_func) { + LPVOID image_base = LoadLibraryA(p_target.ascii().get_data()); + if (image_base) { + PIMAGE_NT_HEADERS nt_headers = (PIMAGE_NT_HEADERS)((DWORD_PTR)image_base + ((PIMAGE_DOS_HEADER)image_base)->e_lfanew); + PIMAGE_IMPORT_DESCRIPTOR import_descriptor = (PIMAGE_IMPORT_DESCRIPTOR)((DWORD_PTR)image_base + nt_headers->OptionalHeader.DataDirectory[IMAGE_DIRECTORY_ENTRY_IMPORT].VirtualAddress); + while (import_descriptor->Name != 0) { + LPCSTR library_name = (LPCSTR)((DWORD_PTR)image_base + import_descriptor->Name); + if (String(library_name).to_lower() == p_module) { + PIMAGE_THUNK_DATA original_first_thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)image_base + import_descriptor->OriginalFirstThunk); + PIMAGE_THUNK_DATA first_thunk = (PIMAGE_THUNK_DATA)((DWORD_PTR)image_base + import_descriptor->FirstThunk); + + while ((LPVOID)original_first_thunk->u1.AddressOfData != nullptr) { + PIMAGE_IMPORT_BY_NAME function_import = (PIMAGE_IMPORT_BY_NAME)((DWORD_PTR)image_base + original_first_thunk->u1.AddressOfData); + if (String(function_import->Name).to_lower() == p_symbol.to_lower()) { + DWORD old_protect = 0; + VirtualProtect((LPVOID)(&first_thunk->u1.Function), 8, PAGE_READWRITE, &old_protect); + + LPVOID old_func = (LPVOID)first_thunk->u1.Function; + first_thunk->u1.Function = (DWORD_PTR)p_hook_func; + + VirtualProtect((LPVOID)(&first_thunk->u1.Function), 8, old_protect, nullptr); + return old_func; + } + original_first_thunk++; + first_thunk++; + } + } + import_descriptor++; + } + } + return nullptr; +} + OS_Windows::OS_Windows(HINSTANCE _hInstance) { hInstance = _hInstance; + Original_GetProcAddress = (GetProcAddressType)install_iat_hook("dinput8.dll", "kernel32.dll", "GetProcAddress", (LPVOID)Hook_GetProcAddress); + Original_HidD_GetProductString = (HidD_GetProductStringType)install_iat_hook("dinput8.dll", "hid.dll", "HidD_GetProductString", (LPVOID)Hook_HidD_GetProductString); + _init_encodings(); // Reset CWD to ensure long path is used.