From 69be584cc57c1e0f93943dd9539a9b4b4051436f Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 30 Aug 2026 11:21:10 +0200 Subject: [PATCH 1/2] WUP --- .gitignore | 3 - Launcher/Build-Installer.ps1 | 2 +- .../InstalledLayout.cs | 2 +- README.md | 2 + THIRD-PARTY-NOTICES.md | 3 +- aurora-main/CMakeLists.txt | 2 + aurora-main/cmake/AuroraLibUSB.cmake | 43 ++ aurora-main/cmake/AuroraSDL3Provider.cmake | 3 + aurora-main/cmake/libusb/config.h | 8 + aurora-main/include/dolphin/pad.h | 1 - aurora-main/lib/dolphin/pad/pad.cpp | 1 - runtime/CMakeLists.txt | 9 - runtime/cmake/PublicProducts.cmake | 2 +- runtime/include/runtime_config.h | 28 -- runtime/include/wup028_adapter.h | 46 -- runtime/src/hle/input/pad.cpp | 23 - runtime/src/main.cpp | 17 - runtime/src/settings_overlay.cpp | 95 +--- runtime/src/wup028_adapter.cpp | 450 ------------------ 19 files changed, 65 insertions(+), 675 deletions(-) create mode 100644 aurora-main/cmake/AuroraLibUSB.cmake create mode 100644 aurora-main/cmake/libusb/config.h delete mode 100644 runtime/include/wup028_adapter.h delete mode 100644 runtime/src/wup028_adapter.cpp diff --git a/.gitignore b/.gitignore index 96b8348..1c69c0a 100644 --- a/.gitignore +++ b/.gitignore @@ -25,9 +25,6 @@ Code.pul # Build output /build/ /build-*/ -/.build-output/ -/.build-test/ -/native-build/ /dist/ /out/ [Bb]in/ diff --git a/Launcher/Build-Installer.ps1 b/Launcher/Build-Installer.ps1 index bc954a3..b123610 100644 --- a/Launcher/Build-Installer.ps1 +++ b/Launcher/Build-Installer.ps1 @@ -98,7 +98,7 @@ Assert-Directory $dependencySources 'Pinned offline dependency sources' # to compile (launcher/Prepare-NativePrebuilt.ps1). # Kept in step with InstalledLayout.DependencyNames by Test-PinnedFacts.ps1: the installed host # refuses to call a toolkit complete unless every one of these directories is present. -$requiredDependencies = @('abseil-cpp','cppwinrt','dawn_prebuilt','fmt','freetype','imgui','native_prebuilt','png','SDL','sqlite3','tracy','xxhash','zlib','zstd') +$requiredDependencies = @('abseil-cpp','cppwinrt','dawn_prebuilt','fmt','freetype','imgui','libusb','native_prebuilt','png','SDL','sqlite3','tracy','xxhash','zlib','zstd') # The precompiled archives are only interchangeable with what the user's machine # compiles if both came from this toolchain and this flag set, so a stale package diff --git a/Launcher/WiiCompiled.Setup.Windows/InstalledLayout.cs b/Launcher/WiiCompiled.Setup.Windows/InstalledLayout.cs index 1372e03..598343e 100644 --- a/Launcher/WiiCompiled.Setup.Windows/InstalledLayout.cs +++ b/Launcher/WiiCompiled.Setup.Windows/InstalledLayout.cs @@ -26,7 +26,7 @@ internal static class InstalledLayout /// public static readonly string[] DependencyNames = [ - "abseil-cpp", "cppwinrt", "dawn_prebuilt", "fmt", "freetype", "imgui", "native_prebuilt", + "abseil-cpp", "cppwinrt", "dawn_prebuilt", "fmt", "freetype", "imgui", "libusb", "native_prebuilt", "png", "SDL", "sqlite3", "tracy", "xxhash", "zlib", "zstd" ]; } diff --git a/README.md b/README.md index 134f2a8..8dd8426 100644 --- a/README.md +++ b/README.md @@ -57,6 +57,8 @@ The port does NOT pretend to be a Wii Remote or Classic Controller. Mappings are positional (`south`, `east`, `west`, `north`) rather than Xbox-labelled, so the same config makes sense on Xbox, PlayStation, Nintendo and generic SDL pads alike, and extra inputs like paddles, touchpads and share buttons show up when the hardware reports them. +The official Wii U / Switch GameCube adapter (WUP-028) works too; as with Dolphin, on Windows the +adapter must be switched to the WinUSB driver once (Zadig). ## Requirements diff --git a/THIRD-PARTY-NOTICES.md b/THIRD-PARTY-NOTICES.md index ee637b5..6214087 100644 --- a/THIRD-PARTY-NOTICES.md +++ b/THIRD-PARTY-NOTICES.md @@ -118,7 +118,7 @@ Source: . Full license text: ## Fetched at build time and redistributed in release builds -These are pinned in `aurora-main/extern/CMakeLists.txt` and +These are pinned in `aurora-main/extern/CMakeLists.txt`, `aurora-main/CMakeLists.txt` and `aurora-main/cmake/AuroraDawnProvider.cmake`. They are not stored in this repository; the build downloads them, and release installers carry the resulting binaries. Their license texts are included in the installer's `licenses/` folder. @@ -129,6 +129,7 @@ included in the installer's `licenses/` folder. | Tint (part of Dawn) | with Dawn | BSD-3-Clause | | | DirectXShaderCompiler (`dxcompiler.dll`) | with Dawn | NCSA / University of Illinois Open Source | | | SDL | 3.4.4 | zlib | | +| libusb (linked into SDL on Windows) | 1.0.30 | LGPL-2.1-or-later | | | Abseil | LTS 20240722.0 | Apache-2.0 | | | Dear ImGui | 1.91.9b-docking | MIT | | | {fmt} | 11.1.4 | MIT | | diff --git a/aurora-main/CMakeLists.txt b/aurora-main/CMakeLists.txt index 8ff456f..7035769 100644 --- a/aurora-main/CMakeLists.txt +++ b/aurora-main/CMakeLists.txt @@ -16,6 +16,7 @@ option(AURORA_CACHE_USE_ZSTD "Compress WebGPU cache entries with zstd" ON) set(AURORA_DAWN_VERSION "v20260603.191052" CACHE STRING "Dawn version tag (https://github.com/encounter/dawn-build/releases)") set(AURORA_SDL3_VERSION "3.4.4" CACHE STRING "SDL3 version tag (https://github.com/libsdl-org/SDL/releases)") set(AURORA_NOD_VERSION "v2.0.0-alpha.8" CACHE STRING "nod version tag (https://github.com/encounter/nod/releases)") +set(AURORA_LIBUSB_VERSION "1.0.30" CACHE STRING "libusb version tag (https://github.com/libusb/libusb/releases)") # Platform-specific defaults if (CMAKE_CROSSCOMPILING) @@ -44,6 +45,7 @@ set(AURORA_SDL3_PROVIDER "${_default_provider}" CACHE STRING set_property(CACHE AURORA_SDL3_PROVIDER PROPERTY STRINGS auto vendor system package) set(AURORA_SDL3_LINKAGE "${_default_linkage}" CACHE STRING "SDL3 linkage type preference") set_property(CACHE AURORA_SDL3_LINKAGE PROPERTY STRINGS shared static) +option(AURORA_SDL3_LIBUSB "Build the vendored SDL3 with libusb on Windows (official GameCube adapter support)" ON) # nod (if AURORA_ENABLE_DVD) set(AURORA_NOD_PROVIDER "${_default_provider}" CACHE STRING diff --git a/aurora-main/cmake/AuroraLibUSB.cmake b/aurora-main/cmake/AuroraLibUSB.cmake new file mode 100644 index 0000000..b82a719 --- /dev/null +++ b/aurora-main/cmake/AuroraLibUSB.cmake @@ -0,0 +1,43 @@ +# libusb for SDL3's HIDAPI joystick drivers on Windows. +# +# The official GameCube adapter (WUP-028) is a vendor-specific USB device, not HID, so SDL3 +# can only reach it through libusb - which the official SDL3 packages leave out. The vendored +# SDL3 build compiles libusb from the pinned upstream release and links it in statically. + +include(FetchContent) +FetchContent_Declare(libusb + URL "https://github.com/libusb/libusb/releases/download/v${AURORA_LIBUSB_VERSION}/libusb-${AURORA_LIBUSB_VERSION}.tar.bz2" + DOWNLOAD_EXTRACT_TIMESTAMP TRUE +) +# Upstream ships no CMakeLists.txt, so this only populates the source tree. +FetchContent_MakeAvailable(libusb) + +set(_libusb_root "${libusb_SOURCE_DIR}/libusb") +add_library(usb-1.0 STATIC + "${_libusb_root}/core.c" + "${_libusb_root}/descriptor.c" + "${_libusb_root}/hotplug.c" + "${_libusb_root}/io.c" + "${_libusb_root}/strerror.c" + "${_libusb_root}/sync.c" + "${_libusb_root}/os/events_windows.c" + "${_libusb_root}/os/threads_windows.c" + "${_libusb_root}/os/windows_common.c" + "${_libusb_root}/os/windows_usbdk.c" + "${_libusb_root}/os/windows_winusb.c" +) +target_include_directories(usb-1.0 + PUBLIC "${_libusb_root}" + PRIVATE "${CMAKE_CURRENT_LIST_DIR}/libusb" "${_libusb_root}/os" +) +set_target_properties(usb-1.0 PROPERTIES UNITY_BUILD OFF) +add_library(LibUSB::LibUSB ALIAS usb-1.0) + +# SDL's FindLibUSB expects an installed copy. Satisfy its presence checks with this target +# instead: the alias pre-empts the imported target it would otherwise create, and the link +# probe is answered up front because the archive does not exist until build time. +set(LibUSB_INCLUDE_PATH "${_libusb_root}" CACHE PATH "" FORCE) +set(LibUSB_LIBRARY "usb-1.0" CACHE STRING "" FORCE) +set(HAVE_LIBUSB_H 1 CACHE INTERNAL "") +set(SDL_HIDAPI_LIBUSB ON CACHE BOOL "" FORCE) +set(SDL_HIDAPI_LIBUSB_SHARED OFF CACHE BOOL "" FORCE) diff --git a/aurora-main/cmake/AuroraSDL3Provider.cmake b/aurora-main/cmake/AuroraSDL3Provider.cmake index 458d424..d48aef7 100644 --- a/aurora-main/cmake/AuroraSDL3Provider.cmake +++ b/aurora-main/cmake/AuroraSDL3Provider.cmake @@ -129,6 +129,9 @@ elseif (_aurora_sdl3_provider STREQUAL "vendor") endif () if (WIN32) set(SDL_LIBC ON CACHE BOOL "Use the system C library" FORCE) + if (AURORA_SDL3_LIBUSB) + include("${CMAKE_CURRENT_LIST_DIR}/AuroraLibUSB.cmake") + endif () endif () include(FetchContent) diff --git a/aurora-main/cmake/libusb/config.h b/aurora-main/cmake/libusb/config.h new file mode 100644 index 0000000..fb63d5b --- /dev/null +++ b/aurora-main/cmake/libusb/config.h @@ -0,0 +1,8 @@ +/* libusb build configuration for the Windows backend (MinGW/Clang). */ +#pragma once + +#define PLATFORM_WINDOWS 1 +#define ENABLE_LOGGING 1 +#define DEFAULT_VISIBILITY +#define HAVE_STRUCT_TIMESPEC 1 +#define PRINTF_FORMAT(a, b) __attribute__((__format__(__printf__, a, b))) diff --git a/aurora-main/include/dolphin/pad.h b/aurora-main/include/dolphin/pad.h index 4c96538..6f821c3 100644 --- a/aurora-main/include/dolphin/pad.h +++ b/aurora-main/include/dolphin/pad.h @@ -229,7 +229,6 @@ s32 PADGetNativeButtonPressed(u32 port); PADSignedNativeAxis PADGetNativeAxisPulled(u32 port); void PADRestoreDefaultMapping(u32 port); void PADBlockInput(bool block); -bool PADIsInputBlocked(void); /** * Set the default controller mapping used. diff --git a/aurora-main/lib/dolphin/pad/pad.cpp b/aurora-main/lib/dolphin/pad/pad.cpp index 9cdea6c..ff8d4c6 100644 --- a/aurora-main/lib/dolphin/pad/pad.cpp +++ b/aurora-main/lib/dolphin/pad/pad.cpp @@ -1540,7 +1540,6 @@ void PADBlockInput(const bool block) { } } -bool PADIsInputBlocked() { return g_blockPAD.load(std::memory_order_acquire); } SDL_Gamepad* PADGetSDLGamepadForIndex(const u32 index) { const auto* ctrl = __PADGetControllerForIndex(index); diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 9a3aad1..7f811fd 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -237,15 +237,6 @@ set(MKW_CPU_BASELINE_SOURCE "${CMAKE_CURRENT_LIST_DIR}/src/host_cpu_baseline.cpp list(REMOVE_ITEM SOURCES ${MKW_BASE_PRODUCT_SOURCE} ${MKW_RETRO_REWIND_PRODUCT_SOURCE} ${MKW_CPU_BASELINE_SOURCE}) -# WUP-028 (official GameCube adapter) support talks to the adapter over WinUSB, which only -# exists on Windows - SDL3 already exposes the same hardware as a normal joystick on Linux/macOS, -# so this file has nothing to do there. Every call site into it is separately gated behind -# #if defined(_WIN32) (see wup028_adapter.cpp's own header comment), so it's safe to simply not -# compile it at all on other platforms rather than build a stub implementation. -if(NOT WIN32) - list(REMOVE_ITEM SOURCES "${CMAKE_CURRENT_LIST_DIR}/src/wup028_adapter.cpp") -endif() - # The translator emits the complete, content-addressed source graph. Consuming # this one manifest keeps configure independent of the 28k generated function # files and of optional Retro Rewind artifacts such as code.map. diff --git a/runtime/cmake/PublicProducts.cmake b/runtime/cmake/PublicProducts.cmake index d1fff5e..580ca77 100644 --- a/runtime/cmake/PublicProducts.cmake +++ b/runtime/cmake/PublicProducts.cmake @@ -210,7 +210,7 @@ function(mkw_configure_product target) if(WIN32) target_link_libraries(${target} PRIVATE - dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp setupapi winusb) + dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp) set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE) else() diff --git a/runtime/include/runtime_config.h b/runtime/include/runtime_config.h index 0a0debf..c028a2b 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -63,11 +63,6 @@ struct RuntimeUserConfig { // comma-separated SDL-style physical button names ("south", or // "dpad_up,left_shoulder") as values; pressing either bound button counts. std::array, 12> controllerButtons; -#ifdef _WIN32 - // One-based physical WUP-028 adapter port assigned to each game port. - // Zero or a missing value means the adapter does not own that game port. - std::array gameCubeAdapterPorts{}; -#endif }; namespace RuntimeConfigFile { @@ -367,14 +362,6 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) { config.controllerButtons[index] = FindConfigValue(document, "controller", buttonKeys[index]); } -#ifdef _WIN32 - for (size_t index = 0; index < config.gameCubeAdapterPorts.size(); ++index) { - const std::string key = "adapter_port_" + std::to_string(index + 1); - if (auto value = FindConfigUint(document, "controller", key); value && *value <= 4) { - config.gameCubeAdapterPorts[index] = *value; - } - } -#endif config.widescreen = FindConfigValue(document, "video", "widescreen"); config.windowPosX = FindConfigInt(document, "video", "window_x"); @@ -638,21 +625,6 @@ inline bool SetControllerButton(size_t index, std::string value) { return WriteSetting("controller", kControllerButtonKeys[index], FormatString(value)); } -#ifdef _WIN32 -inline int GameCubeAdapterPort(size_t gamePort) { - if (gamePort >= Get().gameCubeAdapterPorts.size()) return -1; - const uint32_t physicalPort = Get().gameCubeAdapterPorts[gamePort]; - return physicalPort >= 1 && physicalPort <= 4 ? static_cast(physicalPort - 1) : -1; -} - -inline bool SetGameCubeAdapterPort(size_t gamePort, int physicalPort) { - if (gamePort >= Mutable().gameCubeAdapterPorts.size() || physicalPort < -1 || physicalPort >= 4) return false; - const uint32_t storedPort = physicalPort < 0 ? 0u : static_cast(physicalPort + 1); - Mutable().gameCubeAdapterPorts[gamePort] = storedPort; - return WriteSetting("controller", "adapter_port_" + std::to_string(gamePort + 1), std::to_string(storedPort)); -} -#endif - inline bool SetAudioVolume(float value) { value = std::clamp(value, 0.0f, 1.0f); Mutable().audioVolume = value; diff --git a/runtime/include/wup028_adapter.h b/runtime/include/wup028_adapter.h deleted file mode 100644 index c39efba..0000000 --- a/runtime/include/wup028_adapter.h +++ /dev/null @@ -1,46 +0,0 @@ -#pragma once - -#include -#include -#include - -struct PADStatus; - -namespace Wup028Adapter { - -enum class ConnectionState : uint8_t { - Searching, - DriverError, - Connected, -}; - -struct AdapterInfo { - ConnectionState state = ConnectionState::Searching; - std::string deviceName; - std::string detail; - float pollRateHz = 0.0f; - uint8_t inputEndpoint = 0; - uint8_t outputEndpoint = 0; - std::array ports{}; - // Raw adapter type/status byte. High nibble 1 is wired, 2 is wireless. - std::array portStatus{}; - std::array portChangeSequence{}; -}; - -// Starts the project-owned WUP-028 worker. Safe to call more than once. -void Initialize(); -void Shutdown(); - -// Returns true while an official adapter is open. Each entry is a native -// GameCube port; an empty port is represented by PAD_ERR_NO_CONTROLLER. -bool Read(std::array& statuses); -// Assigns a physical adapter port to a game port. Pass -1 to leave the game -// port under Aurora's normal controller assignment. -void SetPortAssignment(uint32_t gamePort, int physicalPort); -int GetPortAssignment(uint32_t gamePort); -// Returns true when the command belongs to an active WUP-028, allowing the -// caller to avoid also sending it to Aurora's unrelated controller backend. -bool SetRumble(uint32_t port, bool enabled); -AdapterInfo GetInfo(); - -} // namespace Wup028Adapter diff --git a/runtime/src/hle/input/pad.cpp b/runtime/src/hle/input/pad.cpp index 2a71351..606dd0a 100644 --- a/runtime/src/hle/input/pad.cpp +++ b/runtime/src/hle/input/pad.cpp @@ -1,9 +1,6 @@ #include "hle_stubs.h" #include "memory.h" #include "hle/controller_status_contract.h" -#ifdef _WIN32 -#include "wup028_adapter.h" -#endif #include #include @@ -36,9 +33,6 @@ void WritePadStatus(uint32_t base, const PADStatus& status) { extern "C" uint32_t PAD__Init_HLE() { -#if defined(_WIN32) - Wup028Adapter::Initialize(); -#endif return PADInit() ? 1u : 0u; } PPC_NATIVE_OVERRIDE(801AF2F0, PAD__Init_HLE, uint32_t, (), ()); @@ -51,17 +45,6 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr) PADStatus statuses[PAD_CHANMAX]{}; uint32_t rumbleMask = PADRead(statuses); -#if defined(_WIN32) - std::array adapterStatuses{}; - if (Wup028Adapter::Read(adapterStatuses) && !PADIsInputBlocked()) { - for (uint32_t port = 0; port < PAD_CHANMAX; ++port) { - if (adapterStatuses[port].err == PAD_ERR_NONE) { - statuses[port] = adapterStatuses[port]; - rumbleMask |= PAD_CHAN0_BIT >> port; - } - } - } -#endif try { for (uint32_t i = 0; i < PAD_CHANMAX; ++i) { @@ -90,12 +73,6 @@ PPC_NATIVE_OVERRIDE(801AF1E4, PAD__Recalibrate_HLE, uint32_t, (uint32_t mask), ( extern "C" void PAD__ControlMotor_HLE(int32_t chan, uint32_t command) { -#if defined(_WIN32) - if (!Wup028Adapter::SetRumble(static_cast(chan), command == PAD_MOTOR_RUMBLE)) { - PADControlMotor(chan, command); - } -#else PADControlMotor(chan, command); -#endif } PPC_NATIVE_OVERRIDE_VOID(801AF908, PAD__ControlMotor_HLE, (int32_t chan, uint32_t command), (chan, command)); diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index 4a36ac2..316220e 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -49,7 +49,6 @@ #include "system_bridge.h" #include "ppc_runtime.h" #include "aurora_events.h" -#include "wup028_adapter.h" #include "fiber_manager.h" #include "hle_stubs.h" #include "runtime_config.h" @@ -59,7 +58,6 @@ #include #include #include -#include #include // Defined in `runtime/src/hle/vi.cpp` (used by GX/VI HLE). @@ -1378,12 +1376,6 @@ int RuntimeMain(int argc, char** argv) { } aurora_set_frame_worker_wait_callback(ServiceGuestTimingDuringAuroraFrameWait); GxGuestWrite::InstallAuroraHooks(); -#if defined(_WIN32) - Wup028Adapter::Initialize(); - for (uint32_t port = 0; port < PAD_CHANMAX; ++port) { - if (Wup028Adapter::GetPortAssignment(port) >= 0) PADClearPort(port); - } -#endif UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width, auroraInfo.windowSize.native_fb_height); settings_overlay::InitializeRuntimeSettings(); @@ -1425,9 +1417,6 @@ int RuntimeMain(int argc, char** argv) { // Shutdown fiber system Fiber::GuestFiberManager::Shutdown(); WindowPlacementPersistence::Flush(true); -#if defined(_WIN32) - Wup028Adapter::Shutdown(); -#endif aurora_shutdown(); SetRuntimeExitCodeImpl(0); ShutdownProcessTranscript(); @@ -1445,9 +1434,6 @@ int RuntimeMain(int argc, char** argv) { SetRuntimeExitCodeImpl(1); Fiber::GuestFiberManager::Shutdown(); WindowPlacementPersistence::Flush(true); -#if defined(_WIN32) - Wup028Adapter::Shutdown(); -#endif aurora_shutdown(); ShutdownProcessTranscript(); return 1; @@ -1459,9 +1445,6 @@ int RuntimeMain(int argc, char** argv) { SetRuntimeExitCodeImpl(1); Fiber::GuestFiberManager::Shutdown(); WindowPlacementPersistence::Flush(true); -#if defined(_WIN32) - Wup028Adapter::Shutdown(); -#endif aurora_shutdown(); ShutdownProcessTranscript(); return 1; diff --git a/runtime/src/settings_overlay.cpp b/runtime/src/settings_overlay.cpp index cb2497c..9fa64a7 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -1,7 +1,4 @@ #include "settings_overlay.h" -#ifdef _WIN32 -#include "wup028_adapter.h" -#endif #include "audio_backend.h" #include "controller_mapping_wizard.h" #include "game_graphics_options.h" @@ -314,41 +311,6 @@ void ApplyConfiguredMappings() { } } -void DrawGameCubeAdapterInfo() { - // The official GameCube adapter is Windows-only (see wup028_adapter.cpp); on Linux/macOS, - // Wup028Adapter is a permanently-disconnected stub, and SDL3 already exposes the same - // hardware as a normal joystick, so this menu would only ever show "Searching" and four - // perpetually-empty adapter ports - confusing clutter for a feature that can't do anything - // on this platform. Skip it entirely rather than render a menu that never has content. -#if defined(_WIN32) - ImGui::Separator(); - if (!ImGui::BeginMenu("GameCube adapter info")) return; - - const auto adapter = Wup028Adapter::GetInfo(); - const char* state = adapter.state == Wup028Adapter::ConnectionState::Connected - ? "Connected" - : adapter.state == Wup028Adapter::ConnectionState::DriverError ? "Driver error" - : "Searching"; - ImGui::Text("Status: %s", state); - if (!adapter.deviceName.empty()) { - ImGui::Text("Device: %s", adapter.deviceName.c_str()); - } - ImGui::TextWrapped("%s", adapter.detail.c_str()); - if (adapter.state == Wup028Adapter::ConnectionState::Connected) { - ImGui::Text("Poll rate: %.1f reports/s", adapter.pollRateHz); - ImGui::Text("Endpoints: IN 0x%02X, OUT 0x%02X", adapter.inputEndpoint, adapter.outputEndpoint); - for (size_t port = 0; port < adapter.ports.size(); ++port) { - const uint8_t type = adapter.portStatus[port] & 0x30; - const char* typeName = type == 0x10 ? "wired" : type == 0x20 ? "wireless" : "none"; - ImGui::Text("Adapter port %u: %s (type %s, raw 0x%02X)", static_cast(port + 1), - adapter.ports[port] ? "Controller connected" : "Empty", typeName, - adapter.portStatus[port]); - } - } - ImGui::EndMenu(); -#endif -} - void DrawControllerSettings() { for (int port = 0; port < PAD_MAX_CONTROLLERS; ++port) { const std::string label = "Port " + std::to_string(port + 1); @@ -360,50 +322,10 @@ void DrawControllerSettings() { ImGui::Separator(); const uint32_t selectedGamePort = static_cast(g_controllerPort); -#if defined(_WIN32) - const int adapterAssignment = Wup028Adapter::GetPortAssignment(selectedGamePort); - if (adapterAssignment >= 0) { - ImGui::Text("Assigned: GameCube adapter port %d", adapterAssignment + 1); - } else -#endif - { - const char* currentName = PADGetName(selectedGamePort); - ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None"); - } -#if defined(_WIN32) - // Windows-only, same reasoning as DrawGameCubeAdapterInfo() above: on other platforms - // adapterAssignment is always -1 and every port would always read "(empty)", so this submenu - // would never have anything real to offer. - if (ImGui::BeginMenu("Assign GameCube adapter port")) { - if (ImGui::MenuItem("None", nullptr, adapterAssignment < 0)) { - Wup028Adapter::SetPortAssignment(selectedGamePort, -1); - RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1); - } - const auto adapter = Wup028Adapter::GetInfo(); - for (int physicalPort = 0; physicalPort < PAD_CHANMAX; ++physicalPort) { - const std::string label = "Adapter port " + std::to_string(physicalPort + 1) + - (adapter.ports[static_cast(physicalPort)] ? " (connected)" : " (empty)"); - if (ImGui::MenuItem(label.c_str(), nullptr, adapterAssignment == physicalPort)) { - for (uint32_t gamePort = 0; gamePort < PAD_CHANMAX; ++gamePort) { - if (gamePort != selectedGamePort && Wup028Adapter::GetPortAssignment(gamePort) == physicalPort) { - RuntimeConfigFile::SetGameCubeAdapterPort(gamePort, -1); - } - } - PADClearPort(selectedGamePort); - Wup028Adapter::SetPortAssignment(selectedGamePort, physicalPort); - RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, physicalPort); - g_configuredControllerIndices.fill(std::numeric_limits::min()); - } - } - ImGui::EndMenu(); - } -#endif + const char* currentName = PADGetName(selectedGamePort); + ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None"); if (ImGui::MenuItem("Unassign controller")) { PADClearPort(selectedGamePort); -#if defined(_WIN32) - Wup028Adapter::SetPortAssignment(selectedGamePort, -1); - RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1); -#endif g_configuredControllerIndices.fill(std::numeric_limits::min()); } ImGui::Separator(); @@ -411,9 +333,6 @@ void DrawControllerSettings() { const uint32_t controllerCount = PADCount(); if (controllerCount == 0) { ImGui::TextDisabled("No controller connected"); -#if defined(_WIN32) - DrawGameCubeAdapterInfo(); -#endif return; } @@ -422,10 +341,6 @@ void DrawControllerSettings() { const char* name = PADGetNameForControllerIndex(index); ImGui::PushID(static_cast(index)); if (ImGui::MenuItem(name != nullptr ? name : "Unknown controller")) { -#if defined(_WIN32) - Wup028Adapter::SetPortAssignment(selectedGamePort, -1); - RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1); -#endif PADSetPortForIndex(index, selectedGamePort); g_configuredControllerIndices.fill(std::numeric_limits::min()); ApplyConfiguredMappings(); @@ -439,9 +354,6 @@ void DrawControllerSettings() { PADButtonMapping* mappings = PADGetButtonMappings(static_cast(g_controllerPort), &mappingCount); if (mappings == nullptr || mappingCount != PAD_BUTTON_COUNT) { ImGui::TextDisabled("Assign a controller to edit its buttons"); -#if defined(_WIN32) - DrawGameCubeAdapterInfo(); -#endif return; } @@ -581,9 +493,6 @@ void DrawControllerSettings() { ImGui::TextUnformatted(kControllerButtons[i].label); ImGui::PopID(); } -#if defined(_WIN32) - DrawGameCubeAdapterInfo(); -#endif } void DrawAudioSettings() { diff --git a/runtime/src/wup028_adapter.cpp b/runtime/src/wup028_adapter.cpp deleted file mode 100644 index dec92f8..0000000 --- a/runtime/src/wup028_adapter.cpp +++ /dev/null @@ -1,450 +0,0 @@ -#include "wup028_adapter.h" - -#include "runtime_log.h" -#include "runtime_config.h" - -#include -#include -#include -#include -#include -#include -#include - -#include -#include -#include -#include -#include -#include -#include -#include -#include -#include - -namespace Wup028Adapter { -namespace { - -constexpr uint16_t kNintendoVendor = 0x057e; -constexpr uint16_t kAdapterProduct = 0x0337; -constexpr size_t kReportSize = 37; -constexpr auto kInputReportTimeout = std::chrono::milliseconds(500); - -std::mutex g_mutex; -std::array g_statuses{}; -std::array g_rumble{}; -std::array g_portAssignments{{-1, -1, -1, -1}}; -std::thread g_worker; -std::atomic_bool g_stop{false}; -std::atomic_bool g_running{false}; -std::atomic_bool g_connected{false}; -AdapterInfo g_info; - -struct Device { - HANDLE file = INVALID_HANDLE_VALUE; - HANDLE event = nullptr; - WINUSB_INTERFACE_HANDLE usb = nullptr; - UCHAR inputPipe = 0; - UCHAR outputPipe = 0; - - ~Device() { Close(); } - void Close() { - if (usb != nullptr) WinUsb_Free(usb); - if (event != nullptr) CloseHandle(event); - if (file != INVALID_HANDLE_VALUE) CloseHandle(file); - usb = nullptr; - event = nullptr; - file = INVALID_HANDLE_VALUE; - } -}; - -bool Transfer(Device& device, bool input, UCHAR pipe, UCHAR* data, ULONG size, ULONG& transferred, - DWORD timeoutMs, bool* timedOut = nullptr) { - if (timedOut != nullptr) *timedOut = false; - ResetEvent(device.event); - OVERLAPPED operation{}; - operation.hEvent = device.event; - const BOOL started = input ? WinUsb_ReadPipe(device.usb, pipe, data, size, &transferred, &operation) - : WinUsb_WritePipe(device.usb, pipe, data, size, &transferred, &operation); - if (started) return true; - if (GetLastError() != ERROR_IO_PENDING) return false; - const DWORD wait = WaitForSingleObject(device.event, timeoutMs); - if (wait == WAIT_OBJECT_0) { - return WinUsb_GetOverlappedResult(device.usb, &operation, &transferred, FALSE); - } - CancelIoEx(device.file, &operation); - WaitForSingleObject(device.event, INFINITE); - WinUsb_GetOverlappedResult(device.usb, &operation, &transferred, FALSE); - if (timedOut != nullptr && wait == WAIT_TIMEOUT) *timedOut = true; - return false; -} - -struct DeviceMatch { - std::wstring path; - std::string name; -}; - -std::string WideToUtf8(const wchar_t* value) { - if (value == nullptr || *value == L'\0') return {}; - const int size = WideCharToMultiByte(CP_UTF8, 0, value, -1, nullptr, 0, nullptr, nullptr); - if (size <= 1) return {}; - std::string result(static_cast(size), '\0'); - WideCharToMultiByte(CP_UTF8, 0, value, -1, result.data(), size, nullptr, nullptr); - result.resize(static_cast(size - 1)); - return result; -} - -DeviceMatch FindAdapter() { - HDEVINFO devices = SetupDiGetClassDevsW(&GUID_DEVINTERFACE_USB_DEVICE, nullptr, nullptr, - DIGCF_PRESENT | DIGCF_DEVICEINTERFACE); - if (devices == INVALID_HANDLE_VALUE) return {}; - - DeviceMatch result; - for (DWORD index = 0;; ++index) { - SP_DEVICE_INTERFACE_DATA iface{sizeof(iface)}; - if (!SetupDiEnumDeviceInterfaces(devices, nullptr, &GUID_DEVINTERFACE_USB_DEVICE, index, &iface)) break; - - DWORD required = 0; - SetupDiGetDeviceInterfaceDetailW(devices, &iface, nullptr, 0, &required, nullptr); - if (required < sizeof(SP_DEVICE_INTERFACE_DETAIL_DATA_W)) continue; - std::vector storage(required); - auto* detail = reinterpret_cast(storage.data()); - detail->cbSize = sizeof(*detail); - SP_DEVINFO_DATA deviceInfo{sizeof(deviceInfo)}; - if (!SetupDiGetDeviceInterfaceDetailW(devices, &iface, detail, required, nullptr, &deviceInfo)) continue; - - std::wstring lower(detail->DevicePath); - std::transform(lower.begin(), lower.end(), lower.begin(), - [](wchar_t c) { return static_cast(std::towlower(c)); }); - if (lower.find(L"vid_057e") != std::wstring::npos && lower.find(L"pid_0337") != std::wstring::npos) { - result.path = detail->DevicePath; - std::array description{}; - if (SetupDiGetDeviceRegistryPropertyW(devices, &deviceInfo, SPDRP_FRIENDLYNAME, nullptr, - reinterpret_cast(description.data()), - static_cast(description.size() * sizeof(wchar_t)), nullptr) || - SetupDiGetDeviceRegistryPropertyW(devices, &deviceInfo, SPDRP_DEVICEDESC, nullptr, - reinterpret_cast(description.data()), - static_cast(description.size() * sizeof(wchar_t)), nullptr)) { - result.name = WideToUtf8(description.data()); - } - break; - } - } - SetupDiDestroyDeviceInfoList(devices); - return result; -} - -bool Open(Device& device, std::string& name, std::string& error) { - const auto match = FindAdapter(); - if (match.path.empty()) { - error = "No VID 057E / PID 0337 adapter is present"; - return false; - } - name = match.name.empty() ? "WUP-028-compatible adapter" : match.name; - device.file = CreateFileW(match.path.c_str(), GENERIC_READ | GENERIC_WRITE, - FILE_SHARE_READ | FILE_SHARE_WRITE, nullptr, OPEN_EXISTING, - FILE_ATTRIBUTE_NORMAL | FILE_FLAG_OVERLAPPED, nullptr); - if (device.file == INVALID_HANDLE_VALUE || !WinUsb_Initialize(device.file, &device.usb)) { - error = "WinUSB could not open the adapter (Windows error " + std::to_string(GetLastError()) + ")"; - device.Close(); - return false; - } - device.event = CreateEventW(nullptr, TRUE, FALSE, nullptr); - if (device.event == nullptr) { - error = "Could not create the WinUSB transfer event"; - device.Close(); - return false; - } - - USB_INTERFACE_DESCRIPTOR descriptor{}; - if (!WinUsb_QueryInterfaceSettings(device.usb, 0, &descriptor)) { - error = "WinUSB could not query the adapter interface"; - device.Close(); - return false; - } - for (UCHAR index = 0; index < descriptor.bNumEndpoints; ++index) { - WINUSB_PIPE_INFORMATION pipe{}; - if (!WinUsb_QueryPipe(device.usb, 0, index, &pipe)) continue; - if ((pipe.PipeType == UsbdPipeTypeInterrupt || pipe.PipeType == UsbdPipeTypeBulk) && - USB_ENDPOINT_DIRECTION_IN(pipe.PipeId) && device.inputPipe == 0) { - device.inputPipe = pipe.PipeId; - } else if ((pipe.PipeType == UsbdPipeTypeInterrupt || pipe.PipeType == UsbdPipeTypeBulk) && - USB_ENDPOINT_DIRECTION_OUT(pipe.PipeId) && device.outputPipe == 0) { - device.outputPipe = pipe.PipeId; - } - } - if (device.inputPipe == 0 || device.outputPipe == 0) { - error = "The adapter has no usable input/output endpoint"; - device.Close(); - return false; - } - - UCHAR command = 0x13; // Enable the adapter's 37-byte input stream. - ULONG written = 0; - if (!Transfer(device, false, device.outputPipe, &command, 1, written, 1000) || written != 1) { - error = "The adapter rejected input initialization on endpoint 0x"; - const char hex[] = "0123456789ABCDEF"; - error += hex[device.outputPipe >> 4]; - error += hex[device.outputPipe & 15]; - device.Close(); - return false; - } - return true; -} - -int8_t Axis(uint8_t raw) { - constexpr int kCenter = 128; - constexpr int kCenterTolerance = 10; - if (raw >= kCenter - kCenterTolerance && raw <= kCenter + kCenterTolerance) return 0; - return static_cast(std::clamp(static_cast(raw) - kCenter, -128, 127)); -} - -PADStatus DecodePort(const uint8_t* p) { - PADStatus out{}; - // The WUP-028 protocol uses type 1 for wired pads and type 2 for - // WaveBird/wireless receivers. Testing only bit 0x10 drops every type-2 - // controller and makes otherwise valid ports appear empty. - if ((p[0] & 0x30) == 0) { - out.err = PAD_ERR_NO_CONTROLLER; - return out; - } - if (p[1] & 0x01) out.button |= PAD_BUTTON_A; - if (p[1] & 0x02) out.button |= PAD_BUTTON_B; - if (p[1] & 0x04) out.button |= PAD_BUTTON_X; - if (p[1] & 0x08) out.button |= PAD_BUTTON_Y; - if (p[1] & 0x10) out.button |= PAD_BUTTON_LEFT; - if (p[1] & 0x20) out.button |= PAD_BUTTON_RIGHT; - if (p[1] & 0x40) out.button |= PAD_BUTTON_DOWN; - if (p[1] & 0x80) out.button |= PAD_BUTTON_UP; - if (p[2] & 0x01) out.button |= PAD_BUTTON_START; - if (p[2] & 0x02) out.button |= PAD_TRIGGER_Z; - if (p[2] & 0x04) out.button |= PAD_TRIGGER_R; - if (p[2] & 0x08) out.button |= PAD_TRIGGER_L; - out.stickX = Axis(p[3]); - out.stickY = Axis(p[4]); - out.substickX = Axis(p[5]); - out.substickY = Axis(p[6]); - out.triggerL = p[7]; - out.triggerR = p[8]; - out.err = PAD_ERR_NONE; - return out; -} - -bool SendRumble(Device& device, const std::array& motors) { - std::array report{{0x11, motors[0], motors[1], motors[2], motors[3]}}; - ULONG written = 0; - return Transfer(device, false, device.outputPipe, report.data(), report.size(), written, 1000) && - written == report.size(); -} - -bool RefreshInputStream(Device& device) { - UCHAR command = 0x13; - ULONG written = 0; - return Transfer(device, false, device.outputPipe, &command, 1, written, 1000) && written == 1; -} - -void ClearConnectedPorts(const char* reason) { - std::lock_guard lock(g_mutex); - g_rumble.fill(0); - for (size_t port = 0; port < g_info.ports.size(); ++port) { - if (g_info.ports[port]) { - g_info.ports[port] = false; - ++g_info.portChangeSequence[port]; - RT_LOG(RT_TAG_RUNTIME) << "GameCube adapter port " << (port + 1) - << " controller disconnected (" << reason << ")" << std::endl; - } - g_statuses[port] = {}; - g_statuses[port].err = PAD_ERR_NO_CONTROLLER; - g_info.portStatus[port] = 0; - } -} - -void Worker() { - std::string lastError; - while (!g_stop.load(std::memory_order_acquire)) { - Device device; - std::string name; - std::string error; - if (!Open(device, name, error)) { - g_connected.store(false, std::memory_order_release); - { - std::lock_guard lock(g_mutex); - g_info.state = error.starts_with("No VID") ? ConnectionState::Searching : ConnectionState::DriverError; - g_info.deviceName = name; - g_info.detail = error; - g_info.pollRateHz = 0.0f; - g_info.inputEndpoint = 0; - g_info.outputEndpoint = 0; - g_info.ports.fill(false); - g_info.portStatus.fill(0); - } - if (error != lastError && !error.starts_with("No VID")) { - RT_LOG(RT_TAG_RUNTIME) << "GameCube adapter: " << error << std::endl; - } - lastError = error; - std::this_thread::sleep_for(std::chrono::seconds(1)); - continue; - } - RT_LOG(RT_TAG_RUNTIME) << name << " connected (input endpoint 0x" << std::hex - << static_cast(device.inputPipe) << ", output endpoint 0x" - << static_cast(device.outputPipe) << std::dec << ")" << std::endl; - lastError.clear(); - g_connected.store(true, std::memory_order_release); - { - std::lock_guard lock(g_mutex); - g_info.state = ConnectionState::Connected; - g_info.deviceName = name; - g_info.detail = "Receiving native GameCube reports"; - g_info.inputEndpoint = device.inputPipe; - g_info.outputEndpoint = device.outputPipe; - } - std::array sentRumble{}; - auto rateStart = std::chrono::steady_clock::now(); - uint32_t rateReports = 0; - std::array reportedPorts{}; - auto lastReport = std::chrono::steady_clock::now(); - - while (!g_stop.load(std::memory_order_acquire)) { - std::array report{}; - ULONG read = 0; - bool timedOut = false; - if (!Transfer(device, true, device.inputPipe, report.data(), report.size(), read, 100, &timedOut)) { - if (timedOut && std::chrono::steady_clock::now() - lastReport < kInputReportTimeout) continue; - break; - } - if (read != report.size() || report[0] != 0x21) { - if (std::chrono::steady_clock::now() - lastReport >= kInputReportTimeout) break; - continue; - } - lastReport = std::chrono::steady_clock::now(); - ++rateReports; - std::array decoded{}; - for (size_t port = 0; port < decoded.size(); ++port) decoded[port] = DecodePort(report.data() + 1 + port * 9); - std::array desired{}; - std::array transitions{}; - bool refreshStream = false; - { - std::lock_guard lock(g_mutex); - g_statuses = decoded; - desired = g_rumble; - for (size_t port = 0; port < decoded.size(); ++port) { - g_info.portStatus[port] = report[1 + port * 9]; - const bool present = decoded[port].err == PAD_ERR_NONE; - if (present != reportedPorts[port]) { - reportedPorts[port] = present; - g_info.ports[port] = present; - ++g_info.portChangeSequence[port]; - transitions[port] = present ? 1 : -1; - } - } - const auto now = std::chrono::steady_clock::now(); - const float seconds = std::chrono::duration(now - rateStart).count(); - if (seconds >= 1.0f) { - g_info.pollRateHz = static_cast(rateReports) / seconds; - rateReports = 0; - rateStart = now; - refreshStream = true; - } - } - for (size_t port = 0; port < transitions.size(); ++port) { - if (transitions[port] != 0) { - RT_LOG(RT_TAG_RUNTIME) << "GameCube adapter port " << (port + 1) << " controller " - << (transitions[port] > 0 ? "connected" : "disconnected") << std::endl; - } - } - if (desired != sentRumble) { - if (!SendRumble(device, desired)) break; - sentRumble = desired; - } - if (refreshStream && !RefreshInputStream(device)) break; - } - // Do not leave a motor latched on when stopping or abandoning this handle. - SendRumble(device, {}); - g_connected.store(false, std::memory_order_release); - ClearConnectedPorts("adapter unavailable"); - { - std::lock_guard lock(g_mutex); - g_info.state = ConnectionState::Searching; - g_info.detail = "Adapter disconnected; waiting for reconnect"; - g_info.pollRateHz = 0.0f; - } - if (!g_stop.load(std::memory_order_acquire)) { - RT_LOG(RT_TAG_RUNTIME) << "WUP-028 GameCube adapter disconnected; waiting for reconnect" << std::endl; - } - } -} - -} // namespace - -void Initialize() { - bool expected = false; - if (!g_running.compare_exchange_strong(expected, true)) return; - for (auto& status : g_statuses) status.err = PAD_ERR_NO_CONTROLLER; - for (size_t gamePort = 0; gamePort < g_portAssignments.size(); ++gamePort) { - g_portAssignments[gamePort] = static_cast(RuntimeConfigFile::GameCubeAdapterPort(gamePort)); - } - g_stop.store(false, std::memory_order_release); - g_worker = std::thread(Worker); -} - -void Shutdown() { - if (!g_running.exchange(false)) return; - g_stop.store(true, std::memory_order_release); - if (g_worker.joinable()) g_worker.join(); - g_connected.store(false, std::memory_order_release); -} - -bool Read(std::array& statuses) { - if (!g_connected.load(std::memory_order_acquire)) return false; - std::lock_guard lock(g_mutex); - for (auto& status : statuses) status.err = PAD_ERR_NO_CONTROLLER; - for (size_t gamePort = 0; gamePort < statuses.size(); ++gamePort) { - const int physicalPort = g_portAssignments[gamePort]; - if (physicalPort >= 0) statuses[gamePort] = g_statuses[static_cast(physicalPort)]; - } - return true; -} - -void SetPortAssignment(uint32_t gamePort, int physicalPort) { - if (gamePort >= g_portAssignments.size() || physicalPort < -1 || physicalPort >= PAD_CHANMAX) return; - std::lock_guard lock(g_mutex); - const int oldPhysicalPort = g_portAssignments[gamePort]; - if (oldPhysicalPort >= 0) g_rumble[static_cast(oldPhysicalPort)] = 0; - if (physicalPort >= 0) { - for (auto& assignment : g_portAssignments) { - if (assignment == physicalPort) { - assignment = -1; - g_rumble[static_cast(physicalPort)] = 0; - } - } - } - g_portAssignments[gamePort] = static_cast(physicalPort); -} - -int GetPortAssignment(uint32_t gamePort) { - if (gamePort >= g_portAssignments.size()) return -1; - std::lock_guard lock(g_mutex); - return g_portAssignments[gamePort]; -} - -bool SetRumble(uint32_t port, bool enabled) { - if (port >= g_rumble.size() || !g_connected.load(std::memory_order_acquire)) return false; - std::lock_guard lock(g_mutex); - if (!g_connected.load(std::memory_order_acquire)) return false; - const int physicalPort = g_portAssignments[port]; - if (physicalPort < 0) return false; - const size_t adapterPort = static_cast(physicalPort); - if (g_statuses[adapterPort].err != PAD_ERR_NONE) { - g_rumble[adapterPort] = 0; - return false; - } - g_rumble[adapterPort] = enabled ? 1 : 0; - return true; -} - -AdapterInfo GetInfo() { - std::lock_guard lock(g_mutex); - return g_info; -} - -} // namespace Wup028Adapter From 3fb7373c4f73b0ffe8c66a7ac5ee2be13c9d46fd Mon Sep 17 00:00:00 2001 From: patchzyy <64382339+patchzyy@users.noreply.github.com> Date: Sun, 30 Aug 2026 11:34:45 +0200 Subject: [PATCH 2/2] Update AuroraLibUSB.cmake --- aurora-main/cmake/AuroraLibUSB.cmake | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/aurora-main/cmake/AuroraLibUSB.cmake b/aurora-main/cmake/AuroraLibUSB.cmake index b82a719..2739736 100644 --- a/aurora-main/cmake/AuroraLibUSB.cmake +++ b/aurora-main/cmake/AuroraLibUSB.cmake @@ -38,6 +38,6 @@ add_library(LibUSB::LibUSB ALIAS usb-1.0) # probe is answered up front because the archive does not exist until build time. set(LibUSB_INCLUDE_PATH "${_libusb_root}" CACHE PATH "" FORCE) set(LibUSB_LIBRARY "usb-1.0" CACHE STRING "" FORCE) -set(HAVE_LIBUSB_H 1 CACHE INTERNAL "") +set(HAVE_LIBUSB_H 1 CACHE INTERNAL "" FORCE) set(SDL_HIDAPI_LIBUSB ON CACHE BOOL "" FORCE) set(SDL_HIDAPI_LIBUSB_SHARED OFF CACHE BOOL "" FORCE)