Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 20 additions & 3 deletions crash_handler_stub/WatchDogTimer.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,9 @@ class WatchDogTimer::Impl
std::mutex m_mutex;
std::atomic<bool> 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)
Expand Down Expand Up @@ -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<std::mutex> lk(m_mutex);
m_cond_var.wait_for(lk, check_interval);
}
Expand All @@ -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;
}
}

Expand Down
7 changes: 7 additions & 0 deletions docs/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +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
- 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
Expand All @@ -46,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
Expand All @@ -68,6 +72,9 @@ Version 1.4.0 (Lupin): Not yet released
- 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
Expand Down
6 changes: 6 additions & 0 deletions game_patch/input/input.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@

#include "../rf/player/control_config.h"

// 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();
Expand Down
32 changes: 31 additions & 1 deletion game_patch/input/key.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -114,7 +116,12 @@ FunHook<int(int16_t)> key_to_ascii_hook{

int get_key_name(int key, char* buf, size_t buf_len)
{
LONG lparam = (key & 0x7F) << 16;
// 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;
}
Expand Down Expand Up @@ -509,6 +516,26 @@ FunHook<void(int, int, int)> key_msg_handler_hook{
},
};

// 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<void(int)> 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
Expand Down Expand Up @@ -539,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();
}
10 changes: 10 additions & 0 deletions game_patch/input/mouse.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -420,6 +420,16 @@ CodeInjection static_zoom_sensitivity_patch2 {
},
};

// 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;
rf::key_process_event(CTRL_EXTRA_MOUSE_SCAN_BASE + extra, down ? 1 : 0, 0);
}

void mouse_apply_patch()
{
// Handle zoom sens customization
Expand Down
1 change: 1 addition & 0 deletions game_patch/input/mouse.h
Original file line number Diff line number Diff line change
Expand Up @@ -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);
16 changes: 15 additions & 1 deletion game_patch/os/os.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
#include <cwctype>
#include <patch_common/FunHook.h>
#include <patch_common/AsmWriter.h>
#include <timeapi.h>
#include <xlog/xlog.h>
#include "../rf/os/os.h"
#include "../rf/multi.h"
Expand All @@ -12,7 +13,7 @@
#include "../multi/multi.h"
#include "os.h"
#include "win32_console.h"
#include <timeapi.h>
#include "../input/mouse.h"

FunHook<void()> os_poll_hook{
0x00524B60,
Expand Down Expand Up @@ -89,6 +90,19 @@ 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_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:
Expand Down
5 changes: 5 additions & 0 deletions game_patch/rf/ui.h
Original file line number Diff line number Diff line change
Expand Up @@ -288,6 +288,11 @@ namespace rf::ui
static auto& options_controls_bindings_down_on_click = addr_as_ref<void(int x, int y)>(0x00450130);
static auto& options_controls_current_gadget = addr_as_ref<int>(0x0063FFE8);
static auto& options_controls_waiting_for_key = addr_as_ref<bool>(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<void(int scan_code, int mouse_btn_id)>(0x0044FFD0);
static auto& options_controls_stop_waiting_for_key = addr_as_ref<void()>(0x0044FC00);

// multi -> create game
static auto& create_game_map_list_up_on_click = addr_as_ref<void(int x, int y)>(0x004451F0);
Expand Down
Loading