From dff33b36c72ac05f0f609be65887699b5a73b662 Mon Sep 17 00:00:00 2001 From: Simon Sievert Date: Sat, 26 Aug 2023 11:52:04 +0200 Subject: [PATCH] nsyshid: add libusb backend This adds a backend for nsyshid, that uses libusb to provide passthrough access to real usb devices. --- .github/workflows/build.yml | 2 +- BUILD.md | 2 +- CMakeLists.txt | 6 + src/Cafe/CMakeLists.txt | 8 + .../OS/libs/nsyshid/AttachDefaultBackends.cpp | 16 +- src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp | 611 ++++++++++++++++++ src/Cafe/OS/libs/nsyshid/BackendLibusb.h | 94 +++ vcpkg.json | 3 +- 8 files changed, 738 insertions(+), 4 deletions(-) create mode 100644 src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp create mode 100644 src/Cafe/OS/libs/nsyshid/BackendLibusb.h diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index a91d562b85..eb6ac09953 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 5ff9bfd583..da6c03cec9 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 34a28a06a9..87f44aff87 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -102,6 +102,12 @@ if (WIN32) endif() option(ENABLE_CUBEB "Enabled cubeb backend" ON) +# usb hid backends +option(ENABLE_NSYSHID_LIBUSB "Enables the libusb backend for nsyshid" ON) +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/src/Cafe/CMakeLists.txt b/src/Cafe/CMakeLists.txt index cfcfaf7097..d9d1bbb5ad 100644 --- a/src/Cafe/CMakeLists.txt +++ b/src/Cafe/CMakeLists.txt @@ -438,6 +438,8 @@ add_library(CemuCafe 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/nsyskbd/nsyskbd.cpp OS/libs/nsyskbd/nsyskbd.h OS/libs/nsysnet/nsysnet.cpp @@ -528,6 +530,12 @@ if (ENABLE_WAYLAND) target_link_libraries(CemuCafe PUBLIC Wayland::Client) endif() +if (ENABLE_NSYSHID_LIBUSB) + find_package(libusb CONFIG REQUIRED) + target_include_directories(CemuCafe PRIVATE ${LIBUSB_INCLUDE_DIRS}) + target_link_libraries(CemuCafe PRIVATE ${LIBUSB_LIBRARIES}) +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 index 087b5a2b9b..da65ee1279 100644 --- a/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp +++ b/src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp @@ -1,8 +1,22 @@ #include "nsyshid.h" #include "Backend.h" +#if NSYSHID_ENABLE_BACKEND_LIBUSB + +#include "BackendLibusb.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 } } diff --git a/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp new file mode 100644 index 0000000000..93c7093f1e --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp @@ -0,0 +1,611 @@ +#include "BackendLibusb.h" + +#if NSYSHID_ENABLE_BACKEND_LIBUSB + +namespace nsyshid::backend::libusb { + BackendLibusb::BackendLibusb() : ctx(nullptr), + initReturnCode(0), + callbackRegistered(false), + hotplugCallbackHandle(0), + hotplugThreadStop(false) { + initReturnCode = libusb_init(&ctx); + if (initReturnCode < 0) { + ctx = nullptr; + cemuLog_logDebug(LogType::Force, "nsyshid::BackendLibusb: failed to initialize libusb with return code %i", + initReturnCode); + return; + } + + if (libusb_has_capability(LIBUSB_CAP_HAS_HOTPLUG)) { + int ret = libusb_hotplug_register_callback(ctx, + LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED | + LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, + 0, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + LIBUSB_HOTPLUG_MATCH_ANY, + hotplugCallback, + this, + &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"); + callbackRegistered = true; + hotplugThread = std::thread([this] { + while (!hotplugThreadStop) { + timeval timeout{ + .tv_sec = 1, + .tv_usec = 0, + }; + int ret = libusb_handle_events_timeout_completed(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 initReturnCode == 0; + } + + void BackendLibusb::attachVisibleDevices() { + // add all currently connected devices + libusb_device **devices; + ssize_t deviceCount = libusb_get_device_list(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->vendorId, device->productId)) { + if (!attachDevice(device)) { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb: failed to attach device: {:04x}:{:04x}", + device->vendorId, + device->productId); + } + } else { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb: device not on whitelist: {:04x}:{:04x}", + device->vendorId, + device->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->vendorId, device->productId)) { + if (!attachDevice(device)) { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::onHotplug(): failed to attach device: {:04x}:{:04x}", + device->vendorId, + device->productId); + } + } else { + cemuLog_log(LogType::Force, + "nsyshid::BackendLibusb::onHotplug(): device not on whitelist: {:04x}:{:04x}", + device->vendorId, + device->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 (callbackRegistered) { + hotplugThreadStop = true; + libusb_hotplug_deregister_callback(ctx, hotplugCallbackHandle); + hotplugThread.join(); + } + detachAllDevices(); + if (ctx) { + libusb_exit(ctx); + 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->vendorId && + desc.idProduct == device->productId && + busNumber == device->libusbBusNumber && + deviceAddress == device->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(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->libusbHasEndpointIn, + device->libusbEndpointIn, + device->maxPacketSizeRX, + device->libusbHasEndpointOut, + device->libusbEndpointOut, + device->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->vendorId, + device->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), + ctx(ctx), + libusbHandle(nullptr), + libusbBusNumber(libusbBusNumber), + libusbDeviceAddress(libusbDeviceAddress), + libusbHasEndpointIn(false), + libusbEndpointIn(0), + libusbHasEndpointOut(false), + libusbEndpointOut(0) { + + } + + DeviceLibusb::~DeviceLibusb() { + if (libusbHandle != nullptr) { + libusb_close(libusbHandle); + libusbHandle = nullptr; + } + } + + bool DeviceLibusb::open() { + if (isOpened()) { + return true; + } + libusb_device **devices; + ssize_t deviceCount = libusb_get_device_list(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->vendorId && + desc.idProduct == this->productId && + libusb_get_bus_number(dev) == this->libusbBusNumber && + libusb_get_device_address(dev) == this->libusbDeviceAddress) { + + // we found our device! + found = dev; + break; + } + } + + if (found != nullptr) { + { + int ret = libusb_open(dev, &(this->libusbHandle)); + if (ret < 0) { + cemuLog_log(LogType::Force, + "nsyshid::DeviceLibusb::open(): failed to open device; return code: %i", + ret); + libusb_free_device_list(devices, 1); + return false; + } + } + if (libusb_kernel_driver_active(this->libusbHandle, 0) == 1) { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::open(): kernel driver active"); + if (libusb_detach_kernel_driver(this->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->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() { + if (isOpened()) { + libusb_close(libusbHandle); + libusbHandle = nullptr; + } + } + + bool DeviceLibusb::isOpened() { + return libusbHandle != nullptr; + } + + Device::ReadResult DeviceLibusb::read(uint8 *data, sint32 length, sint32 &bytesRead) { + if (!isOpened()) { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::read(): cannot read from a non-opened device"); + return ReadResult::Error; + } + + const unsigned int timeout = 50; + int actualLength; + int ret = 0; + do { + ret = libusb_bulk_transfer(this->libusbHandle, + this->libusbEndpointIn, + data, + length, + &actualLength, + timeout); + } while (ret == LIBUSB_ERROR_TIMEOUT && actualLength == 0); + 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) { + if (!isOpened()) { + cemuLog_logDebug(LogType::Force, "nsyshid::DeviceLibusb::write(): cannot write to a non-opened device"); + return WriteResult::Error; + } + bytesWritten = 0; + int actualLength = 0; + int ret = libusb_bulk_transfer(this->libusbHandle, + this->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) { + if (!isOpened()) { + 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(this->libusbHandle); + 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) { + if (!isOpened()) { + 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(this->libusbHandle, 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) { + if (!isOpened()) { + 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(this->libusbHandle, + bmRequestType, + bRequest, + wValue, + wIndex, + reportData, + length, + timeout); +#endif + + // pretend that everything is fine + return true; + } +} + +#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 0000000000..9cd46c505d --- /dev/null +++ b/src/Cafe/OS/libs/nsyshid/BackendLibusb.h @@ -0,0 +1,94 @@ +#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 *ctx; + int initReturnCode; + bool callbackRegistered; + libusb_hotplug_callback_handle hotplugCallbackHandle; + std::thread hotplugThread; + std::atomic 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 libusbBusNumber; + uint8 libusbDeviceAddress; + bool libusbHasEndpointIn; + uint8 libusbEndpointIn; + bool libusbHasEndpointOut; + uint8 libusbEndpointOut; + + private: + libusb_context *ctx; + libusb_device_handle *libusbHandle; + }; +} + +#endif //NSYSHID_ENABLE_BACKEND_LIBUSB + +#endif //CEMU_NSYSHID_BACKEND_LIBUSB_H diff --git a/vcpkg.json b/vcpkg.json index 940ed74843..7ea8058ef5 100644 --- a/vcpkg.json +++ b/vcpkg.json @@ -46,6 +46,7 @@ "name": "curl", "default-features": false, "features": [ "openssl" ] - } + }, + "libusb" ] }