#include "hle_stubs.h" #include "memory.h" #include "hle/controller_status_contract.h" #include "input_bindings.h" #include "wii_remote_input.h" #include #include #include #include #include #include #include #include namespace { std::atomic g_rumbleEnabled{true}; bool NativeButtonHeld(SDL_Gamepad* gamepad, uint32_t nativeButton) { if (gamepad == nullptr || nativeButton == PAD_NATIVE_BUTTON_INVALID || nativeButton >= SDL_GAMEPAD_BUTTON_COUNT) { return false; } return SDL_GetGamepadButton(gamepad, static_cast(nativeButton)); } // A digital button bound to L or R has no analog travel of its own. On real // hardware the click only engages at full depression, so report a full pull. void FillTriggersHeldByButtons(PADStatus* statuses) { if (InputBindings::InputBlocked()) { return; } for (uint32_t port = 0; port < PAD_CHANMAX; ++port) { if (statuses[port].err != PAD_ERR_NONE) { continue; } const s32 index = PADGetIndexForPort(port); if (index < 0) { continue; } SDL_Gamepad* gamepad = PADGetSDLGamepadForIndex(static_cast(index)); if (gamepad == nullptr) { continue; } const auto scan = [&](PADButtonMapping* mappings, u32 count) { if (mappings == nullptr) { return; } for (u32 i = 0; i < count; ++i) { const PADButtonMapping& mapping = mappings[i]; if (mapping.padButton != PAD_TRIGGER_L && mapping.padButton != PAD_TRIGGER_R) { continue; } if (!NativeButtonHeld(gamepad, mapping.nativeButton)) { continue; } if (mapping.padButton == PAD_TRIGGER_L) { statuses[port].triggerLeft = 255; } else { statuses[port].triggerRight = 255; } } }; u32 count = 0; scan(PADGetButtonMappings(port, &count), count); count = 0; scan(PADGetAltButtonMappings(port, &count), count); } } void WritePadStatus(uint32_t base, const PADStatus& status) { const auto guestStatus = PadStatusContract::Encode({ status.button, status.stickX, status.stickY, status.substickX, status.substickY, status.triggerL, status.triggerR, status.analogA, status.analogB, status.err, }); uint8_t* dst = Memory::GetPointer(base, guestStatus.size()); std::memcpy(dst, guestStatus.data(), guestStatus.size()); } } // namespace extern "C" void PAD_HLE_SetRumbleEnabled(bool enabled) { g_rumbleEnabled.store(enabled, std::memory_order_relaxed); } extern "C" uint32_t PAD__Init_HLE() { return PADInit() ? 1u : 0u; } PPC_NATIVE_OVERRIDE(801AF2F0, PAD__Init_HLE, uint32_t, (), ()); // PADRead: gathers every GameCube pad source for the frame and writes the statuses to guest memory. extern "C" uint32_t PAD__Read_HLE(uint32_t statusPtr) { if (statusPtr == 0) { return 0; } PADStatus statuses[PAD_CHANMAX]{}; // Keep looking for a Bluetooth Wii Remote that dropped out (or was turned on late). WiiRemoteInput::Poll(); uint32_t rumbleMask = PADRead(statuses); // Wii Remotes reach the game through KPAD, not as GameCube pads. This also // applies while input is blocked (overlay open) so the port does not flip // between "connected" and "no controller" every time the overlay toggles. WiiRemoteInput::HideRemotesFromPad(statuses, PAD_CHANMAX); FillTriggersHeldByButtons(statuses); InputBindings::Apply(statuses); try { for (uint32_t i = 0; i < PAD_CHANMAX; ++i) { WritePadStatus(statusPtr + static_cast(i * PadStatusContract::kGuestStatusSize), statuses[i]); } } catch (const Memory::AccessViolation&) { return 0; } return rumbleMask; } PPC_NATIVE_OVERRIDE(801AF44C, PAD__Read_HLE, uint32_t, (uint32_t statusPtr), (statusPtr)); extern "C" uint32_t PAD__Reset_HLE(uint32_t mask) { return PADReset(mask) ? 1u : 0u; } PPC_NATIVE_OVERRIDE(801AF0DC, PAD__Reset_HLE, uint32_t, (uint32_t mask), (mask)); extern "C" uint32_t PAD__Recalibrate_HLE(uint32_t mask) { return PADRecalibrate(mask) ? 1u : 0u; } PPC_NATIVE_OVERRIDE(801AF1E4, PAD__Recalibrate_HLE, uint32_t, (uint32_t mask), (mask)); extern "C" void PAD__ControlMotor_HLE(int32_t chan, uint32_t command) { if (command == PAD_MOTOR_RUMBLE && !g_rumbleEnabled.load(std::memory_order_relaxed)) { command = PAD_MOTOR_STOP; } PADControlMotor(chan, command); } PPC_NATIVE_OVERRIDE_VOID(801AF908, PAD__ControlMotor_HLE, (int32_t chan, uint32_t command), (chan, command));