From a5c4692bd974617d3055981a566c86ac091adecd Mon Sep 17 00:00:00 2001 From: AL2009man Date: Fri, 15 May 2026 16:18:59 -0400 Subject: [PATCH 1/6] Alt Key fix --- game_patch/os/os.cpp | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/game_patch/os/os.cpp b/game_patch/os/os.cpp index dcdfdcde..48225fa9 100644 --- a/game_patch/os/os.cpp +++ b/game_patch/os/os.cpp @@ -92,6 +92,11 @@ LRESULT WINAPI wnd_proc(HWND wnd_handle, UINT msg, WPARAM w_param, LPARAM l_para } return DefWindowProcA(wnd_handle, msg, w_param, l_param); + case WM_SYSCOMMAND: + if ((w_param & 0xFFF0) == SC_KEYMENU) + return 0; + return DefWindowProcA(wnd_handle, msg, w_param, l_param); + case WM_QUIT: case WM_CLOSE: case WM_DESTROY: From d2387c6241183c99cc59f05d511f014d0b7774e2 Mon Sep 17 00:00:00 2001 From: AL2009man Date: Fri, 15 May 2026 17:11:38 -0400 Subject: [PATCH 2/6] Adds additional KB+M bindings for Mouse Buttons and Alt keys --- game_patch/input/input.h | 9 +++++++++ game_patch/input/key.cpp | 30 ++++++++++++++++++++++++++++++ game_patch/input/mouse.cpp | 24 ++++++++++++++++++++++++ game_patch/input/mouse.h | 1 + game_patch/misc/ui.cpp | 38 ++++++++++++++++++++++++++++++++++++-- game_patch/os/os.cpp | 9 +++++++++ 6 files changed, 109 insertions(+), 2 deletions(-) diff --git a/game_patch/input/input.h b/game_patch/input/input.h index 347b0bad..d92aad1d 100644 --- a/game_patch/input/input.h +++ b/game_patch/input/input.h @@ -2,7 +2,16 @@ #include "../rf/player/control_config.h" +// Sentinel scan code injected into Input Rebind UI, allowing additional input bindings. +static constexpr int CTRL_REBIND_SENTINEL = 0x58; // KEY_F12 + +// Custom scan codes for extra mouse buttons (Mouse 4 and above), stored in scan_codes[0]. +static constexpr int CTRL_EXTRA_MOUSE_SCAN_BASE = 0x75; +static constexpr int CTRL_EXTRA_MOUSE_SCAN_COUNT = 5; + rf::ControlConfigAction get_af_control(rf::AlpineControlConfigAction alpine_control); rf::String get_action_bind_name(int action); void mouse_apply_patch(); +int mouse_take_pending_rebind(); void key_apply_patch(); +int key_take_pending_extra_rebind(); diff --git a/game_patch/input/key.cpp b/game_patch/input/key.cpp index 15ac1789..077ec4e9 100644 --- a/game_patch/input/key.cpp +++ b/game_patch/input/key.cpp @@ -19,8 +19,10 @@ #include "../rf/player/player.h" #include "../rf/os/console.h" #include "../rf/os/os.h" +#include "../rf/ui.h" #include "../multi/alpine_packets.h" #include "../os/console.h" +#include "input.h" static int starting_alpine_control_index = -1; @@ -114,6 +116,11 @@ FunHook key_to_ascii_hook{ int get_key_name(int key, char* buf, size_t buf_len) { + // Extra mouse buttons (Mouse 4+): stored as custom scan codes + if (key >= CTRL_EXTRA_MOUSE_SCAN_BASE && key < CTRL_EXTRA_MOUSE_SCAN_BASE + CTRL_EXTRA_MOUSE_SCAN_COUNT) { + int n = snprintf(buf, buf_len, "Mouse %d", (key - CTRL_EXTRA_MOUSE_SCAN_BASE) + 4); + return n > 0 ? n : 0; + } LONG lparam = (key & 0x7F) << 16; if (key & 0x80) { lparam |= 1 << 24; @@ -488,6 +495,16 @@ CodeInjection item_touch_weapon_autoswitch_patch{ } }; +// Pending extra key rebind scan code (-1 = none). +static int g_pending_extra_key_rebind = -1; + +int key_take_pending_extra_rebind() +{ + int sc = g_pending_extra_key_rebind; + g_pending_extra_key_rebind = -1; + return sc; +} + FunHook key_msg_handler_hook{ 0x0051EBA0, [] (const int msg, const int w_param, int l_param) { @@ -503,6 +520,19 @@ FunHook key_msg_handler_hook{ || w_param == VK_HOME) { l_param |= KF_EXTENDED << 16; } + // Alt keys arrive via WM_SYSKEYDOWN but RF's handler doesn't feed them into its + // key system — inject the sentinel so the rebind UI records the press, then swap on fall edge. + if ((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) + && rf::ui::options_controls_waiting_for_key + && g_pending_extra_key_rebind < 0) { + int win32_scan = (l_param >> 16) & 0x1FF; + int rf_scan = (win32_scan & 0x7F) | ((win32_scan & 0x100) ? 0x80 : 0); + if (rf_scan == rf::KEY_LALT || rf_scan == rf::KEY_RALT) { + g_pending_extra_key_rebind = rf_scan; + rf::key_process_event(CTRL_REBIND_SENTINEL, 1, 0); + return; + } + } } } key_msg_handler_hook.call_target(msg, w_param, l_param); diff --git a/game_patch/input/mouse.cpp b/game_patch/input/mouse.cpp index aad8db7e..b5f76faa 100644 --- a/game_patch/input/mouse.cpp +++ b/game_patch/input/mouse.cpp @@ -11,6 +11,7 @@ #include "../rf/multi.h" #include "../rf/player/player.h" #include "../rf/player/camera.h" +#include "../rf/ui.h" #include "../misc/alpine_settings.h" #include "../main/main.h" #include "mouse.h" @@ -420,6 +421,29 @@ CodeInjection static_zoom_sensitivity_patch2 { }, }; +// Pending extra mouse button rebind (rf_btn index, -1 = none). +static int g_pending_mouse_extra_btn_rebind = -1; + +int mouse_take_pending_rebind() +{ + int btn = g_pending_mouse_extra_btn_rebind; + g_pending_mouse_extra_btn_rebind = -1; + return btn; +} + +void mouse_handle_xbutton_wm(int rf_btn, bool down) +{ + int extra = rf_btn - 3; + if (extra < 0 || extra >= CTRL_EXTRA_MOUSE_SCAN_COUNT) + return; + if (down && g_pending_mouse_extra_btn_rebind < 0 && rf::ui::options_controls_waiting_for_key) { + g_pending_mouse_extra_btn_rebind = rf_btn; + rf::key_process_event(CTRL_REBIND_SENTINEL, 1, 0); + } else { + rf::key_process_event(CTRL_EXTRA_MOUSE_SCAN_BASE + extra, down ? 1 : 0, 0); + } +} + void mouse_apply_patch() { // Handle zoom sens customization diff --git a/game_patch/input/mouse.h b/game_patch/input/mouse.h index 14ff3e51..cdb03ae2 100644 --- a/game_patch/input/mouse.h +++ b/game_patch/input/mouse.h @@ -2,3 +2,4 @@ void mouse_apply_patch(); void consume_raw_mouse_deltas(float& out_pitch, float& out_yaw, bool apply_scope_sens); +void mouse_handle_xbutton_wm(int rf_btn, bool down); diff --git a/game_patch/misc/ui.cpp b/game_patch/misc/ui.cpp index 1be087da..821ca135 100644 --- a/game_patch/misc/ui.cpp +++ b/game_patch/misc/ui.cpp @@ -21,6 +21,7 @@ #include "../object/object.h" #include "../graphics/d3d11/gr_d3d11_mesh.h" #include "../rf/level.h" +#include "../input/input.h" #define DEBUG_UI_LAYOUT 0 #define SHARP_UI_TEXT 1 @@ -1537,8 +1538,41 @@ CodeInjection options_render_alpine_panel_patch{ int index = rf::ui::options_current_panel; //xlog::warn("render index {}", index); - if (index == 3 && !rf::ui::options_controls_waiting_for_key) { - render_ctrl_camscale_btns(); + if (index == 3) { + static bool s_was_waiting = false; + bool now_waiting = rf::ui::options_controls_waiting_for_key; + if (s_was_waiting && !now_waiting) { + int16_t new_sc = -1; + int xbtn = mouse_take_pending_rebind(); + if (xbtn >= 0) + new_sc = static_cast(CTRL_EXTRA_MOUSE_SCAN_BASE + (xbtn - 3)); + if (new_sc < 0) { + int kbd_sc = key_take_pending_extra_rebind(); + if (kbd_sc >= 0) + new_sc = static_cast(kbd_sc); + } + if (new_sc >= 0 && rf::local_player) { + auto& cc = rf::local_player->settings.controls; + int n = std::min(cc.num_bindings, 128); + bool found = false; + for (int i = 0; i < n && !found; ++i) { + for (int slot = 0; slot < 2 && !found; ++slot) { + if (cc.bindings[i].scan_codes[slot] == static_cast(CTRL_REBIND_SENTINEL)) { + for (int j = 0; j < n; ++j) + for (int s = 0; s < 2; ++s) + if ((j != i || s != slot) && cc.bindings[j].scan_codes[s] == new_sc) + cc.bindings[j].scan_codes[s] = -1; + cc.bindings[i].scan_codes[slot] = new_sc; + found = true; + } + } + } + rf::key_process_event(CTRL_REBIND_SENTINEL, 0, 0); + } + } + s_was_waiting = now_waiting; + if (!rf::ui::options_controls_waiting_for_key) + render_ctrl_camscale_btns(); } // render alpine options panel diff --git a/game_patch/os/os.cpp b/game_patch/os/os.cpp index 48225fa9..97093f7e 100644 --- a/game_patch/os/os.cpp +++ b/game_patch/os/os.cpp @@ -12,6 +12,7 @@ #include "../multi/multi.h" #include "os.h" #include "win32_console.h" +#include "../input/mouse.h" #include @@ -97,6 +98,14 @@ LRESULT WINAPI wnd_proc(HWND wnd_handle, UINT msg, WPARAM w_param, LPARAM l_para return 0; return DefWindowProcA(wnd_handle, msg, w_param, l_param); + case WM_XBUTTONDOWN: + mouse_handle_xbutton_wm(2 + HIWORD(w_param), true); + return TRUE; + + case WM_XBUTTONUP: + mouse_handle_xbutton_wm(2 + HIWORD(w_param), false); + return TRUE; + case WM_QUIT: case WM_CLOSE: case WM_DESTROY: From 8c20c9a52e8a25038b0bd9a0d1d4f0767d9e33cf Mon Sep 17 00:00:00 2001 From: AL2009man Date: Sun, 17 May 2026 19:39:47 -0400 Subject: [PATCH 3/6] added changelog note --- docs/CHANGELOG.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 980551df..26d460e3 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -19,6 +19,9 @@ Version 1.4.0 (Lupin): Not yet released - Add `fflink_gsk` dedicated server config field and `sv_fflink_status` / `sv_fflink_resync` console commands for FactionFiles session key exchange - Make clock clutter objects correctly display the current local real world time +[@AL2009man](https://github.com/AL2009man) +-Add support for Additional Mouse Button and Left/Right Alt to the Controls binding UI + [@is-this-c](https://github.com/is-this-c) - Disable `Refresh Selected` in the server browser, only if `Refresh Selected` was pressed - Never disable `Add Server` in the server browser @@ -30,6 +33,9 @@ Version 1.4.0 (Lupin): Not yet released - Fix incorrect clickable area size for launcher FFLink button - Fix "allow clientside mods from legacy directories" option not applying correctly for DDS files +[@AL2009man](https://github.com/AL2009man) +- Fix brief game freeze every time the Alt key is pressed. + [@is-this-c](https://github.com/is-this-c) - For `Refresh Selected`, re-enable `Get Servers` etc. immediately upon response instead of waiting for timeout - Disable weapon cycle selection, if `Mouse 3` is pressed From d04f325ebada5c7336b6d56c8b2a16e1afbc8cbe Mon Sep 17 00:00:00 2001 From: AL2009man Date: Sat, 20 Jun 2026 18:58:40 -0400 Subject: [PATCH 4/6] minor fix grammer improvements --- docs/CHANGELOG.md | 2 +- game_patch/input/input.h | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 38886da1..ddd36919 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -22,7 +22,7 @@ Version 1.4.0 (Lupin): Not yet released - Add mini scoreboard HUD element to FFA game types [@AL2009man](https://github.com/AL2009man) --Add support for Additional Mouse Button and Left/Right Alt to the Controls binding UI +- Add support for additional mouse buttons and Left/Right Alt in the controls binding UI [@is-this-c](https://github.com/is-this-c) - Allow players to join between levels diff --git a/game_patch/input/input.h b/game_patch/input/input.h index d92aad1d..fe654713 100644 --- a/game_patch/input/input.h +++ b/game_patch/input/input.h @@ -5,7 +5,7 @@ // Sentinel scan code injected into Input Rebind UI, allowing additional input bindings. static constexpr int CTRL_REBIND_SENTINEL = 0x58; // KEY_F12 -// Custom scan codes for extra mouse buttons (Mouse 4 and above), stored in scan_codes[0]. +// Custom scan codes for extra mouse buttons (Mouse 4 and above) static constexpr int CTRL_EXTRA_MOUSE_SCAN_BASE = 0x75; static constexpr int CTRL_EXTRA_MOUSE_SCAN_COUNT = 5; From f4bc14e017bbd1b6282e1531e7a355c2d7ee14f3 Mon Sep 17 00:00:00 2001 From: AL2009man Date: Sat, 20 Jun 2026 19:12:35 -0400 Subject: [PATCH 5/6] decouple extra rebind session --- game_patch/misc/ui.cpp | 64 ++++++++++++++++++++++-------------------- 1 file changed, 34 insertions(+), 30 deletions(-) diff --git a/game_patch/misc/ui.cpp b/game_patch/misc/ui.cpp index 0863a166..2b53e511 100644 --- a/game_patch/misc/ui.cpp +++ b/game_patch/misc/ui.cpp @@ -1531,6 +1531,37 @@ static void handle_ctrl_camscale_btns(int x, int y) ctrl_camscale_on_click(x, y); } +static void apply_pending_extra_rebind() +{ + int16_t new_sc = -1; + int xbtn = mouse_take_pending_rebind(); + if (xbtn >= 0) + new_sc = static_cast(CTRL_EXTRA_MOUSE_SCAN_BASE + (xbtn - 3)); + if (new_sc < 0) { + int kbd_sc = key_take_pending_extra_rebind(); + if (kbd_sc >= 0) + new_sc = static_cast(kbd_sc); + } + if (new_sc < 0 || !rf::local_player) + return; + auto& cc = rf::local_player->settings.controls; + int n = std::min(cc.num_bindings, 128); + for (int i = 0; i < n; ++i) { + for (int slot = 0; slot < 2; ++slot) { + if (cc.bindings[i].scan_codes[slot] != static_cast(CTRL_REBIND_SENTINEL)) + continue; + for (int j = 0; j < n; ++j) + for (int s = 0; s < 2; ++s) + if ((j != i || s != slot) && cc.bindings[j].scan_codes[s] == new_sc) + cc.bindings[j].scan_codes[s] = -1; + cc.bindings[i].scan_codes[slot] = new_sc; + goto done; + } + } +done: + rf::key_process_event(CTRL_REBIND_SENTINEL, 0, 0); +} + // handle alpine options panel rendering CodeInjection options_render_alpine_panel_patch{ 0x0044F80B, @@ -1541,37 +1572,10 @@ CodeInjection options_render_alpine_panel_patch{ if (index == 3) { static bool s_was_waiting = false; bool now_waiting = rf::ui::options_controls_waiting_for_key; - if (s_was_waiting && !now_waiting) { - int16_t new_sc = -1; - int xbtn = mouse_take_pending_rebind(); - if (xbtn >= 0) - new_sc = static_cast(CTRL_EXTRA_MOUSE_SCAN_BASE + (xbtn - 3)); - if (new_sc < 0) { - int kbd_sc = key_take_pending_extra_rebind(); - if (kbd_sc >= 0) - new_sc = static_cast(kbd_sc); - } - if (new_sc >= 0 && rf::local_player) { - auto& cc = rf::local_player->settings.controls; - int n = std::min(cc.num_bindings, 128); - bool found = false; - for (int i = 0; i < n && !found; ++i) { - for (int slot = 0; slot < 2 && !found; ++slot) { - if (cc.bindings[i].scan_codes[slot] == static_cast(CTRL_REBIND_SENTINEL)) { - for (int j = 0; j < n; ++j) - for (int s = 0; s < 2; ++s) - if ((j != i || s != slot) && cc.bindings[j].scan_codes[s] == new_sc) - cc.bindings[j].scan_codes[s] = -1; - cc.bindings[i].scan_codes[slot] = new_sc; - found = true; - } - } - } - rf::key_process_event(CTRL_REBIND_SENTINEL, 0, 0); - } - } + if (s_was_waiting && !now_waiting) + apply_pending_extra_rebind(); s_was_waiting = now_waiting; - if (!rf::ui::options_controls_waiting_for_key) + if (!now_waiting) render_ctrl_camscale_btns(); } From 662ec991c743c8a217a72d25f0845040f02b1c88 Mon Sep 17 00:00:00 2001 From: Chris Parsons Date: Tue, 7 Jul 2026 10:14:13 -0230 Subject: [PATCH 6/6] optimize implementation, resolve regressive conflict with F12/M5 and Henkan/M8, require fresh alt press to force crash a hung process --- crash_handler_stub/WatchDogTimer.cpp | 23 +++++++++++-- docs/CHANGELOG.md | 13 ++++---- game_patch/input/input.h | 11 +++---- game_patch/input/key.cpp | 48 ++++++++++++++-------------- game_patch/input/mouse.cpp | 20 ++---------- game_patch/misc/ui.cpp | 42 ++---------------------- game_patch/rf/ui.h | 5 +++ 7 files changed, 65 insertions(+), 97 deletions(-) diff --git a/crash_handler_stub/WatchDogTimer.cpp b/crash_handler_stub/WatchDogTimer.cpp index b680cc5d..fdf7fed8 100644 --- a/crash_handler_stub/WatchDogTimer.cpp +++ b/crash_handler_stub/WatchDogTimer.cpp @@ -21,6 +21,9 @@ class WatchDogTimer::Impl std::mutex m_mutex; std::atomic m_exiting; bool m_running = false; + // Touched only by the checker thread: Alt kill-switch arming state, reset when no hang is active + bool m_kill_armed = false; + bool m_hang_reported = false; public: Impl(std::chrono::milliseconds timeout) : m_timeout(timeout) @@ -77,6 +80,12 @@ class WatchDogTimer::Impl if (check_for_time_out()) { handle_time_out(); } + else { + // No hang in progress (or it recovered) - the next hang requires a fresh + // Alt press before it can be killed + m_kill_armed = false; + m_hang_reported = false; + } std::unique_lock lk(m_mutex); m_cond_var.wait_for(lk, check_interval); } @@ -91,12 +100,20 @@ class WatchDogTimer::Impl void handle_time_out() { - if (GetAsyncKeyState(VK_MENU) & 0x8000) { + // Only treat Alt as a kill request if it was pressed after the hang began: Alt can be + // held as a regular game binding, so an Alt that was already down when the game stopped + // responding must be observed released once before it arms the kill switch. + bool alt_down = (GetAsyncKeyState(VK_MENU) & 0x8000) != 0; + if (!alt_down) { + m_kill_armed = true; + } + if (alt_down && m_kill_armed) { xlog::info("Crash of not responding process has been requested"); crash_observed_thread(); } - else { - xlog::info("Process is not responding! Hold the Alt key for a few seconds to kill the process and generate a crash report that will allow to debug the problem..."); + else if (!m_hang_reported) { + xlog::info("Process is not responding! Release the Alt key if it is held, then hold it for a few seconds to kill the process and generate a crash report that will help in debugging the problem..."); + m_hang_reported = true; } } diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index be143723..b8415ebb 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -32,9 +32,7 @@ Version 1.4.0 (Lupin): Not yet released - Add `Sort` options (by name or by UID, with an optional `Group by type` toggle) to the editor's Select Objects and Show/Hide Objects windows - Add `camera4` console command for static camera in single player - Add `camera5` console command for tripod (follow player) camera in single player - -[@AL2009man](https://github.com/AL2009man) -- Add support for additional mouse buttons and Left/Right Alt in the controls binding UI +- Require a fresh `Alt` press to kill an unresponsive process [@is-this-c](https://github.com/is-this-c) - Allow players to join between levels @@ -49,6 +47,9 @@ Version 1.4.0 (Lupin): Not yet released - Add detection of manually loaded levels - Highlight an active level in a server's rotation via background color instead of text color +[@AL2009man](https://github.com/AL2009man) +- Add support for binding controls to additional mouse buttons and `Alt` keys + ### Bug fixes [@GooberRF](https://github.com/GooberRF) - Fix team balance not properly randomizing the distribution order of equal-scoring human players @@ -65,15 +66,15 @@ Version 1.4.0 (Lupin): Not yet released - Fix unbounded read when a request to play a sound above `g_num_sounds` is made - Fix camera angle snapping when switching between free look and third person camera modes -[@AL2009man](https://github.com/AL2009man) -- Fix brief game freeze every time the Alt key is pressed. - [@is-this-c](https://github.com/is-this-c) - Clear cached server config output after a shuffle of a server's rotation - For `Refresh Selected`, re-enable `Get Servers` etc. immediately upon response instead of waiting for timeout - Disable weapon cycle selection, if `Mouse 3` is pressed - For `Run` games, rename `Score` column to `Deaths`, and compare `Loads` in `std::ranges::sort` +[@AL2009man](https://github.com/AL2009man) +- Fix brief game freeze whenever an `Alt` key is pressed + Version 1.3.0 (Bakeapple): Released Apr-22-2026 -------------------------------- ### Major features diff --git a/game_patch/input/input.h b/game_patch/input/input.h index fe654713..10dbc26b 100644 --- a/game_patch/input/input.h +++ b/game_patch/input/input.h @@ -2,16 +2,13 @@ #include "../rf/player/control_config.h" -// Sentinel scan code injected into Input Rebind UI, allowing additional input bindings. -static constexpr int CTRL_REBIND_SENTINEL = 0x58; // KEY_F12 - -// Custom scan codes for extra mouse buttons (Mouse 4 and above) -static constexpr int CTRL_EXTRA_MOUSE_SCAN_BASE = 0x75; +// Custom scan codes for extra mouse buttons (Mouse 4 and above). Placed in the extended +// range (0x80 | scan) at slots no physical keyboard emits, so they cannot collide with +// real key presses. +static constexpr int CTRL_EXTRA_MOUSE_SCAN_BASE = 0xF5; static constexpr int CTRL_EXTRA_MOUSE_SCAN_COUNT = 5; rf::ControlConfigAction get_af_control(rf::AlpineControlConfigAction alpine_control); rf::String get_action_bind_name(int action); void mouse_apply_patch(); -int mouse_take_pending_rebind(); void key_apply_patch(); -int key_take_pending_extra_rebind(); diff --git a/game_patch/input/key.cpp b/game_patch/input/key.cpp index 077ec4e9..caddc83d 100644 --- a/game_patch/input/key.cpp +++ b/game_patch/input/key.cpp @@ -121,7 +121,7 @@ int get_key_name(int key, char* buf, size_t buf_len) int n = snprintf(buf, buf_len, "Mouse %d", (key - CTRL_EXTRA_MOUSE_SCAN_BASE) + 4); return n > 0 ? n : 0; } - LONG lparam = (key & 0x7F) << 16; + LONG lparam = (key & 0x7F) << 16; if (key & 0x80) { lparam |= 1 << 24; } @@ -495,16 +495,6 @@ CodeInjection item_touch_weapon_autoswitch_patch{ } }; -// Pending extra key rebind scan code (-1 = none). -static int g_pending_extra_key_rebind = -1; - -int key_take_pending_extra_rebind() -{ - int sc = g_pending_extra_key_rebind; - g_pending_extra_key_rebind = -1; - return sc; -} - FunHook key_msg_handler_hook{ 0x0051EBA0, [] (const int msg, const int w_param, int l_param) { @@ -520,25 +510,32 @@ FunHook key_msg_handler_hook{ || w_param == VK_HOME) { l_param |= KF_EXTENDED << 16; } - // Alt keys arrive via WM_SYSKEYDOWN but RF's handler doesn't feed them into its - // key system — inject the sentinel so the rebind UI records the press, then swap on fall edge. - if ((msg == WM_KEYDOWN || msg == WM_SYSKEYDOWN) - && rf::ui::options_controls_waiting_for_key - && g_pending_extra_key_rebind < 0) { - int win32_scan = (l_param >> 16) & 0x1FF; - int rf_scan = (win32_scan & 0x7F) | ((win32_scan & 0x100) ? 0x80 : 0); - if (rf_scan == rf::KEY_LALT || rf_scan == rf::KEY_RALT) { - g_pending_extra_key_rebind = rf_scan; - rf::key_process_event(CTRL_REBIND_SENTINEL, 1, 0); - return; - } - } } } key_msg_handler_hook.call_target(msg, w_param, l_param); }, }; +// Records the pressed key into the selected binding row when the controls panel is +// waiting for input (scan code from the key queue, or -1 to check mouse buttons). +// Stock RF rejects Left/Right Alt and any scan code its internal key name table has +// no name for, which blocks Alpine's extra mouse button scan codes — accept both +// directly; everything else keeps stock behavior (including the Esc/PrtScn/Pause +// blacklist and mouse button handling). +FunHook options_controls_record_binding_hook{ + 0x0044FE60, + [](int key) { + bool is_extra_mouse_scan = key >= CTRL_EXTRA_MOUSE_SCAN_BASE + && key < CTRL_EXTRA_MOUSE_SCAN_BASE + CTRL_EXTRA_MOUSE_SCAN_COUNT; + if (is_extra_mouse_scan || key == rf::KEY_LALT || key == rf::KEY_RALT) { + rf::ui::options_controls_assign_binding(key, -1); + rf::ui::options_controls_stop_waiting_for_key(); + return; + } + options_controls_record_binding_hook.call_target(key); + }, +}; + void key_apply_patch() { // Handle Alpine chat menus @@ -569,4 +566,7 @@ void key_apply_patch() // Num pads need a patch to support `PgUp`, `PgDown`, `End`, and `Home`. key_msg_handler_hook.install(); + + // Allow binding Alt keys and extra mouse buttons in the controls options panel + options_controls_record_binding_hook.install(); } diff --git a/game_patch/input/mouse.cpp b/game_patch/input/mouse.cpp index b5f76faa..bd5cdd1f 100644 --- a/game_patch/input/mouse.cpp +++ b/game_patch/input/mouse.cpp @@ -11,7 +11,6 @@ #include "../rf/multi.h" #include "../rf/player/player.h" #include "../rf/player/camera.h" -#include "../rf/ui.h" #include "../misc/alpine_settings.h" #include "../main/main.h" #include "mouse.h" @@ -421,27 +420,14 @@ CodeInjection static_zoom_sensitivity_patch2 { }, }; -// Pending extra mouse button rebind (rf_btn index, -1 = none). -static int g_pending_mouse_extra_btn_rebind = -1; - -int mouse_take_pending_rebind() -{ - int btn = g_pending_mouse_extra_btn_rebind; - g_pending_mouse_extra_btn_rebind = -1; - return btn; -} - +// Feed extra mouse buttons (Mouse 4+) into RF's key system as custom scan codes. +// The controls binding UI picks them up from the key queue like any key press. void mouse_handle_xbutton_wm(int rf_btn, bool down) { int extra = rf_btn - 3; if (extra < 0 || extra >= CTRL_EXTRA_MOUSE_SCAN_COUNT) return; - if (down && g_pending_mouse_extra_btn_rebind < 0 && rf::ui::options_controls_waiting_for_key) { - g_pending_mouse_extra_btn_rebind = rf_btn; - rf::key_process_event(CTRL_REBIND_SENTINEL, 1, 0); - } else { - rf::key_process_event(CTRL_EXTRA_MOUSE_SCAN_BASE + extra, down ? 1 : 0, 0); - } + rf::key_process_event(CTRL_EXTRA_MOUSE_SCAN_BASE + extra, down ? 1 : 0, 0); } void mouse_apply_patch() diff --git a/game_patch/misc/ui.cpp b/game_patch/misc/ui.cpp index 46cc2d56..e9cc77ca 100644 --- a/game_patch/misc/ui.cpp +++ b/game_patch/misc/ui.cpp @@ -22,7 +22,6 @@ #include "../object/object.h" #include "../graphics/d3d11/gr_d3d11_mesh.h" #include "../rf/level.h" -#include "../input/input.h" #define DEBUG_UI_LAYOUT 0 #define SHARP_UI_TEXT 1 @@ -1532,37 +1531,6 @@ static void handle_ctrl_camscale_btns(int x, int y) ctrl_camscale_on_click(x, y); } -static void apply_pending_extra_rebind() -{ - int16_t new_sc = -1; - int xbtn = mouse_take_pending_rebind(); - if (xbtn >= 0) - new_sc = static_cast(CTRL_EXTRA_MOUSE_SCAN_BASE + (xbtn - 3)); - if (new_sc < 0) { - int kbd_sc = key_take_pending_extra_rebind(); - if (kbd_sc >= 0) - new_sc = static_cast(kbd_sc); - } - if (new_sc < 0 || !rf::local_player) - return; - auto& cc = rf::local_player->settings.controls; - int n = std::min(cc.num_bindings, 128); - for (int i = 0; i < n; ++i) { - for (int slot = 0; slot < 2; ++slot) { - if (cc.bindings[i].scan_codes[slot] != static_cast(CTRL_REBIND_SENTINEL)) - continue; - for (int j = 0; j < n; ++j) - for (int s = 0; s < 2; ++s) - if ((j != i || s != slot) && cc.bindings[j].scan_codes[s] == new_sc) - cc.bindings[j].scan_codes[s] = -1; - cc.bindings[i].scan_codes[slot] = new_sc; - goto done; - } - } -done: - rf::key_process_event(CTRL_REBIND_SENTINEL, 0, 0); -} - // handle alpine options panel rendering CodeInjection options_render_alpine_panel_patch{ 0x0044F80B, @@ -1570,14 +1538,8 @@ CodeInjection options_render_alpine_panel_patch{ int index = rf::ui::options_current_panel; //xlog::warn("render index {}", index); - if (index == 3) { - static bool s_was_waiting = false; - bool now_waiting = rf::ui::options_controls_waiting_for_key; - if (s_was_waiting && !now_waiting) - apply_pending_extra_rebind(); - s_was_waiting = now_waiting; - if (!now_waiting) - render_ctrl_camscale_btns(); + if (index == 3 && !rf::ui::options_controls_waiting_for_key) { + render_ctrl_camscale_btns(); } // render alpine options panel diff --git a/game_patch/rf/ui.h b/game_patch/rf/ui.h index fea23ef7..23f426f6 100644 --- a/game_patch/rf/ui.h +++ b/game_patch/rf/ui.h @@ -288,6 +288,11 @@ namespace rf::ui static auto& options_controls_bindings_down_on_click = addr_as_ref(0x00450130); static auto& options_controls_current_gadget = addr_as_ref(0x0063FFE8); static auto& options_controls_waiting_for_key = addr_as_ref(0x00640060); + + // Assigns to the currently selected binding row and clears the same scan code / + // mouse button from all other bindings (pass -1 for the unused parameter) + static auto& options_controls_assign_binding = addr_as_ref(0x0044FFD0); + static auto& options_controls_stop_waiting_for_key = addr_as_ref(0x0044FC00); // multi -> create game static auto& create_game_map_list_up_on_click = addr_as_ref(0x004451F0);