You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: .cursor/skills/new-release/SKILL.md
+1-1Lines changed: 1 addition & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,6 +1,6 @@
1
1
---
2
2
name: new-release
3
-
description: Bumps SoF Buddy version, updates VERSION and hdr/version.h, then git add/commit/push to trigger a GitHub Actions release. Use when creating a new release, cutting a build, or when the user asks to release, bump version, or push a new build. Agent must study commit history and changes to carefully create a changelog for the release.
3
+
description: Bumps SoF Buddy version, updates VERSION and hdr/version.h, then git add/commit/push to trigger a GitHub Actions release. Use when creating a new release, cutting a build, or when the user asks to release, bump version, or push a new build. Agent must study commit history and changes to carefully create a changelog for the release. Do not skip over anything, if there is change in any feature, it must be documented in the changelog and change. This command is useful eg. `git diff v4.3-build143 --stat`
Copy file name to clipboardExpand all lines: CHANGELOG.md
+7Lines changed: 7 additions & 0 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,5 +1,12 @@
1
1
# Changelog
2
2
3
+
## v4.4
4
+
5
+
- Raw mouse: reworked raw input pipeline to buffer and drain high-Hz `WM_INPUT` via `GetRawInputBuffer`, skip zero-delta packets, and keep the game's original mouse cvars/flow intact while sourcing movement from hardware deltas instead of OS cursor position.
6
+
- Raw mouse: hardened window/foreground and clip handling (no cursor warping on clip refresh, proper unregister on failure/teardown, disabled-path hooks now pure passthrough with no side effects when `_sofbuddy_rawmouse` is 0).
7
+
- Raw mouse docs: expanded feature README with callback/override details, disabled-path behavior, jitter/high-polling guidance, and configuration notes so users understand how and when to enable it.
8
+
- Docs: README now surfaces a prominent “#1 thing you need to know” section and in-game command docs that emphasize `F12` / `sofbuddy_menu sof_buddy` as the primary entry point into SoF Buddy.
Copy file name to clipboardExpand all lines: src/features/raw_mouse/README.md
+63-35Lines changed: 63 additions & 35 deletions
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -1,59 +1,86 @@
1
1
# Raw Mouse Input
2
2
3
3
## Purpose
4
-
Implements raw mouse input support for Soldier of Fortune using Windows Raw Input API instead of the default GetCursorPos/SetCursorPos cursor management. Provides smoother, more direct mouse response without Windows acceleration/smoothing while preserving all original SoF mouse cvars.
4
+
Implements raw mouse input support for Soldier of Fortune using the Windows Raw Input API instead of the default GetCursorPos/SetCursorPos cursor management. Provides smoother, more direct mouse response without Windows acceleration/smoothing while preserving all original SoF mouse cvars.
5
5
6
6
## Callbacks
7
7
-**EarlyStartup** (Post, Priority: 70)
8
-
-`raw_mouse_EarlyStartup()` - Patches SetCursorPos calls in exe to install hooks
8
+
-`raw_mouse_EarlyStartup()` - Patches three `SetCursorPos` call sites in the exe (IN_Frame, IN_MouseMove, IN_MenuMouse) to redirect through `hkSetCursorPos`, and resolves the real `SetCursorPos` via `GetProcAddress` for passthrough use
9
9
-**RefDllLoaded** (Post, Priority: 70)
10
-
-`raw_mouse_RefDllLoaded()` - Registers raw input device with Windows API
10
+
-`raw_mouse_RefDllLoaded()` - Creates the `_sofbuddy_rawmouse` cvar with a change callback; if the archived value is non-zero, immediately registers raw input
11
11
12
12
## Hooks
13
13
-**IN_MouseMove** (Post, Priority: 100)
14
-
-`in_mousemove_callback()` - Resets delta accumulators after each frame
14
+
-`in_mousemove_callback()` - Polls buffered raw input to ensure minimum latency, then resets `raw_mouse_delta_x/y` to 0 after the game has consumed the frame's mouse input.
15
15
-**IN_MenuMouse** (Post, Priority: 100)
16
-
-`in_menumouse_callback()` - Consumes accumulated deltas after menu mouse handling (same as IN_MouseMove)
16
+
-`in_menumouse_callback()` - Same as IN_MouseMove; polls and resets deltas.
17
17
18
18
## OverrideHooks
19
19
-**GetCursorPos** (user32.dll)
20
-
-`getcursorpos_override_callback()` - Returns virtual cursor position `(window_center + raw_mouse_delta_x/y)`when raw input enabled; never calls real GetCursorPos for look/menu
20
+
-`getcursorpos_override_callback()` - When enabled, returns virtual cursor position `(window_center + delta_x/y)`instead of the real OS cursor position. On the first call (or after a reset), seeds `window_center` from the real cursor position via one call to the original `GetCursorPos`. When disabled, passes through to the original.
21
21
-**DispatchMessageA** (user32.dll)
22
-
-`dispatchmessagea_override_callback()` - On WM_INPUT, extracts relative raw deltas (ignores absolute) and accumulates via `raw_mouse_accumulate_delta()`
22
+
-`dispatchmessagea_override_callback()` - When enabled: intercepts `WM_INPUT` messages, processes the current packet, and immediately polls `GetRawInputBuffer` to drain any queued input. This handles high-polling-rate mice efficiently. Also handles registration/clipping on window events.
23
23
24
24
## CustomDetours
25
-
-**SetCursorPos** (user32.dll, via GetProcAddress)
26
-
-`hkSetCursorPos()` - When raw input enabled: updates internal `window_center`, returns TRUE without calling the real SetCursorPos (OS cursor is never warped). Cursor clip is refreshed by DispatchMessageA on focus/move/size events and during registration.
27
-
- Patched at exe addresses: `0x2004A0B2`, `0x2004A410`, `0x2004A579`
25
+
-**SetCursorPos** (user32.dll, via binary patch + GetProcAddress)
26
+
-`hkSetCursorPos()` - When enabled: updates internal `window_center` to the coordinates the game wants to recenter to, and returns TRUE **without** calling the real `SetCursorPos` (the OS cursor is never warped by the game). When disabled: calls the real `SetCursorPos` normally.
- Returns `(window_center.x + raw_mouse_delta_x, window_center.y + raw_mouse_delta_y)` so the game sees movement (e.g. swipe left → negative delta_x → position left of center)
45
-
- Game logic unchanged: it reads “cursor” position and uses it for look/menu; we never call the real GetCursorPos for that path
46
-
47
-
4.**No Cursor Warping** (SetCursorPos hook):
48
-
- Game’s recenter calls are intercepted: we update `window_center` and return TRUE without calling the real SetCursorPos, so the OS cursor is never moved by the game
49
-
50
-
5.**Cursor Confinement** (ClipCursor):
51
-
- While raw input is enabled and the game window is foregrounded, cursor is clipped to the game client area inset by 64px on all edges to reduce edge-of-screen issues
52
-
- On each clip refresh (e.g. after alt-tab), if the OS cursor is outside the clip rect it is warped back inside via the real SetCursorPos so the cursor is always within bounds when regaining focus
53
-
- Clip is automatically released when raw input is disabled or focus is lost
- After the game processes the frame, `raw_mouse_consume_deltas()` resets `raw_mouse_delta_x/y` to 0, so the virtual cursor is effectively back at window_center for the next frame
32
+
The game's original mouse pipeline is: `SetCursorPos` (center cursor) → game logic → `GetCursorPos` (read cursor) → compute delta → apply to view. This feature intercepts that pipeline so the game's own mouse code continues to work unchanged, but the underlying input comes from raw hardware deltas instead of OS cursor position:
33
+
34
+
1.**CVar Creation** (RefDllLoaded callback):
35
+
-`create_raw_mouse_cvars()` registers `_sofbuddy_rawmouse` with a change callback (`raw_mouse_on_change`)
36
+
- If the archived value is already non-zero (user previously enabled it), the change callback fires immediately
37
+
38
+
2.**Registration** (cvar change callback):
39
+
- When the cvar is set to non-zero, `raw_mouse_on_change()` calls `raw_mouse_register_input()`
40
+
- This registers for raw mouse input using `RegisterRawInputDevices()` with `hwndTarget` set to the active window
41
+
- Deltas are reset and `window_center` is invalidated so the next `GetCursorPos` call seeds it fresh
42
+
43
+
3.**Message Processing** (DispatchMessageA hook, only when enabled):
44
+
- Intercepts `WM_INPUT` messages
45
+
- Processes the current message's data immediately via `GetRawInputData`.
46
+
- Calls `GetRawInputBuffer` to drain any remaining input events in the queue in one batch. This prevents the message queue from being flooded by high-Hz mice (e.g. 1000Hz-8000Hz).
47
+
- Absolute mouse packets are ignored; only relative deltas are accumulated via `raw_mouse_accumulate_delta()`
48
+
- On window focus/move/size events, ensures registration is current and refreshes the cursor clip rect
- The game sees this as a normal cursor position offset from center, so its existing delta calculation works unchanged
53
+
- On the very first call (or after a toggle), `window_center` is seeded from the real OS cursor position via one call to the original `GetCursorPos`
54
+
55
+
5.**No Cursor Warping** (SetCursorPos hook):
56
+
- The game calls `SetCursorPos` every frame to recenter the cursor
57
+
- The hook intercepts this: it records the coordinates as `window_center` and returns TRUE, but **does not call the real SetCursorPos**
58
+
- This prevents the OS cursor from being physically moved, which would otherwise generate synthetic mouse events that conflict with raw input
59
+
60
+
6.**Cursor Confinement** (ClipCursor):
61
+
- While raw input is enabled and the game window is foregrounded, the OS cursor is clipped to the game client area inset by 64px on all sides to prevent edge-of-screen issues
62
+
- Clip is automatically released when raw input is disabled, focus is lost, or the window cannot be resolved
63
+
- Clip refresh is only performed when raw mouse is enabled — when disabled, the DispatchMessageA hook has zero side effects
No cursor clipping, delta accumulation, or registration occurs. The game behaves identically to an unhooked state.
77
+
78
+
### Jitter & high polling rate
79
+
-**No cursor warp on clip refresh.** We do not call `SetCursorPos` to snap the OS cursor back inside the clip rect when refreshing. Warping generates synthetic mouse events that mix with raw deltas and cause spikey/jittery movement. Only `ClipCursor` is applied.
80
+
-**Zero-delta packets skipped.**`ProcessRawInput` ignores `WM_INPUT` with `lLastX == 0 && lLastY == 0` so spurious (0,0) reports don’t add noise.
81
+
-**Buffered Input.** Uses `GetRawInputBuffer` in both the message loop (draining the queue) and the frame loop (fetching latest data). This minimizes overhead for 1000Hz+ mice and ensures the lowest possible input latency.
82
+
-**Legacy input left enabled.** We register with `dwFlags = 0` (no `RIDEV_NOLEGACY`) so window dragging and other system mouse behavior keep working.
83
+
-**Windows 11.** KB5028185 and 24H2 improve behavior for high-polling-rate mice (throttling/coalescing for background listeners, USB polling optimizations). No code change required; up-to-date Win11 can help on high-Hz hardware.
57
84
58
85
### Preserved CVars
59
86
All original SoF mouse cvars remain functional:
@@ -70,6 +97,7 @@ All original SoF mouse cvars remain functional:
70
97
-**_sofbuddy_rawmouse** (default: 0, archived)
71
98
- Set to 1 to enable raw mouse input
72
99
- Set to 0 to use legacy cursor-based input
100
+
- Changes take effect immediately via the cvar change callback
73
101
74
102
## Benefits
75
103
- Smoother, more direct mouse response (no Windows acceleration/smoothing)
0 commit comments