mirror of
https://github.com/mitch030504/Wiicompiled_VR_Frame.git
synced 2026-10-06 03:00:14 +02:00
Bluetooth Wii Remote support: Wii Remote / Wii Wheel, Nunchuk and Classic Controller through KPAD (#73)
* Bluetooth Wii Remote support: the game reads a real Wii Remote through KPAD Enable SDL3's HIDAPI Wii driver and hand a paired Wii Remote (bare or with Nunchuk) to the game as a real Wii Remote: WPADProbe reports CORE/FREESTYLE and KPADRead fills KPADStatus[0] from SDL every frame (buttons, accelerometer in KPAD's g frame, Nunchuk stick and accelerometer), while the GameCube pad view of that port reports no controller. The game's own motion code then handles wheelies, tricks and Wii Wheel steering. Classic Controllers and Wii U Pro Controllers keep going through the GameCube pad path with a default button table picked by name. SDL's Wii driver drops a remote on a failed Bluetooth read or when the Nunchuk is plugged or unplugged and never re-adds it, so the runtime keeps rescanning (Dolphin style) while no Wii controller is present by toggling the driver hint off and, a few frames later, on again; a dropped remote is back within 1-2 s. Settings live in the F10 overlay under Wii Remotes (Bluetooth) and in Config.toml (wii_remotes, wii_continuous_scan). * Fix Wii U Pro / Classic Controller ZL and ZR not registering SDL's Wii driver reports ZL/ZR as the LEFT_TRIGGER/RIGHT_TRIGGER analog axes, never as digital shoulder buttons. Binding them to LEFT_SHOULDER/RIGHT_SHOULDER meant they never fired and also disabled aurora's own analog-trigger fallback (a button table entry for PAD_TRIGGER_L/R marks the trigger as "handled", even when the bound digital button never actually presses). Leaving them unbound lets the default axis mapping drive them like every other analog-trigger pad. Reported by an end-to-end tester connecting a real Classic Controller to a Wii Remote. * Wii Remotes menu: live raw D-pad/ZL/ZR readout for Classic Controller / Wii U Pro Diagnostic aid for a reported issue where the Classic Controller's D-pad does not do anything in-game (no wheelies). Shows what SDL itself sees so a driver-level problem (nothing lights up) can be told apart from a mapping problem (it lights up but the game does not react). * Fix Classic Controller D-pad input * Address CodeRabbit review on PR #73 - PADRead: hide KPAD-served ports even while input is blocked so the port error state does not flip when the overlay opens/closes. - WPADProbe: run the Wii Remote rescan state machine before probing so a reconnect probe before the next PADRead can see the remote. - EnsureSensors: only cache the gamepad id once every accelerometer enabled, so a failed activation is retried. - ConfigureSdlHints: reset the in-flight rescan bookkeeping. - Settings overlay: disable "Rescan now" while Wii Remotes are turned off. * Bluetooth Wii Remote: fix wheel steering, native Classic Controller, extension hot-swap Accelerometer - The SDL -> KPAD conversion negated the wrong axis: SDL's z is the remote's +Y (towards the user), so KPAD acc is (-wiiX, -wiiZ, +wiiY). Fixes mirrored Wii Wheel steering. - Drop reports whose accelerometer bytes arrive zeroed (+-5.12 g on every axis, a few times a minute over Bluetooth) and repeat the last good sample; they read as a full-lock steer plus a 9 g shake. - One-button zero-point calibration in the overlay (remote flat, buttons up), stored in Config.toml as wii_accel_offset_x/y/z. SDL's read of the remote's factory calibration times out over Bluetooth and falls back to a nominal zero point, which left a per-axis bias of up to ~0.3 g on the tested remote. - Live accelerometer readout and an optional per-frame CSV trace (wii_accel_trace = true) for debugging. Classic Controller through KPAD/WPAD - WPADProbe reports WPAD_DEV_CLASSIC; KPADRead fills ex_status.cl and KPADGetUnifiedWpadStatus the raw WPADCLStatus (WPAD_CL_BUTTON_* bits, sticks in the SDK's signed -512..511 range, triggers), so the game shows the Classic layout and icons and no button mapping is involved. Ports served through KPAD are hidden from PADRead; only the Wii U Pro Controller stays a GameCube pad. Extension hot-swap - SDL's Wii driver destroys the joystick on an extension change but keeps the HID handle open, and HIDAPI never re-creates a joystick for such a device. Patch the vendored SDL at configure time (AuroraSDL3Patches.cmake, wired into AuroraSDL3Provider.cmake for both the downloaded tarball and a pre-provided FETCHCONTENT_SOURCE_DIR_SDL) so the joystick is rebuilt in place with the new extension type, without touching the Bluetooth handle. - Keep a vanished remote's channel alive with neutral input for up to 3 s while SDL re-creates the joystick, so the game never sees a disconnection. The driver-hint rescan stays as a fallback for real drops, starting 3 s after the loss, and also runs from the overlay's per-frame Draw. Log rescans. Mappings / overlay - Do not apply the shared positional [controller] bindings to Wii pads: that override is what made a Classic Controller's A/B and X/Y look swapped. - Raw D-pad fallback also for the Wii U Pro Controller; overlay readouts read joystick buttons directly (SDL's generated HIDAPI mapping expects a hat). - Overlay: Classic Controller readout, accelerometer readout and calibration. - README: Bluetooth Wii Remote section and known limitations. * Review pass on the Wii Remote input path - EffectiveKind: stop bridging an extension swap once a different controller has taken the port, and note that everything touching the scanner state runs on the guest thread. - KPADGetUnifiedWpadStatus: fill every requested entry (the SDK returns `count` recent samples), capped at KPAD's 16 read buffers. - IsKpadKind gets internal linkage; the calibration accessors get their comments; clarify why Draw() also runs Poll(). * Drop the dead Classic-Controller-as-GameCube-pad matching A Wii Remote with a Classic Controller is served through KPAD and its port is hidden from PADRead, so the name matches that once gave it a GameCube button table and the raw D-pad fallback could never take effect any more. Both now match only the Wii U Pro Controller, and the default table is renamed accordingly (g_defaultButtonsWiiUPro). --------- Co-authored-by: LOL <andresguerra2k26@gmail.com> Co-authored-by: Nick <89667145+Nick1232345@users.noreply.github.com>
This commit is contained in:
12 files changed
+1612
-9
No files matched your search
@@ -0,0 +1,126 @@
|
||||
# Source-level fixes applied to the vendored SDL3 tree before it is built.
|
||||
#
|
||||
# Each patch is an exact string replacement, checked and idempotent: a tree that
|
||||
# already carries the fix is left alone, a tree where the anchor text is missing
|
||||
# (a different SDL version) stops the configure with a clear message instead of
|
||||
# silently building without the fix.
|
||||
#
|
||||
# Used two ways:
|
||||
# - included by AuroraSDL3Provider.cmake, which calls aurora_sdl3_apply_patches()
|
||||
# on a pre-provided source tree (FETCHCONTENT_SOURCE_DIR_SDL);
|
||||
# - run as `cmake -DSDL_SOURCE_DIR=<dir> -P AuroraSDL3Patches.cmake` from the
|
||||
# FetchContent PATCH_COMMAND for a freshly extracted tarball.
|
||||
|
||||
function(_aurora_sdl3_replace file description old new)
|
||||
file(READ "${file}" _content)
|
||||
string(FIND "${_content}" "${new}" _already)
|
||||
if (NOT _already EQUAL -1)
|
||||
return()
|
||||
endif ()
|
||||
string(FIND "${_content}" "${old}" _anchor)
|
||||
if (_anchor EQUAL -1)
|
||||
message(FATAL_ERROR "aurora: SDL3 patch '${description}' does not apply to ${file}; "
|
||||
"the vendored SDL version changed, review AuroraSDL3Patches.cmake")
|
||||
endif ()
|
||||
string(REPLACE "${old}" "${new}" _content "${_content}")
|
||||
file(WRITE "${file}" "${_content}")
|
||||
message(STATUS "aurora: SDL3 patch applied: ${description}")
|
||||
endfunction()
|
||||
|
||||
function(aurora_sdl3_apply_patches sdl_source_dir)
|
||||
set(_wii "${sdl_source_dir}/src/joystick/hidapi/SDL_hidapi_wii.c")
|
||||
if (NOT EXISTS "${_wii}")
|
||||
message(FATAL_ERROR "aurora: SDL3 source tree at ${sdl_source_dir} has no SDL_hidapi_wii.c")
|
||||
endif ()
|
||||
|
||||
# Wii Remote: rebuild the joystick in place after an extension change.
|
||||
#
|
||||
# When a Nunchuk or Classic Controller is plugged in or pulled out, the driver
|
||||
# flags the joystick as disconnected so it can come back with the new name and
|
||||
# capabilities. But the HID device stays open, and HIDAPI only removes a
|
||||
# joystick-less device once its handle is closed, so nothing ever re-creates
|
||||
# the joystick: the remote is gone for good until the application toggles the
|
||||
# driver hint (which closes and re-opens the Bluetooth HID handle, something
|
||||
# some Windows Bluetooth stacks answer by dropping the link). Instead, when the
|
||||
# device has no joystick, probe the extension again (two bounded attempts, at
|
||||
# most once a second) and connect a new joystick on the still-open handle.
|
||||
_aurora_sdl3_replace("${_wii}" "Wii Remote in-place reconnect (context field)"
|
||||
[==[ Uint64 m_ulNextMotionPlusCheck;
|
||||
bool m_bDisconnected;
|
||||
]==]
|
||||
[==[ Uint64 m_ulNextMotionPlusCheck;
|
||||
bool m_bDisconnected;
|
||||
Uint64 m_ulNextReconnect; /* WiiCompiled: see HIDAPI_DriverWii_UpdateDevice */
|
||||
]==])
|
||||
_aurora_sdl3_replace("${_wii}" "Wii Remote in-place reconnect (bounded extension probe)"
|
||||
[==[static EWiiExtensionControllerType ReadExtensionControllerType(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
|
||||
EWiiExtensionControllerType eExtensionControllerType = k_eWiiExtensionControllerType_Unknown;
|
||||
const int MAX_ATTEMPTS = 20;
|
||||
int attempts = 0;
|
||||
]==]
|
||||
[==[static EWiiExtensionControllerType ReadExtensionControllerTypeAttempts(SDL_HIDAPI_Device *device, int MAX_ATTEMPTS)
|
||||
{
|
||||
SDL_DriverWii_Context *ctx = (SDL_DriverWii_Context *)device->context;
|
||||
EWiiExtensionControllerType eExtensionControllerType = k_eWiiExtensionControllerType_Unknown;
|
||||
int attempts = 0;
|
||||
]==])
|
||||
_aurora_sdl3_replace("${_wii}" "Wii Remote in-place reconnect (probe wrapper)"
|
||||
[==[static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
]==]
|
||||
[==[static EWiiExtensionControllerType ReadExtensionControllerType(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
return ReadExtensionControllerTypeAttempts(device, 20);
|
||||
}
|
||||
|
||||
static void UpdateDeviceIdentity(SDL_HIDAPI_Device *device)
|
||||
{
|
||||
]==])
|
||||
_aurora_sdl3_replace("${_wii}" "Wii Remote in-place reconnect (UpdateDevice)"
|
||||
[==[ if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
|
||||
now = SDL_GetTicks();
|
||||
]==]
|
||||
[==[ if (device->num_joysticks > 0) {
|
||||
joystick = SDL_GetJoystickFromID(device->joysticks[0]);
|
||||
} else {
|
||||
/* WiiCompiled: the joystick was dropped (extension plugged or unplugged,
|
||||
* or a failed read) but the HID device is still open, and the device
|
||||
* list only removes a joystick-less device once its handle is closed.
|
||||
* Rebuild the joystick in place, so an extension change behaves like it
|
||||
* does on the console: the remote never goes away, it just changes
|
||||
* type. If the remote does not answer, keep the device and retry. */
|
||||
now = SDL_GetTicks();
|
||||
if (ctx->m_ulNextReconnect != 0 && now < ctx->m_ulNextReconnect) {
|
||||
return false;
|
||||
}
|
||||
ctx->m_ulNextReconnect = now + 1000;
|
||||
{
|
||||
EWiiExtensionControllerType type = ReadExtensionControllerTypeAttempts(device, 2);
|
||||
if (type == k_eWiiExtensionControllerType_Unknown) {
|
||||
return false;
|
||||
}
|
||||
ctx->m_bDisconnected = false;
|
||||
ctx->m_ulLastInput = now;
|
||||
ctx->m_ulNextReconnect = 0;
|
||||
ctx->m_eExtensionControllerType = type;
|
||||
UpdateDeviceIdentity(device);
|
||||
SDL_LogDebug(SDL_LOG_CATEGORY_INPUT, "HIDAPI Wii: Reconnected joystick as %s", device->name);
|
||||
return HIDAPI_JoystickConnected(device, NULL);
|
||||
}
|
||||
}
|
||||
|
||||
now = SDL_GetTicks();
|
||||
]==])
|
||||
endfunction()
|
||||
|
||||
# Script mode (FetchContent PATCH_COMMAND).
|
||||
if (CMAKE_SCRIPT_MODE_FILE AND DEFINED SDL_SOURCE_DIR)
|
||||
aurora_sdl3_apply_patches("${SDL_SOURCE_DIR}")
|
||||
endif ()
|
||||
@@ -135,9 +135,18 @@ elseif (_aurora_sdl3_provider STREQUAL "vendor")
|
||||
endif ()
|
||||
|
||||
include(FetchContent)
|
||||
# Source fixes for the vendored SDL (see AuroraSDL3Patches.cmake). A freshly
|
||||
# downloaded tarball is patched by the PATCH_COMMAND; a tree handed in through
|
||||
# FETCHCONTENT_SOURCE_DIR_SDL skips the download steps, so patch it here.
|
||||
set(_aurora_sdl3_patches "${CMAKE_CURRENT_LIST_DIR}/AuroraSDL3Patches.cmake")
|
||||
include("${_aurora_sdl3_patches}")
|
||||
if (DEFINED FETCHCONTENT_SOURCE_DIR_SDL AND EXISTS "${FETCHCONTENT_SOURCE_DIR_SDL}")
|
||||
aurora_sdl3_apply_patches("${FETCHCONTENT_SOURCE_DIR_SDL}")
|
||||
endif ()
|
||||
FetchContent_Declare(SDL
|
||||
URL "https://github.com/libsdl-org/SDL/releases/download/release-${AURORA_SDL3_VERSION}/SDL3-${AURORA_SDL3_VERSION}.tar.gz"
|
||||
DOWNLOAD_EXTRACT_TIMESTAMP TRUE
|
||||
PATCH_COMMAND "${CMAKE_COMMAND}" -DSDL_SOURCE_DIR=<SOURCE_DIR> -P "${_aurora_sdl3_patches}"
|
||||
EXCLUDE_FROM_ALL
|
||||
)
|
||||
FetchContent_MakeAvailable(SDL)
|
||||
|
||||
@@ -4,6 +4,7 @@
|
||||
#include <dolphin/pad.h>
|
||||
#include <dolphin/si.h>
|
||||
#include <SDL3/SDL_mouse.h>
|
||||
#include <SDL3/SDL_joystick.h>
|
||||
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
@@ -193,6 +194,32 @@ std::array<PADButtonMapping, PAD_BUTTON_COUNT> g_defaultButtonsJoyPair{{
|
||||
{SDL_GAMEPAD_BUTTON_DPAD_RIGHT, PAD_BUTTON_RIGHT},
|
||||
}};
|
||||
|
||||
// Wii U Pro Controllers through SDL's HIDAPI Wii driver. No SDL_GamepadType
|
||||
// singles them out, so they are picked by the name the driver gives them (see
|
||||
// __PADSetDefaultMapping). Wii Remotes, with or without a Nunchuk or Classic
|
||||
// Controller, are read by the game through KPAD instead and never get a
|
||||
// GameCube mapping (the runtime hides those ports from PADRead).
|
||||
|
||||
// Nintendo's labelled layout: A on the right accelerates, B at the bottom
|
||||
// brakes. SDL's Wii driver reports ZL/ZR as the LEFT_TRIGGER/RIGHT_TRIGGER
|
||||
// axes, never as shoulder buttons, so they are left unbound here and picked up
|
||||
// by aurora's default axis mapping (g_defaultAxes) the same way every
|
||||
// analog-trigger pad's L/R is.
|
||||
std::array<PADButtonMapping, PAD_BUTTON_COUNT> g_defaultButtonsWiiUPro{{
|
||||
{SDL_GAMEPAD_BUTTON_EAST, PAD_BUTTON_A},
|
||||
{SDL_GAMEPAD_BUTTON_SOUTH, PAD_BUTTON_B},
|
||||
{SDL_GAMEPAD_BUTTON_NORTH, PAD_BUTTON_X},
|
||||
{SDL_GAMEPAD_BUTTON_WEST, PAD_BUTTON_Y},
|
||||
{SDL_GAMEPAD_BUTTON_START, PAD_BUTTON_START},
|
||||
{SDL_GAMEPAD_BUTTON_BACK, PAD_TRIGGER_Z},
|
||||
{PAD_NATIVE_BUTTON_INVALID, PAD_TRIGGER_L},
|
||||
{PAD_NATIVE_BUTTON_INVALID, PAD_TRIGGER_R},
|
||||
{SDL_GAMEPAD_BUTTON_DPAD_UP, PAD_BUTTON_UP},
|
||||
{SDL_GAMEPAD_BUTTON_DPAD_DOWN, PAD_BUTTON_DOWN},
|
||||
{SDL_GAMEPAD_BUTTON_DPAD_LEFT, PAD_BUTTON_LEFT},
|
||||
{SDL_GAMEPAD_BUTTON_DPAD_RIGHT, PAD_BUTTON_RIGHT},
|
||||
}};
|
||||
|
||||
std::array<PADKeyButtonBinding, PAD_BUTTON_COUNT> g_defaultKeys{{
|
||||
{PAD_KEY_INVALID, PAD_BUTTON_A},
|
||||
{PAD_KEY_INVALID, PAD_BUTTON_B},
|
||||
@@ -409,8 +436,26 @@ static void reset_alt_button_mapping(aurora::input::GameController* controller)
|
||||
}
|
||||
}
|
||||
|
||||
// SDL's hidapi Wii driver names the pad "Nintendo Wii U Pro Controller"; the
|
||||
// other names it produces are Wii Remotes, which the game reads through KPAD
|
||||
// and which therefore never take a GameCube mapping.
|
||||
static bool wii_default_mapping(const aurora::input::GameController* controller,
|
||||
std::array<PADButtonMapping, PAD_BUTTON_COUNT>& out) {
|
||||
const char* name = SDL_GetGamepadName(controller->m_controller);
|
||||
if (name == nullptr || SDL_strstr(name, "Wii U Pro Controller") == nullptr) {
|
||||
return false;
|
||||
}
|
||||
out = g_defaultButtonsWiiUPro;
|
||||
return true;
|
||||
}
|
||||
|
||||
// Picks the default button table for a controller by name (Wii pads) or SDL gamepad type.
|
||||
void __PADSetDefaultMapping(aurora::input::GameController* controller) /* NOLINT(*-reserved-identifier) */
|
||||
{
|
||||
if (wii_default_mapping(controller, controller->m_buttonMapping)) {
|
||||
reset_alt_button_mapping(controller);
|
||||
return;
|
||||
}
|
||||
switch (SDL_GetGamepadType(controller->m_controller)) {
|
||||
case SDL_GAMEPAD_TYPE_XBOX360:
|
||||
controller->m_buttonMapping = g_defaultButtonsXBox360;
|
||||
@@ -745,6 +790,47 @@ u32 PADRead(PADStatus* status) {
|
||||
|
||||
if (controller) {
|
||||
EnsureMappingLoaded(controller);
|
||||
|
||||
// Wii U Pro Controller raw D-pad fallback. SDL's HIDAPI Wii driver posts
|
||||
// the D-pad as joystick buttons 11-14 (the SDL_GAMEPAD_BUTTON_DPAD_*
|
||||
// values) and never as a hat, but the mapping SDL generates for HIDAPI
|
||||
// pads binds the D-pad to hat 0, so SDL_GetGamepadButton(DPAD_*) stays
|
||||
// false. Keep this restricted to the Wii driver's pad so raw button
|
||||
// indices don't interfere with other controller types.
|
||||
const char* name = SDL_GetGamepadName(controller->m_controller);
|
||||
const bool isWiiUPro = name != nullptr && SDL_strstr(name, "Wii U Pro Controller") != nullptr;
|
||||
|
||||
if (isWiiUPro) {
|
||||
SDL_Joystick* joystick =
|
||||
SDL_GetGamepadJoystick(controller->m_controller);
|
||||
|
||||
uint32_t raw = 0;
|
||||
const int buttonCount = SDL_GetNumJoystickButtons(joystick);
|
||||
|
||||
for (int b = 0; b < buttonCount && b < 32; ++b) {
|
||||
if (SDL_GetJoystickButton(joystick, b)) {
|
||||
raw |= (1u << b);
|
||||
}
|
||||
}
|
||||
|
||||
// Up = button 11
|
||||
if (raw & (1u << 11)) {
|
||||
status[i].button |= PAD_BUTTON_UP;
|
||||
}
|
||||
// Down = button 12
|
||||
if (raw & (1u << 12)) {
|
||||
status[i].button |= PAD_BUTTON_DOWN;
|
||||
}
|
||||
// Left = button 13
|
||||
if (raw & (1u << 13)) {
|
||||
status[i].button |= PAD_BUTTON_LEFT;
|
||||
}
|
||||
// Right = button 14
|
||||
if (raw & (1u << 14)) {
|
||||
status[i].button |= PAD_BUTTON_RIGHT;
|
||||
}
|
||||
}
|
||||
|
||||
bool leftTriggerSet = false;
|
||||
bool rightTriggerSet = false;
|
||||
std::ranges::for_each(controller->m_buttonMapping, [&controller, &i, &status, &leftTriggerSet,
|
||||
@@ -778,6 +864,7 @@ u32 PADRead(PADStatus* status) {
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
// TODO: Add serializable mappings for these (probably not necessary)?
|
||||
static constexpr std::array<std::pair<SDL_GamepadButton, PADExtButton>, PAD_EXT_BUTTON_COUNT> kExtButtonMappings{{
|
||||
{SDL_GAMEPAD_BUTTON_BACK, PAD_BUTTON_BACK},
|
||||
|
||||
Reference in new issue
Block a user