From d37c22aad00cf7250404b599b015d217c3e5f810 Mon Sep 17 00:00:00 2001 From: Gliniak <153369+Gliniak@users.noreply.github.com> Date: Fri, 13 Mar 2026 22:31:24 +0100 Subject: [PATCH] [HID] Redesigned support for Portals - Added hotplug support on Windows - Added support for XamInputNonControllerGetRawEx & XamInputNonControllerSetRawEx --- src/xenia/CMakeLists.txt | 2 +- src/xenia/app/emulator_window.cc | 14 ++ src/xenia/app/emulator_window.h | 2 + src/xenia/hid/CMakeLists.txt | 2 +- src/xenia/hid/input_system.cc | 7 +- src/xenia/hid/input_system.h | 6 +- src/xenia/hid/portal/CMakeLists.txt | 13 ++ src/xenia/hid/portal/hardware_portal.cc | 120 +++++++++++++++++ src/xenia/hid/portal/hardware_portal.h | 52 ++++++++ src/xenia/hid/portal/portal.cc | 74 +++++++++++ src/xenia/hid/portal/portal.h | 55 ++++++++ src/xenia/hid/skylander/CMakeLists.txt | 14 -- src/xenia/hid/skylander/skylander_emulated.cc | 32 ----- src/xenia/hid/skylander/skylander_emulated.h | 34 ----- src/xenia/hid/skylander/skylander_hardware.cc | 121 ------------------ src/xenia/hid/skylander/skylander_hardware.h | 47 ------- src/xenia/hid/skylander/skylander_portal.cc | 19 --- src/xenia/hid/skylander/skylander_portal.h | 40 ------ src/xenia/kernel/xam/xam_input.cc | 77 +++++++---- src/xenia/ui/window.cc | 7 + src/xenia/ui/window.h | 2 + src/xenia/ui/window_listener.h | 1 + src/xenia/ui/window_win.cc | 24 +++- src/xenia/ui/window_win.h | 2 + 24 files changed, 427 insertions(+), 340 deletions(-) create mode 100644 src/xenia/hid/portal/CMakeLists.txt create mode 100644 src/xenia/hid/portal/hardware_portal.cc create mode 100644 src/xenia/hid/portal/hardware_portal.h create mode 100644 src/xenia/hid/portal/portal.cc create mode 100644 src/xenia/hid/portal/portal.h delete mode 100644 src/xenia/hid/skylander/CMakeLists.txt delete mode 100644 src/xenia/hid/skylander/skylander_emulated.cc delete mode 100644 src/xenia/hid/skylander/skylander_emulated.h delete mode 100644 src/xenia/hid/skylander/skylander_hardware.cc delete mode 100644 src/xenia/hid/skylander/skylander_hardware.h delete mode 100644 src/xenia/hid/skylander/skylander_portal.cc delete mode 100644 src/xenia/hid/skylander/skylander_portal.h diff --git a/src/xenia/CMakeLists.txt b/src/xenia/CMakeLists.txt index ad6c41a2f..2817f1f27 100644 --- a/src/xenia/CMakeLists.txt +++ b/src/xenia/CMakeLists.txt @@ -20,7 +20,7 @@ add_subdirectory(gpu/null) add_subdirectory(gpu/vulkan) add_subdirectory(hid) add_subdirectory(hid/nop) -add_subdirectory(hid/skylander) +add_subdirectory(hid/portal) add_subdirectory(kernel) add_subdirectory(patcher) add_subdirectory(ui) diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 4e1b506c2..7703faca0 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -297,6 +297,20 @@ void EmulatorWindow::EmulatorWindowListener::OnMouseUp(ui::MouseEvent& e) { emulator_window_.OnMouseUp(e); } +void EmulatorWindow::EmulatorWindowListener::OnUsbDeviceChanged( + bool is_arrival) { + auto* portal = emulator_window_.emulator()->input_system()->GetPortal(); + if (!portal) { + return; + } + + if (is_arrival) { + portal->OnDeviceArrival(); + } else { + portal->OnDeviceRemoval(); + } +} + void EmulatorWindow::DisplayConfigGameConfigLoadCallback::PostGameConfigLoad() { emulator_window_.ApplyDisplayConfigForCvars(); } diff --git a/src/xenia/app/emulator_window.h b/src/xenia/app/emulator_window.h index e748b91a7..bbc1a7c63 100644 --- a/src/xenia/app/emulator_window.h +++ b/src/xenia/app/emulator_window.h @@ -150,6 +150,8 @@ class EmulatorWindow { void OnMouseDown(ui::MouseEvent& e) override; void OnMouseUp(ui::MouseEvent& e) override; + void OnUsbDeviceChanged(bool is_arrival) override; + private: EmulatorWindow& emulator_window_; }; diff --git a/src/xenia/hid/CMakeLists.txt b/src/xenia/hid/CMakeLists.txt index 8bbb1ee56..6fe7086cb 100644 --- a/src/xenia/hid/CMakeLists.txt +++ b/src/xenia/hid/CMakeLists.txt @@ -7,5 +7,5 @@ if(_hid_demo AND _hid_srcs) list(REMOVE_ITEM _hid_srcs ${_hid_demo}) set_target_properties(xenia-hid PROPERTIES SOURCES "${_hid_srcs}") endif() -target_link_libraries(xenia-hid PUBLIC xenia-base xenia-hid-skylander) +target_link_libraries(xenia-hid PUBLIC xenia-base xenia-hid-portal) xe_target_defaults(xenia-hid) diff --git a/src/xenia/hid/input_system.cc b/src/xenia/hid/input_system.cc index f10d8696d..b5224f596 100644 --- a/src/xenia/hid/input_system.cc +++ b/src/xenia/hid/input_system.cc @@ -16,9 +16,8 @@ #include "xenia/hid/input_driver.h" #include "xenia/kernel/util/shim_utils.h" -#include "xenia/hid/skylander/skylander_emulated.h" #ifdef XE_PLATFORM_WIN32 -#include "xenia/hid/skylander/skylander_hardware.h" +#include "xenia/hid/portal/hardware_portal.h" #endif // XE_PLATFORM_WIN32 namespace xe { @@ -34,10 +33,8 @@ DEFINE_double( "Defines deadzone level for right stick. Allowed range [0.0-1.0].", "HID"); InputSystem::InputSystem(xe::ui::Window* window) : window_(window) { - skylander_portal_ = std::make_unique(); - #ifdef XE_PLATFORM_WIN32 - skylander_portal_ = std::make_unique(); + portal_ = std::make_unique(); #endif // XE_PLATFORM_WIN32 } diff --git a/src/xenia/hid/input_system.h b/src/xenia/hid/input_system.h index 774471684..b3a3629aa 100644 --- a/src/xenia/hid/input_system.h +++ b/src/xenia/hid/input_system.h @@ -16,7 +16,7 @@ #include "xenia/base/mutex.h" #include "xenia/hid/input.h" #include "xenia/hid/input_driver.h" -#include "xenia/hid/skylander/skylander_portal.h" +#include "xenia/hid/portal/portal.h" #include "xenia/xbox.h" namespace xe { @@ -56,7 +56,7 @@ class InputSystem { uint32_t GetLastUsedSlot() const { return last_used_slot; } - SkylanderPortal* GetSkylanderPortal() { return skylander_portal_.get(); } + Portal* GetPortal() { return portal_.get(); } std::unique_lock lock(); @@ -77,7 +77,7 @@ class InputSystem { std::vector> drivers_; - std::unique_ptr skylander_portal_; + std::unique_ptr portal_; std::bitset connected_slots = {}; std::array, XUserMaxUserCount> diff --git a/src/xenia/hid/portal/CMakeLists.txt b/src/xenia/hid/portal/CMakeLists.txt new file mode 100644 index 000000000..437f4327f --- /dev/null +++ b/src/xenia/hid/portal/CMakeLists.txt @@ -0,0 +1,13 @@ +add_library(xenia-hid-portal STATIC + ${CMAKE_CURRENT_SOURCE_DIR}/portal.h + ${CMAKE_CURRENT_SOURCE_DIR}/portal.cc +) + +if(WIN32) + target_sources(xenia-hid-portal PRIVATE + ${CMAKE_CURRENT_SOURCE_DIR}/hardware_portal.h + ${CMAKE_CURRENT_SOURCE_DIR}/hardware_portal.cc + ) + target_link_libraries(xenia-hid-portal PUBLIC libusb) +endif() +xe_target_defaults(xenia-hid-portal) diff --git a/src/xenia/hid/portal/hardware_portal.cc b/src/xenia/hid/portal/hardware_portal.cc new file mode 100644 index 000000000..98b8b274f --- /dev/null +++ b/src/xenia/hid/portal/hardware_portal.cc @@ -0,0 +1,120 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2025 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/hid/portal/hardware_portal.h" +#include "xenia/base/logging.h" + +namespace xe { +namespace hid { + +HardwarePortal::HardwarePortal() : Portal() { + libusb_init(&context_); + OpenDevice(); +} + +HardwarePortal::~HardwarePortal() { + if (handle_) { + CloseDevice(); + } + + libusb_exit(context_); +} + +void HardwarePortal::OpenDevice() { + if (!context_ || connected_) { + return; + } + + libusb_device** devs; + const ssize_t cnt = libusb_get_device_list(context_, &devs); + if (cnt < 0) { + // No device available... It might appear later. + return; + } + + for (ssize_t i = 0; i < cnt; ++i) { + libusb_device* dev = devs[i]; + + libusb_device_descriptor desc; + if (libusb_get_device_descriptor(dev, &desc) == 0) { + // Check if this device matches the target Vendor ID and Product ID. + // Small limitation. We're only supporting one device being connected at + // the time. + for (const auto& entry : kPortalVendorProductIdList) { + if (desc.idVendor == entry.first && desc.idProduct == entry.second) { + if (libusb_open(dev, &handle_) == 0) { + libusb_claim_interface(handle_, 0); + connected_ = true; + // We found device. No need to go thorugh remaining IDs. + break; + } + } + } + } + // We found device. No need to go thorugh remaining devices. + if (connected_) { + break; + } + } + libusb_free_device_list(devs, 0); +} + +void HardwarePortal::CloseDevice() { + if (!connected_) { + return; + } + + libusb_release_interface(handle_, 0); + libusb_close(handle_); + connected_ = false; + handle_ = nullptr; +} + +X_STATUS HardwarePortal::ReadInternal(std::span data, + int32_t& read_count) { + const int result = libusb_interrupt_transfer( + handle_, read_endpoint, data.data(), static_cast(data.size()), + &read_count, timeout); + + switch (result) { + case LIBUSB_ERROR_NO_DEVICE: + return X_ERROR_DEVICE_NOT_CONNECTED; + break; + case LIBUSB_ERROR_TIMEOUT: + return X_ERROR_SUCCESS; + default: + break; + } + + if (result < 0) { + XELOGW("Portal[Read] returned error: {:08X}", result); + return X_ERROR_FUNCTION_FAILED; + } + return X_ERROR_SUCCESS; +} + +X_STATUS HardwarePortal::WriteInternal(std::span data) { + const int result = libusb_interrupt_transfer( + handle_, write_endpoint, data.data(), static_cast(data.size()), + nullptr, timeout); + + if (result < 0) { + XELOGW("Portal[Write] returned error: {:08X}", result); + return X_ERROR_DEVICE_NOT_CONNECTED; + } + + return X_ERROR_SUCCESS; +}; + +void HardwarePortal::OnDeviceArrival() { OpenDevice(); }; + +void HardwarePortal::OnDeviceRemoval() { CloseDevice(); }; + +} // namespace hid +} // namespace xe diff --git a/src/xenia/hid/portal/hardware_portal.h b/src/xenia/hid/portal/hardware_portal.h new file mode 100644 index 000000000..4f0c6db0c --- /dev/null +++ b/src/xenia/hid/portal/hardware_portal.h @@ -0,0 +1,52 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2025 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_HID_PORTAL_HARDWARE_PORTAL_H_ +#define XENIA_HID_PORTAL_HARDWARE_PORTAL_H_ + +#include +#include "xenia/hid/portal/portal.h" + +#include "third_party/libusb/libusb/libusb.h" + +namespace xe { +namespace hid { + +constexpr std::array, 2> + kPortalVendorProductIdList = { + std::pair{0x1430, 0x1F17}, + std::pair{0x24C6, 0xFA00}}; + +class HardwarePortal final : public Portal { + public: + HardwarePortal(); + ~HardwarePortal() override; + + virtual void OnDeviceArrival() override; + virtual void OnDeviceRemoval() override; + + private: + virtual void OpenDevice() override; + virtual void CloseDevice() override; + virtual X_STATUS ReadInternal(std::span data, + int32_t& read_count) override; + virtual X_STATUS WriteInternal(std::span data) override; + + const uint8_t read_endpoint = 0x81; + const uint8_t write_endpoint = 0x02; + const uint16_t timeout = 100; + + libusb_context* context_ = nullptr; + libusb_device_handle* handle_ = nullptr; +}; + +} // namespace hid +} // namespace xe + +#endif // XENIA_HID_PORTAL_HARDWARE_PORTAL_H_ diff --git a/src/xenia/hid/portal/portal.cc b/src/xenia/hid/portal/portal.cc new file mode 100644 index 000000000..429c4cf41 --- /dev/null +++ b/src/xenia/hid/portal/portal.cc @@ -0,0 +1,74 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2025 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#include "xenia/hid/portal/portal.h" + +namespace xe { +namespace hid { + +Portal::Portal() {} +Portal::~Portal() {} + +bool Portal::IsConnected() { + std::lock_guard guard(lock_); + return connected_; +} + +X_STATUS Portal::Read(std::span data, uint32_t& bytes_read, + uint16_t& state) { + std::lock_guard guard(lock_); + + if (!connected_) { + return X_ERROR_DEVICE_NOT_CONNECTED; + } + + if (data.size() > kPortalBufferSize) { + return X_ERROR_INVALID_PARAMETER; + } + + int32_t read_count = 0; + X_STATUS status = ReadInternal(data, read_count); + + if (XSUCCEEDED(status)) { + // According to XAM decompilation logic here is reversed. Difference in + // status should return 1, but these values are taken from internal parts of + // xinput raw implementation, so it might be completely opposite. + state = previous_status_ == status; + bytes_read = read_count; + } + + previous_status_ = status; + + if (status == X_ERROR_DEVICE_NOT_CONNECTED) { + CloseDevice(); + } + + return status; +} + +X_STATUS Portal::Write(std::span data) { + std::lock_guard guard(lock_); + + if (!connected_) { + return X_ERROR_DEVICE_NOT_CONNECTED; + } + + if (data.size() > kPortalBufferSize) { + return X_ERROR_INVALID_PARAMETER; + } + + const X_STATUS status = WriteInternal(data); + if (status == X_ERROR_DEVICE_NOT_CONNECTED) { + CloseDevice(); + } + return status; +} + +} // namespace hid +} // namespace xe diff --git a/src/xenia/hid/portal/portal.h b/src/xenia/hid/portal/portal.h new file mode 100644 index 000000000..baeace9d2 --- /dev/null +++ b/src/xenia/hid/portal/portal.h @@ -0,0 +1,55 @@ +/** + ****************************************************************************** + * Xenia : Xbox 360 Emulator Research Project * + ****************************************************************************** + * Copyright 2025 Xenia Canary. All rights reserved. * + * Released under the BSD license - see LICENSE in the root for more details. * + ****************************************************************************** + */ + +#ifndef XENIA_HID_PORTAL_PORTAL_H_ +#define XENIA_HID_PORTAL_PORTAL_H_ + +#include + +#include "xenia/base/mutex.h" +#include "xenia/xbox.h" + +namespace xe { +namespace hid { + +constexpr uint8_t kPortalBufferSize = 0x20; + +class Portal { + public: + Portal(); + virtual ~Portal(); + + bool IsConnected(); + + virtual X_STATUS Read(std::span data, uint32_t& bytes_read, + uint16_t& state); + virtual X_STATUS Write(std::span data); + + virtual void OnDeviceArrival() = 0; + virtual void OnDeviceRemoval() = 0; + + protected: + bool connected_ = false; + + private: + virtual void OpenDevice() = 0; + virtual void CloseDevice() = 0; + + virtual X_STATUS ReadInternal(std::span data, + int32_t& read_count) = 0; + virtual X_STATUS WriteInternal(std::span data) = 0; + + xe_mutex lock_; + X_STATUS previous_status_ = 0; +}; + +} // namespace hid +} // namespace xe + +#endif diff --git a/src/xenia/hid/skylander/CMakeLists.txt b/src/xenia/hid/skylander/CMakeLists.txt deleted file mode 100644 index 8ae07a842..000000000 --- a/src/xenia/hid/skylander/CMakeLists.txt +++ /dev/null @@ -1,14 +0,0 @@ -add_library(xenia-hid-skylander STATIC - ${CMAKE_CURRENT_SOURCE_DIR}/skylander_portal.h - ${CMAKE_CURRENT_SOURCE_DIR}/skylander_portal.cc - ${CMAKE_CURRENT_SOURCE_DIR}/skylander_emulated.h - ${CMAKE_CURRENT_SOURCE_DIR}/skylander_emulated.cc -) -if(WIN32) - target_sources(xenia-hid-skylander PRIVATE - ${CMAKE_CURRENT_SOURCE_DIR}/skylander_hardware.h - ${CMAKE_CURRENT_SOURCE_DIR}/skylander_hardware.cc - ) - target_link_libraries(xenia-hid-skylander PUBLIC libusb) -endif() -xe_target_defaults(xenia-hid-skylander) diff --git a/src/xenia/hid/skylander/skylander_emulated.cc b/src/xenia/hid/skylander/skylander_emulated.cc deleted file mode 100644 index 1a0a0bf8b..000000000 --- a/src/xenia/hid/skylander/skylander_emulated.cc +++ /dev/null @@ -1,32 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Xenia Canary. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/hid/skylander/skylander_emulated.h" - -namespace xe { -namespace hid { - -SkylanderPortalEmulated::SkylanderPortalEmulated() : SkylanderPortal() {} - -SkylanderPortalEmulated::~SkylanderPortalEmulated() {} - -bool SkylanderPortalEmulated::init_device() { return false; } - -void SkylanderPortalEmulated::destroy_device() {} - -X_STATUS SkylanderPortalEmulated::read(std::vector& data) { - return X_ERROR_DEVICE_NOT_CONNECTED; -} - -X_STATUS SkylanderPortalEmulated::write(std::vector& data) { - return X_ERROR_DEVICE_NOT_CONNECTED; -}; - -} // namespace hid -} // namespace xe diff --git a/src/xenia/hid/skylander/skylander_emulated.h b/src/xenia/hid/skylander/skylander_emulated.h deleted file mode 100644 index b49ab6223..000000000 --- a/src/xenia/hid/skylander/skylander_emulated.h +++ /dev/null @@ -1,34 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Xenia Canary. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_EMULATED_H_ -#define XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_EMULATED_H_ - -#include "xenia/hid/skylander/skylander_portal.h" - -namespace xe { -namespace hid { - -class SkylanderPortalEmulated final : public SkylanderPortal { - public: - SkylanderPortalEmulated(); - ~SkylanderPortalEmulated() override; - - X_STATUS read(std::vector& data) override; - X_STATUS write(std::vector& data) override; - - private: - bool init_device() override; - void destroy_device() override; -}; - -} // namespace hid -} // namespace xe - -#endif diff --git a/src/xenia/hid/skylander/skylander_hardware.cc b/src/xenia/hid/skylander/skylander_hardware.cc deleted file mode 100644 index 113f7b9b1..000000000 --- a/src/xenia/hid/skylander/skylander_hardware.cc +++ /dev/null @@ -1,121 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Xenia Canary. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/hid/skylander/skylander_hardware.h" - -namespace xe { -namespace hid { - -SkylanderPortalLibusb::SkylanderPortalLibusb() : SkylanderPortal() { - libusb_init(&context_); -} - -SkylanderPortalLibusb::~SkylanderPortalLibusb() { - if (handle_) { - destroy_device(); - } - - libusb_exit(context_); -} - -bool SkylanderPortalLibusb::init_device() { - if (!context_) { - return false; - } - - libusb_device** devs; - ssize_t cnt = libusb_get_device_list(context_, &devs); - if (cnt < 0) { - // No device available... It might appear later. - return false; - } - - bool device_found = false; - for (ssize_t i = 0; i < cnt; ++i) { - libusb_device* dev = devs[i]; - - struct libusb_device_descriptor desc; - if (libusb_get_device_descriptor(dev, &desc) == 0) { - // Check if this device matches the target Vendor ID and Product ID - if (desc.idVendor == portal_vendor_product_id.first && - desc.idProduct == portal_vendor_product_id.second) { - libusb_device_handle* handle; - if (libusb_open(dev, &handle) == 0) { - libusb_claim_interface(handle, 0); - handle_ = reinterpret_cast(handle); - device_found = true; - } - } - } - } - libusb_free_device_list(devs, 0); - - return device_found; -} - -void SkylanderPortalLibusb::destroy_device() { - libusb_release_interface(reinterpret_cast(handle_), 0); - libusb_close(reinterpret_cast(handle_)); - handle_ = nullptr; -} - -X_STATUS SkylanderPortalLibusb::read(std::vector& data) { - if (!is_initialized()) { - if (!init_device()) { - return X_ERROR_DEVICE_NOT_CONNECTED; - } - } - - if (data.size() > skylander_buffer_size) { - return X_ERROR_INVALID_PARAMETER; - } - - const int result = libusb_interrupt_transfer( - reinterpret_cast(handle_), read_endpoint, - data.data(), static_cast(data.size()), nullptr, timeout); - - if (result == LIBUSB_ERROR_NO_DEVICE) { - return X_ERROR_DEVICE_NOT_CONNECTED; - } - - if (result < 0) { - destroy_device(); - } - - return X_ERROR_SUCCESS; -} - -X_STATUS SkylanderPortalLibusb::write(std::vector& data) { - if (!is_initialized()) { - if (!init_device()) { - return X_ERROR_DEVICE_NOT_CONNECTED; - } - } - - if (data.size() > skylander_buffer_size) { - return X_ERROR_INVALID_PARAMETER; - } - - const int result = libusb_interrupt_transfer( - reinterpret_cast(handle_), write_endpoint, - data.data(), static_cast(data.size()), nullptr, timeout); - - if (result == LIBUSB_ERROR_NO_DEVICE) { - return X_ERROR_DEVICE_NOT_CONNECTED; - } - - if (result < 0) { - destroy_device(); - } - - return X_ERROR_SUCCESS; -}; - -} // namespace hid -} // namespace xe diff --git a/src/xenia/hid/skylander/skylander_hardware.h b/src/xenia/hid/skylander/skylander_hardware.h deleted file mode 100644 index 493e5eb36..000000000 --- a/src/xenia/hid/skylander/skylander_hardware.h +++ /dev/null @@ -1,47 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Xenia Canary. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_LIBUSB_H_ -#define XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_LIBUSB_H_ - -// Include order is important here. Including skylander_portal.h after libusb -// will cause conflicts. -#include "xenia/hid/skylander/skylander_portal.h" - -#include "third_party/libusb/libusb/libusb.h" - -namespace xe { -namespace hid { - -class SkylanderPortalLibusb final : public SkylanderPortal { - public: - SkylanderPortalLibusb(); - ~SkylanderPortalLibusb() override; - - X_STATUS read(std::vector& data) override; - X_STATUS write(std::vector& data) override; - - private: - bool init_device() override; - void destroy_device() override; - - bool is_initialized() const { return handle_ != nullptr; } - - const uint8_t read_endpoint = 0x81; - const uint8_t write_endpoint = 0x02; - const uint16_t timeout = 300; - - libusb_context* context_ = nullptr; - uintptr_t* handle_ = nullptr; -}; - -} // namespace hid -} // namespace xe - -#endif diff --git a/src/xenia/hid/skylander/skylander_portal.cc b/src/xenia/hid/skylander/skylander_portal.cc deleted file mode 100644 index 817fa9f15..000000000 --- a/src/xenia/hid/skylander/skylander_portal.cc +++ /dev/null @@ -1,19 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Xenia Canary. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#include "xenia/hid/skylander/skylander_portal.h" - -namespace xe { -namespace hid { - -SkylanderPortal::SkylanderPortal() {} -SkylanderPortal::~SkylanderPortal() {} - -} // namespace hid -} // namespace xe diff --git a/src/xenia/hid/skylander/skylander_portal.h b/src/xenia/hid/skylander/skylander_portal.h deleted file mode 100644 index 34301d423..000000000 --- a/src/xenia/hid/skylander/skylander_portal.h +++ /dev/null @@ -1,40 +0,0 @@ -/** - ****************************************************************************** - * Xenia : Xbox 360 Emulator Research Project * - ****************************************************************************** - * Copyright 2025 Xenia Canary. All rights reserved. * - * Released under the BSD license - see LICENSE in the root for more details. * - ****************************************************************************** - */ - -#ifndef XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_H_ -#define XENIA_HID_SKYLANDER_SKYLANDER_PORTAL_H_ - -#include - -#include "xenia/xbox.h" - -namespace xe { -namespace hid { - -constexpr std::pair portal_vendor_product_id = {0x1430, - 0x1F17}; -constexpr uint8_t skylander_buffer_size = 0x20; - -class SkylanderPortal { - public: - SkylanderPortal(); - virtual ~SkylanderPortal(); - - virtual X_STATUS read(std::vector& data) = 0; - virtual X_STATUS write(std::vector& data) = 0; - - private: - virtual bool init_device() = 0; - virtual void destroy_device() = 0; -}; - -} // namespace hid -} // namespace xe - -#endif diff --git a/src/xenia/kernel/xam/xam_input.cc b/src/xenia/kernel/xam/xam_input.cc index 4d8487b15..6179f7e5f 100644 --- a/src/xenia/kernel/xam/xam_input.cc +++ b/src/xenia/kernel/xam/xam_input.cc @@ -246,43 +246,76 @@ X_HRESULT_result_t XamUserGetDeviceContext_entry(dword_t user_index, } DECLARE_XAM_EXPORT1(XamUserGetDeviceContext, kInput, kStub); -X_HRESULT_result_t XamInputNonControllerGetRaw_entry( - lpdword_t state_ptr, lpdword_t buffer_length_ptr, lpdword_t buffer_ptr) { +X_HRESULT_result_t XamInputNonControllerGetRawEx_entry( + dword_t device_id, lpdword_t buffer_ptr, lpdword_t buffer_length_ptr, + lpword_t state_ptr) { + if (device_id != 5 && device_id != 6) { + return X_ERROR_INVALID_PARAMETER; + } if (!state_ptr || !buffer_length_ptr || !buffer_ptr) { return X_ERROR_INVALID_PARAMETER; } - const uint32_t data_size = *buffer_length_ptr; - - if (data_size == 0 || data_size > 0x20) { + if (*buffer_length_ptr == 0 || *buffer_length_ptr > hid::kPortalBufferSize) { return X_ERROR_INVALID_PARAMETER; } - auto input_system = kernel_state()->emulator()->input_system(); + auto portal = kernel_state()->emulator()->input_system()->GetPortal(); + if (!portal) { + return X_ERROR_DEVICE_NOT_CONNECTED; + } - std::vector data(data_size, 0); - const auto result = input_system->GetSkylanderPortal()->read(data); - *state_ptr = 1; - memcpy(buffer_ptr, data.data(), data.size()); + uint32_t bytes_read = *buffer_length_ptr; + uint16_t state = 0; + const auto result = portal->Read( + {kernel_memory()->TranslateVirtual(buffer_ptr.guest_address()), + *buffer_length_ptr}, + bytes_read, state); + + if (XSUCCEEDED(result)) { + *buffer_length_ptr = bytes_read; + *state_ptr = state; + } return result; } -DECLARE_XAM_EXPORT1(XamInputNonControllerGetRaw, kInput, kStub); +DECLARE_XAM_EXPORT1(XamInputNonControllerGetRawEx, kInput, kSketchy); + +X_HRESULT_result_t XamInputNonControllerSetRawEx_entry(dword_t device_id, + lpdword_t buffer_ptr, + dword_t buffer_length) { + if (device_id != 5 && device_id != 6) { + return X_ERROR_INVALID_PARAMETER; + } + if (!buffer_ptr || !buffer_length || buffer_length > hid::kPortalBufferSize) { + return X_ERROR_INVALID_PARAMETER; + } + + auto portal = kernel_state()->emulator()->input_system()->GetPortal(); + if (!portal) { + return X_ERROR_DEVICE_NOT_CONNECTED; + } + + return portal->Write( + {kernel_memory()->TranslateVirtual(buffer_ptr.guest_address()), + buffer_length}); +} +DECLARE_XAM_EXPORT1(XamInputNonControllerSetRawEx, kInput, kSketchy); + +X_HRESULT_result_t XamInputNonControllerGetRaw_entry( + lpword_t state_ptr, lpdword_t buffer_length_ptr, lpdword_t buffer_ptr) { + return XamInputNonControllerGetRawEx_entry(5, buffer_ptr, buffer_length_ptr, + state_ptr); +} +DECLARE_XAM_EXPORT1(XamInputNonControllerGetRaw, kInput, kSketchy); X_HRESULT_result_t XamInputNonControllerSetRaw_entry(dword_t buffer_length, lpdword_t buffer_ptr) { - if (!buffer_ptr || !buffer_length || buffer_length > 0x20) { - return X_ERROR_INVALID_PARAMETER; - } - - auto input_system = kernel_state()->emulator()->input_system(); - - std::vector data(buffer_length, 0); - memcpy(data.data(), buffer_ptr, buffer_length); - - return input_system->GetSkylanderPortal()->write(data); + // Normally there are handled separatelly with different first param, but + // whatever. + return XamInputNonControllerSetRawEx_entry(5, buffer_ptr, buffer_length); } -DECLARE_XAM_EXPORT1(XamInputNonControllerSetRaw, kInput, kStub); +DECLARE_XAM_EXPORT1(XamInputNonControllerSetRaw, kInput, kSketchy); } // namespace xam } // namespace kernel diff --git a/src/xenia/ui/window.cc b/src/xenia/ui/window.cc index 8164f0d57..2185a8cfe 100644 --- a/src/xenia/ui/window.cc +++ b/src/xenia/ui/window.cc @@ -490,6 +490,13 @@ void Window::OnMonitorUpdate(MonitorUpdateEvent& e) { } } +void Window::OnUsbDeviceChanged( + bool is_arrival, WindowDestructionReceiver& destruction_receiver) { + SendEventToListeners( + [is_arrival](auto listener) { listener->OnUsbDeviceChanged(is_arrival); }, + destruction_receiver); +} + bool Window::OnActualSizeUpdate( uint32_t new_physical_width, uint32_t new_physical_height, WindowDestructionReceiver& destruction_receiver) { diff --git a/src/xenia/ui/window.h b/src/xenia/ui/window.h index aaf7983a8..b0a207a47 100644 --- a/src/xenia/ui/window.h +++ b/src/xenia/ui/window.h @@ -553,6 +553,8 @@ class Window { void OnDpiChanged(UISetupEvent& e, WindowDestructionReceiver& destruction_receiver); void OnMonitorUpdate(MonitorUpdateEvent& e); + void OnUsbDeviceChanged(bool is_arrival, + WindowDestructionReceiver& destruction_receiver); // For calling when the platform changes something in the non-maximized, // non-fullscreen size of the window. void OnDesiredLogicalSizeUpdate(uint32_t new_desired_logical_width, diff --git a/src/xenia/ui/window_listener.h b/src/xenia/ui/window_listener.h index f2b44a01e..c22322a9e 100644 --- a/src/xenia/ui/window_listener.h +++ b/src/xenia/ui/window_listener.h @@ -34,6 +34,7 @@ class WindowListener { virtual void OnLostFocus(UISetupEvent& e) {} virtual void OnFileDrop(FileDropEvent& e) {} + virtual void OnUsbDeviceChanged(bool is_arrival) {} }; class WindowInputListener { diff --git a/src/xenia/ui/window_win.cc b/src/xenia/ui/window_win.cc index 51ce2da52..07857915e 100644 --- a/src/xenia/ui/window_win.cc +++ b/src/xenia/ui/window_win.cc @@ -19,6 +19,7 @@ #include "xenia/ui/virtual_key.h" #include "xenia/ui/windowed_app_context_win.h" +#include #include #include @@ -48,6 +49,10 @@ Win32Window::~Win32Window() { DeleteTimerQueueTimer(nullptr, cursor_auto_hide_timer_, nullptr); cursor_auto_hide_timer_ = nullptr; } + if (usb_device_notify_) { + UnregisterDeviceNotification(usb_device_notify_); + usb_device_notify_ = nullptr; + } if (hwnd_) { // Set hwnd_ to null to ignore events from now on since this Win32Window is // entering an indeterminate state. @@ -203,6 +208,14 @@ bool Win32Window::OpenImpl() { // Enable file dragging from external sources DragAcceptFiles(hwnd_, true); + // Register USB listener + DEV_BROADCAST_DEVICEINTERFACE filter = {}; + filter.dbcc_size = sizeof(filter); + filter.dbcc_devicetype = DBT_DEVTYP_DEVICEINTERFACE; + usb_device_notify_ = RegisterDeviceNotificationW( + hwnd_, &filter, + DEVICE_NOTIFY_WINDOW_HANDLE | DEVICE_NOTIFY_ALL_INTERFACE_CLASSES); + // Apply the initial state from the Window that the window shouldn't be // visibly transitioned to. @@ -1084,7 +1097,16 @@ LRESULT Win32Window::WndProc(HWND hWnd, UINT message, WPARAM wParam, MonitorUpdateEvent update_event{this, true}; OnMonitorUpdate(update_event); } break; - + case WM_DEVICECHANGE: { + if (wParam == DBT_DEVICEARRIVAL || wParam == DBT_DEVICEREMOVECOMPLETE) { + bool is_arrival = (wParam == DBT_DEVICEARRIVAL); + WindowDestructionReceiver destruction_receiver(this); + OnUsbDeviceChanged(is_arrival, destruction_receiver); + if (destruction_receiver.IsWindowDestroyedOrClosed()) { + break; + } + } + } break; case WM_DPICHANGED: { // Note that for some reason, WM_DPICHANGED is not sent when the window is // borderless fullscreen with per-monitor DPI awareness v1. diff --git a/src/xenia/ui/window_win.h b/src/xenia/ui/window_win.h index 38c50a061..93365939e 100644 --- a/src/xenia/ui/window_win.h +++ b/src/xenia/ui/window_win.h @@ -151,6 +151,8 @@ class Win32Window : public Window { // Whether the cursor has been hidden after the expiration of the timer, and // hasn't been revealed yet. bool cursor_currently_auto_hidden_ = false; + + HDEVNOTIFY usb_device_notify_ = nullptr; }; class Win32MenuItem : public MenuItem {