mirror of
https://github.com/cemu-project/Cemu.git
synced 2024-11-27 03:24:17 +01:00
nsyshid: Add backends for cross platform USB passthrough support (#950)
This commit is contained in:
parent
2a735f1fb7
commit
98b5a8758a
2
.github/workflows/build.yml
vendored
2
.github/workflows/build.yml
vendored
@ -232,7 +232,7 @@ jobs:
|
|||||||
- name: "Install system dependencies"
|
- name: "Install system dependencies"
|
||||||
run: |
|
run: |
|
||||||
brew update
|
brew update
|
||||||
brew install llvm@15 ninja nasm molten-vk
|
brew install llvm@15 ninja nasm molten-vk automake libtool
|
||||||
|
|
||||||
- name: "Bootstrap vcpkg"
|
- name: "Bootstrap vcpkg"
|
||||||
run: |
|
run: |
|
||||||
|
2
BUILD.md
2
BUILD.md
@ -86,7 +86,7 @@ You can skip this section if you have an Intel Mac. Every time you compile, you
|
|||||||
|
|
||||||
### Installing dependencies
|
### 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
|
### Build Cemu using cmake and clang
|
||||||
1. `git clone --recursive https://github.com/cemu-project/Cemu`
|
1. `git clone --recursive https://github.com/cemu-project/Cemu`
|
||||||
|
@ -102,6 +102,23 @@ if (WIN32)
|
|||||||
endif()
|
endif()
|
||||||
option(ENABLE_CUBEB "Enabled cubeb backend" ON)
|
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)
|
option(ENABLE_WXWIDGETS "Build with wxWidgets UI (Currently required)" ON)
|
||||||
|
|
||||||
set(THREADS_PREFER_PTHREAD_FLAG true)
|
set(THREADS_PREFER_PTHREAD_FLAG true)
|
||||||
|
20
cmake/Findlibusb.cmake
Normal file
20
cmake/Findlibusb.cmake
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
# SPDX-FileCopyrightText: 2022 Andrea Pappacoda <andrea@pappacoda.it>
|
||||||
|
# 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
|
||||||
|
)
|
@ -434,6 +434,14 @@ add_library(CemuCafe
|
|||||||
OS/libs/nn_uds/nn_uds.h
|
OS/libs/nn_uds/nn_uds.h
|
||||||
OS/libs/nsyshid/nsyshid.cpp
|
OS/libs/nsyshid/nsyshid.cpp
|
||||||
OS/libs/nsyshid/nsyshid.h
|
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.cpp
|
||||||
OS/libs/nsyskbd/nsyskbd.h
|
OS/libs/nsyskbd/nsyskbd.h
|
||||||
OS/libs/nsysnet/nsysnet.cpp
|
OS/libs/nsysnet/nsysnet.cpp
|
||||||
@ -524,6 +532,17 @@ if (ENABLE_WAYLAND)
|
|||||||
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
|
target_link_libraries(CemuCafe PUBLIC Wayland::Client)
|
||||||
endif()
|
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)
|
if (ENABLE_WXWIDGETS)
|
||||||
target_link_libraries(CemuCafe PRIVATE wx::base wx::core)
|
target_link_libraries(CemuCafe PRIVATE wx::base wx::core)
|
||||||
endif()
|
endif()
|
||||||
|
41
src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp
Normal file
41
src/Cafe/OS/libs/nsyshid/AttachDefaultBackends.cpp
Normal file
@ -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<backend::libusb::BackendLibusb>();
|
||||||
|
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<backend::windows::BackendWindowsHID>();
|
||||||
|
if (backendWindowsHID->IsInitialisedOk())
|
||||||
|
{
|
||||||
|
AttachBackend(backendWindowsHID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif // NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
||||||
|
}
|
||||||
|
} // namespace nsyshid::backend
|
141
src/Cafe/OS/libs/nsyshid/Backend.h
Normal file
141
src/Cafe/OS/libs/nsyshid/Backend.h
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
#ifndef CEMU_NSYSHID_BACKEND_H
|
||||||
|
#define CEMU_NSYSHID_BACKEND_H
|
||||||
|
|
||||||
|
#include <list>
|
||||||
|
#include <memory>
|
||||||
|
#include <mutex>
|
||||||
|
|
||||||
|
#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>& device);
|
||||||
|
|
||||||
|
void DetachDevice(const std::shared_ptr<Device>& device);
|
||||||
|
|
||||||
|
std::shared_ptr<Device> FindDevice(std::function<bool(const std::shared_ptr<Device>&)> 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<std::shared_ptr<Device>> m_devices;
|
||||||
|
std::recursive_mutex m_devicesMutex;
|
||||||
|
bool m_isAttached;
|
||||||
|
};
|
||||||
|
|
||||||
|
namespace backend
|
||||||
|
{
|
||||||
|
void AttachDefaultBackends();
|
||||||
|
}
|
||||||
|
} // namespace nsyshid
|
||||||
|
|
||||||
|
#endif // CEMU_NSYSHID_BACKEND_H
|
791
src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp
Normal file
791
src/Cafe/OS/libs/nsyshid/BackendLibusb.cpp
Normal file
@ -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<BackendLibusb*>(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<Device> 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<Device>& d) -> bool {
|
||||||
|
auto device = std::dynamic_pointer_cast<DeviceLibusb>(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<Device> 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<DeviceLibusb>(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<std::mutex> 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<std::mutex> 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<uint8> 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<uint32>(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::HandleLock> DeviceLibusb::AquireHandleLock()
|
||||||
|
{
|
||||||
|
return std::make_unique<HandleLock>(&m_libusbHandle,
|
||||||
|
m_handleMutex,
|
||||||
|
m_handleInUseCounter,
|
||||||
|
m_handleInUseCounterDecremented,
|
||||||
|
*this);
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceLibusb::HandleLock::HandleLock(libusb_device_handle** handle,
|
||||||
|
std::mutex& handleMutex,
|
||||||
|
std::atomic<sint32>& handleInUseCounter,
|
||||||
|
std::condition_variable& handleInUseCounterDecremented,
|
||||||
|
DeviceLibusb& device)
|
||||||
|
: m_handle(nullptr),
|
||||||
|
m_handleMutex(handleMutex),
|
||||||
|
m_handleInUseCounter(handleInUseCounter),
|
||||||
|
m_handleInUseCounterDecremented(handleInUseCounterDecremented)
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(handleMutex);
|
||||||
|
if (device.IsOpened() && handle != nullptr && handleInUseCounter >= 0)
|
||||||
|
{
|
||||||
|
this->m_handle = *handle;
|
||||||
|
this->m_handleInUseCounter++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
DeviceLibusb::HandleLock::~HandleLock()
|
||||||
|
{
|
||||||
|
if (IsValid())
|
||||||
|
{
|
||||||
|
std::lock_guard<std::mutex> 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
|
129
src/Cafe/OS/libs/nsyshid/BackendLibusb.h
Normal file
129
src/Cafe/OS/libs/nsyshid/BackendLibusb.h
Normal file
@ -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 <libusb-1.0/libusb.h>
|
||||||
|
#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<bool> 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<Device> CheckAndCreateDevice(libusb_device* dev);
|
||||||
|
|
||||||
|
std::shared_ptr<Device> 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<sint32> 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<sint32>& 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<sint32>& m_handleInUseCounter;
|
||||||
|
std::condition_variable& m_handleInUseCounterDecremented;
|
||||||
|
};
|
||||||
|
|
||||||
|
std::unique_ptr<HandleLock> AquireHandleLock();
|
||||||
|
};
|
||||||
|
} // namespace nsyshid::backend::libusb
|
||||||
|
|
||||||
|
#endif // NSYSHID_ENABLE_BACKEND_LIBUSB
|
||||||
|
|
||||||
|
#endif // CEMU_NSYSHID_BACKEND_LIBUSB_H
|
454
src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp
Normal file
454
src/Cafe/OS/libs/nsyshid/BackendWindowsHID.cpp
Normal file
@ -0,0 +1,454 @@
|
|||||||
|
#include "BackendWindowsHID.h"
|
||||||
|
|
||||||
|
#if NSYSHID_ENABLE_BACKEND_WINDOWS_HID
|
||||||
|
|
||||||
|
#include <setupapi.h>
|
||||||
|
#include <initguid.h>
|
||||||
|
#include <hidsdi.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);
|
||||||
|
|
||||||
|
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<Device> 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<DeviceWindowsHID>(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<uint32>(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
|
66
src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h
Normal file
66
src/Cafe/OS/libs/nsyshid/BackendWindowsHID.h
Normal file
@ -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<Device> 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
|
53
src/Cafe/OS/libs/nsyshid/Whitelist.cpp
Normal file
53
src/Cafe/OS/libs/nsyshid/Whitelist.cpp
Normal file
@ -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<uint16, uint16>(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<uint16, uint16>(vendorId, productId));
|
||||||
|
}
|
||||||
|
|
||||||
|
std::list<std::tuple<uint16, uint16>> Whitelist::GetDevices()
|
||||||
|
{
|
||||||
|
return m_devices;
|
||||||
|
}
|
||||||
|
|
||||||
|
void Whitelist::RemoveAllDevices()
|
||||||
|
{
|
||||||
|
m_devices.clear();
|
||||||
|
}
|
||||||
|
} // namespace nsyshid
|
32
src/Cafe/OS/libs/nsyshid/Whitelist.h
Normal file
32
src/Cafe/OS/libs/nsyshid/Whitelist.h
Normal file
@ -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<std::tuple<uint16, uint16>> GetDevices();
|
||||||
|
|
||||||
|
void RemoveAllDevices();
|
||||||
|
|
||||||
|
private:
|
||||||
|
Whitelist();
|
||||||
|
|
||||||
|
// vendorId, productId
|
||||||
|
std::list<std::tuple<uint16, uint16>> m_devices;
|
||||||
|
};
|
||||||
|
} // namespace nsyshid
|
||||||
|
|
||||||
|
#endif // CEMU_NSYSHID_WHITELIST_H
|
File diff suppressed because it is too large
Load Diff
@ -1,5 +1,12 @@
|
|||||||
#pragma once
|
#pragma once
|
||||||
|
|
||||||
namespace nsyshid
|
namespace nsyshid
|
||||||
{
|
{
|
||||||
|
class Backend;
|
||||||
|
|
||||||
|
void AttachBackend(const std::shared_ptr<Backend>& backend);
|
||||||
|
|
||||||
|
void DetachBackend(const std::shared_ptr<Backend>& backend);
|
||||||
|
|
||||||
void load();
|
void load();
|
||||||
}
|
} // namespace nsyshid
|
||||||
|
@ -3,6 +3,9 @@
|
|||||||
#include <hidsdi.h>
|
#include <hidsdi.h>
|
||||||
#include <SetupAPI.h>
|
#include <SetupAPI.h>
|
||||||
|
|
||||||
|
#pragma comment(lib, "Setupapi.lib")
|
||||||
|
#pragma comment(lib, "hid.lib")
|
||||||
|
|
||||||
WinWiimoteDevice::WinWiimoteDevice(HANDLE handle, std::vector<uint8_t> identifier)
|
WinWiimoteDevice::WinWiimoteDevice(HANDLE handle, std::vector<uint8_t> identifier)
|
||||||
: m_handle(handle), m_identifier(std::move(identifier))
|
: m_handle(handle), m_identifier(std::move(identifier))
|
||||||
{
|
{
|
||||||
|
@ -46,6 +46,7 @@
|
|||||||
"name": "curl",
|
"name": "curl",
|
||||||
"default-features": false,
|
"default-features": false,
|
||||||
"features": [ "openssl" ]
|
"features": [ "openssl" ]
|
||||||
}
|
},
|
||||||
|
"libusb"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user