From 98b5a8758ab30bb4d04ab99143737b48a43aa71f Mon Sep 17 00:00:00 2001 From: Simon <113838661+ssievert42@users.noreply.github.com> Date: Tue, 19 Sep 2023 01:27:40 +0200 Subject: [PATCH] nsyshid: Add backends for cross platform USB passthrough support (#950) --- .github/workflows/build.yml | 2 +- BUILD.md | 2 +- CMakeLists.txt | 17 + cmake/Findlibusb.cmake | 20 + src/Cafe/CMakeLists.txt | 19 + .../OS/libs/nsyshid/AttachDefaultBackends.cpp | 41 + src/Cafe/OS/libs/nsyshid/Backend.h | 141 +++ src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 791 +++++++++++++ src/Cafe/OS/libs/nsyshid/BackendLibusb.h | 129 ++ .../OS/libs/nsyshid/BackendWindowsHID.cpp | 454 +++++++ src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h | 66 ++ src/Cafe/OS/libs/nsyshid/Whitelist.cpp | 53 + src/Cafe/OS/libs/nsyshid/Whitelist.h | 32 + src/Cafe/OS/libs/nsyshid/nsyshid.cpp | 1040 ++++++++--------- src/Cafe/OS/libs/nsyshid/nsyshid.h | 9 +- .../api/Wiimote/windows/WinWiimoteDevice.cpp | 3 + vcpkg.json | 3 +- 17 files changed, 2298 insertions(+), 524 deletions(-) create mode 100644 cmake/Findlibusb.cmake create mode 100644 src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/Backend.h create mode 100644 src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/BackendLibusb.h create mode 100644 src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h create mode 100644 src/Cafe/OS/libs/nsyshid/Whitelist.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/Whitelist.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a91d562b..eb6ac099 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -232,7 +232,7 @@ jobs: - name: "Install system dependencies" run: | brew update - brew install llvm@15 ninja nasm molten-vk + brew install llvm@15 ninja nasm molten-vk automake libtool - name: "Bootstrap vcpkg" run: | diff --git a/BUILD.md b/BUILD.md index 5ff9bfd5..da6c03ce 100644 --- a/BUILD.md +++ b/BUILD.md @@ -86,7 +86,7 @@ You can skip this section if you have an Intel Mac. Every time you compile, you ### Installing dependencies -`brew install boost git cmake llvm ninja nasm molten-vk` +`brew install boost git cmake llvm ninja nasm molten-vk automake libtool` ### Build Cemu using cmake and clang 1. `git clone --recursive https://github.com/cemu-project/Cemu` diff --git a/CMakeLists.txt b/CMakeLists.txt index 34a28a06..a5749acb 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,23 @@ if (WIN32) endif() option(ENABLE_CUBEB "Enabled cubeb backend" ON) +# usb hid backends +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 () + option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON) set(THREADS_PREFER_PTHREAD_FLAG true) diff --git a/cmake/Findlibusb.cmake b/cmake/Findlibusb.cmake new file mode 100644 index 00000000..85da6736 --- /dev/null +++ b/cmake/Findlibusb.cmake @@ -0,0 +1,20 @@ +# SPDX-FileCopyrightText: 2022 Andrea Pappacoda +# SPDX-License-Identifier: ISC + +find_package(libusb CONFIG) +if (NOT libusb_FOUND) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_search_module(libusb IMPORTED_TARGET GLOBAL libusb-1.0 libusb) + if (libusb_FOUND) + add_library(libusb::libusb ALIAS PkgConfig::libusb) + endif () + endif () +endif () + +find_package_handle_standard_args(libusb + REQUIRED_VARS + libusb_LINK_LIBRARIES + libusb_FOUND + VERSION_VAR libusb_VERSION +) diff --git a/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index b7656789..29c5a0b3 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -434,6 +434,14 @@ add_library(CemuCafe OS/libs/nn_uds/nn_uds.h OS/libs/nsyshid/nsyshid.cpp OS/libs/nsyshid/nsyshid.h + OS/libs/nsyshid/Backend.h + OS/libs/nsyshid/AttachDefaultBackends.cpp + OS/libs/nsyshid/Whitelist.cpp + 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 @@ -524,6 +532,17 @@ if (ENABLE_WAYLAND) target_link_libraries(CemuCafe PUBLIC Wayland::Client) endif() +if (ENABLE_NSYSHID_LIBUSB) + if (ENABLE_VCPKG) + find_package(libusb CONFIG REQUIRED) + target_include_directories(CemuCafe PRIVATE ${LIBUSB_INCLUDE_DIRS}) + target_link_libraries(CemuCafe PRIVATE ${LIBUSB_LIBRARIES}) + else () + find_package(libusb MODULE REQUIRED) + target_link_libraries(CemuCafe PRIVATE libusb::libusb) + endif () +endif () + if (ENABLE_WXWIDGETS) target_link_libraries(CemuCafe PRIVATE wx::base wx::core) endif() diff --git a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp new file mode 100644 index 00000000..6e6cb123 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp @@ -0,0 +1,41 @@ +#include "nsyshid.h" +#include "Backend.h" + +#if NSYSHID_ENABLE_BACKEND_LIBUSB + +#include "BackendLibusb.h" + +#endif + +#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#include "BackendWindowsHID.h" + +#endif + +namespace nsyshid::backend +{ + void AttachDefaultBackends() + { +#if NSYSHID_ENABLE_BACKEND_LIBUSB + // add libusb backend + { + auto backendLibusb = std::make_shared(); + if (backendLibusb->IsInitialisedOk()) + { + AttachBackend(backendLibusb); + } + } +#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 + } +} // namespace nsyshid::backend diff --git a/src/Cafe/OS/libs/nsyshid/Backend.h b/src/Cafe/OS/libs/nsyshid/Backend.h new file mode 100644 index 00000000..641104f5 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Backend.h @@ -0,0 +1,141 @@ +#ifndef CEMU_NSYSHID_BACKEND_H +#define CEMU_NSYSHID_BACKEND_H + +#include +#include +#include + +#include "Common/precompiled.h" + +namespace nsyshid +{ + typedef struct + { + /* +0x00 */ uint32be handle; + /* +0x04 */ uint32 ukn04; + /* +0x08 */ uint16 vendorId; // little-endian ? + /* +0x0A */ uint16 productId; // little-endian ? + /* +0x0C */ uint8 ifIndex; + /* +0x0D */ uint8 subClass; + /* +0x0E */ uint8 protocol; + /* +0x0F */ uint8 paddingGuessed0F; + /* +0x10 */ uint16be maxPacketSizeRX; + /* +0x12 */ uint16be maxPacketSizeTX; + } HID_t; + + static_assert(offsetof(HID_t, vendorId) == 0x8, ""); + static_assert(offsetof(HID_t, productId) == 0xA, ""); + static_assert(offsetof(HID_t, ifIndex) == 0xC, ""); + static_assert(offsetof(HID_t, protocol) == 0xE, ""); + + class Device { + public: + Device() = delete; + + Device(uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol); + + Device(const Device& device) = delete; + + Device& operator=(const Device& device) = delete; + + virtual ~Device() = default; + + HID_t* m_hid; // this info is passed to applications and must remain intact + + uint16 m_vendorId; + uint16 m_productId; + uint8 m_interfaceIndex; + uint8 m_interfaceSubClass; + uint8 m_protocol; + uint16 m_maxPacketSizeRX; + uint16 m_maxPacketSizeTX; + + virtual void AssignHID(HID_t* hid); + + virtual bool Open() = 0; + + virtual void Close() = 0; + + virtual bool IsOpened() = 0; + + enum class ReadResult + { + Success, + Error, + ErrorTimeout, + }; + + virtual ReadResult Read(uint8* data, sint32 length, sint32& bytesRead) = 0; + + enum class WriteResult + { + Success, + Error, + ErrorTimeout, + }; + + virtual WriteResult Write(uint8* data, sint32 length, sint32& bytesWritten) = 0; + + virtual bool GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) = 0; + + virtual bool SetProtocol(uint32 ifIndef, uint32 protocol) = 0; + + virtual bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) = 0; + }; + + class Backend { + public: + Backend(); + + Backend(const Backend& backend) = delete; + + Backend& operator=(const Backend& backend) = delete; + + virtual ~Backend() = default; + + void DetachAllDevices(); + + // called from nsyshid when this backend is attached - do not call this yourself! + void OnAttach(); + + // called from nsyshid when this backend is detached - do not call this yourself! + void OnDetach(); + + bool IsBackendAttached(); + + virtual bool IsInitialisedOk() = 0; + + protected: + // try to attach a device - only works if this backend is attached + bool AttachDevice(const std::shared_ptr& device); + + void DetachDevice(const std::shared_ptr& device); + + std::shared_ptr FindDevice(std::function&)> isWantedDevice); + + bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); + + // called from OnAttach() - attach devices that your backend can see here + virtual void AttachVisibleDevices() = 0; + + private: + std::list> m_devices; + std::recursive_mutex m_devicesMutex; + bool m_isAttached; + }; + + namespace backend + { + void AttachDefaultBackends(); + } +} // namespace nsyshid + +#endif // CEMU_NSYSHID_BACKEND_H diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp new file mode 100644 index 00000000..4f88b7ed --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -0,0 +1,791 @@ +#include "BackendLibusb.h" + +#if NSYSHID_ENABLE_BACKEND_LIBUSB + +namespace nsyshid::backend::libusb +{ + BackendLibusb::BackendLibusb() + : m_ctx(nullptr), + m_initReturnCode(0), + m_callbackRegistered(false), + m_hotplugCallbackHandle(0), + m_hotplugThreadStop(false) + { + m_initReturnCode = libusb_init(&m_ctx); + if (m_initReturnCode < 0) + { + m_ctx = nullptr; + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb with return code %i", + m_initReturnCode); + return; + } + + if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) + { + int ret = libusb_hotplug_register_callback(m_ctx, + (libusb_hotplug_event)(LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT), + (libusb_hotplug_flag)0, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + HotplugCallback, + this, + &m_hotplugCallbackHandle); + if (ret != LIBUSB_SUCCESS) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb: failed to register hotplug callback with return code %i", + ret); + } + else + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: registered hotplug callback"); + m_callbackRegistered = true; + m_hotplugThread = std::thread([this] { + while (!m_hotplugThreadStop) + { + timeval timeout{ + .tv_sec = 1, + .tv_usec = 0, + }; + int ret = libusb_handle_events_timeout_completed(m_ctx, &timeout, nullptr); + if (ret != 0) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb: hotplug thread: error handling events: {}", + ret); + std::this_thread::sleep_for(std::chrono::milliseconds(1000)); + } + } + }); + } + } + else + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: hotplug not supported by this version of libusb"); + } + } + + bool BackendLibusb::IsInitialisedOk() + { + return m_initReturnCode == 0; + } + + void BackendLibusb::AttachVisibleDevices() + { + // add all currently connected devices + libusb_device** devices; + ssize_t deviceCount = libusb_get_device_list(m_ctx, &devices); + if (deviceCount < 0) + { + cemuLog_log(LogType::Force, "nsyshid::BackendLibusb: failed to get usb devices"); + return; + } + libusb_device* dev; + for (int i = 0; (dev = devices[i]) != nullptr; i++) + { + auto device = CheckAndCreateDevice(dev); + if (device != nullptr) + { + if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId)) + { + if (!AttachDevice(device)) + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb: failed to attach device: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + else + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb: device not on whitelist: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + } + + libusb_free_device_list(devices, 1); + } + + int BackendLibusb::HotplugCallback(libusb_context* ctx, + libusb_device* dev, + libusb_hotplug_event event, + void* user_data) + { + if (user_data) + { + BackendLibusb* backend = static_cast(user_data); + return backend->OnHotplug(dev, event); + } + return 0; + } + + int BackendLibusb::OnHotplug(libusb_device* dev, libusb_hotplug_event event) + { + struct libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(dev, &desc); + if (ret < 0) + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): failed to get device descriptor"); + return 0; + } + + switch (event) + { + case LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED: + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device arrived: {:04x}:{:04x}", + desc.idVendor, + desc.idProduct); + auto device = CheckAndCreateDevice(dev); + if (device != nullptr) + { + if (IsDeviceWhitelisted(device->m_vendorId, device->m_productId)) + { + if (!AttachDevice(device)) + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::OnHotplug(): failed to attach device: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + else + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::OnHotplug(): device not on whitelist: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + } + break; + case LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT: + { + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb::OnHotplug(): device left: {:04x}:{:04x}", + desc.idVendor, + desc.idProduct); + auto device = FindLibusbDevice(dev); + if (device != nullptr) + { + DetachDevice(device); + } + } + break; + } + + return 0; + } + + BackendLibusb::~BackendLibusb() + { + if (m_callbackRegistered) + { + m_hotplugThreadStop = true; + libusb_hotplug_deregister_callback(m_ctx, m_hotplugCallbackHandle); + m_hotplugThread.join(); + } + DetachAllDevices(); + if (m_ctx) + { + libusb_exit(m_ctx); + m_ctx = nullptr; + } + } + + std::shared_ptr BackendLibusb::FindLibusbDevice(libusb_device* dev) + { + libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(dev, &desc); + if (ret < 0) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb::FindLibusbDevice(): failed to get device descriptor"); + return nullptr; + } + uint8 busNumber = libusb_get_bus_number(dev); + uint8 deviceAddress = libusb_get_device_address(dev); + auto device = FindDevice([desc, busNumber, deviceAddress](const std::shared_ptr& d) -> bool { + auto device = std::dynamic_pointer_cast(d); + if (device != nullptr && + desc.idVendor == device->m_vendorId && + desc.idProduct == device->m_productId && + busNumber == device->m_libusbBusNumber && + deviceAddress == device->m_libusbDeviceAddress) + { + // we found our device! + return true; + } + return false; + }); + + if (device != nullptr) + { + return device; + } + return nullptr; + } + + std::shared_ptr BackendLibusb::CheckAndCreateDevice(libusb_device* dev) + { + struct libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(dev, &desc); + if (ret < 0) + { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::CheckAndCreateDevice(): failed to get device descriptor; return code: %i", + ret); + return nullptr; + } + if (desc.idVendor == 0x0e6f && desc.idProduct == 0x0241) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::BackendLibusb::CheckAndCreateDevice(): lego dimensions portal detected"); + } + auto device = std::make_shared(m_ctx, + desc.idVendor, + desc.idProduct, + 1, + 2, + 0, + libusb_get_bus_number(dev), + libusb_get_device_address(dev)); + // figure out device endpoints + if (!FindDefaultDeviceEndpoints(dev, + device->m_libusbHasEndpointIn, + device->m_libusbEndpointIn, + device->m_maxPacketSizeRX, + device->m_libusbHasEndpointOut, + device->m_libusbEndpointOut, + device->m_maxPacketSizeTX)) + { + // most likely couldn't read config descriptor + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::CheckAndCreateDevice(): failed to find default endpoints for device: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + return nullptr; + } + return device; + } + + bool BackendLibusb::FindDefaultDeviceEndpoints(libusb_device* dev, bool& endpointInFound, uint8& endpointIn, + uint16& endpointInMaxPacketSize, bool& endpointOutFound, + uint8& endpointOut, uint16& endpointOutMaxPacketSize) + { + endpointInFound = false; + endpointIn = 0; + endpointInMaxPacketSize = 0; + endpointOutFound = false; + endpointOut = 0; + endpointOutMaxPacketSize = 0; + + struct libusb_config_descriptor* conf = nullptr; + int ret = libusb_get_active_config_descriptor(dev, &conf); + + if (ret == 0) + { + for (uint8 interfaceIndex = 0; interfaceIndex < conf->bNumInterfaces; interfaceIndex++) + { + const struct libusb_interface& interface = conf->interface[interfaceIndex]; + for (int altsettingIndex = 0; altsettingIndex < interface.num_altsetting; altsettingIndex++) + { + const struct libusb_interface_descriptor& altsetting = interface.altsetting[altsettingIndex]; + for (uint8 endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++) + { + const struct libusb_endpoint_descriptor& endpoint = altsetting.endpoint[endpointIndex]; + // figure out direction + if ((endpoint.bEndpointAddress & (1 << 7)) != 0) + { + // in + if (!endpointInFound) + { + endpointInFound = true; + endpointIn = endpoint.bEndpointAddress; + endpointInMaxPacketSize = endpoint.wMaxPacketSize; + } + } + else + { + // out + if (!endpointOutFound) + { + endpointOutFound = true; + endpointOut = endpoint.bEndpointAddress; + endpointOutMaxPacketSize = endpoint.wMaxPacketSize; + } + } + } + } + } + libusb_free_config_descriptor(conf); + return true; + } + return false; + } + + DeviceLibusb::DeviceLibusb(libusb_context* ctx, + uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol, + uint8 libusbBusNumber, + uint8 libusbDeviceAddress) + : Device(vendorId, + productId, + interfaceIndex, + interfaceSubClass, + protocol), + m_ctx(ctx), + m_libusbHandle(nullptr), + m_handleInUseCounter(-1), + m_libusbBusNumber(libusbBusNumber), + m_libusbDeviceAddress(libusbDeviceAddress), + m_libusbHasEndpointIn(false), + m_libusbEndpointIn(0), + m_libusbHasEndpointOut(false), + m_libusbEndpointOut(0) + { + } + + DeviceLibusb::~DeviceLibusb() + { + CloseDevice(); + } + + bool DeviceLibusb::Open() + { + std::unique_lock lock(m_handleMutex); + if (IsOpened()) + { + return true; + } + // we may still be in the process of closing the device; wait for that to finish + while (m_handleInUseCounter != -1) + { + m_handleInUseCounterDecremented.wait(lock); + } + + libusb_device** devices; + ssize_t deviceCount = libusb_get_device_list(m_ctx, &devices); + if (deviceCount < 0) + { + cemuLog_log(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to get usb devices"); + return false; + } + libusb_device* dev; + libusb_device* found = nullptr; + for (int i = 0; (dev = devices[i]) != nullptr; i++) + { + struct libusb_device_descriptor desc; + int ret = libusb_get_device_descriptor(dev, &desc); + if (ret < 0) + { + cemuLog_log(LogType::Force, + "nsyshid::DeviceLibusb::open(): failed to get device descriptor; return code: %i", + ret); + libusb_free_device_list(devices, 1); + return false; + } + if (desc.idVendor == this->m_vendorId && + desc.idProduct == this->m_productId && + libusb_get_bus_number(dev) == this->m_libusbBusNumber && + libusb_get_device_address(dev) == this->m_libusbDeviceAddress) + { + // we found our device! + found = dev; + break; + } + } + + if (found != nullptr) + { + { + int ret = libusb_open(dev, &(this->m_libusbHandle)); + if (ret < 0) + { + this->m_libusbHandle = nullptr; + cemuLog_log(LogType::Force, + "nsyshid::DeviceLibusb::open(): failed to open device; return code: %i", + ret); + libusb_free_device_list(devices, 1); + return false; + } + this->m_handleInUseCounter = 0; + } + if (libusb_kernel_driver_active(this->m_libusbHandle, 0) == 1) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver active"); + if (libusb_detach_kernel_driver(this->m_libusbHandle, 0) == 0) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver detached"); + } + else + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): failed to detach kernel driver"); + } + } + { + int ret = libusb_claim_interface(this->m_libusbHandle, 0); + if (ret != 0) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): cannot claim interface"); + } + } + } + + libusb_free_device_list(devices, 1); + return found != nullptr; + } + + void DeviceLibusb::Close() + { + CloseDevice(); + } + + void DeviceLibusb::CloseDevice() + { + std::unique_lock lock(m_handleMutex); + if (IsOpened()) + { + auto handle = m_libusbHandle; + m_libusbHandle = nullptr; + while (m_handleInUseCounter > 0) + { + m_handleInUseCounterDecremented.wait(lock); + } + libusb_release_interface(handle, 0); + libusb_close(handle); + m_handleInUseCounter = -1; + m_handleInUseCounterDecremented.notify_all(); + } + } + + bool DeviceLibusb::IsOpened() + { + return m_libusbHandle != nullptr && m_handleInUseCounter >= 0; + } + + Device::ReadResult DeviceLibusb::Read(uint8* data, sint32 length, sint32& bytesRead) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::read(): cannot read from a non-opened device\n"); + return ReadResult::Error; + } + + const unsigned int timeout = 50; + int actualLength = 0; + int ret = 0; + do + { + ret = libusb_bulk_transfer(handleLock->GetHandle(), + this->m_libusbEndpointIn, + data, + length, + &actualLength, + timeout); + } + while (ret == LIBUSB_ERROR_TIMEOUT && actualLength == 0 && IsOpened()); + + if (ret == 0 || ret == LIBUSB_ERROR_TIMEOUT) + { + // success + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): read {} of {} bytes", + actualLength, + length); + bytesRead = actualLength; + return ReadResult::Success; + } + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::read(): failed with error code: {}", + ret); + return ReadResult::Error; + } + + Device::WriteResult DeviceLibusb::Write(uint8* data, sint32 length, sint32& bytesWritten) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::write(): cannot write to a non-opened device\n"); + return WriteResult::Error; + } + + bytesWritten = 0; + int actualLength = 0; + int ret = libusb_bulk_transfer(handleLock->GetHandle(), + this->m_libusbEndpointOut, + data, + length, + &actualLength, + 0); + + if (ret == 0) + { + // success + bytesWritten = actualLength; + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::write(): wrote {} of {} bytes", + bytesWritten, + length); + return WriteResult::Success; + } + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::write(): failed with error code: {}", + ret); + return WriteResult::Error; + } + + bool DeviceLibusb::GetDescriptor(uint8 descType, + uint8 descIndex, + uint8 lang, + uint8* output, + uint32 outputMaxLength) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::getDescriptor(): device is not opened"); + return false; + } + + if (descType == 0x02) + { + struct libusb_config_descriptor* conf = nullptr; + libusb_device* dev = libusb_get_device(handleLock->GetHandle()); + int ret = libusb_get_active_config_descriptor(dev, &conf); + + if (ret == 0) + { + std::vector configurationDescriptor(conf->wTotalLength); + uint8* currentWritePtr = &configurationDescriptor[0]; + + // configuration descriptor + cemu_assert_debug(conf->bLength == LIBUSB_DT_CONFIG_SIZE); + *(uint8*)(currentWritePtr + 0) = conf->bLength; // bLength + *(uint8*)(currentWritePtr + 1) = conf->bDescriptorType; // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = conf->wTotalLength; // wTotalLength + *(uint8*)(currentWritePtr + 4) = conf->bNumInterfaces; // bNumInterfaces + *(uint8*)(currentWritePtr + 5) = conf->bConfigurationValue; // bConfigurationValue + *(uint8*)(currentWritePtr + 6) = conf->iConfiguration; // iConfiguration + *(uint8*)(currentWritePtr + 7) = conf->bmAttributes; // bmAttributes + *(uint8*)(currentWritePtr + 8) = conf->MaxPower; // MaxPower + currentWritePtr = currentWritePtr + conf->bLength; + + for (uint8_t interfaceIndex = 0; interfaceIndex < conf->bNumInterfaces; interfaceIndex++) + { + const struct libusb_interface& interface = conf->interface[interfaceIndex]; + for (int altsettingIndex = 0; altsettingIndex < interface.num_altsetting; altsettingIndex++) + { + // interface descriptor + const struct libusb_interface_descriptor& altsetting = interface.altsetting[altsettingIndex]; + cemu_assert_debug(altsetting.bLength == LIBUSB_DT_INTERFACE_SIZE); + *(uint8*)(currentWritePtr + 0) = altsetting.bLength; // bLength + *(uint8*)(currentWritePtr + 1) = altsetting.bDescriptorType; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = altsetting.bInterfaceNumber; // bInterfaceNumber + *(uint8*)(currentWritePtr + 3) = altsetting.bAlternateSetting; // bAlternateSetting + *(uint8*)(currentWritePtr + 4) = altsetting.bNumEndpoints; // bNumEndpoints + *(uint8*)(currentWritePtr + 5) = altsetting.bInterfaceClass; // bInterfaceClass + *(uint8*)(currentWritePtr + 6) = altsetting.bInterfaceSubClass; // bInterfaceSubClass + *(uint8*)(currentWritePtr + 7) = altsetting.bInterfaceProtocol; // bInterfaceProtocol + *(uint8*)(currentWritePtr + 8) = altsetting.iInterface; // iInterface + currentWritePtr = currentWritePtr + altsetting.bLength; + + if (altsetting.extra_length > 0) + { + // unknown descriptors - copy the ones that we can identify ourselves + const unsigned char* extraReadPointer = altsetting.extra; + while (extraReadPointer - altsetting.extra < altsetting.extra_length) + { + uint8 bLength = *(uint8*)(extraReadPointer + 0); + if (bLength == 0) + { + // prevent endless loop + break; + } + if (extraReadPointer + bLength - altsetting.extra > altsetting.extra_length) + { + // prevent out of bounds read + break; + } + uint8 bDescriptorType = *(uint8*)(extraReadPointer + 1); + // HID descriptor + if (bDescriptorType == LIBUSB_DT_HID && bLength == 9) + { + *(uint8*)(currentWritePtr + 0) = + *(uint8*)(extraReadPointer + 0); // bLength + *(uint8*)(currentWritePtr + 1) = + *(uint8*)(extraReadPointer + 1); // bDescriptorType + *(uint16be*)(currentWritePtr + 2) = + *(uint16*)(extraReadPointer + 2); // bcdHID + *(uint8*)(currentWritePtr + 4) = + *(uint8*)(extraReadPointer + 4); // bCountryCode + *(uint8*)(currentWritePtr + 5) = + *(uint8*)(extraReadPointer + 5); // bNumDescriptors + *(uint8*)(currentWritePtr + 6) = + *(uint8*)(extraReadPointer + 6); // bDescriptorType + *(uint16be*)(currentWritePtr + 7) = + *(uint16*)(extraReadPointer + 7); // wDescriptorLength + currentWritePtr += bLength; + } + extraReadPointer += bLength; + } + } + + for (int endpointIndex = 0; endpointIndex < altsetting.bNumEndpoints; endpointIndex++) + { + // endpoint descriptor + const struct libusb_endpoint_descriptor& endpoint = altsetting.endpoint[endpointIndex]; + cemu_assert_debug(endpoint.bLength == LIBUSB_DT_ENDPOINT_SIZE || + endpoint.bLength == LIBUSB_DT_ENDPOINT_AUDIO_SIZE); + *(uint8*)(currentWritePtr + 0) = endpoint.bLength; + *(uint8*)(currentWritePtr + 1) = endpoint.bDescriptorType; + *(uint8*)(currentWritePtr + 2) = endpoint.bEndpointAddress; + *(uint8*)(currentWritePtr + 3) = endpoint.bmAttributes; + *(uint16be*)(currentWritePtr + 4) = endpoint.wMaxPacketSize; + *(uint8*)(currentWritePtr + 6) = endpoint.bInterval; + if (endpoint.bLength == LIBUSB_DT_ENDPOINT_AUDIO_SIZE) + { + *(uint8*)(currentWritePtr + 7) = endpoint.bRefresh; + *(uint8*)(currentWritePtr + 8) = endpoint.bSynchAddress; + } + currentWritePtr += endpoint.bLength; + } + } + } + uint32 bytesWritten = currentWritePtr - &configurationDescriptor[0]; + libusb_free_config_descriptor(conf); + cemu_assert_debug(bytesWritten <= conf->wTotalLength); + + memcpy(output, &configurationDescriptor[0], + std::min(outputMaxLength, bytesWritten)); + return true; + } + else + { + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::getDescriptor(): failed to get config descriptor with error code: {}", + ret); + return false; + } + } + else + { + cemu_assert_unimplemented(); + } + return false; + } + + bool DeviceLibusb::SetProtocol(uint32 ifIndex, uint32 protocol) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetProtocol(): device is not opened"); + return false; + } + + // ToDo: implement this +#if 0 + // is this correct? Discarding "ifIndex" seems like a bad idea + int ret = libusb_set_configuration(handleLock->getHandle(), protocol); + if (ret == 0) { + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::setProtocol(): success"); + return true; + } + cemuLog_logDebug(LogType::Force, + "nsyshid::DeviceLibusb::setProtocol(): failed with error code: {}", + ret); + return false; +#endif + + // pretend that everything is fine + return true; + } + + bool DeviceLibusb::SetReport(uint8* reportData, sint32 length, uint8* originalData, + sint32 originalLength) + { + auto handleLock = AquireHandleLock(); + if (!handleLock->IsValid()) + { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::SetReport(): device is not opened"); + return false; + } + + // ToDo: implement this +#if 0 + // not sure if libusb_control_transfer() is the right candidate for this + int ret = libusb_control_transfer(handleLock->getHandle(), + bmRequestType, + bRequest, + wValue, + wIndex, + reportData, + length, + timeout); +#endif + + // pretend that everything is fine + return true; + } + + std::unique_ptr DeviceLibusb::AquireHandleLock() + { + return std::make_unique(&m_libusbHandle, + m_handleMutex, + m_handleInUseCounter, + m_handleInUseCounterDecremented, + *this); + } + + DeviceLibusb::HandleLock::HandleLock(libusb_device_handle** handle, + std::mutex& handleMutex, + std::atomic& handleInUseCounter, + std::condition_variable& handleInUseCounterDecremented, + DeviceLibusb& device) + : m_handle(nullptr), + m_handleMutex(handleMutex), + m_handleInUseCounter(handleInUseCounter), + m_handleInUseCounterDecremented(handleInUseCounterDecremented) + { + std::lock_guard lock(handleMutex); + if (device.IsOpened() && handle != nullptr && handleInUseCounter >= 0) + { + this->m_handle = *handle; + this->m_handleInUseCounter++; + } + } + + DeviceLibusb::HandleLock::~HandleLock() + { + if (IsValid()) + { + std::lock_guard lock(m_handleMutex); + m_handleInUseCounter--; + m_handleInUseCounterDecremented.notify_all(); + } + } + + bool DeviceLibusb::HandleLock::IsValid() + { + return m_handle != nullptr; + } + + libusb_device_handle* DeviceLibusb::HandleLock::GetHandle() + { + return m_handle; + } +} // namespace nsyshid::backend::libusb + +#endif // NSYSHID_ENABLE_BACKEND_LIBUSB diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.h b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h new file mode 100644 index 00000000..216be6ce --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -0,0 +1,129 @@ +#ifndef CEMU_NSYSHID_BACKEND_LIBUSB_H +#define CEMU_NSYSHID_BACKEND_LIBUSB_H + +#include "nsyshid.h" + +#if NSYSHID_ENABLE_BACKEND_LIBUSB + +#include +#include "Backend.h" + +namespace nsyshid::backend::libusb +{ + class BackendLibusb : public nsyshid::Backend { + public: + BackendLibusb(); + + ~BackendLibusb(); + + bool IsInitialisedOk() override; + + protected: + void AttachVisibleDevices() override; + + private: + libusb_context* m_ctx; + int m_initReturnCode; + bool m_callbackRegistered; + libusb_hotplug_callback_handle m_hotplugCallbackHandle; + std::thread m_hotplugThread; + std::atomic m_hotplugThreadStop; + + // called by libusb + static int HotplugCallback(libusb_context* ctx, libusb_device* dev, + libusb_hotplug_event event, void* user_data); + + int OnHotplug(libusb_device* dev, libusb_hotplug_event event); + + std::shared_ptr CheckAndCreateDevice(libusb_device* dev); + + std::shared_ptr FindLibusbDevice(libusb_device* dev); + + bool FindDefaultDeviceEndpoints(libusb_device* dev, + bool& endpointInFound, uint8& endpointIn, uint16& endpointInMaxPacketSize, + bool& endpointOutFound, uint8& endpointOut, uint16& endpointOutMaxPacketSize); + }; + + class DeviceLibusb : public nsyshid::Device { + public: + DeviceLibusb(libusb_context* ctx, + uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol, + uint8 libusbBusNumber, + uint8 libusbDeviceAddress); + + ~DeviceLibusb() override; + + 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 ifIndex, uint32 protocol) override; + + bool SetReport(uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength) override; + + uint8 m_libusbBusNumber; + uint8 m_libusbDeviceAddress; + bool m_libusbHasEndpointIn; + uint8 m_libusbEndpointIn; + bool m_libusbHasEndpointOut; + uint8 m_libusbEndpointOut; + + private: + void CloseDevice(); + + libusb_context* m_ctx; + std::mutex m_handleMutex; + std::atomic m_handleInUseCounter; + std::condition_variable m_handleInUseCounterDecremented; + libusb_device_handle* m_libusbHandle; + + class HandleLock { + public: + HandleLock() = delete; + + HandleLock(libusb_device_handle** handle, + std::mutex& handleMutex, + std::atomic& handleInUseCounter, + std::condition_variable& handleInUseCounterDecremented, + DeviceLibusb& device); + + ~HandleLock(); + + HandleLock(const HandleLock&) = delete; + + HandleLock& operator=(const HandleLock&) = delete; + + bool IsValid(); + + libusb_device_handle* GetHandle(); + + private: + libusb_device_handle* m_handle; + std::mutex& m_handleMutex; + std::atomic& m_handleInUseCounter; + std::condition_variable& m_handleInUseCounterDecremented; + }; + + std::unique_ptr AquireHandleLock(); + }; +} // namespace nsyshid::backend::libusb + +#endif // NSYSHID_ENABLE_BACKEND_LIBUSB + +#endif // CEMU_NSYSHID_BACKEND_LIBUSB_H diff --git a/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp new file mode 100644 index 00000000..520a0d31 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp @@ -0,0 +1,454 @@ +#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->m_vendorId, device->m_productId)) + { + if (!AttachDevice(device)) + { + cemuLog_log(LogType::Force, + "nsyshid::BackendWindowsHID: failed to attach device: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + } + } + else + { + cemuLog_log(LogType::Force, + "nsyshid::BackendWindowsHID: device not on whitelist: {:04x}:{:04x}", + device->m_vendorId, + device->m_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->m_maxPacketSizeRX = maxPacketInputLength; + device->m_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), + m_devicePath(devicePath), + m_hFile(INVALID_HANDLE_VALUE) + { + } + + DeviceWindowsHID::~DeviceWindowsHID() + { + if (m_hFile != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hFile); + m_hFile = INVALID_HANDLE_VALUE; + } + } + + bool DeviceWindowsHID::Open() + { + if (IsOpened()) + { + return true; + } + m_hFile = OpenDevice(m_devicePath); + if (m_hFile == INVALID_HANDLE_VALUE) + { + return false; + } + HidD_SetNumInputBuffers(m_hFile, 2); // don't cache too many reports + return true; + } + + void DeviceWindowsHID::Close() + { + if (m_hFile != INVALID_HANDLE_VALUE) + { + CloseHandle(m_hFile); + m_hFile = INVALID_HANDLE_VALUE; + } + } + + bool DeviceWindowsHID::IsOpened() + { + return m_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->m_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->m_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->m_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->m_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 + 2) = 0x81; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = + this->m_maxPacketSizeRX; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 0x01; // bInterval + currentWritePtr = currentWritePtr + 7; + // endpoint descriptor 2 + *(uint8*)(currentWritePtr + 0) = 7; // bLength + *(uint8*)(currentWritePtr + 1) = 0x05; // bDescriptorType + *(uint8*)(currentWritePtr + 2) = 0x02; // bEndpointAddress + *(uint8*)(currentWritePtr + 3) = 0x03; // bmAttributes + *(uint16be*)(currentWritePtr + 4) = + this->m_maxPacketSizeTX; // wMaxPacketSize + *(uint8*)(currentWritePtr + 6) = 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->m_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); + } +} // namespace nsyshid::backend::windows + +#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 00000000..049b33e4 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h @@ -0,0 +1,66 @@ +#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* m_devicePath; + HANDLE m_hFile; + }; + + HANDLE OpenDevice(wchar_t* devicePath); + + void _debugPrintHex(std::string prefix, uint8* data, size_t len); +} // namespace nsyshid::backend::windows + +#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID + +#endif // CEMU_NSYSHID_BACKEND_WINDOWS_HID_H diff --git a/src/Cafe/OS/libs/nsyshid/Whitelist.cpp b/src/Cafe/OS/libs/nsyshid/Whitelist.cpp new file mode 100644 index 00000000..f20e4c45 --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Whitelist.cpp @@ -0,0 +1,53 @@ +#include "Whitelist.h" + +namespace nsyshid +{ + Whitelist& Whitelist::GetInstance() + { + static Whitelist whitelist; + return whitelist; + } + + Whitelist::Whitelist() + { + // add known devices + { + // lego dimensions portal + m_devices.emplace_back(0x0e6f, 0x0241); + // skylanders portal + m_devices.emplace_back(0x1430, 0x0150); + // disney infinity base + m_devices.emplace_back(0x0e6f, 0x0129); + } + } + + bool Whitelist::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) + { + auto it = std::find(m_devices.begin(), m_devices.end(), + std::tuple(vendorId, productId)); + return it != m_devices.end(); + } + + void Whitelist::AddDevice(uint16 vendorId, uint16 productId) + { + if (!IsDeviceWhitelisted(vendorId, productId)) + { + m_devices.emplace_back(vendorId, productId); + } + } + + void Whitelist::RemoveDevice(uint16 vendorId, uint16 productId) + { + m_devices.remove(std::tuple(vendorId, productId)); + } + + std::list> Whitelist::GetDevices() + { + return m_devices; + } + + void Whitelist::RemoveAllDevices() + { + m_devices.clear(); + } +} // namespace nsyshid diff --git a/src/Cafe/OS/libs/nsyshid/Whitelist.h b/src/Cafe/OS/libs/nsyshid/Whitelist.h new file mode 100644 index 00000000..73b7742b --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/Whitelist.h @@ -0,0 +1,32 @@ +#ifndef CEMU_NSYSHID_WHITELIST_H +#define CEMU_NSYSHID_WHITELIST_H + +namespace nsyshid +{ + class Whitelist { + public: + static Whitelist& GetInstance(); + + Whitelist(const Whitelist&) = delete; + + Whitelist& operator=(const Whitelist&) = delete; + + bool IsDeviceWhitelisted(uint16 vendorId, uint16 productId); + + void AddDevice(uint16 vendorId, uint16 productId); + + void RemoveDevice(uint16 vendorId, uint16 productId); + + std::list> GetDevices(); + + void RemoveAllDevices(); + + private: + Whitelist(); + + // vendorId, productId + std::list> m_devices; + }; +} // namespace nsyshid + +#endif // CEMU_NSYSHID_WHITELIST_H diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp index 9b9d2d61..b21e2a43 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.cpp +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.cpp @@ -1,289 +1,259 @@ #include "Cafe/OS/common/OSCommon.h" #include "Cafe/HW/Espresso/PPCCallback.h" #include +#include #include "nsyshid.h" - -#if BOOST_OS_WINDOWS - -#include -#include -#include - #include "Cafe/OS/libs/coreinit/coreinit_Thread.h" - -#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); +#include "Backend.h" +#include "Whitelist.h" namespace nsyshid { - typedef struct - { - /* +0x00 */ uint32be handle; - /* +0x04 */ uint32 ukn04; - /* +0x08 */ uint16 vendorId; // little-endian ? - /* +0x0A */ uint16 productId; // little-endian ? - /* +0x0C */ uint8 ifIndex; - /* +0x0D */ uint8 subClass; - /* +0x0E */ uint8 protocol; - /* +0x0F */ uint8 paddingGuessed0F; - /* +0x10 */ uint16be maxPacketSizeRX; - /* +0x12 */ uint16be maxPacketSizeTX; - }HIDDevice_t; - - static_assert(offsetof(HIDDevice_t, vendorId) == 0x8, ""); - static_assert(offsetof(HIDDevice_t, productId) == 0xA, ""); - static_assert(offsetof(HIDDevice_t, ifIndex) == 0xC, ""); - static_assert(offsetof(HIDDevice_t, protocol) == 0xE, ""); - - typedef struct _HIDDeviceInfo_t - { - uint32 handle; - uint32 physicalDeviceInstance; - uint16 vendorId; - uint16 productId; - uint8 interfaceIndex; - uint8 interfaceSubClass; - uint8 protocol; - HIDDevice_t* hidDevice; // this info is passed to applications and must remain intact - wchar_t* devicePath; - _HIDDeviceInfo_t* next; - // host - HANDLE hFile; - }HIDDeviceInfo_t; - - HIDDeviceInfo_t* firstDevice = nullptr; + std::list> backendList; + std::list> deviceList; typedef struct _HIDClient_t { - MEMPTR<_HIDClient_t> next; uint32be callbackFunc; // attach/detach callback - }HIDClient_t; + } HIDClient_t; - HIDClient_t* firstHIDClient = nullptr; + std::list HIDClientList; - 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 attachClientToList(HIDClient_t* hidClient) + std::recursive_mutex hidMutex; + + void AttachClientToList(HIDClient_t* hidClient) { + std::lock_guard lock(hidMutex); // todo - append at the beginning or end of the list? List order matters because it also controls the order in which attach callbacks are called - if (firstHIDClient) - { - hidClient->next = firstHIDClient; - firstHIDClient = hidClient; - } - else - { - hidClient->next = nullptr; - firstHIDClient = hidClient; - } + HIDClientList.push_front(hidClient); } - void attachDeviceToList(HIDDeviceInfo_t* hidDeviceInfo) + void DetachClientFromList(HIDClient_t* hidClient) { - if (firstDevice) - { - hidDeviceInfo->next = firstDevice; - firstDevice = hidDeviceInfo; - } - else - { - hidDeviceInfo->next = nullptr; - firstDevice = hidDeviceInfo; - } + std::lock_guard lock(hidMutex); + HIDClientList.remove(hidClient); } - HIDDeviceInfo_t* getHIDDeviceInfoByHandle(uint32 handle, bool openFileHandle = false) + std::shared_ptr GetDeviceByHandle(uint32 handle, bool openIfClosed = false) { - HIDDeviceInfo_t* deviceItr = firstDevice; - while (deviceItr) + std::shared_ptr device; { - if (deviceItr->handle == handle) + std::lock_guard lock(hidMutex); + for (const auto& d : deviceList) { - if (openFileHandle && deviceItr->hFile == INVALID_HANDLE_VALUE) + if (d->m_hid->handle == handle) { - deviceItr->hFile = openDevice(deviceItr->devicePath); - if (deviceItr->hFile == INVALID_HANDLE_VALUE) - { - cemuLog_log(LogType::Force, "HID: Failed to open device \"{}\"", boost::nowide::narrow(std::wstring(deviceItr->devicePath))); - return nullptr; - } - HidD_SetNumInputBuffers(deviceItr->hFile, 2); // dont cache too many reports + device = d; + break; } - return deviceItr; } - deviceItr = deviceItr->next; + } + if (device != nullptr) + { + if (openIfClosed && !device->IsOpened()) + { + if (!device->Open()) + { + return nullptr; + } + } + return device; } return nullptr; } uint32 _lastGeneratedHidHandle = 1; - uint32 generateHIDHandle() + uint32 GenerateHIDHandle() { + std::lock_guard lock(hidMutex); _lastGeneratedHidHandle++; return _lastGeneratedHidHandle; } const int HID_MAX_NUM_DEVICES = 128; - SysAllocator _devicePool; - std::bitset _devicePoolMask; + SysAllocator HIDPool; + std::queue HIDPoolIndexQueue; - HIDDevice_t* getFreeDevice() + void InitHIDPoolIndexQueue() { - for (sint32 i = 0; i < HID_MAX_NUM_DEVICES; i++) + static bool HIDPoolIndexQueueInitialized = false; + std::lock_guard lock(hidMutex); + if (HIDPoolIndexQueueInitialized) { - if (_devicePoolMask.test(i) == false) - { - _devicePoolMask.set(i); - return _devicePool.GetPtr() + i; - } + return; + } + HIDPoolIndexQueueInitialized = true; + for (size_t i = 0; i < HID_MAX_NUM_DEVICES; i++) + { + HIDPoolIndexQueue.push(i); } - return nullptr; } - void checkAndAddDevice(wchar_t* devicePath, HANDLE hDevice) + HID_t* GetFreeHID() { - HIDD_ATTRIBUTES hidAttr; - hidAttr.Size = sizeof(HIDD_ATTRIBUTES); - if (HidD_GetAttributes(hDevice, &hidAttr) == FALSE) - return; - HIDDevice_t* hidDevice = getFreeDevice(); - if (hidDevice == nullptr) + std::lock_guard lock(hidMutex); + InitHIDPoolIndexQueue(); + if (HIDPoolIndexQueue.empty()) { - cemuLog_log(LogType::Force, "HID: Maximum number of supported devices exceeded"); - return; + return nullptr; } - - HIDDeviceInfo_t* deviceInfo = (HIDDeviceInfo_t*)malloc(sizeof(HIDDeviceInfo_t)); - memset(deviceInfo, 0, sizeof(HIDDeviceInfo_t)); - deviceInfo->devicePath = _wcsdup(devicePath); - deviceInfo->vendorId = hidAttr.VendorID; - deviceInfo->productId = hidAttr.ProductID; - deviceInfo->hFile = INVALID_HANDLE_VALUE; - // generate handle - deviceInfo->handle = generateHIDHandle(); - // 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; - } - // setup HIDDevice struct - deviceInfo->hidDevice = hidDevice; - memset(hidDevice, 0, sizeof(HIDDevice_t)); - hidDevice->handle = deviceInfo->handle; - hidDevice->vendorId = deviceInfo->vendorId; - hidDevice->productId = deviceInfo->productId; - hidDevice->maxPacketSizeRX = maxPacketInputLength; - hidDevice->maxPacketSizeTX = maxPacketOutputLength; - - hidDevice->ukn04 = 0x11223344; - - hidDevice->ifIndex = 1; - hidDevice->protocol = 0; - hidDevice->subClass = 2; - - // todo - other values - //hidDevice->ifIndex = 1; - - - attachDeviceToList(deviceInfo); - + size_t index = HIDPoolIndexQueue.front(); + HIDPoolIndexQueue.pop(); + return HIDPool.GetPtr() + index; } - void initDeviceList() + void ReleaseHID(HID_t* device) { - if (firstDevice) - return; - 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) + // this should never happen, but having a safeguard can't hurt + if (device == nullptr) { - 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) - { - checkAndAddDevice(DevIntfDetailData->DevicePath, hHIDDevice); - CloseHandle(hHIDDevice); - } - } - HeapFree(GetProcessHeap(), 0, DevIntfDetailData); - // next - SetupDiEnumDeviceInterfaces(hDevInfo, NULL, &GUID_DEVINTERFACE_HID, ++dwMemberIdx, &DevIntfData); - } - SetupDiDestroyDeviceInfoList(hDevInfo); + cemu_assert_error(); } + std::lock_guard lock(hidMutex); + InitHIDPoolIndexQueue(); + size_t index = device - HIDPool.GetPtr(); + HIDPoolIndexQueue.push(index); } const int HID_CALLBACK_DETACH = 0; const int HID_CALLBACK_ATTACH = 1; - uint32 doAttachCallback(HIDClient_t* hidClient, HIDDeviceInfo_t* deviceInfo) + uint32 DoAttachCallback(HIDClient_t* hidClient, const std::shared_ptr& device) { - return PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(deviceInfo->hidDevice), HID_CALLBACK_ATTACH); + return PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), + memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_ATTACH); } - void doDetachCallback(HIDClient_t* hidClient, HIDDeviceInfo_t* deviceInfo) + void DoAttachCallbackAsync(HIDClient_t* hidClient, const std::shared_ptr& device) { - PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), memory_getVirtualOffsetFromPointer(deviceInfo->hidDevice), HID_CALLBACK_DETACH); + coreinitAsyncCallback_add(hidClient->callbackFunc, 3, memory_getVirtualOffsetFromPointer(hidClient), + memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_ATTACH); + } + + void DoDetachCallback(HIDClient_t* hidClient, const std::shared_ptr& device) + { + PPCCoreCallback(hidClient->callbackFunc, memory_getVirtualOffsetFromPointer(hidClient), + memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_DETACH); + } + + void DoDetachCallbackAsync(HIDClient_t* hidClient, const std::shared_ptr& device) + { + coreinitAsyncCallback_add(hidClient->callbackFunc, 3, memory_getVirtualOffsetFromPointer(hidClient), + memory_getVirtualOffsetFromPointer(device->m_hid), HID_CALLBACK_DETACH); + } + + void AttachBackend(const std::shared_ptr& backend) + { + { + std::lock_guard lock(hidMutex); + backendList.push_back(backend); + } + backend->OnAttach(); + } + + void DetachBackend(const std::shared_ptr& backend) + { + { + std::lock_guard lock(hidMutex); + backendList.remove(backend); + } + backend->OnDetach(); + } + + void DetachAllBackends() + { + std::list> backendListCopy; + { + std::lock_guard lock(hidMutex); + backendListCopy = backendList; + backendList.clear(); + } + for (const auto& backend : backendListCopy) + { + backend->OnDetach(); + } + } + + void AttachDefaultBackends() + { + backend::AttachDefaultBackends(); + } + + bool AttachDevice(const std::shared_ptr& device) + { + std::lock_guard lock(hidMutex); + + // is the device already attached? + { + auto it = std::find(deviceList.begin(), deviceList.end(), device); + if (it != deviceList.end()) + { + cemuLog_logDebug(LogType::Force, + "nsyshid.AttachDevice(): failed to attach device: {:04x}:{:04x}: already attached", + device->m_vendorId, + device->m_productId); + return false; + } + } + + HID_t* hidDevice = GetFreeHID(); + if (hidDevice == nullptr) + { + cemuLog_logDebug(LogType::Force, + "nsyshid.AttachDevice(): failed to attach device: {:04x}:{:04x}: no free device slots left", + device->m_vendorId, + device->m_productId); + return false; + } + hidDevice->handle = GenerateHIDHandle(); + device->AssignHID(hidDevice); + deviceList.push_back(device); + + // do attach callbacks + for (auto client : HIDClientList) + { + DoAttachCallbackAsync(client, device); + } + + cemuLog_logDebug(LogType::Force, "nsyshid.AttachDevice(): device attached: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + return true; + } + + void DetachDevice(const std::shared_ptr& device) + { + { + std::lock_guard lock(hidMutex); + + // remove from list + auto it = std::find(deviceList.begin(), deviceList.end(), device); + if (it == deviceList.end()) + { + cemuLog_logDebug(LogType::Force, "nsyshid.DetachDevice(): device not found: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); + return; + } + deviceList.erase(it); + + // do detach callbacks + for (auto client : HIDClientList) + { + DoDetachCallbackAsync(client, device); + } + ReleaseHID(device->m_hid); + } + + device->Close(); + + cemuLog_logDebug(LogType::Force, "nsyshid.DetachDevice(): device removed: {:04x}:{:04x}", + device->m_vendorId, + device->m_productId); } void export_HIDAddClient(PPCInterpreter_t* hCPU) @@ -292,15 +262,14 @@ namespace nsyshid ppcDefineParamMPTR(callbackFuncMPTR, 1); cemuLog_logDebug(LogType::Force, "nsyshid.HIDAddClient(0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4]); hidClient->callbackFunc = callbackFuncMPTR; - attachClientToList(hidClient); - initDeviceList(); + + std::lock_guard lock(hidMutex); + AttachClientToList(hidClient); + // do attach callbacks - HIDDeviceInfo_t* deviceItr = firstDevice; - while (deviceItr) + for (const auto& device : deviceList) { - if (doAttachCallback(hidClient, deviceItr) != 0) - break; - deviceItr = deviceItr->next; + DoAttachCallback(hidClient, device); } osLib_returnFromFunction(hCPU, 0); @@ -310,14 +279,14 @@ namespace nsyshid { ppcDefineParamTypePtr(hidClient, HIDClient_t, 0); cemuLog_logDebug(LogType::Force, "nsyshid.HIDDelClient(0x{:08x})", hCPU->gpr[3]); - - // todo + + std::lock_guard lock(hidMutex); + DetachClientFromList(hidClient); + // do detach callbacks - HIDDeviceInfo_t* deviceItr = firstDevice; - while (deviceItr) + for (const auto& device : deviceList) { - doDetachCallback(hidClient, deviceItr); - deviceItr = deviceItr->next; + DoDetachCallback(hidClient, device); } osLib_returnFromFunction(hCPU, 0); @@ -325,127 +294,68 @@ namespace nsyshid void export_HIDGetDescriptor(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU8(descType, 1); // r4 - ppcDefineParamU8(descIndex, 2); // r5 - ppcDefineParamU8(lang, 3); // r6 - ppcDefineParamUStr(output, 4); // r7 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU8(descType, 1); // r4 + ppcDefineParamU8(descIndex, 2); // r5 + ppcDefineParamU8(lang, 3); // r6 + ppcDefineParamUStr(output, 4); // r7 ppcDefineParamU32(outputMaxLength, 5); // r8 - ppcDefineParamMPTR(cbFuncMPTR, 6); // r9 - ppcDefineParamMPTR(cbParamMPTR, 7); // r10 + ppcDefineParamMPTR(cbFuncMPTR, 6); // r9 + ppcDefineParamMPTR(cbParamMPTR, 7); // r10 - HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle); - if (hidDeviceInfo) + int returnValue = -1; + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + if (device) { - HANDLE hHIDDevice = openDevice(hidDeviceInfo->devicePath); - if (hHIDDevice != INVALID_HANDLE_VALUE) + memset(output, 0, outputMaxLength); + if (device->GetDescriptor(descType, descIndex, lang, output, outputMaxLength)) { - 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) = 0x40; // 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) = 0x40; // wMaxPacketSize - *(uint8*)(currentWritePtr + 5) = 0x01; // bInterval - currentWritePtr = currentWritePtr + 7; - - cemu_assert_debug((currentWritePtr - configurationDescriptor) == 0x29); - - memcpy(output, configurationDescriptor, std::min(outputMaxLength, sizeof(configurationDescriptor))); - } - else - { - cemu_assert_unimplemented(); - } - CloseHandle(hHIDDevice); + returnValue = 0; } else { - cemu_assert_unimplemented(); + returnValue = -1; } } else { cemu_assert_suspicious(); } - osLib_returnFromFunction(hCPU, 0); + osLib_returnFromFunction(hCPU, returnValue); } void _debugPrintHex(std::string prefix, uint8* data, size_t len) { - char debugOutput[1024] = { 0 }; + 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); } - void doHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode, MPTR buffer, sint32 length) + void DoHIDTransferCallback(MPTR callbackFuncMPTR, MPTR callbackParamMPTR, uint32 hidHandle, uint32 errorCode, + MPTR buffer, sint32 length) { coreinitAsyncCallback_add(callbackFuncMPTR, 5, hidHandle, errorCode, buffer, length, callbackParamMPTR); } void export_HIDSetIdle(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(ifIndex, 1); // r4 - ppcDefineParamU32(ukn, 2); // r5 - ppcDefineParamU32(duration, 3); // r6 - ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU32(ifIndex, 1); // r4 + ppcDefineParamU32(ukn, 2); // r5 + ppcDefineParamU32(duration, 3); // r6 + ppcDefineParamMPTR(callbackFuncMPTR, 4); // r7 ppcDefineParamMPTR(callbackParamMPTR, 5); // r8 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetIdle(...)"); // todo if (callbackFuncMPTR) { - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); } else { @@ -456,67 +366,78 @@ namespace nsyshid void export_HIDSetProtocol(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(ifIndex, 1); // r4 - ppcDefineParamU32(protocol, 2); // r5 - ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU32(ifIndex, 1); // r4 + ppcDefineParamU32(protocol, 2); // r5 + ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(...)"); - - if (callbackFuncMPTR) + + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + sint32 returnCode = -1; + if (device) { - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + if (!device->IsOpened()) + { + cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetProtocol(): error: device is not opened"); + } + else + { + if (device->SetProtocol(ifIndex, protocol)) + { + returnCode = 0; + } + } } else { - cemu_assert_unimplemented(); + cemu_assert_suspicious(); } - osLib_returnFromFunction(hCPU, 0); // for non-async version, return number of bytes transferred + + if (callbackFuncMPTR) + { + DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidHandle, 0, MPTR_NULL, 0); + } + osLib_returnFromFunction(hCPU, returnCode); } // handler for async HIDSetReport transfers - void _hidSetReportAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + void _hidSetReportAsync(std::shared_ptr device, uint8* reportData, sint32 length, + uint8* originalData, + sint32 originalLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) { - sint32 retryCount = 0; - while (true) + cemuLog_logDebug(LogType::Force, "_hidSetReportAsync begin"); + if (device->SetReport(reportData, length, originalData, originalLength)) { - BOOL r = HidD_SetOutputReport(hidDeviceInfo->hFile, reportData, length); - if (r != FALSE) - break; - Sleep(20); // retry - retryCount++; - if (retryCount >= 40) - { - cemuLog_log(LogType::Force, "HID async SetReport failed"); - sint32 errorCode = -1; - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(originalData), 0); - free(reportData); - return; - } + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + 0, + memory_getVirtualOffsetFromPointer(originalData), + originalLength); + } + else + { + DoHIDTransferCallback(callbackFuncMPTR, + callbackParamMPTR, + device->m_hid->handle, + -1, + memory_getVirtualOffsetFromPointer(originalData), + 0); } - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, 0, memory_getVirtualOffsetFromPointer(originalData), originalLength); free(reportData); } // handler for synchronous HIDSetReport transfers - sint32 _hidSetReportSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* reportData, sint32 length, uint8* originalData, sint32 originalLength, OSThread_t* osThread) + sint32 _hidSetReportSync(std::shared_ptr device, uint8* reportData, sint32 length, + uint8* originalData, + sint32 originalLength, OSThread_t* osThread) { - //cemuLog_logDebug(LogType::Force, "_hidSetReportSync begin"); _debugPrintHex("_hidSetReportSync Begin", reportData, length); - sint32 retryCount = 0; sint32 returnCode = 0; - while (true) + if (device->SetReport(reportData, length, originalData, originalLength)) { - BOOL r = HidD_SetOutputReport(hidDeviceInfo->hFile, reportData, length); - if (r != FALSE) - { - returnCode = originalLength; - break; - } - Sleep(100); // retry - retryCount++; - if (retryCount >= 10) - assert_dbg(); + returnCode = originalLength; } free(reportData); cemuLog_logDebug(LogType::Force, "_hidSetReportSync end. returnCode: {}", returnCode); @@ -526,14 +447,15 @@ namespace nsyshid void export_HIDSetReport(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamU32(reportRelatedUkn, 1); // r4 - ppcDefineParamU32(reportId, 2); // r5 - ppcDefineParamUStr(data, 3); // r6 - ppcDefineParamU32(dataLength, 4); // r7 - ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamU32(reportRelatedUkn, 1); // r4 + ppcDefineParamU32(reportId, 2); // r5 + ppcDefineParamUStr(data, 3); // r6 + ppcDefineParamU32(dataLength, 4); // r7 + ppcDefineParamMPTR(callbackFuncMPTR, 5); // r8 ppcDefineParamMPTR(callbackParamMPTR, 6); // r9 - cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport({},0x{:02x},0x{:02x},...)", hidHandle, reportRelatedUkn, reportId); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDSetReport({},0x{:02x},0x{:02x},...)", hidHandle, reportRelatedUkn, + reportId); _debugPrintHex("HIDSetReport", data, dataLength); @@ -542,8 +464,8 @@ namespace nsyshid assert_dbg(); #endif - HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true); - if (hidDeviceInfo == nullptr) + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDSetReport(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); @@ -552,19 +474,20 @@ namespace nsyshid // prepare report data // note: Currently we need to pad the data to 0x20 bytes for it to work (plus one extra byte for HidD_SetOutputReport) - // Does IOSU pad data to 0x20 byte? Also check if this is specific to Skylanders portal - sint32 paddedLength = (dataLength +0x1F)&~0x1F; - uint8* reportData = (uint8*)malloc(paddedLength+1); - memset(reportData, 0, paddedLength+1); + // Does IOSU pad data to 0x20 byte? Also check if this is specific to Skylanders portal + sint32 paddedLength = (dataLength + 0x1F) & ~0x1F; + uint8* reportData = (uint8*)malloc(paddedLength + 1); + memset(reportData, 0, paddedLength + 1); reportData[0] = 0; memcpy(reportData + 1, data, dataLength); - // issue request (synchronous or asynchronous) sint32 returnCode = 0; if (callbackFuncMPTR == MPTR_NULL) { - std::future res = std::async(std::launch::async, &_hidSetReportSync, hidDeviceInfo, reportData, paddedLength + 1, data, dataLength, coreinitThread_getCurrentThreadDepr(hCPU)); + std::future res = std::async(std::launch::async, &_hidSetReportSync, device, reportData, + paddedLength + 1, data, dataLength, + coreinitThread_getCurrentThreadDepr(hCPU)); coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); PPCCore_switchToScheduler(); returnCode = res.get(); @@ -572,110 +495,88 @@ namespace nsyshid else { // asynchronous - std::thread(&_hidSetReportAsync, hidDeviceInfo, reportData, paddedLength+1, data, dataLength, callbackFuncMPTR, callbackParamMPTR).detach(); + std::thread(&_hidSetReportAsync, device, reportData, paddedLength + 1, data, dataLength, + callbackFuncMPTR, callbackParamMPTR) + .detach(); returnCode = 0; } osLib_returnFromFunction(hCPU, returnCode); } - sint32 _hidReadInternalSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength) + sint32 _hidReadInternalSync(std::shared_ptr device, uint8* data, sint32 maxLength) { - DWORD bt; - OVERLAPPED ovlp = { 0 }; - ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - uint8* tempBuffer = (uint8*)malloc(maxLength + 1); - sint32 transferLength = 0; // minus report byte - - _debugPrintHex("HID_READ_BEFORE", data, maxLength); - cemuLog_logDebug(LogType::Force, "HidRead Begin (Length 0x{:08x})", maxLength); - BOOL readResult = ReadFile(hidDeviceInfo->hFile, tempBuffer, maxLength + 1, &bt, &ovlp); - if (readResult != FALSE) + if (!device->IsOpened()) { - // 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); + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): cannot read from a non-opened device"); + return -1; } - else + memset(data, 0, maxLength); + + sint32 bytesRead = 0; + Device::ReadResult readResult = device->Read(data, maxLength, bytesRead); + switch (readResult) { - // wait for result - cemuLog_logDebug(LogType::Force, "HidRead WaitForResult (error 0x{:08x})", GetLastError()); - // async hid read is never supposed to return unless there is an 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 -108; - } - - - cemuLog_logDebug(LogType::Force, "HidRead WaitHalfComplete"); - GetOverlappedResult(hidDeviceInfo->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; - if (bt != 0) + case Device::ReadResult::Success: { - 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); - - returnCode = transferLength; + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read {} of {} bytes", + bytesRead, + maxLength); + return bytesRead; } - else + break; + case Device::ReadResult::Error: { - cemuLog_log(LogType::Force, "Failed HID read"); - returnCode = -1; + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read error"); + return -1; } - free(tempBuffer); - CloseHandle(ovlp.hEvent); - return returnCode; + break; + case Device::ReadResult::ErrorTimeout: + { + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read error: timeout"); + return -108; + } + break; + } + cemuLog_logDebug(LogType::Force, "nsyshid.hidReadInternalSync(): read error: unknown"); + return -1; } - void _hidReadAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + void _hidReadAsync(std::shared_ptr device, + uint8* data, sint32 maxLength, + MPTR callbackFuncMPTR, + MPTR callbackParamMPTR) { - sint32 returnCode = _hidReadInternalSync(hidDeviceInfo, data, maxLength); + sint32 returnCode = _hidReadInternalSync(device, data, maxLength); sint32 errorCode = 0; if (returnCode < 0) - errorCode = returnCode; // dont return number of bytes in error code - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode>0)?returnCode:0); + errorCode = returnCode; // don't return number of bytes in error code + DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, errorCode, + memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0); } - sint32 _hidReadSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, OSThread_t* osThread) + sint32 _hidReadSync(std::shared_ptr device, + uint8* data, + sint32 maxLength, + OSThread_t* osThread) { - sint32 returnCode = _hidReadInternalSync(hidDeviceInfo, data, maxLength); + sint32 returnCode = _hidReadInternalSync(device, data, maxLength); coreinit_resumeThread(osThread, 1000); return returnCode; } void export_HIDRead(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamUStr(data, 1); // r4 - ppcDefineParamU32(maxLength, 2); // r5 - ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamUStr(data, 1); // r4 + ppcDefineParamU32(maxLength, 2); // r5 + ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 - cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDRead(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], + hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); - HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true); - if (hidDeviceInfo == nullptr) + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDRead(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); @@ -685,13 +586,14 @@ namespace nsyshid if (callbackFuncMPTR != MPTR_NULL) { // asynchronous transfer - std::thread(&_hidReadAsync, hidDeviceInfo, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); + std::thread(&_hidReadAsync, device, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); returnCode = 0; } else { // synchronous transfer - std::future res = std::async(std::launch::async, &_hidReadSync, hidDeviceInfo, data, maxLength, coreinitThread_getCurrentThreadDepr(hCPU)); + std::future res = std::async(std::launch::async, &_hidReadSync, device, data, maxLength, + coreinitThread_getCurrentThreadDepr(hCPU)); coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); PPCCore_switchToScheduler(); returnCode = res.get(); @@ -700,81 +602,78 @@ namespace nsyshid osLib_returnFromFunction(hCPU, returnCode); } - sint32 _hidWriteInternalSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength) + sint32 _hidWriteInternalSync(std::shared_ptr device, uint8* data, sint32 maxLength) { - DWORD bt; - OVERLAPPED ovlp = { 0 }; - ovlp.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); - - uint8* tempBuffer = (uint8*)malloc(maxLength + 1); - memcpy(tempBuffer + 1, data, maxLength); - tempBuffer[0] = 0; // report byte? - cemuLog_logDebug(LogType::Force, "HidWrite Begin (Length 0x{:08x})", maxLength); - BOOL WriteResult = WriteFile(hidDeviceInfo->hFile, tempBuffer, maxLength + 1, &bt, &ovlp); - if (WriteResult != FALSE) + if (!device->IsOpened()) { - // sometimes we get the result immediately - cemuLog_logDebug(LogType::Force, "HidWrite Result received immediately (error 0x{:08x}) Length 0x{:08x}", GetLastError()); + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): cannot write to a non-opened device"); + return -1; } - else + sint32 bytesWritten = 0; + Device::WriteResult writeResult = device->Write(data, maxLength, bytesWritten); + switch (writeResult) { - // 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 -108; - } - - - cemuLog_logDebug(LogType::Force, "HidWrite WaitHalfComplete"); - GetOverlappedResult(hidDeviceInfo->hFile, &ovlp, &bt, false); - cemuLog_logDebug(LogType::Force, "HidWrite WaitComplete"); + case Device::WriteResult::Success: + { + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): wrote {} of {} bytes", bytesWritten, + maxLength); + return bytesWritten; } - sint32 returnCode = 0; - if (bt != 0) - returnCode = maxLength; - else - returnCode = -1; - - free(tempBuffer); - CloseHandle(ovlp.hEvent); - return returnCode; + break; + case Device::WriteResult::Error: + { + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): write error"); + return -1; + } + break; + case Device::WriteResult::ErrorTimeout: + { + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): write error: timeout"); + return -108; + } + break; + } + cemuLog_logDebug(LogType::Force, "nsyshid.hidWriteInternalSync(): write error: unknown"); + return -1; } - void _hidWriteAsync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, MPTR callbackFuncMPTR, MPTR callbackParamMPTR) + void _hidWriteAsync(std::shared_ptr device, + uint8* data, + sint32 maxLength, + MPTR callbackFuncMPTR, + MPTR callbackParamMPTR) { - sint32 returnCode = _hidWriteInternalSync(hidDeviceInfo, data, maxLength); + sint32 returnCode = _hidWriteInternalSync(device, data, maxLength); sint32 errorCode = 0; if (returnCode < 0) - errorCode = returnCode; // dont return number of bytes in error code - doHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, hidDeviceInfo->handle, errorCode, memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0); + errorCode = returnCode; // don't return number of bytes in error code + DoHIDTransferCallback(callbackFuncMPTR, callbackParamMPTR, device->m_hid->handle, errorCode, + memory_getVirtualOffsetFromPointer(data), (returnCode > 0) ? returnCode : 0); } - sint32 _hidWriteSync(HIDDeviceInfo_t* hidDeviceInfo, uint8* data, sint32 maxLength, OSThread_t* osThread) + sint32 _hidWriteSync(std::shared_ptr device, + uint8* data, + sint32 maxLength, + OSThread_t* osThread) { - sint32 returnCode = _hidWriteInternalSync(hidDeviceInfo, data, maxLength); + sint32 returnCode = _hidWriteInternalSync(device, data, maxLength); coreinit_resumeThread(osThread, 1000); return returnCode; } void export_HIDWrite(PPCInterpreter_t* hCPU) { - ppcDefineParamU32(hidHandle, 0); // r3 - ppcDefineParamUStr(data, 1); // r4 - ppcDefineParamU32(maxLength, 2); // r5 - ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 + ppcDefineParamU32(hidHandle, 0); // r3 + ppcDefineParamUStr(data, 1); // r4 + ppcDefineParamU32(maxLength, 2); // r5 + ppcDefineParamMPTR(callbackFuncMPTR, 3); // r6 ppcDefineParamMPTR(callbackParamMPTR, 4); // r7 - cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDWrite(0x{:x},0x{:08x},0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], + hCPU->gpr[4], hCPU->gpr[5], hCPU->gpr[6], hCPU->gpr[7]); - HIDDeviceInfo_t* hidDeviceInfo = getHIDDeviceInfoByHandle(hidHandle, true); - if (hidDeviceInfo == nullptr) + std::shared_ptr device = GetDeviceByHandle(hidHandle, true); + if (device == nullptr) { cemuLog_log(LogType::Force, "nsyshid.HIDWrite(): Unable to find device with hid handle {}", hidHandle); osLib_returnFromFunction(hCPU, -1); @@ -784,13 +683,14 @@ namespace nsyshid if (callbackFuncMPTR != MPTR_NULL) { // asynchronous transfer - std::thread(&_hidWriteAsync, hidDeviceInfo, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); + std::thread(&_hidWriteAsync, device, data, maxLength, callbackFuncMPTR, callbackParamMPTR).detach(); returnCode = 0; } else { // synchronous transfer - std::future res = std::async(std::launch::async, &_hidWriteSync, hidDeviceInfo, data, maxLength, coreinitThread_getCurrentThreadDepr(hCPU)); + std::future res = std::async(std::launch::async, &_hidWriteSync, device, data, maxLength, + coreinitThread_getCurrentThreadDepr(hCPU)); coreinit_suspendThread(coreinitThread_getCurrentThreadDepr(hCPU), 1000); PPCCore_switchToScheduler(); returnCode = res.get(); @@ -804,7 +704,8 @@ namespace nsyshid ppcDefineParamU32(errorCode, 0); ppcDefineParamTypePtr(ukn0, uint32be, 1); ppcDefineParamTypePtr(ukn1, uint32be, 2); - cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], hCPU->gpr[4], hCPU->gpr[5]); + cemuLog_logDebug(LogType::Force, "nsyshid.HIDDecodeError(0x{:08x},0x{:08x},0x{:08x})", hCPU->gpr[3], + hCPU->gpr[4], hCPU->gpr[5]); // todo *ukn0 = 0x3FF; @@ -813,6 +714,114 @@ namespace nsyshid osLib_returnFromFunction(hCPU, 0); } + void Backend::DetachAllDevices() + { + std::lock_guard lock(this->m_devicesMutex); + if (m_isAttached) + { + for (const auto& device : this->m_devices) + { + nsyshid::DetachDevice(device); + } + this->m_devices.clear(); + } + } + + bool Backend::AttachDevice(const std::shared_ptr& device) + { + std::lock_guard lock(this->m_devicesMutex); + if (m_isAttached && nsyshid::AttachDevice(device)) + { + this->m_devices.push_back(device); + return true; + } + return false; + } + + void Backend::DetachDevice(const std::shared_ptr& device) + { + std::lock_guard lock(this->m_devicesMutex); + if (m_isAttached) + { + nsyshid::DetachDevice(device); + this->m_devices.remove(device); + } + } + + std::shared_ptr Backend::FindDevice(std::function&)> isWantedDevice) + { + std::lock_guard lock(this->m_devicesMutex); + auto it = std::find_if(this->m_devices.begin(), this->m_devices.end(), std::move(isWantedDevice)); + if (it != this->m_devices.end()) + { + return *it; + } + return nullptr; + } + + bool Backend::IsDeviceWhitelisted(uint16 vendorId, uint16 productId) + { + return Whitelist::GetInstance().IsDeviceWhitelisted(vendorId, productId); + } + + Backend::Backend() + : m_isAttached(false) + { + } + + void Backend::OnAttach() + { + std::lock_guard lock(this->m_devicesMutex); + m_isAttached = true; + AttachVisibleDevices(); + } + + void Backend::OnDetach() + { + std::lock_guard lock(this->m_devicesMutex); + DetachAllDevices(); + m_isAttached = false; + } + + bool Backend::IsBackendAttached() + { + std::lock_guard lock(this->m_devicesMutex); + return m_isAttached; + } + + Device::Device(uint16 vendorId, + uint16 productId, + uint8 interfaceIndex, + uint8 interfaceSubClass, + uint8 protocol) + : m_hid(nullptr), + m_vendorId(vendorId), + m_productId(productId), + m_interfaceIndex(interfaceIndex), + m_interfaceSubClass(interfaceSubClass), + m_protocol(protocol), + m_maxPacketSizeRX(0x20), + m_maxPacketSizeTX(0x20) + { + } + + void Device::AssignHID(HID_t* hid) + { + if (hid != nullptr) + { + hid->vendorId = this->m_vendorId; + hid->productId = this->m_productId; + hid->ifIndex = this->m_interfaceIndex; + hid->subClass = this->m_interfaceSubClass; + hid->protocol = this->m_protocol; + hid->ukn04 = 0x11223344; + hid->paddingGuessed0F = 0; + hid->maxPacketSizeRX = this->m_maxPacketSizeRX; + hid->maxPacketSizeTX = this->m_maxPacketSizeTX; + } + this->m_hid = hid; + } + void load() { osLib_addFunction("nsyshid", "HIDAddClient", export_HIDAddClient); @@ -826,19 +835,10 @@ namespace nsyshid osLib_addFunction("nsyshid", "HIDWrite", export_HIDWrite); osLib_addFunction("nsyshid", "HIDDecodeError", export_HIDDecodeError); - firstHIDClient = nullptr; + + // initialise whitelist + Whitelist::GetInstance(); + + AttachDefaultBackends(); } -} - -#else - -namespace nsyshid -{ - void load() - { - // unimplemented - }; -}; - - -#endif +} // namespace nsyshid diff --git a/src/Cafe/OS/libs/nsyshid/nsyshid.h b/src/Cafe/OS/libs/nsyshid/nsyshid.h index 051b4e7c..1478adf9 100644 --- a/src/Cafe/OS/libs/nsyshid/nsyshid.h +++ b/src/Cafe/OS/libs/nsyshid/nsyshid.h @@ -1,5 +1,12 @@ #pragma once + namespace nsyshid { + class Backend; + + void AttachBackend(const std::shared_ptr& backend); + + void DetachBackend(const std::shared_ptr& backend); + void load(); -} \ No newline at end of file +} // namespace nsyshid diff --git a/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp b/src/input/api/Wiimote/windows/WinWiimoteDevice.cpp index 546a9615..09d73013 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)) { diff --git a/vcpkg.json b/vcpkg.json index 940ed748..7ea8058e 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -46,6 +46,7 @@ "name": "curl", "default-features": false, "features": [ "openssl" ] - } + }, + "libusb" ] }