diff --git a/.gitmodules b/.gitmodules index 7bb327b53..6c40a386d 100644 --- a/.gitmodules +++ b/.gitmodules @@ -103,3 +103,6 @@ [submodule "third_party/pugixml"] path = third_party/pugixml url = https://github.com/zeux/pugixml.git +[submodule "third_party/libusb"] + path = third_party/libusb + url = https://github.com/libusb/libusb.git diff --git a/premake5.lua b/premake5.lua index 323ca622e..d9b02cb36 100644 --- a/premake5.lua +++ b/premake5.lua @@ -273,6 +273,10 @@ workspace("xenia") include("third_party/zlib.lua") include("third_party/pugixml.lua") + if os.istarget("windows") then + include("third_party/libusb.lua") + end + if not os.istarget("android") then -- SDL2 requires sdl2-config, and as of November 2020 isn't high-quality on -- Android yet, most importantly in game controllers - the keycode and axis @@ -307,6 +311,7 @@ workspace("xenia") include("src/xenia/gpu/vulkan") include("src/xenia/hid") include("src/xenia/hid/nop") + include("src/xenia/hid/skylander") include("src/xenia/kernel") include("src/xenia/patcher") include("src/xenia/ui") diff --git a/src/xenia/cpu/ppc/testing/premake5.lua b/src/xenia/cpu/ppc/testing/premake5.lua index 1a7823138..736d02184 100644 --- a/src/xenia/cpu/ppc/testing/premake5.lua +++ b/src/xenia/cpu/ppc/testing/premake5.lua @@ -17,6 +17,7 @@ project("xenia-cpu-ppc-tests") "xenia-base", "xenia-kernel", "xenia-patcher", + "xenia-hid-skylander", }) files({ "ppc_testing_main.cc", diff --git a/src/xenia/cpu/testing/premake5.lua b/src/xenia/cpu/testing/premake5.lua index a02cd7f7d..23c0c6fb9 100644 --- a/src/xenia/cpu/testing/premake5.lua +++ b/src/xenia/cpu/testing/premake5.lua @@ -10,6 +10,7 @@ test_suite("xenia-cpu-tests", project_root, ".", { "xenia-core", "xenia-cpu", "xenia-gpu", + "xenia-hid-skylander", -- TODO(benvanik): cut these dependencies? "xenia-kernel", diff --git a/src/xenia/gpu/d3d12/premake5.lua b/src/xenia/gpu/d3d12/premake5.lua index 3bdee3233..34ca32947 100644 --- a/src/xenia/gpu/d3d12/premake5.lua +++ b/src/xenia/gpu/d3d12/premake5.lua @@ -34,6 +34,7 @@ project("xenia-gpu-d3d12-trace-viewer") "xenia-gpu-d3d12", "xenia-hid", "xenia-hid-nop", + "xenia-hid-skylander", "xenia-kernel", "xenia-patcher", "xenia-ui", @@ -86,6 +87,7 @@ project("xenia-gpu-d3d12-trace-dump") "xenia-gpu-d3d12", "xenia-hid", "xenia-hid-nop", + "xenia-hid-skylander", "xenia-kernel", "xenia-ui", "xenia-ui-d3d12", diff --git a/src/xenia/gpu/vulkan/premake5.lua b/src/xenia/gpu/vulkan/premake5.lua index a79749e65..b27610286 100644 --- a/src/xenia/gpu/vulkan/premake5.lua +++ b/src/xenia/gpu/vulkan/premake5.lua @@ -38,6 +38,7 @@ project("xenia-gpu-vulkan-trace-viewer") "xenia-gpu-vulkan", "xenia-hid", "xenia-hid-nop", + "xenia-hid-skylander", "xenia-kernel", "xenia-patcher", "xenia-ui", @@ -102,6 +103,7 @@ project("xenia-gpu-vulkan-trace-dump") "xenia-gpu-vulkan", "xenia-hid", "xenia-hid-nop", + "xenia-hid-skylander", "xenia-kernel", "xenia-ui", "xenia-ui-vulkan", diff --git a/src/xenia/hid/input_system.cc b/src/xenia/hid/input_system.cc index 10c27bfcd..f10d8696d 100644 --- a/src/xenia/hid/input_system.cc +++ b/src/xenia/hid/input_system.cc @@ -16,6 +16,11 @@ #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" +#endif // XE_PLATFORM_WIN32 + namespace xe { namespace hid { @@ -28,7 +33,13 @@ DEFINE_double( right_stick_deadzone_percentage, 0.0, "Defines deadzone level for right stick. Allowed range [0.0-1.0].", "HID"); -InputSystem::InputSystem(xe::ui::Window* window) : window_(window) {} +InputSystem::InputSystem(xe::ui::Window* window) : window_(window) { + skylander_portal_ = std::make_unique(); + +#ifdef XE_PLATFORM_WIN32 + skylander_portal_ = std::make_unique(); +#endif // XE_PLATFORM_WIN32 +} InputSystem::~InputSystem() = default; diff --git a/src/xenia/hid/input_system.h b/src/xenia/hid/input_system.h index 69b8710b9..774471684 100644 --- a/src/xenia/hid/input_system.h +++ b/src/xenia/hid/input_system.h @@ -16,6 +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/xbox.h" namespace xe { @@ -55,6 +56,8 @@ class InputSystem { uint32_t GetLastUsedSlot() const { return last_used_slot; } + SkylanderPortal* GetSkylanderPortal() { return skylander_portal_.get(); } + std::unique_lock lock(); private: @@ -74,6 +77,8 @@ class InputSystem { std::vector> drivers_; + std::unique_ptr skylander_portal_; + std::bitset connected_slots = {}; std::array, XUserMaxUserCount> controllers_max_joystick_value = {}; diff --git a/src/xenia/hid/premake5.lua b/src/xenia/hid/premake5.lua index d422980cc..f1b897702 100644 --- a/src/xenia/hid/premake5.lua +++ b/src/xenia/hid/premake5.lua @@ -8,6 +8,7 @@ project("xenia-hid") language("C++") links({ "xenia-base", + "xenia-hid-skylander", }) defines({ }) diff --git a/src/xenia/hid/skylander/premake5.lua b/src/xenia/hid/skylander/premake5.lua new file mode 100644 index 000000000..a55c4c7c3 --- /dev/null +++ b/src/xenia/hid/skylander/premake5.lua @@ -0,0 +1,23 @@ +project_root = "../../../.." +include(project_root.."/tools/build") + +group("src") +project("xenia-hid-skylander") + uuid("ddc114da-a279-4868-8b20-53108599bd78") + kind("StaticLib") + language("C++") + files({ + "skylander_portal.h", + "skylander_portal.cc", + "skylander_emulated.h", + "skylander_emulated.cc" + }) + + filter({"platforms:Windows"}) + links({ + "libusb", + }) + files({ + "skylander_hardware.h", + "skylander_hardware.cc" + }) diff --git a/src/xenia/hid/skylander/skylander_emulated.cc b/src/xenia/hid/skylander/skylander_emulated.cc new file mode 100644 index 000000000..d21e1b5dd --- /dev/null +++ b/src/xenia/hid/skylander/skylander_emulated.cc @@ -0,0 +1,32 @@ +/** + ****************************************************************************** + * 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 \ No newline at end of file diff --git a/src/xenia/hid/skylander/skylander_emulated.h b/src/xenia/hid/skylander/skylander_emulated.h new file mode 100644 index 000000000..9008418c4 --- /dev/null +++ b/src/xenia/hid/skylander/skylander_emulated.h @@ -0,0 +1,34 @@ +/** + ****************************************************************************** + * 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 \ No newline at end of file diff --git a/src/xenia/hid/skylander/skylander_hardware.cc b/src/xenia/hid/skylander/skylander_hardware.cc new file mode 100644 index 000000000..e27844768 --- /dev/null +++ b/src/xenia/hid/skylander/skylander_hardware.cc @@ -0,0 +1,121 @@ +/** + ****************************************************************************** + * 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 \ No newline at end of file diff --git a/src/xenia/hid/skylander/skylander_hardware.h b/src/xenia/hid/skylander/skylander_hardware.h new file mode 100644 index 000000000..d8b5b84bc --- /dev/null +++ b/src/xenia/hid/skylander/skylander_hardware.h @@ -0,0 +1,47 @@ +/** + ****************************************************************************** + * 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 \ No newline at end of file diff --git a/src/xenia/hid/skylander/skylander_portal.cc b/src/xenia/hid/skylander/skylander_portal.cc new file mode 100644 index 000000000..fe46d4983 --- /dev/null +++ b/src/xenia/hid/skylander/skylander_portal.cc @@ -0,0 +1,19 @@ +/** + ****************************************************************************** + * 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 \ No newline at end of file diff --git a/src/xenia/hid/skylander/skylander_portal.h b/src/xenia/hid/skylander/skylander_portal.h new file mode 100644 index 000000000..7962184a2 --- /dev/null +++ b/src/xenia/hid/skylander/skylander_portal.h @@ -0,0 +1,40 @@ +/** + ****************************************************************************** + * 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 \ No newline at end of file diff --git a/src/xenia/kernel/xam/xam_input.cc b/src/xenia/kernel/xam/xam_input.cc index c60afed3f..3f49f852f 100644 --- a/src/xenia/kernel/xam/xam_input.cc +++ b/src/xenia/kernel/xam/xam_input.cc @@ -227,6 +227,44 @@ X_HRESULT_result_t XamUserGetDeviceContext_entry( } 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) { + 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) { + return X_ERROR_INVALID_PARAMETER; + } + + auto input_system = kernel_state()->emulator()->input_system(); + + std::vector data(data_size, 0); + const auto result = input_system->GetSkylanderPortal()->read(data); + *state_ptr = 1; + memcpy(buffer_ptr, data.data(), data.size()); + + return result; +} +DECLARE_XAM_EXPORT1(XamInputNonControllerGetRaw, kInput, kStub); + +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); +} +DECLARE_XAM_EXPORT1(XamInputNonControllerSetRaw, kInput, kStub); + } // namespace xam } // namespace kernel } // namespace xe diff --git a/third_party/libusb b/third_party/libusb new file mode 160000 index 000000000..a61afe5f7 --- /dev/null +++ b/third_party/libusb @@ -0,0 +1 @@ +Subproject commit a61afe5f75d969c4561a1d0ad753aa23cee6329a diff --git a/third_party/libusb.lua b/third_party/libusb.lua new file mode 100644 index 000000000..c38165723 --- /dev/null +++ b/third_party/libusb.lua @@ -0,0 +1,46 @@ +group("third_party") +project("libusb") + uuid("5f8b5485-fde5-4a42-8a13-8545fcf6d25b") + kind("StaticLib") + language("C") + defines({ + "_LIB", + }) + includedirs({"libusb/libusb/"}) + + files({ + "libusb/libusb/core.c", + "libusb/libusb/descriptor.c", + "libusb/libusb/hotplug.c", + "libusb/libusb/io.c", + "libusb/libusb/strerror.c", + "libusb/libusb/sync.c", + }) + + filter({"platforms:Windows"}) + includedirs({"libusb/msvc/"}) + files({ + "libusb/libusb/os/events_windows.c", + "libusb/libusb/os/events_windows.h", + "libusb/libusb/os/threads_windows.c", + "libusb/libusb/os/threads_windows.h", + "libusb/libusb/os/windows_common.c", + "libusb/libusb/os/windows_common.h", + "libusb/libusb/os/windows_usbdk.c", + "libusb/libusb/os/windows_usbdk.h", + "libusb/libusb/os/windows_winusb.c", + "libusb/libusb/os/windows_winusb.h" + }) + + filter({"platforms:Linux"}) + files({ + "libusb/libusb/config.h", + "libusb/libusb/os/events_posix.c", + "libusb/libusb/os/events_posix.h", + "libusb/libusb/os/threads_posix.c", + "libusb/libusb/os/threads_posix.h", + "libusb/libusb/os/linux_netlink.c", + "libusb/libusb/os/linux_udev.c", + "libusb/libusb/os/linux_usbfs.c", + "libusb/libusb/os/linux_usbfs.h" + })