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
This commit is contained in:
GalaxisBeast
2026-08-26 20:12:15 -04:00
parent f1c2b60df1
commit 251529d66e
8 changed files with 161 additions and 77 deletions
+1
View File
@@ -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.
+8 -5
View File
@@ -5,6 +5,7 @@
#include <SDL3/SDL_mouse.h>
#include <array>
#include <atomic>
#include <sys/stat.h>
#include <ranges>
@@ -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<PADButton, PAD_CHANMAX> g_suppressedButtons{};
std::array<bool, PAD_CHANMAX> 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) {
+4 -31
View File
@@ -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
+22
View File
@@ -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<std::optional<std::string>, 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<uint32_t, 4> gameCubeAdapterPorts{};
};
namespace RuntimeConfigFile {
@@ -323,6 +326,12 @@ inline RuntimeUserConfig ParseConfigDocument(const toml::value& document) {
config.controllerButtons[index] =
FindConfigValue<std::string>(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<bool>(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<int>(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<uint32_t>(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;
+4
View File
@@ -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<PADStatus, 4>& 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);
+4 -10
View File
@@ -47,20 +47,14 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr)
PADStatus statuses[PAD_CHANMAX]{};
std::array<PADStatus, PAD_CHANMAX> 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 {
+66 -24
View File
@@ -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<unsigned>(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<uint32_t>(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<unsigned>(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<size_t>(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<int32_t>::min());
}
}
ImGui::EndMenu();
}
if (ImGui::MenuItem("Unassign controller")) {
PADClearPort(selectedGamePort);
Wup028Adapter::SetPortAssignment(selectedGamePort, -1);
RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1);
g_configuredControllerIndices.fill(std::numeric_limits<int32_t>::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<uint32_t>(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<int>(index));
if (ImGui::MenuItem(name != nullptr ? name : "Unknown controller")) {
PADSetPortForIndex(index, static_cast<uint32_t>(g_controllerPort));
Wup028Adapter::SetPortAssignment(selectedGamePort, -1);
RuntimeConfigFile::SetGameCubeAdapterPort(selectedGamePort, -1);
PADSetPortForIndex(index, selectedGamePort);
g_configuredControllerIndices.fill(std::numeric_limits<int32_t>::min());
ApplyConfiguredMappings();
}
@@ -375,6 +416,7 @@ void DrawControllerSettings() {
PADButtonMapping* mappings = PADGetButtonMappings(static_cast<uint32_t>(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() {
+52 -7
View File
@@ -1,6 +1,7 @@
#include "wup028_adapter.h"
#include "runtime_log.h"
#include "runtime_config.h"
#include <dolphin/pad.h>
#include <windows.h>
@@ -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<PADStatus, PAD_CHANMAX> g_statuses{};
std::array<uint8_t, PAD_CHANMAX> g_rumble{};
std::array<int8_t, PAD_CHANMAX> 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<int8_t>(std::clamp(static_cast<int>(raw) - 128, -128, 127));
constexpr int kCenter = 128;
constexpr int kCenterTolerance = 10;
if (raw >= kCenter - kCenterTolerance && raw <= kCenter + kCenterTolerance) return 0;
return static_cast<int8_t>(std::clamp(static_cast<int>(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<bool, PAD_CHANMAX> reportedPorts{};
auto lastReport = std::chrono::steady_clock::now();
while (!g_stop.load(std::memory_order_acquire)) {
std::array<UCHAR, kReportSize> 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<PADStatus, PAD_CHANMAX> 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<int8_t>(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<PADStatus, 4>& 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<size_t>(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<size_t>(oldPhysicalPort)] = 0;
if (physicalPort >= 0) {
for (auto& assignment : g_portAssignments) {
if (assignment == physicalPort) {
assignment = -1;
g_rumble[static_cast<size_t>(physicalPort)] = 0;
}
}
}
g_portAssignments[gamePort] = static_cast<int8_t>(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<size_t>(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;
}