1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
//! GLFW callback hook: turns GLFW input events into framework-neutral UiInput.
//!
//! Linux has no window procedure to subclass, which is how the Win32 side does
//! this. What it does have is GLFW's own callbacks, which the game installs once
//! when it creates its window. We put ourselves in front of each one and keep
//! the pointer we displaced, so the contract matches Windows exactly: while the
//! menu is open we swallow the event, and otherwise we chain straight through.
//!
//! Key codes are translated to Win32 virtual-key numbers on the way in. That
//! keeps one bind vocabulary across both platforms, so a config written on
//! Windows means the same thing here.
use std::ffi::c_void;
use std::sync::atomic::{AtomicBool, AtomicPtr, Ordering};
use crate::state::{self, UiInput};
// GLFW actions.
const RELEASE: i32 = 0;
type KeyCb = unsafe extern "C" fn(*mut c_void, i32, i32, i32, i32);
type MouseCb = unsafe extern "C" fn(*mut c_void, i32, i32, i32);
type CursorCb = unsafe extern "C" fn(*mut c_void, f64, f64);
type ScrollCb = unsafe extern "C" fn(*mut c_void, f64, f64);
type CharCb = unsafe extern "C" fn(*mut c_void, u32);
/// The callbacks we displaced, so every event the menu does not want still
/// reaches the game.
static PREV_KEY: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
static PREV_MOUSE: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
static PREV_CURSOR: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
static PREV_SCROLL: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
static PREV_CHAR: AtomicPtr<c_void> = AtomicPtr::new(std::ptr::null_mut());
static INSTALLED: AtomicBool = AtomicBool::new(false);
/// Right mouse button state, which is what `use`-held means for the cheats.
static RMB: AtomicBool = AtomicBool::new(false);
/// Whether the use button is held, tracked from the events we already see.
pub fn right_mouse_down() -> bool {
RMB.load(Ordering::Relaxed)
}
/// Chain ourselves in front of the game's GLFW callbacks.
///
/// # Safety
/// `window` must be the GLFW window the game is rendering into.
pub unsafe fn install(window: *mut c_void) -> bool {
if INSTALLED.load(Ordering::SeqCst) {
return true;
}
let mut any = false;
if let Some(p) = set_cb(window, b"glfwSetKeyCallback\0", key_cb as *mut c_void) {
PREV_KEY.store(p, Ordering::SeqCst);
any = true;
}
if let Some(p) = set_cb(window, b"glfwSetMouseButtonCallback\0", mouse_cb as *mut c_void) {
PREV_MOUSE.store(p, Ordering::SeqCst);
any = true;
}
if let Some(p) = set_cb(window, b"glfwSetCursorPosCallback\0", cursor_cb as *mut c_void) {
PREV_CURSOR.store(p, Ordering::SeqCst);
any = true;
}
if let Some(p) = set_cb(window, b"glfwSetScrollCallback\0", scroll_cb as *mut c_void) {
PREV_SCROLL.store(p, Ordering::SeqCst);
any = true;
}
if let Some(p) = set_cb(window, b"glfwSetCharCallback\0", char_cb as *mut c_void) {
PREV_CHAR.store(p, Ordering::SeqCst);
any = true;
}
INSTALLED.store(any, Ordering::SeqCst);
any
}
/// Put the game's own callbacks back.
///
/// # Safety
/// `window` must be the window `install` was called with.
pub unsafe fn remove(window: *mut c_void) -> bool {
if !INSTALLED.swap(false, Ordering::SeqCst) {
return true;
}
let _ = set_cb(window, b"glfwSetKeyCallback\0", PREV_KEY.swap(std::ptr::null_mut(), Ordering::SeqCst));
let _ = set_cb(window, b"glfwSetMouseButtonCallback\0", PREV_MOUSE.swap(std::ptr::null_mut(), Ordering::SeqCst));
let _ = set_cb(window, b"glfwSetCursorPosCallback\0", PREV_CURSOR.swap(std::ptr::null_mut(), Ordering::SeqCst));
let _ = set_cb(window, b"glfwSetScrollCallback\0", PREV_SCROLL.swap(std::ptr::null_mut(), Ordering::SeqCst));
let _ = set_cb(window, b"glfwSetCharCallback\0", PREV_CHAR.swap(std::ptr::null_mut(), Ordering::SeqCst));
true
}
/// Call one of the `glfwSet*Callback` setters, handing back what it displaced.
unsafe fn set_cb(window: *mut c_void, name: &[u8], cb: *mut c_void) -> Option<*mut c_void> {
let f = crate::glfw::proc(name)?;
let f: unsafe extern "C" fn(*mut c_void, *mut c_void) -> *mut c_void = std::mem::transmute(f);
Some(f(window, cb))
}
/// True when the menu wants this event to itself.
fn menu_open() -> bool {
state::with(|s| s.menu_open).unwrap_or(false)
}
unsafe extern "C" fn key_cb(w: *mut c_void, key: i32, scancode: i32, action: i32, mods: i32) {
let vk = glfw_key_to_vk(key);
if action != RELEASE {
// A rebind in progress captures the next key first — including a new
// menu key — so it is never mistaken for a toggle or a module bind.
if state::with(|s| s.binding.is_some()).unwrap_or(false) {
state::with(|s| s.handle_key(vk));
return;
}
let menu_key = state::with(|s| s.cfg.menu_key).unwrap_or(0x2D);
if vk == menu_key {
state::with(|s| s.toggle_menu());
return;
}
// Module binds fire only while the menu is shut.
let handled =
state::with(|s| if !s.menu_open { s.handle_key(vk) } else { false }).unwrap_or(false);
if handled {
return;
}
}
if menu_open() {
state::with(|s| s.events.push(UiInput::Key(vk, action != RELEASE)));
return;
}
if let Some(prev) = nonnull(&PREV_KEY) {
let prev: KeyCb = std::mem::transmute(prev);
prev(w, key, scancode, action, mods);
}
}
unsafe extern "C" fn mouse_cb(w: *mut c_void, button: i32, action: i32, mods: i32) {
let down = action != RELEASE;
// Tracked whether or not the menu is open: the cheats ask for this, and a
// click that opened the menu should not leave the flag stuck on.
if button == 1 {
RMB.store(down && !menu_open(), Ordering::Relaxed);
}
if menu_open() {
let b = match button {
0 => 0u8,
1 => 1,
_ => 2,
};
state::with(|s| s.events.push(UiInput::MouseButton(b, down)));
return;
}
if let Some(prev) = nonnull(&PREV_MOUSE) {
let prev: MouseCb = std::mem::transmute(prev);
prev(w, button, action, mods);
}
}
unsafe extern "C" fn cursor_cb(w: *mut c_void, x: f64, y: f64) {
if menu_open() {
let (x, y) = (x as f32, y as f32);
state::with(|s| {
s.pointer = (x, y);
s.events.push(UiInput::MouseMove(x, y));
});
return;
}
if let Some(prev) = nonnull(&PREV_CURSOR) {
let prev: CursorCb = std::mem::transmute(prev);
prev(w, x, y);
}
}
unsafe extern "C" fn scroll_cb(w: *mut c_void, dx: f64, dy: f64) {
if menu_open() {
state::with(|s| s.events.push(UiInput::Wheel(dy as f32)));
return;
}
if let Some(prev) = nonnull(&PREV_SCROLL) {
let prev: ScrollCb = std::mem::transmute(prev);
prev(w, dx, dy);
}
}
unsafe extern "C" fn char_cb(w: *mut c_void, codepoint: u32) {
if menu_open() {
if let Some(c) = char::from_u32(codepoint) {
if !c.is_control() {
state::with(|s| s.events.push(UiInput::Char(c)));
}
}
return;
}
if let Some(prev) = nonnull(&PREV_CHAR) {
let prev: CharCb = std::mem::transmute(prev);
prev(w, codepoint);
}
}
fn nonnull(slot: &AtomicPtr<c_void>) -> Option<*mut c_void> {
let p = slot.load(Ordering::SeqCst);
if p.is_null() {
None
} else {
Some(p)
}
}
/// GLFW key code to Win32 virtual-key, so binds mean the same thing on both
/// platforms. Letters, digits and space already share their numbering; the rest
/// are named constants on one side and a contiguous block on the other.
fn glfw_key_to_vk(key: i32) -> u32 {
match key {
// A-Z and 0-9 are ASCII in GLFW and the same values as the VK codes.
32 => 0x20, // space
48..=57 => key as u32, // 0-9
65..=90 => key as u32, // A-Z
256 => 0x1B, // escape
257 => 0x0D, // enter
258 => 0x09, // tab
259 => 0x08, // backspace
260 => 0x2D, // insert
261 => 0x2E, // delete
262 => 0x27, // right
263 => 0x25, // left
264 => 0x28, // down
265 => 0x26, // up
266 => 0x21, // page up
267 => 0x22, // page down
268 => 0x24, // home
269 => 0x23, // end
280 => 0x14, // caps lock
290..=301 => 0x70 + (key - 290) as u32, // F1-F12
320..=329 => 0x60 + (key - 320) as u32, // numpad 0-9
340 | 344 => 0x10, // shift
341 | 345 => 0x11, // control
342 | 346 => 0x12, // alt
// Anything else keeps its GLFW number, offset well clear of the VK
// range so it can still be bound without colliding with a real key.
other => 0x1000 + other as u32,
}
}