From 006af26a6ec2658b0ed3d51beac8f5f51b0ed48d Mon Sep 17 00:00:00 2001 From: ergo720 <45463469+ergo720@users.noreply.github.com> Date: Sun, 31 Oct 2021 14:28:04 +0100 Subject: [PATCH] Added libusb class + libusb log option --- CMakeLists.txt | 2 + src/common/Logging.cpp | 1 + src/common/Logging.h | 1 + src/common/input/InputDevice.cpp | 8 + src/common/input/InputDevice.h | 5 + src/common/input/InputManager.cpp | 12 +- src/common/input/LibusbDevice.cpp | 239 ++++++++++++++++++++++++++++++ src/common/input/LibusbDevice.h | 80 ++++++++++ src/common/input/SdlJoystick.cpp | 2 + src/gui/DlgLoggingConfig.cpp | 2 + src/gui/resource/Cxbx.rc | 1 + src/gui/resource/ResCxbx.h | 1 + 12 files changed, 351 insertions(+), 3 deletions(-) create mode 100644 src/common/input/LibusbDevice.cpp create mode 100644 src/common/input/LibusbDevice.h diff --git a/CMakeLists.txt b/CMakeLists.txt index 82c28cc06..bab152bf7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -65,6 +65,7 @@ file (GLOB CXBXR_HEADER_COMMON "${CXBXR_ROOT_DIR}/src/common/input/layout_xbox_device.h" "${CXBXR_ROOT_DIR}/src/common/input/InputDevice.h" "${CXBXR_ROOT_DIR}/src/common/input/InputManager.h" + "${CXBXR_ROOT_DIR}/src/common/input/LibusbDevice.h" "${CXBXR_ROOT_DIR}/src/common/input/SdlJoystick.h" "${CXBXR_ROOT_DIR}/src/common/input/XInputPad.h" "${CXBXR_ROOT_DIR}/src/common/input/RawDevice.h" @@ -236,6 +237,7 @@ file (GLOB CXBXR_SOURCE_COMMON "${CXBXR_ROOT_DIR}/src/common/input/DInputKeyboardMouse.cpp" "${CXBXR_ROOT_DIR}/src/common/input/InputDevice.cpp" "${CXBXR_ROOT_DIR}/src/common/input/InputManager.cpp" + "${CXBXR_ROOT_DIR}/src/common/input/LibusbDevice.cpp" "${CXBXR_ROOT_DIR}/src/common/input/SdlJoystick.cpp" "${CXBXR_ROOT_DIR}/src/common/input/XInputPad.cpp" "${CXBXR_ROOT_DIR}/src/common/input/RawDevice.cpp" diff --git a/src/common/Logging.cpp b/src/common/Logging.cpp index a260d5bf2..df98a6b07 100644 --- a/src/common/Logging.cpp +++ b/src/common/Logging.cpp @@ -82,6 +82,7 @@ const char* g_EnumModules2String[to_underlying(CXBXR_MODULE::MAX)] = { "XMO ", "RINP ", "JVS ", + "LIBUSB ", "KRNL ", "LOG ", "XBOX ", diff --git a/src/common/Logging.h b/src/common/Logging.h index 532610949..2527ec046 100644 --- a/src/common/Logging.h +++ b/src/common/Logging.h @@ -90,6 +90,7 @@ typedef enum class _CXBXR_MODULE: unsigned int { XMO, RINP, JVS, + LIBUSB, // kernel KRNL, LOG, diff --git a/src/common/input/InputDevice.cpp b/src/common/input/InputDevice.cpp index e333cc7d4..25fe5097a 100644 --- a/src/common/input/InputDevice.cpp +++ b/src/common/input/InputDevice.cpp @@ -77,6 +77,14 @@ std::string GetInputDeviceName(int dev_type) str = "Arcade joystick"; break; + case to_underlying(XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER): + str = "Passthrough steel battalion controller"; + break; + + case to_underlying(XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER): + str = "Passthrough original xbox gamepad"; + break; + case to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID): str = "None"; break; diff --git a/src/common/input/InputDevice.h b/src/common/input/InputDevice.h index 18323d8b8..8c1d80e5b 100644 --- a/src/common/input/InputDevice.h +++ b/src/common/input/InputDevice.h @@ -56,6 +56,9 @@ typedef enum class _XBOX_INPUT_DEVICE : int { STEEL_BATTALION_CONTROLLER, ARCADE_STICK, DEVICE_MAX, + // Devices with the HW_ prefix (= hardware) indicate a real xbox device. Always add these after DEVICE_MAX + HW_STEEL_BATTALION_CONTROLLER, + HW_XBOX_CONTROLLER, } XBOX_INPUT_DEVICE; @@ -120,6 +123,8 @@ public: bool GetPort(std::string_view Port) const; // sets the port this device is attached to void SetPort(std::string_view Port, bool Connect); + // retuns true if it is a libusb device, false otherwise + virtual bool IsLibusb() const { return false; }; protected: diff --git a/src/common/input/InputManager.cpp b/src/common/input/InputManager.cpp index fe3e46740..fee81d514 100644 --- a/src/common/input/InputManager.cpp +++ b/src/common/input/InputManager.cpp @@ -41,6 +41,7 @@ #include "XInputPad.h" #include "RawDevice.h" #include "DInputKeyboardMouse.h" +#include "LibusbDevice.h" #include "InputManager.h" #include "..\devices\usb\XidGamepad.h" #include "core\kernel\exports\EmuKrnl.h" // For EmuLog @@ -91,11 +92,12 @@ void InputDeviceManager::Initialize(bool is_gui, HWND hwnd) m_Cv.wait(lck, []() { return (Sdl::InitStatus != Sdl::NOT_INIT) && (XInput::InitStatus != XInput::NOT_INIT) && - (RawInput::InitStatus != RawInput::NOT_INIT); + (RawInput::InitStatus != RawInput::NOT_INIT) && + (Libusb::InitStatus != Libusb::NOT_INIT); }); lck.unlock(); - if (Sdl::InitStatus < 0 || XInput::InitStatus < 0 || RawInput::InitStatus < 0) { + if (Sdl::InitStatus < 0 || XInput::InitStatus < 0 || RawInput::InitStatus < 0 || Libusb::InitStatus < 0) { CxbxrKrnlAbort("Failed to initialize input subsystem! Consult debug log for more information"); } @@ -616,6 +618,7 @@ void InputDeviceManager::RefreshDevices() XInput::PopulateDevices(); DInput::PopulateDevices(); Sdl::PopulateDevices(); + Libusb::PopulateDevices(); lck.lock(); m_Cv.wait(lck, []() { return Sdl::PopulateOK; @@ -719,7 +722,7 @@ void InputDeviceManager::HotplugHandler(bool is_sdl) std::unique_lock lck(m_Mtx); auto it = std::remove_if(m_Devices.begin(), m_Devices.end(), [](const auto &Device) { - if (StrStartsWith(Device->GetAPI(), "XInput")) { + if (Device->IsLibusb() || StrStartsWith(Device->GetAPI(), "XInput")) { return true; } return false; @@ -730,6 +733,9 @@ void InputDeviceManager::HotplugHandler(bool is_sdl) lck.unlock(); XInput::PopulateDevices(); + // Unfortunately, as documented in this issue https://github.com/libusb/libusb/issues/86, when this was written libusb did not yet support + // device hotplug on Windows, so we add the below call here. This will only work if rawinput detects the libusb device. + Libusb::PopulateDevices(); } for (int port = PORT_1; port <= PORT_4; ++port) { diff --git a/src/common/input/LibusbDevice.cpp b/src/common/input/LibusbDevice.cpp new file mode 100644 index 000000000..f6eed9d6d --- /dev/null +++ b/src/common/input/LibusbDevice.cpp @@ -0,0 +1,239 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +// ****************************************************************** +// * +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the license, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * (c) 2021 ergo720 +// * +// * All rights reserved +// * +// ****************************************************************** + +#define LOG_PREFIX CXBXR_MODULE::LIBUSB + +#include "LibusbDevice.h" +#include "InputManager.h" +#include "core\kernel\support\Emu.h" + +// Sanitiy check: ensure out libusb version is high enough for libusb_get_device_descriptor to succeed +static_assert(LIBUSB_API_VERSION >= 0x01000102); + + +namespace Libusb +{ + int InitStatus = NOT_INIT; + static libusb_device **List = nullptr; + + // These come from here https://github.com/xboxdrv/xboxdrv/blob/ac6ebb1228962220482ea03743cadbe18754246c/src/xpad_device.cpp#L29 + static constexpr uint16_t SupportedDevices_VidPid[][2] = { // vid, pid + 0x0d2f, 0x0002, + 0x045e, 0x0202, + 0x045e, 0x0285, + 0x045e, 0x0287, + 0x045e, 0x0289, + 0x046d, 0xca84, + 0x046d, 0xca88, + 0x05fd, 0x1007, + 0x05fd, 0x107a, + 0x0738, 0x4516, + 0x0738, 0x4522, + 0x0738, 0x4526, + 0x0738, 0x4536, + 0x0738, 0x4556, + 0x0c12, 0x8802, + 0x0c12, 0x8810, + 0x0c12, 0x9902, + 0x0e4c, 0x1097, + 0x0e4c, 0x2390, + 0x0e6f, 0x0003, + 0x0e6f, 0x0005, + 0x0e6f, 0x0006, + 0x0f30, 0x0202, + 0x0f30, 0x8888, + 0x102c, 0xff0c, + 0x044f, 0x0f07, + 0x0e8f, 0x3008, + }; + + static constexpr const char *SupportedDevices_Name[] = { + "Andamiro Pump It Up pad", + "Microsoft X-Box pad v1 (US)", + "Microsoft X-Box pad (Japan)", + "Microsoft Xbox Controller S", + "Microsoft X-Box pad v2 (US)", + "Logitech Xbox Cordless Controller", + "Logitech Compact Controller for Xbox", + "Mad Catz Controller (unverified)", + "InterAct 'PowerPad Pro' X-Box pad (Germany)", + "Mad Catz Control Pad", + "Mad Catz LumiCON", + "Mad Catz Control Pad Pro", + "Mad Catz MicroCON", + "Mad Catz Lynx Wireless Controller", + "Zeroplus Xbox Controller", + "Zeroplus Xbox Controller", + "HAMA VibraX - *FAULTY HARDWARE*", + "Radica Gamester Controller", + "Radica Games Jtech Controller", + "Logic3 Freebird wireless Controller", + "Eclipse wireless Controller", + "Edge wireless Controller", + "Joytech Advanced Controller", + "BigBen XBMiniPad Controller", + "Joytech Wireless Advanced Controller", + "Thrustmaster, Inc. Controller", + "Generic xbox control (dealextreme)", + }; + + static_assert(ARRAY_SIZE(SupportedDevices_VidPid) == ARRAY_SIZE(SupportedDevices_Name)); + + void Init(std::mutex &Mtx) + { + std::unique_lock lck(Mtx); + + // We only use a single libusb session per cxbxr process, so we do not need to use a libusb context + if (libusb_init(nullptr) != 0) { + EmuLog(LOG_LEVEL::ERROR2, "Failed to initialize Libusb!"); + InitStatus = INIT_ERROR; + return; + } + + InitStatus = INIT_SUCCESS; + } + + void DeInit() + { + InitStatus = NOT_INIT; + libusb_exit(nullptr); + } + + void PopulateDevices() + { + // NOTE: the libusb docs say that the list is always appended with a NULL element at the end + ssize_t DevicesConnected = libusb_get_device_list(nullptr, &List) - 1; + if (DevicesConnected < 0) { + EmuLog(LOG_LEVEL::ERROR2, "Failed to enumerate devices. The error was: %s", libusb_strerror(DevicesConnected)); + return; + } + + for (ssize_t i = 0; i < DevicesConnected; ++i) { + libusb_device *LibusbDev = List[i]; + libusb_device_descriptor Desc; + libusb_get_device_descriptor(LibusbDev, &Desc); // always succeeds when LIBUSB_API_VERSION >= 0x01000102 + auto Device = std::make_shared(&Desc, LibusbDev); + if (Device->IsLibusb()) { + g_InputDeviceManager.AddDevice(std::move(Device)); + } + } + + libusb_free_device_list(List, 1); + List = nullptr; + } + + void GetDeviceChanges() + { + g_InputDeviceManager.RemoveDevice([](const auto &Device) { + const LibusbDevice *dev = dynamic_cast(Device); + return dev->IsLibusb(); + }); + PopulateDevices(); + } + + bool LibusbDevice::UpdateInput() + { + // not sure of this yet + return true; + } + + LibusbDevice::LibusbDevice(libusb_device_descriptor *Desc, libusb_device *Dev) + { + m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID; + + // The SBC's VID and PID are taken from https://xboxdevwiki.net/Xbox_Input_Devices#Steel_Battalion_Controller + if ((Desc->idVendor == 0x0a7b) && (Desc->idProduct == 0xd000)) { + m_Type = XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER; + m_Name = "Steel battalion controller"; + assert(Desc->bcdUSB == 0x110); // must be a usb 1.1 device + } + else { + for (size_t i = 0; i < ARRAY_SIZE(SupportedDevices_VidPid); ++i) { + if ((Desc->idVendor = SupportedDevices_VidPid[i][0]) && (Desc->idProduct == SupportedDevices_VidPid[i][1])) { + m_Type = XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER; + m_Name = SupportedDevices_Name[i]; + assert(Desc->bcdUSB == 0x110); // must be a usb 1.1 device + break; + } + } + } + + if (m_Type == XBOX_INPUT_DEVICE::DEVICE_INVALID) { return; } + + // Duke, S and SBC have 1 configuration, 1 interface and 2 endpoints (input and output) and use the default alternate setting zero. + // The code below assumes that third-party controllers follow suit. + if (libusb_open(Dev, &m_hDev) == 0) { + libusb_config_descriptor *Desc; + if (libusb_get_active_config_descriptor(Dev, &Desc) == 0) { + if (Desc->bNumInterfaces == 1) { + auto Iface = Desc->interface[0]; + if (Iface.num_altsetting == 1) { + auto Setting = Iface.altsetting[0]; + if (Setting.bNumEndpoints == 2) { + for (uint8_t i = 0; i < 2; ++i) { + auto Endpoint = Setting.endpoint[i]; + if (Endpoint.bmAttributes & LIBUSB_ENDPOINT_TRANSFER_TYPE_INTERRUPT) { + if (Endpoint.bEndpointAddress & 0x80) { + m_EndpointIn = Endpoint.bEndpointAddress; + m_IntervalIn = Endpoint.bInterval; + } + else { + m_EndpointOut = Endpoint.bEndpointAddress; + m_IntervalOut = Endpoint.bInterval; + } + } + } + } + else { + EmuLog(LOG_LEVEL::INFO, "Rejected device because of unexpected number of endpoints, bNumEndpoints: %d", Setting.bNumEndpoints); + m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID; + } + } + else { + EmuLog(LOG_LEVEL::INFO, "Rejected device because of unexpected number of alternative settings, num_altsetting: %d", Iface.num_altsetting); + m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID; + } + } + else { + EmuLog(LOG_LEVEL::INFO, "Rejected device because of unexpected number of interfaces, bNumInterfaces: %d", Desc->bNumInterfaces); + m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID; + } + libusb_free_config_descriptor(Desc); + } + } + } + + std::string LibusbDevice::GetDeviceName() const + { + return m_Name; + } + + std::string LibusbDevice::GetAPI() const + { + return "Libusb"; + } +} diff --git a/src/common/input/LibusbDevice.h b/src/common/input/LibusbDevice.h new file mode 100644 index 000000000..11134c772 --- /dev/null +++ b/src/common/input/LibusbDevice.h @@ -0,0 +1,80 @@ +// This is an open source non-commercial project. Dear PVS-Studio, please check it. +// PVS-Studio Static Code Analyzer for C, C++ and C#: http://www.viva64.com +// ****************************************************************** +// * +// * This file is part of the Cxbx project. +// * +// * Cxbx and Cxbe are free software; you can redistribute them +// * and/or modify them under the terms of the GNU General Public +// * License as published by the Free Software Foundation; either +// * version 2 of the license, or (at your option) any later version. +// * +// * This program is distributed in the hope that it will be useful, +// * but WITHOUT ANY WARRANTY; without even the implied warranty of +// * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// * GNU General Public License for more details. +// * +// * You should have recieved a copy of the GNU General Public License +// * along with this program; see the file COPYING. +// * If not, write to the Free Software Foundation, Inc., +// * 59 Temple Place - Suite 330, Bostom, MA 02111-1307, USA. +// * +// * (c) 2021 ergo720 +// * +// * All rights reserved +// * +// ****************************************************************** + +#pragma once + +#include "InputDevice.h" +// Suppress warning in libusb about zero sized array +#pragma warning(push) +#pragma warning(disable: 4200) +#include "libusb.h" +#pragma warning(pop) + + +namespace Libusb +{ + typedef enum _INIT_STATUS : int + { + NOT_INIT = -2, + INIT_ERROR, + INIT_SUCCESS, + } + INIT_STATUS; + + extern int InitStatus; + + // initialize Libusb + void Init(std::mutex &Mtx); + // shutdown Libusb + void DeInit(); + // refresh the device list in response to a refresh command from the input GUI + void PopulateDevices(); + // update the device list + void GetDeviceChanges(); + + class LibusbDevice : public InputDevice + { + public: + bool UpdateInput() override; + + LibusbDevice(libusb_device_descriptor *Desc, libusb_device *Dev); + + std::string GetDeviceName() const override; + std::string GetAPI() const override; + bool IsLibusb() const override { return m_Type != XBOX_INPUT_DEVICE::DEVICE_INVALID; } + + + private: + XBOX_INPUT_DEVICE m_Type; + std::string m_Name; + libusb_device_handle *m_hDev; + unsigned char m_EndpointIn; + unsigned char m_EndpointOut; + uint8_t m_IntervalIn; + uint8_t m_IntervalOut; + }; +} diff --git a/src/common/input/SdlJoystick.cpp b/src/common/input/SdlJoystick.cpp index 6a50d662b..f3981d5b7 100644 --- a/src/common/input/SdlJoystick.cpp +++ b/src/common/input/SdlJoystick.cpp @@ -41,6 +41,7 @@ #include "SdlJoystick.h" #include "XInputPad.h" #include "DInputKeyboardMouse.h" +#include "LibusbDevice.h" #include "InputManager.h" // These values are those used by Dolphin! @@ -156,6 +157,7 @@ namespace Sdl else { XInput::GetDeviceChanges(); DInput::GetDeviceChanges(); + Libusb::GetDeviceChanges(); std::string port = std::to_string(*static_cast(Event.user.data1)); int port_num, slot; PortStr2Int(port, &port_num, &slot); diff --git a/src/gui/DlgLoggingConfig.cpp b/src/gui/DlgLoggingConfig.cpp index 80a46a931..d8e261d06 100644 --- a/src/gui/DlgLoggingConfig.cpp +++ b/src/gui/DlgLoggingConfig.cpp @@ -84,6 +84,7 @@ static int g_DlgIndexes[] = { IDC_LOG_XMO, IDC_LOG_RINP, IDC_LOG_JVS, + IDC_LOG_LIBUSB, // Kernel IDC_LOG_KRNL, IDC_LOG_LOG, @@ -386,6 +387,7 @@ INT_PTR CALLBACK DlgLogConfigProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM case IDC_LOG_DINP: case IDC_LOG_RINP: case IDC_LOG_XINP: + case IDC_LOG_LIBUSB: case IDC_LOG_JVS: case IDC_LOG_SDL: case IDC_LOG_FILE: diff --git a/src/gui/resource/Cxbx.rc b/src/gui/resource/Cxbx.rc index e54afd15b..4d0812a73 100644 --- a/src/gui/resource/Cxbx.rc +++ b/src/gui/resource/Cxbx.rc @@ -508,6 +508,7 @@ BEGIN CONTROL "VSHCACHE",IDC_LOG_VSHCACHE,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,68,140,53,10 CONTROL "RINP",IDC_LOG_RINP,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,202,195,32,10 CONTROL "JVS",IDC_LOG_JVS,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,38,206,28,10 + CONTROL "LIBUSB",IDC_LOG_LIBUSB,"Button",BS_AUTOCHECKBOX | BS_LEFTTEXT | WS_TABSTOP,80,206,41,10 END IDD_ABOUT DIALOGEX 0, 0, 310, 177 diff --git a/src/gui/resource/ResCxbx.h b/src/gui/resource/ResCxbx.h index 7d121167e..a17c757c6 100644 --- a/src/gui/resource/ResCxbx.h +++ b/src/gui/resource/ResCxbx.h @@ -94,6 +94,7 @@ #define IDC_LOG_VSHCACHE 962 #define IDC_LOG_RINP 963 #define IDC_LOG_JVS 964 +#define IDC_LOG_LIBUSB 965 #define IDC_DEVICE_LIST_TOP_SLOT 995 #define IDC_DEVICE_LIST_BOTTOM_SLOT 996 #define IDC_DEVICE_TOP_SLOT 997