Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,9 @@ Code.pul
# Build output
/build/
/build-*/
/.build-output/
/.build-test/
/native-build/
/dist/
/out/
[Bb]in/
Expand Down
1 change: 1 addition & 0 deletions aurora-main/include/dolphin/pad.h
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
13 changes: 8 additions & 5 deletions aurora-main/lib/dolphin/pad/pad.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include <SDL3/SDL_mouse.h>

#include <array>
#include <atomic>
#include <sys/stat.h>
#include <ranges>

Expand Down Expand Up @@ -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{};
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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) {
Expand Down
35 changes: 4 additions & 31 deletions aurora-main/lib/input.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
2 changes: 1 addition & 1 deletion runtime/cmake/PublicProducts.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -205,7 +205,7 @@ function(mkw_configure_product target)
endif()

target_link_libraries(${target} PRIVATE
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp)
dbghelp user32 winmm ws2_32 iphlpapi secur32 crypt32 windowsapp setupapi winusb)

set_target_properties(${target} PROPERTIES WIN32_EXECUTABLE TRUE)
foreach(runtime_dll libc++.dll libunwind.dll)
Expand Down
22 changes: 22 additions & 0 deletions runtime/include/runtime_config.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand Down
46 changes: 46 additions & 0 deletions runtime/include/wup028_adapter.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
#pragma once

#include <array>
#include <cstdint>
#include <string>

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<bool, 4> ports{};
// Raw adapter type/status byte. High nibble 1 is wired, 2 is wireless.
std::array<uint8_t, 4> portStatus{};
std::array<uint64_t, 4> 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<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);
AdapterInfo GetInfo();

} // namespace Wup028Adapter
17 changes: 15 additions & 2 deletions runtime/src/hle/input/pad.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
#include "hle_stubs.h"
#include "memory.h"
#include "hle/controller_status_contract.h"
#include "wup028_adapter.h"

#include <algorithm>
#include <cstdio>
Expand Down Expand Up @@ -33,6 +34,7 @@ void WritePadStatus(uint32_t base, const PADStatus& status) {

extern "C" uint32_t PAD__Init_HLE()
{
Wup028Adapter::Initialize();
return PADInit() ? 1u : 0u;
}
PPC_NATIVE_OVERRIDE(801AF2F0, PAD__Init_HLE, uint32_t, (), ());
Expand All @@ -44,7 +46,16 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr)
}

PADStatus statuses[PAD_CHANMAX]{};
const uint32_t rumbleMask = PADRead(statuses);
std::array<PADStatus, PAD_CHANMAX> adapterStatuses{};
uint32_t rumbleMask = PADRead(statuses);
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;
}
}
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.

try {
for (uint32_t i = 0; i < PAD_CHANMAX; ++i) {
Expand Down Expand Up @@ -73,6 +84,8 @@ PPC_NATIVE_OVERRIDE(801AF1E4, PAD__Recalibrate_HLE, uint32_t, (uint32_t mask), (

extern "C" void PAD__ControlMotor_HLE(int32_t chan, uint32_t command)
{
PADControlMotor(chan, command);
if (!Wup028Adapter::SetRumble(static_cast<uint32_t>(chan), command == PAD_MOTOR_RUMBLE)) {
PADControlMotor(chan, command);
}
}
PPC_NATIVE_OVERRIDE_VOID(801AF908, PAD__ControlMotor_HLE, (int32_t chan, uint32_t command), (chan, command));
5 changes: 5 additions & 0 deletions runtime/src/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -47,6 +47,7 @@
#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"
Expand Down Expand Up @@ -1252,6 +1253,7 @@ int RuntimeMain(int argc, char** argv) {
}
aurora_set_frame_worker_wait_callback(ServiceGuestTimingDuringAuroraFrameWait);
GxGuestWrite::InstallAuroraHooks();
Wup028Adapter::Initialize();
UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width,
auroraInfo.windowSize.native_fb_height);
settings_overlay::InitializeRuntimeSettings();
Expand Down Expand Up @@ -1293,6 +1295,7 @@ int RuntimeMain(int argc, char** argv) {
// Shutdown fiber system
Fiber::GuestFiberManager::Shutdown();
WindowPlacementPersistence::Flush(true);
Wup028Adapter::Shutdown();
aurora_shutdown();
SetRuntimeExitCodeImpl(0);
ShutdownProcessTranscript();
Expand All @@ -1310,6 +1313,7 @@ int RuntimeMain(int argc, char** argv) {
SetRuntimeExitCodeImpl(1);
Fiber::GuestFiberManager::Shutdown();
WindowPlacementPersistence::Flush(true);
Wup028Adapter::Shutdown();
aurora_shutdown();
ShutdownProcessTranscript();
return 1;
Expand All @@ -1321,6 +1325,7 @@ int RuntimeMain(int argc, char** argv) {
SetRuntimeExitCodeImpl(1);
Fiber::GuestFiberManager::Shutdown();
WindowPlacementPersistence::Flush(true);
Wup028Adapter::Shutdown();
aurora_shutdown();
ShutdownProcessTranscript();
return 1;
Expand Down
Loading
Loading