diff --git a/.gitmodules b/.gitmodules index 2c5c71a30..8d4a72ff9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -35,3 +35,7 @@ path = import/SDL2 url = https://github.com/libsdl-org/SDL shallow = true +[submodule "import/libusb"] + path = import/libusb + url = https://github.com/libusb/libusb + shallow = true diff --git a/CMakeLists.txt b/CMakeLists.txt index b9563f699..c33006fce 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -41,6 +41,8 @@ add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/projects/libtom") add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/projects/imgui") +add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/projects/libusb") + # Split the files into group for which project is likely # going to be used for both header and source files. # Then move only specific project files into their @@ -61,6 +63,7 @@ file (GLOB CXBXR_HEADER_COMMON "${CXBXR_ROOT_DIR}/src/common/input/DInputKeyboardCodes.h" "${CXBXR_ROOT_DIR}/src/common/input/DInputKeyboardMouse.h" "${CXBXR_ROOT_DIR}/src/common/input/layout_xbox_device.h" + "${CXBXR_ROOT_DIR}/src/common/input/LibusbDevice.h" "${CXBXR_ROOT_DIR}/src/common/input/InputDevice.h" "${CXBXR_ROOT_DIR}/src/common/input/InputManager.h" "${CXBXR_ROOT_DIR}/src/common/input/SdlJoystick.h" @@ -102,6 +105,7 @@ file (GLOB CXBXR_HEADER_GUIv1 "${CXBXR_ROOT_DIR}/src/common/input/EmuDevice.h" "${CXBXR_ROOT_DIR}/src/common/input/InputWindow.h" "${CXBXR_ROOT_DIR}/src/gui/controllers/DlgDukeControllerConfig.h" + "${CXBXR_ROOT_DIR}/src/gui/controllers/DlgLibusbControllerConfig.h" "${CXBXR_ROOT_DIR}/src/gui/controllers/DlgSBControllerConfig.h" "${CXBXR_ROOT_DIR}/src/gui/DlgAbout.h" "${CXBXR_ROOT_DIR}/src/gui/DlgAudioConfig.h" @@ -234,6 +238,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" @@ -268,6 +273,7 @@ file (GLOB CXBXR_SOURCE_GUIv1 "${CXBXR_ROOT_DIR}/src/common/input/EmuDevice.cpp" "${CXBXR_ROOT_DIR}/src/common/input/InputWindow.cpp" "${CXBXR_ROOT_DIR}/src/gui/controllers/DlgDukeControllerConfig.cpp" + "${CXBXR_ROOT_DIR}/src/gui/controllers/DlgLibusbControllerConfig.cpp" "${CXBXR_ROOT_DIR}/src/gui/controllers/DlgSBControllerConfig.cpp" "${CXBXR_ROOT_DIR}/src/gui/DlgAbout.cpp" "${CXBXR_ROOT_DIR}/src/gui/DlgAudioConfig.cpp" @@ -423,7 +429,7 @@ add_subdirectory("${CMAKE_CURRENT_LIST_DIR}/projects/cxbxr-emu") set(cxbxr_INSTALL_files "COPYING" "README.md") # Cxbx-Reloaded project with third-party libraries -set_target_properties(cxbx cxbxr-ldr cxbxr-emu misc-batch SDL2 subhook libXbSymbolDatabase libtommath libtomcrypt imgui +set_target_properties(cxbx cxbxr-ldr cxbxr-emu misc-batch SDL2 subhook libXbSymbolDatabase libtommath libtomcrypt imgui libusb PROPERTIES FOLDER Cxbx-Reloaded ) diff --git a/README.md b/README.md index 69d2da304..8fb4a02bc 100644 --- a/README.md +++ b/README.md @@ -15,6 +15,7 @@ Cxbx-Reloaded is an emulator for running Microsoft Xbox (and eventually, Chihiro * [32-bit (x86) Visual C++ 2019 Redistributable](https://aka.ms/vs/16/release/vc_redist.x86.exe) * [Npcap *(used for network emulation)*](https://nmap.org/npcap/#download) * Make sure to enable winpcap compatibility mode! + * WinUSB compliant driver *(optional, only needed for USB pass-through of original xbox controllers and the steel battalion controller)* ### Wine **NOTICE: Please use the latest stable release version of Wine. If it does not work for you, then roll back to Wine 5.0.3 which is the last known working version.** diff --git a/import/libusb b/import/libusb new file mode 160000 index 000000000..8d7616886 --- /dev/null +++ b/import/libusb @@ -0,0 +1 @@ +Subproject commit 8d7616886e062f58d0de12db2acd5d81e06240bb diff --git a/projects/cxbx/CMakeLists.txt b/projects/cxbx/CMakeLists.txt index 89ddd8f7a..750a101c9 100644 --- a/projects/cxbx/CMakeLists.txt +++ b/projects/cxbx/CMakeLists.txt @@ -12,9 +12,10 @@ include_directories( "${CXBXR_ROOT_DIR}/src/common" "${CXBXR_ROOT_DIR}/src/common/Win32" "${CXBXR_ROOT_DIR}/import/OpenXDK/include" + "${CXBXR_ROOT_DIR}/import/DirectX9/include" "${CXBXR_ROOT_DIR}/import/distorm/include" "${CXBXR_ROOT_DIR}/import/glew-2.0.0/include" - "${CXBXR_ROOT_DIR}/import/DirectX9/include" + "${CXBXR_ROOT_DIR}/import/libusb/libusb" "${CXBXR_ROOT_DIR}/import/simpleini" "${CXBXR_ROOT_DIR}/import/winpcap/Include" "${CXBXR_ROOT_DIR}/import/xxHash" @@ -193,6 +194,7 @@ target_link_libraries(cxbx libtomcrypt SDL2 imgui + libusb ${WINS_LIB} ) diff --git a/projects/cxbxr-emu/CMakeLists.txt b/projects/cxbxr-emu/CMakeLists.txt index 602f01597..184c15eae 100644 --- a/projects/cxbxr-emu/CMakeLists.txt +++ b/projects/cxbxr-emu/CMakeLists.txt @@ -15,9 +15,10 @@ include_directories( "${CXBXR_ROOT_DIR}/src/common" "${CXBXR_ROOT_DIR}/src/common/Win32" "${CXBXR_ROOT_DIR}/import/OpenXDK/include" + "${CXBXR_ROOT_DIR}/import/DirectX9/include" "${CXBXR_ROOT_DIR}/import/distorm/include" "${CXBXR_ROOT_DIR}/import/glew-2.0.0/include" - "${CXBXR_ROOT_DIR}/import/DirectX9/include" + "${CXBXR_ROOT_DIR}/import/libusb/libusb" "${CXBXR_ROOT_DIR}/import/simpleini" "${CXBXR_ROOT_DIR}/import/winpcap/Include" "${CXBXR_ROOT_DIR}/import/xxHash" @@ -169,6 +170,7 @@ target_link_libraries(cxbxr-emu libtomcrypt SDL2 imgui + libusb ${WINS_LIB} ) diff --git a/projects/libusb/CMakeLists.txt b/projects/libusb/CMakeLists.txt new file mode 100644 index 000000000..fb9a71fef --- /dev/null +++ b/projects/libusb/CMakeLists.txt @@ -0,0 +1,51 @@ +cmake_minimum_required (VERSION 3.8) +project(libusb LANGUAGES CXX) +# Since libusb doesn't have CMake, we'll make an interface project here. + +if ("${CMAKE_CXX_COMPILER_ID}" STREQUAL "MSVC") + add_compile_definitions( + _CRT_SECURE_NO_WARNINGS + _CRT_NONSTDC_NO_DEPRECATE + ) +endif() + +include_directories( + "${CXBXR_ROOT_DIR}/import/libusb/msvc" + "${CXBXR_ROOT_DIR}/import/libusb/libusb") + +file (GLOB HEADERS + "${CXBXR_ROOT_DIR}/import/libusb/libusb/libusb.h" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/libusbi.h" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/version.h" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/version_nano.h" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/msvc/config.h" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/events_windows.h" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/threads_windows.h" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/windows_common.h" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/windows_usbdk.h" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/windows_winusb.h" +) + +file (GLOB SOURCES + "${CXBXR_ROOT_DIR}/import/libusb/libusb/core.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/descriptor.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/hotplug.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/io.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/strerror.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/sync.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/events_windows.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/threads_windows.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/windows_common.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/windows_usbdk.c" + "${CXBXR_ROOT_DIR}/import/libusb/libusb/os/windows_winusb.c" +) + +source_group(TREE ${CXBXR_ROOT_DIR}/import/libusb/libusb PREFIX header FILES ${HEADERS}) + +source_group(TREE ${CXBXR_ROOT_DIR}/import/libusb/libusb PREFIX source FILES ${SOURCES}) + +add_library(${PROJECT_NAME} ${HEADERS} ${SOURCES}) + +target_include_directories(${PROJECT_NAME} + PUBLIC "${CXBXR_ROOT_DIR}/import/libusb" +) 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/Settings.cpp b/src/common/Settings.cpp index c3574a6b9..b4735bbc3 100644 --- a/src/common/Settings.cpp +++ b/src/common/Settings.cpp @@ -785,19 +785,21 @@ void Settings::SyncToEmulator() g_EmuShared->SetInputSlotTypeSettings(&m_input_port[i].SlotType[SLOT_BOTTOM], i, SLOT_BOTTOM); if (m_input_port[i].Type != to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID)) { g_EmuShared->SetInputDevNameSettings(m_input_port[i].DeviceName.c_str(), i); - auto it = std::find_if(m_input_profiles[m_input_port[i].Type].begin(), - m_input_profiles[m_input_port[i].Type].end(), [this, i](const auto& profile) { - if (profile.ProfileName == m_input_port[i].ProfileName) { - return true; + if (m_input_port[i].Type < to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX)) { + auto it = std::find_if(m_input_profiles[m_input_port[i].Type].begin(), + m_input_profiles[m_input_port[i].Type].end(), [this, i](const auto &profile) { + if (profile.ProfileName == m_input_port[i].ProfileName) { + return true; + } + return false; + }); + if (it != m_input_profiles[m_input_port[i].Type].end()) { + char controls_name[HIGHEST_NUM_BUTTONS][HOST_BUTTON_NAME_LENGTH]; + for (int index = 0; index < dev_num_buttons[m_input_port[i].Type]; index++) { + strncpy(controls_name[index], it->ControlList[index].c_str(), 30); } - return false; - }); - if (it != m_input_profiles[m_input_port[i].Type].end()) { - char controls_name[HIGHEST_NUM_BUTTONS][HOST_BUTTON_NAME_LENGTH]; - for (int index = 0; index < dev_num_buttons[m_input_port[i].Type]; index++) { - strncpy(controls_name[index], it->ControlList[index].c_str(), 30); + g_EmuShared->SetInputBindingsSettings(controls_name, dev_num_buttons[m_input_port[i].Type], i); } - g_EmuShared->SetInputBindingsSettings(controls_name, dev_num_buttons[m_input_port[i].Type], i); } } } 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..343caa791 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 @@ -85,17 +86,19 @@ void InputDeviceManager::Initialize(bool is_gui, HWND hwnd) XInput::Init(m_Mtx); RawInput::Init(m_Mtx, is_gui, m_hwnd); + Libusb::Init(m_Mtx); Sdl::Init(m_Mtx, m_Cv, is_gui); }); 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"); } @@ -131,6 +134,8 @@ void InputDeviceManager::Initialize(bool is_gui, HWND hwnd) case to_underlying(XBOX_INPUT_DEVICE::ARCADE_STICK): case to_underlying(XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER): + case to_underlying(XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER): + case to_underlying(XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER): ConstructHleInputDevice(&g_devs[CTRL_OFFSET + i], nullptr, type, port); BindHostDevice(type, port); break; @@ -162,6 +167,7 @@ void InputDeviceManager::Shutdown() XInput::DeInit(); RawInput::DeInit(); + Libusb::DeInit(); Sdl::DeInit(m_PollingThread); } @@ -313,6 +319,19 @@ void InputDeviceManager::BindHostDevice(int type, std::string_view port) // MUs don't have any host devices bound, so we just return return; } + else if ((type == to_underlying(XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER)) || + (type == to_underlying(XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER))) { + // libusb devices don't have any profiles, but we still need to attach them to the xbox port + char dev_name[50]; + int port_num, slot; + PortStr2Int(port, &port_num, &slot); + g_EmuShared->GetInputDevNameSettings(dev_name, port_num); + auto dev = FindDevice(std::string(dev_name)); + if (dev != nullptr) { + dev->SetPort(port, true); + } + return; + } char dev_name[50]; char dev_control_names[HIGHEST_NUM_BUTTONS][HOST_BUTTON_NAME_LENGTH]; @@ -343,8 +362,6 @@ void InputDeviceManager::BindHostDevice(int type, std::string_view port) bool InputDeviceManager::UpdateXboxPortInput(int port, void* buffer, int direction, int type) { assert(direction == DIRECTION_IN || direction == DIRECTION_OUT); - assert(type > to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID) && - type < to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX)); bool has_changed = false; // First check if ImGui is focus, then ignore any input update occur. @@ -371,6 +388,12 @@ bool InputDeviceManager::UpdateXboxPortInput(int port, void* buffer, int directi assert(0); break; + case to_underlying(XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER): + case to_underlying(XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER): + has_changed = UpdateInputHw(dev, buffer, direction); + m_Mtx.unlock(); + return has_changed; + case to_underlying(XBOX_INPUT_DEVICE::LIGHT_GUN): case to_underlying(XBOX_INPUT_DEVICE::STEERING_WHEEL): case to_underlying(XBOX_INPUT_DEVICE::IR_DONGLE): @@ -397,8 +420,7 @@ bool InputDeviceManager::UpdateInputXpad(std::shared_ptr& Device, v return false; } - //XpadInput* in_buf = reinterpret_cast(static_cast(Buffer) + 2); lle usb - XpadInput *in_buf = static_cast(Buffer); + XpadInput* in_buf = reinterpret_cast(static_cast(Buffer) + XID_PACKET_HEADER); for (int i = 0; i < 8; i++) { ControlState state = (bindings[i] != nullptr) ? dynamic_cast(bindings[i])->GetState() : 0.0; if (state) { @@ -441,8 +463,7 @@ bool InputDeviceManager::UpdateInputXpad(std::shared_ptr& Device, v } else { if (bindings[24] != nullptr) { - //XpadOutput* out_buf = reinterpret_cast(static_cast(Buffer) + 2); lle usb - XpadOutput* out_buf = reinterpret_cast(Buffer); + XpadOutput* out_buf = reinterpret_cast(static_cast(Buffer) + XID_PACKET_HEADER); dynamic_cast(bindings[24])->SetState(out_buf->left_actuator_strength / static_cast(0xFFFF), out_buf->right_actuator_strength / static_cast(0xFFFF)); } @@ -475,7 +496,7 @@ bool InputDeviceManager::UpdateInputSBC(std::shared_ptr& Device, vo // 9 -> GearLever Up // 10 -> GearLever Down static uint16_t last_in_state[XBOX_NUM_PORTS] = { 0, 0, 0, 0 }; - SBCInput *in_buf = static_cast(Buffer); + SBCInput *in_buf = reinterpret_cast(static_cast(Buffer) + XID_PACKET_HEADER); for (int i = 0; i < 4; i++) { ControlState state = (bindings[i] != nullptr) ? dynamic_cast(bindings[i])->GetState() : 0.0; if (state) { @@ -607,6 +628,11 @@ bool InputDeviceManager::UpdateInputSBC(std::shared_ptr& Device, vo return true; } +bool InputDeviceManager::UpdateInputHw(std::shared_ptr &Device, void *Buffer, int Direction) +{ + return dynamic_cast(Device.get())->ExecuteIo(Buffer, Direction); +} + void InputDeviceManager::RefreshDevices() { std::unique_lock lck(m_Mtx); @@ -616,6 +642,7 @@ void InputDeviceManager::RefreshDevices() XInput::PopulateDevices(); DInput::PopulateDevices(); Sdl::PopulateDevices(); + Libusb::PopulateDevices(); lck.lock(); m_Cv.wait(lck, []() { return Sdl::PopulateOK; @@ -628,13 +655,15 @@ void InputDeviceManager::RefreshDevices() } } -std::vector InputDeviceManager::GetDeviceList() const +std::vector InputDeviceManager::GetDeviceList(std::function Callback) const { std::vector dev_list; std::lock_guard lck(m_Mtx); - std::for_each(m_Devices.begin(), m_Devices.end(), [&dev_list](const auto& Device) { - dev_list.push_back(Device->GetQualifiedName()); + std::for_each(m_Devices.begin(), m_Devices.end(), [&dev_list, &Callback](const auto& Device) { + if (Callback(Device.get())) { + dev_list.push_back(Device->GetQualifiedName()); + } }); return dev_list; @@ -719,7 +748,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 +759,9 @@ void InputDeviceManager::HotplugHandler(bool is_sdl) lck.unlock(); XInput::PopulateDevices(); + // When this was written, libusb did not yet support device hotplug on Windows, as documented in this issue https://github.com/libusb/libusb/issues/86. + // 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/InputManager.h b/src/common/input/InputManager.h index e47f2cfdf..b9d3df104 100644 --- a/src/common/input/InputManager.h +++ b/src/common/input/InputManager.h @@ -52,6 +52,8 @@ #define MU_OFFSET 4 #define MAX_DEVS (XBOX_NUM_PORTS + XBOX_CTRL_NUM_SLOTS * XBOX_NUM_PORTS) +#define XID_PACKET_HEADER 2 + extern int dev_num_buttons[to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX)]; inline XBOX_INPUT_DEVICE input_support_list[] = { @@ -60,6 +62,8 @@ inline XBOX_INPUT_DEVICE input_support_list[] = { XBOX_INPUT_DEVICE::MS_CONTROLLER_S, XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER, XBOX_INPUT_DEVICE::ARCADE_STICK, + XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER, + XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER, }; inline XBOX_INPUT_DEVICE slot_support_list[] = { @@ -71,7 +75,19 @@ inline XBOX_INPUT_DEVICE slot_support_list[] = { #pragma pack(1) -// xpad in/out buffers stripped of the first two bytes +// Class-specific xid descriptor, used by libusb +struct XidDesc { + uint8_t bLength; + uint8_t bDescriptorType; + uint16_t bcdXid; + uint8_t bType; + uint8_t bSubType; + uint8_t bMaxInputReportSize; + uint8_t bMaxOutputReportSize; + uint16_t wAlternateProductIds[4]; +}; + +// xpad in/out buffers stripped of the first two bytes as used by xinput struct XpadInput { uint16_t wButtons; uint8_t bAnalogButtons[8]; @@ -86,6 +102,20 @@ struct XpadOutput { uint16_t right_actuator_strength; }; +// xpad in/out buffers as used by xid +struct XidGamepadInput { + uint8_t bReportId; + uint8_t bLength; + XpadInput InBuffer; +}; + +struct XidGamepadOutput { + uint8_t bReportId; + uint8_t bLength; + XpadOutput OutBuffer; +}; + +// same as above, but for the SBC struct SBCInput { uint16_t wButtons[3]; uint8_t bPad1; @@ -112,11 +142,23 @@ struct SBCOutput { uint8_t LedState[20]; }; +struct XidSBCInput { + uint8_t bReportId; + uint8_t bLength; + SBCInput InBuffer; +}; + +struct XidSBCOutput { + uint8_t bReportId; + uint8_t bLength; + SBCOutput OutBuffer; +}; + #pragma pack() union InputBuff { - XpadInput ctrl; - SBCInput sbc; + XidGamepadInput ctrl; + XidSBCInput sbc; }; struct DeviceInfo { @@ -159,7 +201,7 @@ public: // update device list void RefreshDevices(); // get the name of the devices currently detected - std::vector GetDeviceList() const; + std::vector GetDeviceList(std::function Callback) const; // find device from its gui name std::shared_ptr FindDevice(const std::string& QualifiedName) const; // find device from its sdl id @@ -179,6 +221,8 @@ private: bool UpdateInputXpad(std::shared_ptr& Device, void* Buffer, int Direction, const std::string &Port); // update input for a Steel Battalion controller bool UpdateInputSBC(std::shared_ptr& Device, void* Buffer, int Direction, int Port_num, const std::string &Port); + // update input for a passthrough xbox device + bool UpdateInputHw(std::shared_ptr &Device, void *Buffer, int Direction); // bind a host device to an emulated device void BindHostDevice(int type, std::string_view port); // connect a device to the emulated machine diff --git a/src/common/input/InputWindow.cpp b/src/common/input/InputWindow.cpp index 2caec9292..8104504d4 100644 --- a/src/common/input/InputWindow.cpp +++ b/src/common/input/InputWindow.cpp @@ -78,16 +78,22 @@ void InputWindow::UpdateDeviceList() g_InputDeviceManager.RefreshDevices(); // Populate device list - LRESULT num_devices = SendMessage(m_hwnd_device_list, CB_GETCOUNT, 0, 0); - for (int i = 0; i < num_devices; i++) { + for (int i = 0; i < m_num_devices; i++) { SendMessage(m_hwnd_device_list, CB_DELETESTRING, 0, 0); } - std::vector dev_list = g_InputDeviceManager.GetDeviceList(); + // Add everything but libusb devices + std::vector dev_list = g_InputDeviceManager.GetDeviceList([](const auto &Device) { + if (Device->IsLibusb()) { + return false; + } + return true; + }); for (const auto& str : dev_list) { SendMessage(m_hwnd_device_list, CB_ADDSTRING, 0, reinterpret_cast(str.c_str())); + ++m_num_devices; } - if (!dev_list.empty()) { + if (m_num_devices) { SendMessage(m_hwnd_device_list, CB_SETCURSEL, 0, 0); } @@ -310,9 +316,15 @@ void InputWindow::LoadDefaultProfile() void InputWindow::UpdateCurrentDevice() { - char device_name[50]; - SendMessage(m_hwnd_device_list, WM_GETTEXT, sizeof(device_name), reinterpret_cast(device_name)); - m_host_dev = device_name; + if (m_num_devices) { + char device_name[50]; + SendMessage(m_hwnd_device_list, WM_GETTEXT, sizeof(device_name), reinterpret_cast(device_name)); + m_host_dev = device_name; + } + else { + m_host_dev = ""; + } + EnableDefaultButton(); } diff --git a/src/common/input/InputWindow.h b/src/common/input/InputWindow.h index 4df21cae4..3f61222a4 100644 --- a/src/common/input/InputWindow.h +++ b/src/common/input/InputWindow.h @@ -55,7 +55,7 @@ class InputWindow public: virtual void Initialize(HWND hwnd, int port_num, int dev_type) = 0; ~InputWindow(); - void UpdateDeviceList(); + virtual void UpdateDeviceList(); void BindButton(int ControlID); virtual void ClearBindings() = 0; virtual void UpdateProfile(const std::string& name, int command); @@ -84,6 +84,8 @@ protected: HWND m_hwnd_device_list; // handle of the profile list combobox HWND m_hwnd_profile_list; + // number of devices displayed in the device list combobox + int m_num_devices; // type of the device int m_dev_type; // num of buttons of device under configuration @@ -137,3 +139,21 @@ public: private: int EnableDefaultButton() override; }; + +class LibusbInputWindow : public InputWindow +{ +public: + ~LibusbInputWindow(); + void Initialize(HWND hwnd, int port_num, int dev_type) override; + void ClearBindings() override; + void SaveSlotConfig() override; + void UpdateDeviceList() override; + void TestInput(); + + +private: + int EnableDefaultButton() override; + + // handle of the test button + HWND m_hwnd_device_test; +}; diff --git a/src/common/input/LibusbDevice.cpp b/src/common/input/LibusbDevice.cpp new file mode 100644 index 000000000..c21b32f6c --- /dev/null +++ b/src/common/input/LibusbDevice.cpp @@ -0,0 +1,316 @@ +// 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" +#include "core\hle\XAPI\Xapi.h" + +// Sanity check: ensure that our libusb version is high enough for libusb_get_device_descriptor to succeed and to pass nullptr to libusb_interrupt_transfer +static_assert(LIBUSB_API_VERSION >= 0x01000105); + + +namespace Libusb +{ + int InitStatus = NOT_INIT; + + // 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 + libusb_device **List; + 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); + } + + void GetDeviceChanges() + { + g_InputDeviceManager.RemoveDevice([](const auto &Device) { + return Device->IsLibusb(); + }); + PopulateDevices(); + } + + 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_UcType = XINPUT_DEVTYPE_STEELBATTALION; + m_UcSubType = XINPUT_DEVSUBTYPE_GC_GAMEPAD; + m_Name = "Steel battalion controller"; + m_BufferInSize = sizeof(XidSBCInput); + m_BufferOutSize = sizeof(XidSBCOutput); + 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_UcType = XINPUT_DEVTYPE_GAMEPAD; + m_UcSubType = XINPUT_DEVSUBTYPE_GC_GAMEPAD; + m_Name = SupportedDevices_Name[i]; + m_BufferInSize = sizeof(XidGamepadInput); + m_BufferOutSize = sizeof(XidGamepadOutput); + 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 *ConfDesc; + if (libusb_get_active_config_descriptor(Dev, &ConfDesc) == 0) { + if (ConfDesc->bNumInterfaces == 1) { + auto Iface = ConfDesc->interface[0]; + if (Iface.num_altsetting == 1) { + auto Setting = Iface.altsetting[0]; + m_IfaceNum = Setting.bInterfaceNumber; + if (Setting.bNumEndpoints >= 1) { + m_HasEndpointOut = false; + for (uint8_t i = 0; i < Setting.bNumEndpoints; ++i) { + auto Endpoint = Setting.endpoint[i]; + if (Endpoint.bEndpointAddress & 0x80) { + m_EndpointIn = Endpoint.bEndpointAddress; + m_IntervalIn = Endpoint.bInterval; + } + else { + // third-party controllers that don't support rumble probably won't have an out endpoint + m_EndpointOut = Endpoint.bEndpointAddress; + m_IntervalOut = Endpoint.bInterval; + m_HasEndpointOut = true; + } + } + EmuLog(LOG_LEVEL::INFO, "Out endpoint %s", m_HasEndpointOut ? "present" : "not present"); + if (int err = libusb_claim_interface(m_hDev, m_IfaceNum) != 0) { + EmuLog(LOG_LEVEL::INFO, "Rejected device %s because libusb could not claim its interface. The error was: %s", + m_Name.c_str(), libusb_strerror(err)); + m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID; + } + else { + // Grab the xid descriptor so that we can report real type/subtype values back to the title when it calls XInputGetCapabilities + XidDesc XidDesc; + if (libusb_control_transfer(m_hDev, 0xC1, 6, 0x4200, m_IfaceNum, reinterpret_cast(&XidDesc), sizeof(XidDesc), m_IntervalIn) + == sizeof(XidDesc)) { // submit a GET_DESCRIPTOR request + + // Dump the xid descriptor to the log + EmuLog(LOG_LEVEL::INFO, "Xid descriptor dump:\nbLength: %#010X\nbDescriptorType: %#010X\nbcdXid: %#010X\nbType: %#010X\n" + "bSubType: %#010X\nbMaxInputReportSize: %#010X\nbMaxOutputReportSize: %#010X\nwAlternateProductIds[0]: %#010X\n" + "wAlternateProductIds[1]: %#010X\nwAlternateProductIds[2]: %#010X\nwAlternateProductIds[3]: %#010X\n", + XidDesc.bLength, XidDesc.bDescriptorType, XidDesc.bcdXid, XidDesc.bType, XidDesc.bSubType, XidDesc.bMaxInputReportSize, XidDesc.bMaxOutputReportSize, + XidDesc.wAlternateProductIds[0], XidDesc.wAlternateProductIds[1], XidDesc.wAlternateProductIds[2], XidDesc.wAlternateProductIds[3]); + + if (XidDesc.bDescriptorType == 0x42) { + m_UcType = XidDesc.bType; + m_UcSubType = XidDesc.bSubType; + } + else { + EmuLog(LOG_LEVEL::INFO, "The xid descriptor for device %s reported an unexpected descriptor type, assuming a default subtype", m_Name); + } + } + else { + EmuLog(LOG_LEVEL::INFO, "Could not retrieve the xid descriptor for device %s, assuming a default subtype", m_Name); + } + } + } + else { + EmuLog(LOG_LEVEL::INFO, "Rejected device %s because of unexpected number of endpoints, bNumEndpoints: %d", + m_Name.c_str(), Setting.bNumEndpoints); + m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID; + } + } + else { + EmuLog(LOG_LEVEL::INFO, "Rejected device %s because of unexpected number of alternative settings, num_altsetting: %d", + m_Name.c_str(), Iface.num_altsetting); + m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID; + } + } + else { + EmuLog(LOG_LEVEL::INFO, "Rejected device %s because of unexpected number of interfaces, bNumInterfaces: %d", + m_Name.c_str(), ConfDesc->bNumInterfaces); + m_Type = XBOX_INPUT_DEVICE::DEVICE_INVALID; + } + libusb_free_config_descriptor(ConfDesc); + } + } + + if (m_Type == XBOX_INPUT_DEVICE::DEVICE_INVALID) { libusb_close(m_hDev); } + } + + LibusbDevice::~LibusbDevice() + { + if (m_Type != XBOX_INPUT_DEVICE::DEVICE_INVALID) { + libusb_release_interface(m_hDev, m_IfaceNum); + libusb_close(m_hDev); + } + } + + bool LibusbDevice::UpdateInput() + { + // Dummy, it should never be called. It's only here to override the pure function UpdateInput in InputDevice + assert(0); + return false; + } + + bool LibusbDevice::ExecuteIo(void *Buffer, int Direction) + { + // NOTE: a SET_REPORT control transfer to the SBC doesn't seem to work, the parameters might not be appropriate for it... So, we use + // the interrupt pipes for everything instead + *static_cast(Buffer) = 0; // write bReportId + if (Direction == DIRECTION_IN) { + *(static_cast(Buffer) + 1) = m_BufferInSize; // write bLength + if (libusb_interrupt_transfer(m_hDev, m_EndpointIn, static_cast(Buffer), m_BufferInSize, nullptr, m_IntervalIn) != 0) { + return false; + } + } + else { + if (m_HasEndpointOut) { + *(static_cast(Buffer) + 1) = m_BufferOutSize; // write bLength + if (libusb_interrupt_transfer(m_hDev, m_EndpointOut, static_cast(Buffer), m_BufferOutSize, nullptr, m_IntervalOut) != 0) { + return false; + } + } + } + + return true; + } + + 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..7ed85a1fd --- /dev/null +++ b/src/common/input/LibusbDevice.h @@ -0,0 +1,91 @@ +// 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: + ~LibusbDevice(); + bool UpdateInput() override; + bool ExecuteIo(void *Buffer, int Direction); + + 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; } + XBOX_INPUT_DEVICE GetLibusbType() const { return m_Type; } + uint8_t GetUcType() { return m_UcType; } + uint8_t GetUcSubType() { return m_UcSubType; } + + + private: + XBOX_INPUT_DEVICE m_Type; + uint8_t m_UcType; + uint8_t m_UcSubType; + 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; + uint8_t m_BufferInSize; + uint8_t m_BufferOutSize; + uint8_t m_IfaceNum; + bool m_HasEndpointOut; + }; +} 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/core/hle/XAPI/Xapi.cpp b/src/core/hle/XAPI/Xapi.cpp index d1c968ece..10df2b86d 100644 --- a/src/core/hle/XAPI/Xapi.cpp +++ b/src/core/hle/XAPI/Xapi.cpp @@ -33,6 +33,7 @@ #include "common\input\SdlJoystick.h" #include "common\input\InputManager.h" #include +#include "common\input\LibusbDevice.h" // include this after Shlwapi.h or else it causes an error #include "core\kernel\init\CxbxKrnl.h" #include "Logging.h" #include "core\kernel\support\Emu.h" @@ -113,14 +114,16 @@ bool operator==(xbox::PXPP_DEVICE_TYPE XppType, XBOX_INPUT_DEVICE XidType) { case XBOX_INPUT_DEVICE::MS_CONTROLLER_DUKE: case XBOX_INPUT_DEVICE::MS_CONTROLLER_S: - case XBOX_INPUT_DEVICE::ARCADE_STICK: { + case XBOX_INPUT_DEVICE::ARCADE_STICK: + case XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER: { if (XppType == g_DeviceType_Gamepad) { return true; } } break; - case XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER: { + case XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER: + case XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER: { if (XppType == g_DeviceType_SBC) { return true; } @@ -152,10 +155,12 @@ void UpdateXppState(DeviceState *dev, XBOX_INPUT_DEVICE type, std::string_view p case XBOX_INPUT_DEVICE::MS_CONTROLLER_DUKE: case XBOX_INPUT_DEVICE::MS_CONTROLLER_S: case XBOX_INPUT_DEVICE::ARCADE_STICK: + case XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER: xpp = g_DeviceType_Gamepad; break; case XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER: + case XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER: xpp = g_DeviceType_SBC; break; @@ -208,6 +213,7 @@ void ConstructHleInputDevice(DeviceState *dev, DeviceState *upstream, int type, g_bIsDevicesEmulating = false; return; } + // Set up common device state int port_num, slot; PortStr2Int(port, &port_num, &slot); @@ -223,12 +229,22 @@ void ConstructHleInputDevice(DeviceState *dev, DeviceState *upstream, int type, switch (type) { case to_underlying(XBOX_INPUT_DEVICE::MS_CONTROLLER_DUKE): + case to_underlying(XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER): dev->type = XBOX_INPUT_DEVICE::MS_CONTROLLER_DUKE; dev->info.bAutoPollDefault = true; dev->info.ucType = XINPUT_DEVTYPE_GAMEPAD; dev->info.ucSubType = XINPUT_DEVSUBTYPE_GC_GAMEPAD; dev->info.ucInputStateSize = sizeof(XpadInput); dev->info.ucFeedbackSize = sizeof(XpadOutput); + if (type == to_underlying(XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER)) { + dev->type = XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER; + char dev_name[50]; + g_EmuShared->GetInputDevNameSettings(dev_name, port_num); + if (auto Device = g_InputDeviceManager.FindDevice(std::string(dev_name))) { + dev->info.ucType = dynamic_cast(Device.get())->GetUcType(); + dev->info.ucSubType = dynamic_cast(Device.get())->GetUcSubType(); + } + } break; case to_underlying(XBOX_INPUT_DEVICE::MS_CONTROLLER_S): @@ -241,15 +257,25 @@ void ConstructHleInputDevice(DeviceState *dev, DeviceState *upstream, int type, break; case to_underlying(XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER): + case to_underlying(XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER): dev->type = XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER; - dev->info.buff.sbc.ucGearLever = 8; - dev->info.buff.sbc.sAimingX = static_cast(0x7F); - dev->info.buff.sbc.sAimingY = static_cast(0x7F); + dev->info.buff.sbc.InBuffer.ucGearLever = 8; + dev->info.buff.sbc.InBuffer.sAimingX = static_cast(0x7F); + dev->info.buff.sbc.InBuffer.sAimingY = static_cast(0x7F); dev->info.bAutoPollDefault = true; dev->info.ucType = XINPUT_DEVTYPE_STEELBATTALION; - dev->info.ucSubType = XINPUT_DEVSUBTYPE_GC_GAMEPAD_ALT; + dev->info.ucSubType = XINPUT_DEVSUBTYPE_GC_GAMEPAD; dev->info.ucInputStateSize = sizeof(SBCInput); dev->info.ucFeedbackSize = sizeof(SBCOutput); + if (type == to_underlying(XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER)) { + dev->type = XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER; + char dev_name[50]; + g_EmuShared->GetInputDevNameSettings(dev_name, port_num); + if (auto Device = g_InputDeviceManager.FindDevice(std::string(dev_name))) { + dev->info.ucType = dynamic_cast(Device.get())->GetUcType(); + dev->info.ucSubType = dynamic_cast(Device.get())->GetUcSubType(); + } + } break; case to_underlying(XBOX_INPUT_DEVICE::ARCADE_STICK): @@ -305,7 +331,7 @@ void DestructHleInputDevice(DeviceState *dev) dev->info.ucInputStateSize = 0; dev->info.ucFeedbackSize = 0; dev->info.dwPacketNumber = 0; - std::memset(&dev->info.buff.ctrl, 0, sizeof(XpadInput)); + std::memset(&dev->info.buff.ctrl.InBuffer, 0, sizeof(XpadInput)); break; case XBOX_INPUT_DEVICE::STEEL_BATTALION_CONTROLLER: @@ -315,7 +341,7 @@ void DestructHleInputDevice(DeviceState *dev) dev->info.ucInputStateSize = 0; dev->info.ucFeedbackSize = 0; dev->info.dwPacketNumber = 0; - std::memset(&dev->info.buff.sbc, 0, sizeof(SBCInput)); + std::memset(&dev->info.buff.sbc.InBuffer, 0, sizeof(SBCInput)); break; case XBOX_INPUT_DEVICE::ARCADE_STICK: @@ -325,7 +351,7 @@ void DestructHleInputDevice(DeviceState *dev) dev->info.ucInputStateSize = 0; dev->info.ucFeedbackSize = 0; dev->info.dwPacketNumber = 0; - std::memset(&dev->info.buff.ctrl, 0, sizeof(XpadInput)); + std::memset(&dev->info.buff.ctrl.InBuffer, 0, sizeof(XpadInput)); break; case XBOX_INPUT_DEVICE::MEMORY_UNIT: { @@ -454,7 +480,7 @@ xbox::dword_xt CxbxImpl_XInputHandler(xbox::HANDLE hDevice, xbox::PXINPUT_STATE } if constexpr (!IsXInputPoll) { - std::memcpy((void *)&pState->Gamepad, &g_devs[port].info.buff, g_devs[port].info.ucInputStateSize); + std::memcpy((void *)&pState->Gamepad, reinterpret_cast(&g_devs[port].info.buff) + 2, g_devs[port].info.ucInputStateSize); pState->dwPacketNumber = g_devs[port].info.dwPacketNumber; } @@ -747,7 +773,7 @@ xbox::dword_xt WINAPI xbox::EMUPATCH(XInputSetState) int port = dev->port_idx; if (g_devs[port].info.hHandle == hDevice && !g_devs[port].bPendingRemoval) { pFeedback->Header.dwStatus = ERROR_IO_PENDING; - g_InputDeviceManager.UpdateXboxPortInput(port, (void*)&pFeedback->Rumble, DIRECTION_OUT, to_underlying(g_devs[port].type)); + g_InputDeviceManager.UpdateXboxPortInput(port, (void*)&pFeedback->Header.bReportId, DIRECTION_OUT, to_underlying(g_devs[port].type)); pFeedback->Header.dwStatus = ERROR_SUCCESS; if (pFeedback->Header.hEvent != NULL && ObReferenceObjectByHandle(pFeedback->Header.hEvent, &xbox::ExEventObjectType, (PVOID*)&pFeedback->Header.IoCompletedEvent) == ERROR_SUCCESS) { diff --git a/src/core/hle/XAPI/Xapi.h b/src/core/hle/XAPI/Xapi.h index 1b5d2949f..e28f12465 100644 --- a/src/core/hle/XAPI/Xapi.h +++ b/src/core/hle/XAPI/Xapi.h @@ -214,7 +214,6 @@ XINPUT_CAPABILITIES, *PXINPUT_CAPABILITIES; // ****************************************************************** //general GAMEPAD uses subtype 0x01. #define XINPUT_DEVSUBTYPE_GC_GAMEPAD 0x01 -//SteelBatallion controller uses subtype 0x02 #define XINPUT_DEVSUBTYPE_GC_GAMEPAD_ALT 0x02 #define XINPUT_DEVSUBTYPE_GC_WHEEL 0x10 #define XINPUT_DEVSUBTYPE_GC_ARCADE_STICK 0x20 @@ -243,16 +242,18 @@ XINPUT_STATE, *PXINPUT_STATE; // ****************************************************************** // * XINPUT_FEEDBACK_HEADER // ****************************************************************** -#include "AlignPrefix1.h" +#pragma pack(1) typedef struct _XINPUT_FEEDBACK_HEADER { - xbox::dword_xt dwStatus; + xbox::dword_xt dwStatus; HANDLE OPTIONAL hEvent; - xbox::byte_xt Unknown1[4]; + xbox::byte_xt Unknown1[4]; PVOID IoCompletedEvent; // PKEVENT really - xbox::byte_xt Unknown2[50]; + xbox::byte_xt Unknown2[48]; + xbox::byte_xt bReportId; + xbox::byte_xt bLength; } -#include "AlignPosfix1.h" + XINPUT_FEEDBACK_HEADER, *PXINPUT_FEEDBACK_HEADER; // ****************************************************************** @@ -268,7 +269,7 @@ typedef struct _XINPUT_FEEDBACK }; } XINPUT_FEEDBACK, *PXINPUT_FEEDBACK; - +#pragma pack() // ****************************************************************** // * RTL_HEAP_PARAMETERS // ****************************************************************** diff --git a/src/devices/usb/XidGamepad.cpp b/src/devices/usb/XidGamepad.cpp index 63d5b1331..deaaa6f97 100644 --- a/src/devices/usb/XidGamepad.cpp +++ b/src/devices/usb/XidGamepad.cpp @@ -78,18 +78,6 @@ XidGamepad* g_XidControllerObjArray[4]; #pragma pack(1) -/* Class-specific xid descriptor */ -struct XIDDesc { - uint8_t bLength; - uint8_t bDescriptorType; - uint16_t bcdXid; - uint8_t bType; - uint8_t bSubType; - uint8_t bMaxInputReportSize; - uint8_t bMaxOutputReportSize; - uint16_t wAlternateProductIds[4]; -}; - /* Struct used by the Get_Report request -> button's state */ struct XIDGamepadReport { uint8_t bReportId; @@ -116,7 +104,7 @@ struct USBXIDState { XboxDeviceState dev; // gamepad device status USBEndpoint* intr; // interrupt endpoint of the gamepad - const XIDDesc* xid_desc; // xid-specific descriptor + const XidDesc* xid_desc; // xid-specific descriptor XIDGamepadReport in_state; // Get_Report struct XIDGamepadReport in_state_capabilities; // Get_Capabilities struct (in) @@ -192,7 +180,7 @@ static const USBDesc desc_xbox_gamepad = { &desc_device_xbox_gamepad }; -static const XIDDesc desc_xid_xbox_gamepad = { +static const XidDesc desc_xid_xbox_gamepad = { 0x10, // bLength USB_DT_XID, // bDescriptorType 0x100, // bcdXid diff --git a/src/gui/DlgInputConfig.cpp b/src/gui/DlgInputConfig.cpp index 69b0fa319..3cc0cbe5b 100644 --- a/src/gui/DlgInputConfig.cpp +++ b/src/gui/DlgInputConfig.cpp @@ -28,6 +28,7 @@ #include "windows.h" #include "controllers/DlgDukeControllerConfig.h" #include "controllers/DlgSBControllerConfig.h" +#include "controllers/DlgLibusbControllerConfig.h" #include "resource/ResCxbx.h" #include "input\InputManager.h" #include "Logging.h" @@ -43,6 +44,7 @@ LRESULT CALLBACK WindowsCtrlSubProcNumericFilter(HWND hWnd, UINT uMsg, WPARAM wP HWND g_ChildWnd = NULL; static bool g_bHasOptChanges = false; static bool g_bHasInputChanges[4] = { false, false, false, false }; +static int g_ConfiguredDeviceType[4]; void SyncInputSettings(int port_num, int dev_type, bool is_opt) @@ -59,19 +61,23 @@ void SyncInputSettings(int port_num, int dev_type, bool is_opt) std::string profile_name = g_Settings->m_input_port[port_num].ProfileName; g_EmuShared->SetInputDevNameSettings(dev_name.c_str(), port_num); - auto it = std::find_if(g_Settings->m_input_profiles[dev_type].begin(), - g_Settings->m_input_profiles[dev_type].end(), [&profile_name](const auto &profile) { - if (profile.ProfileName == profile_name) { - return true; + // passthrough devices don't have profiles so skip those + if ((dev_type != to_underlying(XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER)) && + (dev_type != to_underlying(XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER))) { + auto it = std::find_if(g_Settings->m_input_profiles[dev_type].begin(), + g_Settings->m_input_profiles[dev_type].end(), [&profile_name](const auto &profile) { + if (profile.ProfileName == profile_name) { + return true; + } + return false; + }); + if (it != g_Settings->m_input_profiles[dev_type].end()) { + char controls_name[HIGHEST_NUM_BUTTONS][HOST_BUTTON_NAME_LENGTH]; + for (int index = 0; index < dev_num_buttons[dev_type]; index++) { + strncpy(controls_name[index], it->ControlList[index].c_str(), 30); } - return false; - }); - if (it != g_Settings->m_input_profiles[dev_type].end()) { - char controls_name[HIGHEST_NUM_BUTTONS][HOST_BUTTON_NAME_LENGTH]; - for (int index = 0; index < dev_num_buttons[dev_type]; index++) { - strncpy(controls_name[index], it->ControlList[index].c_str(), 30); + g_EmuShared->SetInputBindingsSettings(controls_name, dev_num_buttons[dev_type], port_num); } - g_EmuShared->SetInputBindingsSettings(controls_name, dev_num_buttons[dev_type], port_num); } } } @@ -139,12 +145,12 @@ INT_PTR CALLBACK DlgInputConfigProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPAR } SendMessage(GetDlgItem(hWndDlg, IDC_IGNORE_KBMO_UNFOCUS), BM_SETCHECK, static_cast(g_Settings->m_input_general.IgnoreKbMoUnfocus), 0); - // Reset option/input changes flag + // Reset option/input changes flag and configured dev type g_bHasOptChanges = false; - g_bHasInputChanges[0] = false; - g_bHasInputChanges[1] = false; - g_bHasInputChanges[2] = false; - g_bHasInputChanges[3] = false; + for (int i = 0; i < 4; ++i) { + g_bHasInputChanges[i] = false; + g_ConfiguredDeviceType[i] = g_Settings->m_input_port[i].Type; + } } break; @@ -160,14 +166,12 @@ INT_PTR CALLBACK DlgInputConfigProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPAR if (g_bHasInputChanges[port]) { HWND hHandle = GetDlgItem(hWndDlg, IDC_DEVICE_PORT1 + port); int DeviceType = SendMessage(hHandle, CB_GETITEMDATA, SendMessage(hHandle, CB_GETCURSEL, 0, 0), 0); - g_Settings->m_input_port[port].Type = DeviceType; - if (DeviceType != to_underlying(XBOX_INPUT_DEVICE::MS_CONTROLLER_DUKE) && - DeviceType != to_underlying(XBOX_INPUT_DEVICE::MS_CONTROLLER_S)) { - // Forcefully set the child devices to none. This will happen if the user sets MUs in the controller dialog but - // then they set the parent device to a device that cannot support them in the input dialog - g_Settings->m_input_port[port].SlotType[SLOT_TOP] = to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID); - g_Settings->m_input_port[port].SlotType[SLOT_BOTTOM] = to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID); + // Handle the case where the user has configured the inputs for a device type, but then they changed it to a different one later + if ((DeviceType != g_ConfiguredDeviceType[port]) && (DeviceType != to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID))) { + DeviceType = g_ConfiguredDeviceType[port]; + EmuLogEx(CXBXR_MODULE::GUI, LOG_LEVEL::WARNING, "Mismatch between configured and current device, reverting to the original configured one."); } + g_Settings->m_input_port[port].Type = DeviceType; SyncInputSettings(port, DeviceType, false); } } @@ -196,8 +200,6 @@ INT_PTR CALLBACK DlgInputConfigProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPAR assert(port != -1); HWND hHandle = GetDlgItem(hWndDlg, IDC_DEVICE_PORT1 + port); int DeviceType = SendMessage(hHandle, CB_GETITEMDATA, SendMessage(hHandle, CB_GETCURSEL, 0, 0), 0); - assert(DeviceType > to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID) && - DeviceType < to_underlying(XBOX_INPUT_DEVICE::DEVICE_MAX)); switch (DeviceType) { @@ -215,10 +217,18 @@ INT_PTR CALLBACK DlgInputConfigProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPAR } break; + case to_underlying(XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER): + case to_underlying(XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER): { + DialogBoxParam(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDD_LIBUSB_CFG), hWndDlg, DlgLibUsbControllerConfigProc, + (DeviceType << 8) | port); + } + break; + default: break; } + g_ConfiguredDeviceType[port] = DeviceType; g_bHasInputChanges[port] = true; } } 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/controllers/DlgDukeControllerConfig.cpp b/src/gui/controllers/DlgDukeControllerConfig.cpp index 5f66839e2..8833001ea 100644 --- a/src/gui/controllers/DlgDukeControllerConfig.cpp +++ b/src/gui/controllers/DlgDukeControllerConfig.cpp @@ -56,6 +56,7 @@ void DukeInputWindow::Initialize(HWND hwnd, int port_num, int dev_type) m_port_num = port_num; m_bHasChanges = false; m_bIsBinding = false; + m_num_devices = 0; // Set window icon SetClassLong(m_hwnd_window, GCL_HICON, (LONG)LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_CXBX))); diff --git a/src/gui/controllers/DlgLibusbControllerConfig.cpp b/src/gui/controllers/DlgLibusbControllerConfig.cpp new file mode 100644 index 000000000..60655cd47 --- /dev/null +++ b/src/gui/controllers/DlgLibusbControllerConfig.cpp @@ -0,0 +1,219 @@ +// 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::GUI + +#include "Windows.h" +#include "gui\resource\ResCxbx.h" +#include "input\InputWindow.h" +#include "common\input\LibusbDevice.h" + + +static LibusbInputWindow *g_InputWindow = nullptr; + +LibusbInputWindow::~LibusbInputWindow() +{ + g_Settings->m_input_port[m_port_num].DeviceName = m_host_dev; + g_Settings->m_input_port[m_port_num].ProfileName = ""; + g_Settings->m_input_port[m_port_num].SlotType[SLOT_TOP] = to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID); + g_Settings->m_input_port[m_port_num].SlotType[SLOT_BOTTOM] = to_underlying(XBOX_INPUT_DEVICE::DEVICE_INVALID); +} + +void LibusbInputWindow::Initialize(HWND hwnd, int port_num, int dev_type) +{ + // Save window/device specific variables + m_hwnd_window = hwnd; + m_hwnd_device_list = GetDlgItem(m_hwnd_window, IDC_LIBUSB_LIST); + m_hwnd_device_test = GetDlgItem(m_hwnd_window, IDC_LIBUSB_TEST); + m_dev_type = dev_type; + m_port_num = port_num; + m_bIsBinding = false; + m_num_devices = 0; + m_DeviceConfig = nullptr; + + // Set window icon + SetClassLong(m_hwnd_window, GCL_HICON, (LONG)LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_CXBX))); + + // Set window title + std::string title = (GetInputDeviceName(m_dev_type) + " at port "); + SendMessage(m_hwnd_window, WM_SETTEXT, 0, + reinterpret_cast((title + PortUserFormat(std::to_string(m_port_num))).c_str())); + + // Enumerate devices + UpdateDeviceList(); +} + +void LibusbInputWindow::ClearBindings() +{ + // There are no profiles for libusb devices, so this is a nop +} + +void LibusbInputWindow::SaveSlotConfig() +{ + // There are no slots for libusb devices, so this is a nop +} + +int LibusbInputWindow::EnableDefaultButton() +{ + // The libusb window does not have a default button, so we return a dummy value here + return -1; +} + +void LibusbInputWindow::UpdateDeviceList() +{ + g_InputDeviceManager.RefreshDevices(); + + // Populate device list + for (int i = 0; i < m_num_devices; i++) { + SendMessage(m_hwnd_device_list, CB_DELETESTRING, 0, 0); + } + + // Add only libusb devices and only those that match the type we are configuring + std::vector dev_list = g_InputDeviceManager.GetDeviceList([this](const auto &Device) { + if (Device->IsLibusb()) { + if (to_underlying(dynamic_cast(Device)->GetLibusbType()) == m_dev_type) { + return true; + } + } + return false; + }); + for (const auto &str : dev_list) { + SendMessage(m_hwnd_device_list, CB_ADDSTRING, 0, reinterpret_cast(str.c_str())); + ++m_num_devices; + } + if (m_num_devices) { + SendMessage(m_hwnd_device_list, CB_SETCURSEL, 0, 0); + } + + UpdateCurrentDevice(); +} + +void LibusbInputWindow::TestInput() +{ + using namespace std::chrono; + + // Check if binding thread is still active + if (m_bIsBinding) { + return; + } + + auto dev = g_InputDeviceManager.FindDevice(m_host_dev); + if (dev != nullptr) { + m_bIsBinding = true; + + // Don't block the message processing loop + std::thread([this, dev]() { + auto LibusbDev = dynamic_cast(dev.get()); + EnableWindow(m_hwnd_window, FALSE); + SendMessage(m_hwnd_device_test, WM_SETTEXT, 0, reinterpret_cast("...")); + auto now = system_clock::now(); + auto timeout = now + milliseconds(INPUT_TIMEOUT); + InputBuff buffer[2]; + std::memset(buffer, 0, 2 * sizeof(InputBuff)); + bool detect = false; + while (now <= timeout) { + LibusbDev->ExecuteIo(buffer, DIRECTION_IN); + if (std::memcmp(reinterpret_cast(buffer) + XID_PACKET_HEADER, + reinterpret_cast(&buffer[1]) + XID_PACKET_HEADER, + sizeof(InputBuff) - XID_PACKET_HEADER)) { + detect = true; + break; + } + std::this_thread::sleep_for(milliseconds(10)); + now += milliseconds(10); + } + if (detect) { + SendMessage(m_hwnd_device_test, WM_SETTEXT, 0, reinterpret_cast("Ok!")); + } + else { + SendMessage(m_hwnd_device_test, WM_SETTEXT, 0, reinterpret_cast("Fail!")); + } + std::this_thread::sleep_for(milliseconds(2000)); + SendMessage(m_hwnd_device_test, WM_SETTEXT, 0, reinterpret_cast("Test")); + EnableWindow(m_hwnd_window, TRUE); + m_bIsBinding = false; + }).detach(); + } +} + +INT_PTR CALLBACK DlgLibUsbControllerConfigProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam) +{ + switch (uMsg) + { + case WM_INITDIALOG: + { + int port_num = lParam & 0xFF; + int dev_type = (lParam & 0xFF00) >> 8; + + // Ensure that port_num is a valid xbox port + assert(port_num >= PORT_1 && port_num <= PORT_4); + + // Ensure that the controller type is valid + assert(dev_type == to_underlying(XBOX_INPUT_DEVICE::HW_XBOX_CONTROLLER) || + dev_type == to_underlying(XBOX_INPUT_DEVICE::HW_STEEL_BATTALION_CONTROLLER)); + + g_InputWindow = new LibusbInputWindow; + g_InputWindow->Initialize(hWndDlg, port_num, dev_type); + } + break; + + case WM_CLOSE: + { + delete g_InputWindow; + g_InputWindow = nullptr; + EndDialog(hWndDlg, wParam); + } + break; + + case WM_COMMAND: + { + switch (LOWORD(wParam)) + { + case IDC_LIBUSB_LIST: + if (HIWORD(wParam) == CBN_SELCHANGE) { + g_InputWindow->UpdateCurrentDevice(); + } + break; + + case IDC_REFRESH_DEVICES: + if (HIWORD(wParam) == BN_CLICKED) { + g_InputWindow->UpdateDeviceList(); + } + break; + + case IDC_LIBUSB_TEST: + if (HIWORD(wParam) == BN_CLICKED) { + g_InputWindow->TestInput(); + } + break; + } + } + break; + } + + return FALSE; +} diff --git a/src/gui/controllers/DlgLibusbControllerConfig.h b/src/gui/controllers/DlgLibusbControllerConfig.h new file mode 100644 index 000000000..de77205cd --- /dev/null +++ b/src/gui/controllers/DlgLibusbControllerConfig.h @@ -0,0 +1,30 @@ +// 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 + +INT_PTR CALLBACK DlgLibUsbControllerConfigProc(HWND hWndDlg, UINT uMsg, WPARAM wParam, LPARAM lParam); diff --git a/src/gui/controllers/DlgSBControllerConfig.cpp b/src/gui/controllers/DlgSBControllerConfig.cpp index 7a83dea9a..a9ceac4a5 100644 --- a/src/gui/controllers/DlgSBControllerConfig.cpp +++ b/src/gui/controllers/DlgSBControllerConfig.cpp @@ -47,6 +47,7 @@ void SbcInputWindow::Initialize(HWND hwnd, int port_num, int dev_type) m_port_num = port_num; m_bHasChanges = false; m_bIsBinding = false; + m_num_devices = 0; // Set window icon SetClassLong(m_hwnd_window, GCL_HICON, (LONG)LoadIcon(GetModuleHandle(nullptr), MAKEINTRESOURCE(IDI_CXBX))); diff --git a/src/gui/resource/Cxbx.rc b/src/gui/resource/Cxbx.rc index e54afd15b..6f6719ab2 100644 --- a/src/gui/resource/Cxbx.rc +++ b/src/gui/resource/Cxbx.rc @@ -29,6 +29,7 @@ GUIDELINES DESIGNINFO BEGIN IDD_INPUT_CFG, DIALOG BEGIN + BOTTOMMARGIN, 188 END IDD_XID_DUKE_CFG, DIALOG @@ -90,25 +91,25 @@ END // Dialog // -IDD_INPUT_CFG DIALOGEX 0, 0, 243, 188 +IDD_INPUT_CFG DIALOGEX 0, 0, 265, 192 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU CAPTION "Cxbx-Reloaded : Input Configuration" FONT 8, "Verdana", 0, 0, 0x1 BEGIN - GROUPBOX "Xbox device configuration",IDC_DEVICE,13,10,217,103,WS_GROUP,WS_EX_CLIENTEDGE - COMBOBOX IDC_DEVICE_PORT1,50,25,110,14,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - COMBOBOX IDC_DEVICE_PORT2,50,46,110,14,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - COMBOBOX IDC_DEVICE_PORT3,50,67,110,14,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - COMBOBOX IDC_DEVICE_PORT4,50,88,110,14,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP - PUSHBUTTON "Configure",IDC_CONFIGURE_PORT1,166,25,50,14,BS_FLAT - PUSHBUTTON "Configure",IDC_CONFIGURE_PORT2,166,46,50,14,BS_FLAT - PUSHBUTTON "Configure",IDC_CONFIGURE_PORT3,166,67,50,14,BS_FLAT - PUSHBUTTON "Configure",IDC_CONFIGURE_PORT4,166,88,50,14,BS_FLAT + GROUPBOX "Xbox device configuration",IDC_DEVICE,13,10,240,103,WS_GROUP,WS_EX_CLIENTEDGE + COMBOBOX IDC_DEVICE_PORT1,50,25,134,14,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_DEVICE_PORT2,50,46,134,14,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_DEVICE_PORT3,50,67,134,14,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + COMBOBOX IDC_DEVICE_PORT4,50,88,134,14,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Configure",IDC_CONFIGURE_PORT1,192,25,50,14,BS_FLAT + PUSHBUTTON "Configure",IDC_CONFIGURE_PORT2,192,46,50,14,BS_FLAT + PUSHBUTTON "Configure",IDC_CONFIGURE_PORT3,192,67,50,14,BS_FLAT + PUSHBUTTON "Configure",IDC_CONFIGURE_PORT4,192,88,50,14,BS_FLAT LTEXT "Port 1",IDC_STATIC,23,27,20,10 LTEXT "Port 2",IDC_STATIC,23,48,20,10 LTEXT "Port 3",IDC_STATIC,23,69,20,10 LTEXT "Port 4",IDC_STATIC,23,90,20,10 - GROUPBOX "Options",IDC_INPUT_OPTIONS,13,119,217,64,WS_GROUP,WS_EX_CLIENTEDGE + GROUPBOX "Options",IDC_INPUT_OPTIONS,13,119,240,64,WS_GROUP,WS_EX_CLIENTEDGE EDITTEXT IDC_MOUSE_RANGE,95,129,121,14 EDITTEXT IDC_WHEEL_RANGE,95,147,121,14 LTEXT "Mouse axis range",IDC_STATIC,23,129,69,14,SS_CENTERIMAGE @@ -201,6 +202,15 @@ BEGIN PUSHBUTTON "Test",IDC_RUMBLE_TEST,95,11,45,14,BS_FLAT END +IDD_LIBUSB_CFG DIALOGEX 0, 0, 250, 35 +STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU +FONT 8, "Verdana", 0, 0, 0x1 +BEGIN + COMBOBOX IDC_LIBUSB_LIST,15,11,120,15,CBS_DROPDOWNLIST | WS_VSCROLL | WS_TABSTOP + PUSHBUTTON "Refresh",IDC_REFRESH_DEVICES,140,11,45,14,BS_FLAT + PUSHBUTTON "Test",IDC_LIBUSB_TEST,190,11,45,14,BS_FLAT +END + IDD_SBC_CFG DIALOGEX 0, 0, 854, 280 STYLE DS_SETFONT | DS_MODALFRAME | DS_CENTER | WS_POPUP | WS_CAPTION | WS_SYSMENU FONT 8, "Verdana", 0, 0, 0x1 @@ -508,6 +518,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..c88746822 100644 --- a/src/gui/resource/ResCxbx.h +++ b/src/gui/resource/ResCxbx.h @@ -21,6 +21,7 @@ #define IDD_XID_DUKE_CFG 134 #define IDD_RUMBLE_CFG 135 #define IDD_NETWORK_CFG 136 +#define IDD_LIBUSB_CFG 137 #define IDC_LOG_CANCEL 892 #define IDC_LOG_ACCEPT 893 #define IDC_LOG_ENABLE_GENERAL 894 @@ -94,6 +95,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 @@ -271,6 +273,8 @@ #define IDC_SIGHT_CHANGE_NEGX 1320 #define IDC_SIGHT_CHANGE_NEGY 1321 #define IDC_SIGHT_CHANGE_POSX 1322 +#define IDC_LIBUSB_LIST 1323 +#define IDC_LIBUSB_TEST 1324 #define ID_FILE_EXIT 40005 #define ID_HELP_ABOUT 40008 #define ID_EMULATION_START 40009