From 165fae0aa1fb1aea4ae299bb3552cd906b73f768 Mon Sep 17 00:00:00 2001 From: Alex Vanderveen Date: Mon, 17 Aug 2026 16:52:18 -0400 Subject: [PATCH] Fix the pad-hotkey range check rejecting every chord MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [hotkeys] rewind_pad / save_state_menu_pad accept the recomp-ui pad encoding, where 1000 + a button bitmask is a CHORD. The parse guarded on `n < 256`, which admits single buttons and rejects every chord there is — including the two values this same file writes back out (1272 = select+r3, 2040 = select+r1). A saved chord was silently dropped on load and the defaults quietly reinstated, so the pad hotkeys were never actually configurable. The bound predates the chord encoding; the runtime's own normalize_hotkey_pad_binding has handled combos correctly all along. Replaced with pad_bind_value_ok(), sharing one documented bound with the encoding it validates. Verified against a live boot with rewind_pad = 1017 (select+cross): parsed has=0 value=1272 before, has=1 value=1017 after. Co-Authored-By: Claude Opus 5 (1M context) --- recompiler/src/config_loader.cpp | 4 ++-- recompiler/src/config_loader.h | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index 386b1c64..699c842f 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -2356,14 +2356,14 @@ UserSettings load_user_settings(const fs::path& path) { const toml::value& h = toml::find(doc, "hotkeys"); if (h.contains("rewind_pad")) try_get([&]{ const auto n = toml::find(h, "rewind_pad"); - if (n >= 0 && n < 256) { + if (pad_bind_value_ok(n)) { s.hotkey_pad_rewind = (int)n; s.has_hotkey_pad_rewind = true; } }); if (h.contains("save_state_menu_pad")) try_get([&]{ const auto n = toml::find(h, "save_state_menu_pad"); - if (n >= 0 && n < 256) { + if (pad_bind_value_ok(n)) { s.hotkey_pad_save_state_menu = (int)n; s.has_hotkey_pad_save_state_menu = true; } diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 23cbe8ee..74f90fa7 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -55,6 +55,20 @@ inline constexpr int VIDEO_RENDERER_OPENGL = 1; inline constexpr int VIDEO_RENDERER_VULKAN = 2; inline constexpr int DEFAULT_VIDEO_RENDERER = VIDEO_RENDERER_OPENGL; +// Controller-hotkey bind encoding, mirroring recomp-ui's RECOMP_LAUNCHER_PAD_* +// (recomp_launcher.h): 0 = unbound, 1..99 = button (1 + SDL button code), +// 100..999 = axis, 1000+ = a CHORD, encoded as 1000 + a bitmask of SDL button +// codes. Two buttons held together is the normal case (select+r3), so a real +// value here is routinely five digits. +// +// The bound matters: a naive `< 256` check silently rejects every chord — which +// is exactly what shipped, so a saved rewind_pad = 1272 was dropped on load and +// the pad hotkeys were only ever configurable to single buttons. SDL defines +// ~21 gamepad buttons, so cap the mask at 32 bits' worth and let the runtime +// ignore bits no controller can produce. +inline constexpr int PAD_BIND_MAX = 1000 + (1 << 21); +inline bool pad_bind_value_ok(long long n) { return n >= 0 && n < PAD_BIND_MAX; } + // FMV present reconstruction, ordered least → most smoothing. See // RuntimeConfig::video_fmv_filter for what each one does and why bicubic is // the default. Shared by game.toml/settings parsing and runtime startup.