diff --git a/CMakeLists.txt b/CMakeLists.txt index 87f44aff87..a5749acbcd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -103,7 +103,18 @@ endif() option(ENABLE_CUBEB "Enabled cubeb backend" ON) # usb hid backends -option(ENABLE_NSYSHID_LIBUSB "Enables the libusb backend for nsyshid" ON) +if (WIN32) + option(ENABLE_NSYSHID_WINDOWS_HID "Enables the native Windows HID backend for nsyshid" ON) +endif () +# libusb and windows hid backends shouldn't be active at the same time; otherwise we'd see all devices twice! +if (NOT ENABLE_NSYSHID_WINDOWS_HID) + option(ENABLE_NSYSHID_LIBUSB "Enables the libusb backend for nsyshid" ON) +else () + set(ENABLE_NSYSHID_LIBUSB OFF CACHE BOOL "" FORCE) +endif () +if (ENABLE_NSYSHID_WINDOWS_HID) + add_compile_definitions(NSYSHID_ENABLE_BACKEND_WINDOWS_HID) +endif () if (ENABLE_NSYSHID_LIBUSB) add_compile_definitions(NSYSHID_ENABLE_BACKEND_LIBUSB) endif () diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index d9d1bbb5ad..342ec922e7 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -440,6 +440,8 @@ add_library(CemuCafe OS/libs/nsyshid/Whitelist.h OS/libs/nsyshid/BackendLibusb.cpp OS/libs/nsyshid/BackendLibusb.h + OS/libs/nsyshid/BackendWindowsHID.cpp + OS/libs/nsyshid/BackendWindowsHID.h OS/libs/nsyskbd/nsyskbd.cpp OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp diff --git a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp index da65ee1279..b1ad368f42 100644 --- a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp +++ b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp @@ -7,6 +7,12 @@ #endif +#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#include "BackendWindowsHID.h" + +#endif + namespace nsyshid::backend { void attachDefaultBackends() { #if NSYSHID_ENABLE_BACKEND_LIBUSB @@ -18,5 +24,14 @@ namespace nsyshid::backend { } } #endif //NSYSHID_ENABLE_BACKEND_LIBUSB +#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID + // add windows hid backend + { + auto backendWindowsHID = std::make_shared(); + if (backendWindowsHID->isInitialisedOk()) { + attachBackend(backendWindowsHID); + } + } +#endif //NSYSHID_ENABLE_BACKEND_WINDOWS_HID } } diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp new file mode 100644 index 0000000000..de4e1c7faa --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -0,0 +1,402 @@ +#include "BackendWindowsHID.h" + +#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#include +#include +#include + +#pragma comment(lib, "Setupapi.lib") +#pragma comment(lib, "hid.lib") + +DEFINE_GUID(GUID_DEVINTERFACE_HID, +0x4D1E55B2L, 0xF16F, 0x11CF, 0x88, 0xCB, 0x00, 0x11, 0x11, 0x00, 0x00, 0x30); + +namespace nsyshid::backend::windows { + BackendWindowsHID::BackendWindowsHID() { + + } + + void BackendWindowsHID::attachVisibleDevices() { + // add all currently connected devices + HDEVINFO hDevInfo; + SP_DEVICE_INTERFACE_DATA DevIntfData; + PSP_DEVICE_INTERFACE_DETAIL_DATA DevIntfDetailData; + SP_DEVINFO_DATA DevData; + + DWORD dwSize, dwMemberIdx; + + hDevInfo = SetupDiGetClassDevs(&GUID_DEVINTERFACE_HID, NULL, 0, DIGCF_DEVICEINTERFACE | DIGCF_PRESENT); + + if (hDevInfo != INVALID_HANDLE_VALUE) { + DevIntfData.cbSize = sizeof(SP_DEVICE_INTERFACE_DATA); + dwMemberIdx = 0; + + SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, + dwMemberIdx, &DevIntfData); + + while (GetLastError() != ERROR_NO_MORE_ITEMS) { + DevData.cbSize = sizeof(DevData); + SetupDiGetDeviceInterfaceDetail( + hDevInfo, &DevIntfData, NULL, 0, &dwSize, NULL); + + DevIntfDetailData = (PSP_DEVICE_INTERFACE_DETAIL_DATA) HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, + dwSize); + DevIntfDetailData->cbSize = sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA); + + if (SetupDiGetDeviceInterfaceDetail(hDevInfo, &DevIntfData, + DevIntfDetailData, dwSize, &dwSize, &DevData)) { + HANDLE hHIDDevice = openDevice(DevIntfDetailData->DevicePath); + if (hHIDDevice != INVALID_HANDLE_VALUE) { + auto device = checkAndCreateDevice(DevIntfDetailData->DevicePath, hHIDDevice); + if (device != nullptr) { + if (isDeviceWhitelisted(device->vendorId, device->productId)) { + if (!attachDevice(device)) { + cemuLog_log(LogType::Force, + "nsyshid::BackendWindowsHID: failed to attach device: {:04x}:{:04x}", + device->vendorId, + device->productId); + } + } else { + cemuLog_log(LogType::Force, + "nsyshid::BackendWindowsHID: device not on whitelist: {:04x}:{:04x}", + device->vendorId, + device->productId); + } + } + CloseHandle(hHIDDevice); + } + } + HeapFree(GetProcessHeap(), 0, DevIntfDetailData); + // next + SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, ++dwMemberIdx, &DevIntfData); + } + SetupDiDestroyDeviceInfoList(hDevInfo); + } + } + + BackendWindowsHID::~BackendWindowsHID() { + + } + + bool BackendWindowsHID::isInitialisedOk() { + return true; + } + + std::shared_ptr BackendWindowsHID::checkAndCreateDevice(wchar_t *devicePath, HANDLE hDevice) { + HIDD_ATTRIBUTES hidAttr; + hidAttr.Size = sizeof(HIDD_ATTRIBUTES); + if (HidD_GetAttributes(hDevice, &hidAttr) == FALSE) + return nullptr; + + auto device = std::make_shared(hidAttr.VendorID, + hidAttr.ProductID, + 1, + 2, + 0, + _wcsdup(devicePath)); + // get additional device info + sint32 maxPacketInputLength = -1; + sint32 maxPacketOutputLength = -1; + PHIDP_PREPARSED_DATA ppData = nullptr; + if (HidD_GetPreparsedData(hDevice, &ppData)) { + HIDP_CAPS caps; + if (HidP_GetCaps(ppData, &caps) == HIDP_STATUS_SUCCESS) { + // length includes the report id byte + maxPacketInputLength = caps.InputReportByteLength - 1; + maxPacketOutputLength = caps.OutputReportByteLength - 1; + } + HidD_FreePreparsedData(ppData); + } + if (maxPacketInputLength <= 0 || maxPacketInputLength >= 0xF000) { + cemuLog_log(LogType::Force, "HID: Input packet length not available or out of range (length = {})", + maxPacketInputLength); + maxPacketInputLength = 0x20; + } + if (maxPacketOutputLength <= 0 || maxPacketOutputLength >= 0xF000) { + cemuLog_log(LogType::Force, "HID: Output packet length not available or out of range (length = {})", + maxPacketOutputLength); + maxPacketOutputLength = 0x20; + } + + device->maxPacketSizeRX = maxPacketInputLength; + device->maxPacketSizeTX = maxPacketOutputLength; + + return device; + } + + DeviceWindowsHID::DeviceWindowsHID(uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol, + wchar_t *devicePath) : + Device(vendorId, + productId, + interfaceIndex, + interfaceSubClass, + protocol), + devicePath(devicePath), + hFile(INVALID_HANDLE_VALUE) { + + } + + DeviceWindowsHID::~DeviceWindowsHID() { + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); + hFile = INVALID_HANDLE_VALUE; + } + } + + bool DeviceWindowsHID::open() { + if (isOpened()) { + return true; + } + hFile = openDevice(devicePath); + if (hFile == INVALID_HANDLE_VALUE) { + return false; + } + HidD_SetNumInputBuffers(hFile, 2); // don't cache too many reports + return true; + } + + void DeviceWindowsHID::close() { + if (hFile != INVALID_HANDLE_VALUE) { + CloseHandle(hFile); + hFile = INVALID_HANDLE_VALUE; + } + } + + bool DeviceWindowsHID::isOpened() { + return hFile != INVALID_HANDLE_VALUE; + } + + Device::ReadResult DeviceWindowsHID::read(uint8 *data, sint32 length, sint32 &bytesRead) { + bytesRead = 0; + DWORD bt; + OVERLAPPED ovlp = {0}; + ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + uint8 *tempBuffer = (uint8 *) malloc(length + 1); + sint32 transferLength = 0; // minus report byte + + _debugPrintHex("HID_READ_BEFORE", data, length); + + cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", length); + BOOL readResult = ReadFile(this->hFile, tempBuffer, length + 1, &bt, &ovlp); + if (readResult != FALSE) { + // sometimes we get the result immediately + if (bt == 0) + transferLength = 0; + else + transferLength = bt - 1; + cemuLog_logDebug(LogType::Force, "HidRead Result received immediately (error 0x{:08x}) Length 0x{:08x}", + GetLastError(), transferLength); + } else { + // wait for result + cemuLog_logDebug(LogType::Force, "HidRead WaitForResult (error 0x{:08x})", GetLastError()); + // async hid read is never supposed to return unless there is a response? Lego Dimensions stops HIDRead calls as soon as one of them fails with a non-zero error (which includes time out) + DWORD r = WaitForSingleObject(ovlp.hEvent, 2000 * 100); + if (r == WAIT_TIMEOUT) { + cemuLog_logDebug(LogType::Force, "HidRead internal timeout (error 0x{:08x})", GetLastError()); + // return -108 in case of timeout + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return ReadResult::ErrorTimeout; + } + + cemuLog_logDebug(LogType::Force, "HidRead WaitHalfComplete"); + GetOverlappedResult(this->hFile, &ovlp, &bt, false); + if (bt == 0) + transferLength = 0; + else + transferLength = bt - 1; + cemuLog_logDebug(LogType::Force, "HidRead WaitComplete Length: 0x{:08x}", transferLength); + } + sint32 returnCode = 0; + ReadResult result = ReadResult::Success; + if (bt != 0) { + memcpy(data, tempBuffer + 1, transferLength); + sint32 hidReadLength = transferLength; + + char debugOutput[1024] = {0}; + for (sint32 i = 0; i < transferLength; i++) { + sprintf(debugOutput + i * 3, "%02x ", tempBuffer[1 + i]); + } + cemuLog_logDebug(LogType::Force, "HIDRead data: {}", debugOutput); + + bytesRead = transferLength; + result = ReadResult::Success; + } else { + cemuLog_log(LogType::Force, "Failed HID read"); + result = ReadResult::Error; + } + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return result; + } + + Device::WriteResult DeviceWindowsHID::write(uint8 *data, sint32 length, sint32 &bytesWritten) { + bytesWritten = 0; + DWORD bt; + OVERLAPPED ovlp = {0}; + ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); + + uint8 *tempBuffer = (uint8 *) malloc(length + 1); + memcpy(tempBuffer + 1, data, length); + tempBuffer[0] = 0; // report byte? + + cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", length); + BOOL writeResult = WriteFile(this->hFile, tempBuffer, length + 1, &bt, &ovlp); + if (writeResult != FALSE) { + // sometimes we get the result immediately + cemuLog_logDebug(LogType::Force, "HidWrite Result received immediately (error 0x{:08x}) Length 0x{:08x}", + GetLastError()); + } else { + // wait for result + cemuLog_logDebug(LogType::Force, "HidWrite WaitForResult (error 0x{:08x})", GetLastError()); + // todo - check for error type + DWORD r = WaitForSingleObject(ovlp.hEvent, 2000); + if (r == WAIT_TIMEOUT) { + cemuLog_logDebug(LogType::Force, "HidWrite internal timeout"); + // return -108 in case of timeout + free(tempBuffer); + CloseHandle(ovlp.hEvent); + return WriteResult::ErrorTimeout; + } + + cemuLog_logDebug(LogType::Force, "HidWrite WaitHalfComplete"); + GetOverlappedResult(this->hFile, &ovlp, &bt, false); + cemuLog_logDebug(LogType::Force, "HidWrite WaitComplete"); + } + + free(tempBuffer); + CloseHandle(ovlp.hEvent); + + if (bt != 0) { + bytesWritten = length; + return WriteResult::Success; + } + return WriteResult::Error; + } + + bool DeviceWindowsHID::getDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8 *output, + uint32 outputMaxLength) { + if (!isOpened()) { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceWindowsHID::getDescriptor(): device is not opened"); + return false; + } + if (descType == 0x02) { + uint8 configurationDescriptor[0x29]; + + uint8 *currentWritePtr; + + // configuration descriptor + currentWritePtr = configurationDescriptor + 0; + *(uint8 *) (currentWritePtr + 0) = 9; // bLength + *(uint8 *) (currentWritePtr + 1) = 2; // bDescriptorType + *(uint16be *) (currentWritePtr + 2) = 0x0029; // wTotalLength + *(uint8 *) (currentWritePtr + 4) = 1; // bNumInterfaces + *(uint8 *) (currentWritePtr + 5) = 1; // bConfigurationValue + *(uint8 *) (currentWritePtr + 6) = 0; // iConfiguration + *(uint8 *) (currentWritePtr + 7) = 0x80; // bmAttributes + *(uint8 *) (currentWritePtr + 8) = 0xFA; // MaxPower + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8 *) (currentWritePtr + 0) = 9; // bLength + *(uint8 *) (currentWritePtr + 1) = 0x04; // bDescriptorType + *(uint8 *) (currentWritePtr + 2) = 0; // bInterfaceNumber + *(uint8 *) (currentWritePtr + 3) = 0; // bAlternateSetting + *(uint8 *) (currentWritePtr + 4) = 2; // bNumEndpoints + *(uint8 *) (currentWritePtr + 5) = 3; // bInterfaceClass + *(uint8 *) (currentWritePtr + 6) = 0; // bInterfaceSubClass + *(uint8 *) (currentWritePtr + 7) = 0; // bInterfaceProtocol + *(uint8 *) (currentWritePtr + 8) = 0; // iInterface + currentWritePtr = currentWritePtr + 9; + // configuration descriptor + *(uint8 *) (currentWritePtr + 0) = 9; // bLength + *(uint8 *) (currentWritePtr + 1) = 0x21; // bDescriptorType + *(uint16be *) (currentWritePtr + 2) = 0x0111; // bcdHID + *(uint8 *) (currentWritePtr + 4) = 0x00; // bCountryCode + *(uint8 *) (currentWritePtr + 5) = 0x01; // bNumDescriptors + *(uint8 *) (currentWritePtr + 6) = 0x22; // bDescriptorType + *(uint16be *) (currentWritePtr + 7) = 0x001D; // wDescriptorLength + currentWritePtr = currentWritePtr + 9; + // endpoint descriptor 1 + *(uint8 *) (currentWritePtr + 0) = 7; // bLength + *(uint8 *) (currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8 *) (currentWritePtr + 1) = 0x81; // bEndpointAddress + *(uint8 *) (currentWritePtr + 2) = 0x03; // bmAttributes + *(uint16be *) (currentWritePtr + 3) = + this->maxPacketSizeRX; // wMaxPacketSize + *(uint8 *) (currentWritePtr + 5) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8 *) (currentWritePtr + 0) = 7; // bLength + *(uint8 *) (currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8 *) (currentWritePtr + 1) = 0x02; // bEndpointAddress + *(uint8 *) (currentWritePtr + 2) = 0x03; // bmAttributes + *(uint16be *) (currentWritePtr + 3) = + this->maxPacketSizeTX; // wMaxPacketSize + *(uint8 *) (currentWritePtr + 5) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + + cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); + + memcpy(output, configurationDescriptor, + std::min(outputMaxLength, sizeof(configurationDescriptor))); + return true; + } else { + cemu_assert_unimplemented(); + } + return false; + } + + bool DeviceWindowsHID::setProtocol(uint32 ifIndef, uint32 protocol) { + // ToDo: implement this + // pretend that everything is fine + return true; + } + + bool DeviceWindowsHID::setReport(uint8 *reportData, sint32 length, uint8 *originalData, sint32 originalLength) { + sint32 retryCount = 0; + while (true) { + BOOL r = HidD_SetOutputReport(this->hFile, reportData, length); + if (r != FALSE) + break; + Sleep(20); // retry + retryCount++; + if (retryCount >= 50) { + cemuLog_log(LogType::Force, "nsyshid::DeviceWindowsHID::setReport(): HID SetReport failed"); + return false; + } + } + return true; + } + + HANDLE openDevice(wchar_t *devicePath) { + return CreateFile(devicePath, + GENERIC_READ | GENERIC_WRITE, + FILE_SHARE_READ | + FILE_SHARE_WRITE, + NULL, + OPEN_EXISTING, + FILE_FLAG_OVERLAPPED, + NULL); + } + + void _debugPrintHex(std::string prefix, uint8 *data, size_t len) { + char debugOutput[1024] = {0}; + len = std::min(len, (size_t) 100); + for (sint32 i = 0; i < len; i++) { + sprintf(debugOutput + i * 3, "%02x ", data[i]); + } + fmt::print("{} Data: {}\n", prefix, debugOutput); + cemuLog_logDebug(LogType::Force, "[{}] Data: {}", prefix, debugOutput); + } +} + +#endif //NSYSHID_ENABLE_BACKEND_WINDOWS_HID diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h new file mode 100644 index 0000000000..71ea235ed4 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h @@ -0,0 +1,65 @@ +#ifndef CEMU_NSYSHID_BACKEND_WINDOWS_HID_H +#define CEMU_NSYSHID_BACKEND_WINDOWS_HID_H + +#include "nsyshid.h" + +#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#include "Backend.h" + +namespace nsyshid::backend::windows { + class BackendWindowsHID : public nsyshid::Backend { + public: + BackendWindowsHID(); + + ~BackendWindowsHID(); + + bool isInitialisedOk() override; + + protected: + void attachVisibleDevices() override; + + private: + std::shared_ptr checkAndCreateDevice(wchar_t *devicePath, HANDLE hDevice); + }; + + class DeviceWindowsHID : public nsyshid::Device { + public: + DeviceWindowsHID(uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol, + wchar_t *devicePath); + + ~DeviceWindowsHID(); + + bool open() override; + + void close() override; + + bool isOpened() override; + + ReadResult read(uint8 *data, sint32 length, sint32 &bytesRead) override; + + WriteResult write(uint8 *data, sint32 length, sint32 &bytesWritten) override; + + bool getDescriptor(uint8 descType, uint8 descIndex, uint8 lang, uint8 *output, uint32 outputMaxLength) override; + + bool setProtocol(uint32 ifIndef, uint32 protocol) override; + + bool setReport(uint8 *reportData, sint32 length, uint8 *originalData, sint32 originalLength) override; + + private: + wchar_t *devicePath; + HANDLE hFile; + }; + + HANDLE openDevice(wchar_t *devicePath); + + void _debugPrintHex(std::string prefix, uint8 *data, size_t len); +} + +#endif //NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#endif //CEMU_NSYSHID_BACKEND_WINDOWS_HID_H diff --git a/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp b/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp index 546a961565..09d73013e3 100644 --- a/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp +++ b/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp @@ -3,6 +3,9 @@ #include #include +#pragma comment(lib, "Setupapi.lib") +#pragma comment(lib, "hid.lib") + WinWiimoteDevice::WinWiimoteDevice(HANDLE handle, std::vector identifier) : m_handle(handle), m_identifier(std::move(identifier)) {