mirror of
https://github.com/patchzyy/wiicompiled
synced 2026-09-10 09:11:52 -04:00
be153e0fa0
* Add Dolphin-compatible input expressions and GCPadNew.ini import Rebased onto current main; addresses both CodeRabbit reviews on #89. - Expression engine matching Dolphin's semantics: doubles rather than booleans, 0.5 press threshold, & as min, | as max, and the functions if, min, max, clamp, abs, sqrt, pow, sin, cos, tan, deadzone, timer, toggle, hold, tap, pulse and smooth. Timing uses a steady clock in seconds, as Dolphin does, so a copied expression behaves identically. - Expressions bind to the GameCube buttons and triggers, combined with the existing button mapping rather than replacing it, and are skipped while the settings overlay holds input. - Import reads [GCPadN] from the Dolphin config directory or from GCPadNew.ini beside the executable. Stick axes are not expression driven and keep their normal mapping. - Fixes #74: a digital button bound to L or R now reports a fully pulled analog trigger, plus a PlayStation preset and a vibration toggle. Review fixes: config paths round-trip through RuntimeConfigFile::PathToUtf8 and PathFromUtf8 so non-ASCII paths open correctly on Windows, and the duplicated exists branch is gone; the tap count is clamped before the unsigned conversion; the expression editor uses resizable storage via ImGuiInputTextFlags_CallbackResize so a long expression cannot be saved truncated; clamp bounds are ordered before std::clamp; <cstdlib> is included for std::strtod; non-finite values are rejected at the evaluator boundary as well as at the deadzone and timer divisions; and InputBindings::Reload() runs from InitializeRuntimeSettings rather than the vibration handler. runtime/tests/test_expr.cpp covers operator precedence, each stateful function and every case raised in review. Third review round: smooth() guards NaN as well as infinity so a zero rate cannot latch a non-finite value in node state; division evaluates both operands so stateful functions in the left subtree still update when the divisor is zero; the expression editor clears stale errors when the port changes; and runtime/tests/test_expr.cpp is registered with CTest as mkw_input_expr_tests, following the existing test targets. * Update runtime/src/input_expr.cpp Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update runtime/src/input_expr.cpp Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Update runtime/src/input_expr.cpp Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
155 lines
4.8 KiB
C++
155 lines
4.8 KiB
C++
#include "hle_stubs.h"
|
|
#include "memory.h"
|
|
#include "hle/controller_status_contract.h"
|
|
#include "input_bindings.h"
|
|
#include "wii_remote_input.h"
|
|
|
|
#include <algorithm>
|
|
#include <atomic>
|
|
#include <cstdio>
|
|
#include <cstdint>
|
|
#include <cstdlib>
|
|
#include <cstring>
|
|
|
|
#include <SDL3/SDL_gamepad.h>
|
|
#include <dolphin/pad.h>
|
|
|
|
namespace {
|
|
|
|
std::atomic<bool> 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<SDL_GamepadButton>(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<u32>(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<uint32_t>(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));
|