From 251529d66e0d6e2429180f94e47a5fc33aaf84e0 Mon Sep 17 00:00:00 2001 From: GalaxisBeast Date: Wed, 26 Aug 2026 20:12:15 -0400 Subject: [PATCH] fix issues fix timeout loop, fixed drift issues, fixed infinite rumble, fixed gamecube controller adapter taking over all ports, require assigning a virtual controller port specifically to the gamecube controller adapter, and block inputs from gamecube controller while settings menu (f10 menu) is open --- aurora-main/include/dolphin/pad.h | 1 + aurora-main/lib/dolphin/pad/pad.cpp | 13 +++-- aurora-main/lib/input.cpp | 35 ++--------- runtime/include/runtime_config.h | 22 +++++++ runtime/include/wup028_adapter.h | 4 ++ runtime/src/hle/input/pad.cpp | 14 ++--- runtime/src/settings_overlay.cpp | 90 +++++++++++++++++++++-------- runtime/src/wup028_adapter.cpp | 59 ++++++++++++++++--- 8 files changed, 161 insertions(+), 77 deletions(-) diff --git a/aurora-main/include/dolphin/pad.h b/aurora-main/include/dolphin/pad.h index 6f821c3..4c96538 100644 --- a/aurora-main/include/dolphin/pad.h +++ b/aurora-main/include/dolphin/pad.h @@ -229,6 +229,7 @@ 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 6ae8ecb..ed0cc8b 100644 --- a/aurora-main/lib/dolphin/pad/pad.cpp +++ b/aurora-main/lib/dolphin/pad/pad.cpp @@ -5,6 +5,7 @@ #include #include +#include #include #include @@ -283,7 +284,7 @@ constexpr PADCLampRegion ClampRegion{ bool g_initialized; bool g_keyboardBindingsLoaded = false; -bool g_blockPAD = false; +std::atomic_bool g_blockPAD{false}; bool g_suppressHeldOnRead = false; std::array g_suppressedButtons{}; std::array g_suppressLeftTrigger{}; @@ -659,7 +660,8 @@ u32 PADRead(PADStatus* status) { int numKeys = 0; const bool* kbState = SDL_GetKeyboardState(&numKeys); - const bool captureHeldInput = g_suppressHeldOnRead && !g_blockPAD; + const bool inputBlocked = g_blockPAD.load(std::memory_order_acquire); + const bool captureHeldInput = g_suppressHeldOnRead && !inputBlocked; g_suppressHeldOnRead = false; uint32_t rumbleSupport = 0; @@ -882,7 +884,7 @@ u32 PADRead(PADStatus* status) { } } - if (g_blockPAD) { + if (inputBlocked) { neutralize_status(status[i]); } else { apply_unblock_suppression(status[i], i, captureHeldInput); @@ -1530,12 +1532,13 @@ void PADRestoreDefaultMapping(const u32 port) { } void PADBlockInput(const bool block) { - if (g_blockPAD && !block) { + if (g_blockPAD.exchange(block, std::memory_order_acq_rel) && !block) { g_suppressHeldOnRead = true; } - g_blockPAD = block; } +bool PADIsInputBlocked() { return g_blockPAD.load(std::memory_order_acquire); } + SDL_Gamepad* PADGetSDLGamepadForIndex(const u32 index) { const auto* ctrl = __PADGetControllerForIndex(index); if (ctrl == nullptr) { diff --git a/aurora-main/lib/input.cpp b/aurora-main/lib/input.cpp index 8884f1a..2b3651e 100644 --- a/aurora-main/lib/input.cpp +++ b/aurora-main/lib/input.cpp @@ -329,38 +329,11 @@ void apply_port_preferences() noexcept { } } -// SDL only hands out a player index when the device already had a gamepad mapping -// at connect time, so anything mapped later (the setup wizard) stays at -1. +// Ports are explicit assignments. SDL may choose a player index at connection +// time, but accepting it would make a newly connected controller silently take +// over a game port before the user assigns it in the controller menu. void ensure_player_index(GameController& controller) noexcept { - const int32_t player = SDL_GetGamepadPlayerIndex(controller.m_controller); - if (player >= 0) { - controller.m_playerIndex = player; - return; - } - if (controller.m_playerIndex >= 0) { - return; - } - ensure_port_preferences_loaded(); - const auto claim = [&](bool skipConfiguredPorts) { - for (int32_t port = 0; port < PAD_MAX_CONTROLLERS; ++port) { - if (skipConfiguredPorts && g_portPreferences[port].state != PortPreferenceState::Unset) { - continue; - } - const bool taken = std::any_of(g_GameControllers.begin(), g_GameControllers.end(), [&](const auto& entry) { - return entry.second.m_controller != controller.m_controller && effective_player_index(entry.second) == port; - }); - if (!taken) { - assign_player_index(controller, port); - return true; - } - } - return false; - }; - // Explicitly configured ports are only used as a last resort so a hot-plugged - // controller cannot steal the port its preferred device will claim. - if (!claim(true)) { - claim(false); - } + assign_player_index(controller, -1); } } // namespace diff --git a/runtime/include/runtime_config.h b/runtime/include/runtime_config.h index a350e6c..2501192 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -60,6 +60,9 @@ 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; + // 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{}; }; namespace RuntimeConfigFile { @@ -323,6 +326,12 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) { config.controllerButtons[index] = FindConfigValue(document, "controller", buttonKeys[index]); } + 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; + } + } config.widescreen = FindConfigValue(document, "video", "widescreen"); config.windowPosX = FindConfigInt(document, "video", "window_x"); @@ -586,6 +595,19 @@ inline bool SetControllerButton(size_t index, std::string value) { return WriteSetting("controller", kControllerButtonKeys[index], FormatString(value)); } +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)); +} + 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 index d0b0a4e..c39efba 100644 --- a/runtime/include/wup028_adapter.h +++ b/runtime/include/wup028_adapter.h @@ -34,6 +34,10 @@ 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); diff --git a/runtime/src/hle/input/pad.cpp b/runtime/src/hle/input/pad.cpp index 1272c9e..68645d7 100644 --- a/runtime/src/hle/input/pad.cpp +++ b/runtime/src/hle/input/pad.cpp @@ -47,20 +47,14 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr) PADStatus statuses[PAD_CHANMAX]{}; std::array adapterStatuses{}; - uint32_t rumbleMask = 0; - const bool adapterHasController = Wup028Adapter::Read(adapterStatuses) && - std::any_of(adapterStatuses.begin(), adapterStatuses.end(), [](const PADStatus& status) { - return status.err == PAD_ERR_NONE; - }); - if (adapterHasController) { - std::copy(adapterStatuses.begin(), adapterStatuses.end(), statuses); + uint32_t rumbleMask = PADRead(statuses); + if (Wup028Adapter::Read(adapterStatuses) && !PADIsInputBlocked()) { for (uint32_t port = 0; port < PAD_CHANMAX; ++port) { - if (statuses[port].err == PAD_ERR_NONE) { + if (adapterStatuses[port].err == PAD_ERR_NONE) { + statuses[port] = adapterStatuses[port]; rumbleMask |= PAD_CHAN0_BIT >> port; } } - } else { - rumbleMask = PADRead(statuses); } try { diff --git a/runtime/src/settings_overlay.cpp b/runtime/src/settings_overlay.cpp index 113349e..fcf3389 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -312,6 +312,34 @@ void ApplyConfiguredMappings() { } } +void DrawGameCubeAdapterInfo() { + 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(); +} + void DrawControllerSettings() { for (int port = 0; port < PAD_MAX_CONTROLLERS; ++port) { const std::string label = "Port " + std::to_string(port + 1); @@ -322,47 +350,60 @@ void DrawControllerSettings() { } ImGui::Separator(); - if (ImGui::BeginMenu("GameCube controller adapter")) { - 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()); + const uint32_t selectedGamePort = static_cast(g_controllerPort); + const int adapterAssignment = Wup028Adapter::GetPortAssignment(selectedGamePort); + if (adapterAssignment >= 0) { + ImGui::Text("Assigned: GameCube adapter port %d", adapterAssignment + 1); + } else { + const char* currentName = PADGetName(selectedGamePort); + ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None"); + } + if (ImGui::BeginMenu("Assign GameCube adapter port")) { + if (ImGui::MenuItem("None", nullptr, adapterAssignment < 0)) { + Wup028Adapter::SetPortAssignment(selectedGamePort, -1); + RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1); } - 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]); + 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(); } + if (ImGui::MenuItem("Unassign controller")) { + PADClearPort(selectedGamePort); + Wup028Adapter::SetPortAssignment(selectedGamePort, -1); + RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1); + g_configuredControllerIndices.fill(std::numeric_limits::min()); + } ImGui::Separator(); controller_mapping_wizard::DrawSetupList(); const uint32_t controllerCount = PADCount(); if (controllerCount == 0) { ImGui::TextDisabled("No controller connected"); + DrawGameCubeAdapterInfo(); return; } - const char* currentName = PADGetName(static_cast(g_controllerPort)); - ImGui::Text("Assigned: %s", currentName != nullptr ? currentName : "None"); if (ImGui::BeginMenu("Assign connected controller")) { for (uint32_t index = 0; index < controllerCount; ++index) { const char* name = PADGetNameForControllerIndex(index); ImGui::PushID(static_cast(index)); if (ImGui::MenuItem(name != nullptr ? name : "Unknown controller")) { - PADSetPortForIndex(index, static_cast(g_controllerPort)); + Wup028Adapter::SetPortAssignment(selectedGamePort, -1); + RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1); + PADSetPortForIndex(index, selectedGamePort); g_configuredControllerIndices.fill(std::numeric_limits::min()); ApplyConfiguredMappings(); } @@ -375,6 +416,7 @@ 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"); + DrawGameCubeAdapterInfo(); return; } @@ -514,7 +556,7 @@ void DrawControllerSettings() { ImGui::TextUnformatted(kControllerButtons[i].label); ImGui::PopID(); } - + DrawGameCubeAdapterInfo(); } void DrawAudioSettings() { diff --git a/runtime/src/wup028_adapter.cpp b/runtime/src/wup028_adapter.cpp index d205f41..dec92f8 100644 --- a/runtime/src/wup028_adapter.cpp +++ b/runtime/src/wup028_adapter.cpp @@ -1,6 +1,7 @@ #include "wup028_adapter.h" #include "runtime_log.h" +#include "runtime_config.h" #include #include @@ -27,10 +28,12 @@ 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}; @@ -190,7 +193,10 @@ bool Open(Device& device, std::string& name, std::string& error) { } int8_t Axis(uint8_t raw) { - return static_cast(std::clamp(static_cast(raw) - 128, -128, 127)); + 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) { @@ -296,16 +302,21 @@ void Worker() { 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) continue; + if (timedOut && std::chrono::steady_clock::now() - lastReport < kInputReportTimeout) continue; break; } - if (read != report.size() || report[0] != 0x21) continue; + 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); @@ -347,6 +358,8 @@ void Worker() { } 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"); { @@ -367,6 +380,9 @@ 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); } @@ -381,19 +397,48 @@ void Shutdown() { bool Read(std::array& statuses) { if (!g_connected.load(std::memory_order_acquire)) return false; std::lock_guard lock(g_mutex); - statuses = g_statuses; + 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; - if (g_statuses[port].err != PAD_ERR_NONE) { - g_rumble[port] = 0; + 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[port] = enabled ? 1 : 0; + g_rumble[adapterPort] = enabled ? 1 : 0; return true; }