diff --git a/aurora-main/lib/input.cpp b/aurora-main/lib/input.cpp index 2b3651e..e22e764 100644 --- a/aurora-main/lib/input.cpp +++ b/aurora-main/lib/input.cpp @@ -329,12 +329,52 @@ void apply_port_preferences() noexcept { } } +#if defined(_WIN32) // 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. +// over a game port before the user assigns it in the controller menu - which +// matters here because a manually-assigned WUP-028 adapter port (see +// wup028_adapter.cpp, Windows-only) could otherwise collide with one SDL +// auto-claimed. Elsewhere, with no WUP-028 port to collide with, the original +// auto-claim behavior below is restored instead. void ensure_player_index(GameController& controller) noexcept { assign_player_index(controller, -1); } +#else +// 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. +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); + } +} +#endif } // namespace GameController* get_controller_for_player(uint32_t player) noexcept { diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index 7f811fd..9a3aad1 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -237,6 +237,15 @@ 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/include/runtime_config.h b/runtime/include/runtime_config.h index f8a3bae..0a0debf 100644 --- a/runtime/include/runtime_config.h +++ b/runtime/include/runtime_config.h @@ -63,9 +63,11 @@ 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 { @@ -365,12 +367,14 @@ 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"); @@ -634,6 +638,7 @@ 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]; @@ -646,6 +651,7 @@ inline bool SetGameCubeAdapterPort(size_t gamePort, int physicalPort) { 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); diff --git a/runtime/src/hle/input/pad.cpp b/runtime/src/hle/input/pad.cpp index 68645d7..2a71351 100644 --- a/runtime/src/hle/input/pad.cpp +++ b/runtime/src/hle/input/pad.cpp @@ -1,7 +1,9 @@ #include "hle_stubs.h" #include "memory.h" #include "hle/controller_status_contract.h" +#ifdef _WIN32 #include "wup028_adapter.h" +#endif #include #include @@ -34,7 +36,9 @@ 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, (), ()); @@ -46,8 +50,9 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr) } PADStatus statuses[PAD_CHANMAX]{}; - std::array adapterStatuses{}; 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) { @@ -56,6 +61,7 @@ extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr) } } } +#endif try { for (uint32_t i = 0; i < PAD_CHANMAX; ++i) { @@ -84,8 +90,12 @@ 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 edc6b98..84d7a44 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -1377,7 +1377,9 @@ int RuntimeMain(int argc, char** argv) { } aurora_set_frame_worker_wait_callback(ServiceGuestTimingDuringAuroraFrameWait); GxGuestWrite::InstallAuroraHooks(); +#if defined(_WIN32) Wup028Adapter::Initialize(); +#endif UpdateMkwDynamicAspectSurface(auroraInfo.windowSize.native_fb_width, auroraInfo.windowSize.native_fb_height); settings_overlay::InitializeRuntimeSettings(); @@ -1419,7 +1421,9 @@ 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(); @@ -1437,7 +1441,9 @@ 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; @@ -1449,7 +1455,9 @@ 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 fcf3389..cb2497c 100644 --- a/runtime/src/settings_overlay.cpp +++ b/runtime/src/settings_overlay.cpp @@ -1,5 +1,7 @@ #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" @@ -313,6 +315,12 @@ 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; @@ -338,6 +346,7 @@ void DrawGameCubeAdapterInfo() { } } ImGui::EndMenu(); +#endif } void DrawControllerSettings() { @@ -351,13 +360,20 @@ 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 { + } 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); @@ -381,10 +397,13 @@ void DrawControllerSettings() { } ImGui::EndMenu(); } +#endif 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(); @@ -392,7 +411,9 @@ void DrawControllerSettings() { const uint32_t controllerCount = PADCount(); if (controllerCount == 0) { ImGui::TextDisabled("No controller connected"); +#if defined(_WIN32) DrawGameCubeAdapterInfo(); +#endif return; } @@ -401,8 +422,10 @@ 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(); @@ -416,7 +439,9 @@ 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; } @@ -556,7 +581,9 @@ void DrawControllerSettings() { ImGui::TextUnformatted(kControllerButtons[i].label); ImGui::PopID(); } +#if defined(_WIN32) DrawGameCubeAdapterInfo(); +#endif } void DrawAudioSettings() {