summaryrefslogtreecommitdiffstats
path: root/src
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-08-30 03:32:59 +0200
committersalmonik <salmonik@tuta.io>2026-08-30 03:32:59 +0200
commitaa096fe6886681d3338573cd3af779b57a5b56ab (patch)
tree14aee66fa1c3c168c140f02006aec246f9bbc23c /src
downloadhl2h-aa096fe6886681d3338573cd3af779b57a5b56ab.tar.gz
hl2h-aa096fe6886681d3338573cd3af779b57a5b56ab.zip
init
Diffstat (limited to 'src')
-rw-r--r--src/cinput.cpp142
-rw-r--r--src/hl2h.cpp307
-rw-r--r--src/hl2h.h133
-rw-r--r--src/menu.cpp555
-rw-r--r--src/modules.cpp69
-rw-r--r--src/netvars.cpp185
6 files changed, 1391 insertions, 0 deletions
diff --git a/src/cinput.cpp b/src/cinput.cpp
new file mode 100644
index 0000000..7c40b3b
--- /dev/null
+++ b/src/cinput.cpp
@@ -0,0 +1,142 @@
1// cinput.cpp — CInput discovery, usercmd CRC, get/fix helpers
2#include "hl2h.h"
3
4// ---------------------------------------------------------------------------
5// CRC-32 (standard IEEE reflected, matches Valve's implementation)
6// ---------------------------------------------------------------------------
7
8static uint32_t g_crc_table[256];
9static bool g_crc_inited = false;
10
11static void crc32_init_table() {
12 if (g_crc_inited)
13 return;
14 for (uint32_t i = 0; i < 256; i++) {
15 uint32_t c = i;
16 for (int k = 0; k < 8; k++)
17 c = (c & 1) ? 0xEDB88320u ^ (c >> 1) : c >> 1;
18 g_crc_table[i] = c;
19 }
20 g_crc_inited = true;
21}
22
23// field set verified from client.so GetChecksum disassembly
24uint32_t usercmd_checksum(const CUserCmd *c) {
25 crc32_init_table();
26 uint32_t crc = 0xFFFFFFFFu;
27 auto upd = [&crc](const void *p, size_t n) {
28 const uint8_t *b = (const uint8_t *)p;
29 while (n--)
30 crc = g_crc_table[(crc ^ *b++) & 0xFF] ^ (crc >> 8);
31 };
32 upd(&c->command_number, 4);
33 upd(&c->tick_count, 4);
34 upd(&c->viewangles, 12);
35 upd(&c->forwardmove, 4);
36 upd(&c->sidemove, 4);
37 upd(&c->upmove, 4);
38 upd(&c->buttons, 4);
39 upd(&c->impulse, 1);
40 upd(&c->weaponselect, 4);
41 upd(&c->weaponsubtype, 4);
42 upd(&c->random_seed, 4);
43 upd(&c->mousedx, 2);
44 upd(&c->mousedy, 2);
45 return ~crc;
46}
47
48// CInput: +0xE4 = m_pCommands, +0xE8 = m_pVerifiedCommands
49static constexpr size_t CMDOFF_M_PCOMMANDS = 0xE4;
50static constexpr size_t CMDOFF_M_PVERIFIED = 0xE8;
51static constexpr size_t VERIFIED_STRIDE = 0x58;
52static constexpr size_t VERIFIED_CRC_OFF = 0x54;
53
54CUserCmd *get_usercmd(int seq) {
55 if (!g_input)
56 return nullptr;
57 uintptr_t cmds = *(uintptr_t *)(g_input + CMDOFF_M_PCOMMANDS);
58 if (!cmds)
59 return nullptr;
60 auto *cmd = (CUserCmd *)(cmds + (size_t)(seq % 90) * 0x54);
61 if (cmd->command_number != seq)
62 return nullptr;
63 return cmd;
64}
65
66void fix_verified_crc(int seq, uint32_t crc) {
67 if (!g_input)
68 return;
69 uintptr_t verified = *(uintptr_t *)(g_input + CMDOFF_M_PVERIFIED);
70 if (verified)
71 *(uint32_t *)(verified + (size_t)(seq % 90) * VERIFIED_STRIDE +
72 VERIFIED_CRC_OFF) = crc;
73}
74
75// ---------------------------------------------------------------------------
76// CInput discovery: byte-scan of CHLClient::CreateMove prologue
77// ---------------------------------------------------------------------------
78
79bool find_input_global() {
80 void **vt = VTABLE(g_iface.client);
81 uintptr_t cm = (uintptr_t)vt[21]; // CreateMove slot
82 if (!g_client.contains(cm)) {
83 logf("CreateMove %p outside client module", (void *)cm);
84 return false;
85 }
86 const uint8_t *b = (const uint8_t *)cm;
87
88 // 1) PIC anchor: call __x86.get_pc_thunk + add $imm,%esi
89 uintptr_t anchor = 0;
90 for (int i = 0; i <= 0x100 - 11; i++) {
91 if (b[i] == 0xE8 && b[i + 5] == 0x81 && b[i + 6] == 0xC6) {
92 uint32_t imm;
93 memcpy(&imm, b + i + 7, 4);
94 anchor = cm + (uintptr_t)(i + 5) + imm;
95 break;
96 }
97 }
98 if (!anchor || !g_client.contains(anchor)) {
99 logf("PIC anchor not found in CreateMove");
100 return false;
101 }
102
103 // 2) the input->CreateMove call: "ff 51 0c" preceded by "52", scan back
104 // for "8d 86 imm32" with "8b 10" between
105 for (int c = 3; c <= 0x100 - 3; c++) {
106 if (!(b[c] == 0xFF && b[c + 1] == 0x51 && b[c + 2] == 0x0C &&
107 b[c - 1] == 0x52))
108 continue;
109 for (int i = c - 8; i > c - 44 && i > 2; i--) {
110 if (b[i] != 0x8D || b[i + 1] != 0x86)
111 continue;
112 bool has_mov = false;
113 for (int j = i + 6; j < c - 1; j++)
114 if (b[j] == 0x8B && b[j + 1] == 0x10) {
115 has_mov = true;
116 break;
117 }
118 if (!has_mov)
119 continue;
120 uint32_t disp;
121 memcpy(&disp, b + i + 2, 4);
122 uintptr_t var = anchor + disp;
123 if (!g_client.contains(var))
124 continue;
125 uintptr_t input = *(uintptr_t *)var;
126 if (!input || !g_client.contains(input))
127 continue;
128 uintptr_t vtbl = *(uintptr_t *)input;
129 if (!vtbl || !g_client.contains(vtbl))
130 continue;
131 uintptr_t f3 = *(uintptr_t *)(vtbl + 3 * sizeof(void *));
132 if (!g_client.contains(f3))
133 continue;
134 logf("CInput* = %#lx (ptr var %#lx, vfunc[3] %#lx)", (unsigned long)input,
135 (unsigned long)var, (unsigned long)f3);
136 g_input = input;
137 return true;
138 }
139 }
140 logf("CInput pointer variable not found in CreateMove scan");
141 return false;
142}
diff --git a/src/hl2h.cpp b/src/hl2h.cpp
new file mode 100644
index 0000000..0261ff2
--- /dev/null
+++ b/src/hl2h.cpp
@@ -0,0 +1,307 @@
1// hl2h.cpp — core: logging, vector math, globals, feature logic, setup
2#include "hl2h.h"
3#include <cmath>
4#include <cstdarg>
5
6// ---------------------------------------------------------------------------
7// logging
8// ---------------------------------------------------------------------------
9
10void logf(const char *fmt, ...) {
11 va_list ap, ap2;
12 va_start(ap, fmt);
13 va_copy(ap2, ap);
14 fprintf(stderr, "hl2hake: ");
15 vfprintf(stderr, fmt, ap2);
16 fprintf(stderr, "\n");
17 fflush(stderr);
18 va_end(ap2);
19 if (FILE *f = fopen("/tmp/hl2hake.log", "a")) {
20 fprintf(f, "hl2hake: ");
21 vfprintf(f, fmt, ap);
22 fprintf(f, "\n");
23 fclose(f);
24 }
25 va_end(ap);
26}
27
28// ---------------------------------------------------------------------------
29// globals
30// ---------------------------------------------------------------------------
31
32Module g_client, g_engine;
33GameInterfaces g_iface;
34NetVars g_net;
35uintptr_t g_input = 0;
36
37// ---------------------------------------------------------------------------
38// vector math
39// ---------------------------------------------------------------------------
40
41static constexpr float PI_F = 3.14159265358979323846f;
42
43QAngle vector_angles(const Vector &forward) {
44 QAngle a{};
45 a.z = 0;
46 if (forward.y == 0 && forward.x == 0) {
47 a.y = 0;
48 a.x = (forward.z > 0) ? -90.f : 90.f;
49 } else {
50 a.y = atan2f(forward.y, forward.x) * 180.f / PI_F;
51 if (a.y < 0)
52 a.y += 360.f;
53 a.x = atan2f(-forward.z,
54 sqrtf(forward.x * forward.x + forward.y * forward.y)) *
55 180.f / PI_F;
56 if (a.x < 0)
57 a.x += 360.f;
58 }
59 return a;
60}
61
62Vector angle_forward_vector(const QAngle &angles) {
63 auto DEG2RAD = [](float x) { return x * (PI_F / 180.f); };
64 float sp = sinf(DEG2RAD(angles.x)), cp = cosf(DEG2RAD(angles.x));
65 float sy = sinf(DEG2RAD(angles.y)), cy = cosf(DEG2RAD(angles.y));
66 return {cp * cy, cp * sy, -sp};
67}
68
69float getfov(const QAngle &my_view, const QAngle &aim_ang) {
70 Vector aim = angle_forward_vector(my_view);
71 Vector ang = angle_forward_vector(aim_ang);
72 float len = sqrtf(aim.x * aim.x + aim.y * aim.y + aim.z * aim.z);
73 if (len <= 0)
74 return 0;
75 float dot = (aim.x * ang.x + aim.y * ang.y + aim.z * ang.z) / len;
76 if (dot > 1)
77 dot = 1;
78 if (dot < -1)
79 dot = -1;
80 return acosf(dot) * (180.f / PI_F);
81}
82
83// ---------------------------------------------------------------------------
84// feature logic (bhop, ABH, climb)
85// ---------------------------------------------------------------------------
86
87static int g_modified_logged = 0;
88
89void note_modified(const CUserCmd *cmd, int flags, const float *vel) {
90 // recompute usercmd CRC after modification
91 fix_verified_crc(cmd->command_number, usercmd_checksum(cmd));
92
93 if (g_modified_logged < 5) {
94 logf("cmd modified: seq=%d flags=0x%X buttons=0x%X fw=%.2f "
95 "vel=(%.1f,%.1f,%.1f)",
96 cmd->command_number, flags, cmd->buttons, cmd->forwardmove, vel[0],
97 vel[1], vel[2]);
98 g_modified_logged++;
99 }
100}
101
102// menu state (defined in menu.cpp, referenced here)
103
104static bool g_logged_local = false;
105
106static int engine_get_local_player() {
107 using Fn = int (*)(void *);
108 return vfunc<Fn>(g_iface.engine, 12)(g_iface.engine);
109}
110
111static void *entitylist_get(int idx) {
112 using Fn = void *(*)(void *, int);
113 return vfunc<Fn>(g_iface.entitylist, 3)(g_iface.entitylist, idx);
114}
115
116
117
118
119static void do_move(int seq) {
120 if (!g_input || !g_net.ok())
121 return;
122
123 int localIdx = engine_get_local_player();
124 if (localIdx <= 0 || localIdx > 64)
125 return;
126 if (!g_logged_local) {
127 logf("local player index %d (in-game)", localIdx);
128 g_logged_local = true;
129 }
130
131 void *ent = entitylist_get(localIdx);
132 if (!ent)
133 return;
134
135 CUserCmd *cmd = get_usercmd(seq);
136 if (!cmd)
137 return;
138
139 uintptr_t e = (uintptr_t)ent;
140 uint8_t movetype = *(uint8_t *)(e + g_net.movetype);
141 int flags = *(int *)(e + g_net.flags);
142 bool onground = (flags & FL_ONGROUND) != 0;
143 bool inwater = (flags & FL_INWATER) != 0;
144
145 if (movetype == MOVETYPE_NOCLIP || movetype == MOVETYPE_LADDER)
146 return;
147
148 static bool lastground = false;
149
150 if (middle_mouse_down()) {
151 if (cmd->command_number % 2) {
152 cmd->buttons &= ~IN_JUMP;
153 cmd->buttons |= IN_USE;
154 } else {
155 cmd->buttons &= ~IN_USE;
156 cmd->buttons |= IN_JUMP;
157 }
158 note_modified(cmd, flags, (float *)(e + g_net.velocity));
159 } else if (cmd->buttons & IN_JUMP) {
160 if (inwater && (cmd->buttons & IN_DUCK)) {
161 lastground = onground;
162 return;
163 }
164 const float *vel = (const float *)(e + g_net.velocity);
165 float speed = sqrtf(vel[0] * vel[0] + vel[1] * vel[1] + vel[2] * vel[2]);
166 bool moving = speed > 0.1f;
167 bool backmove = false;
168 if (moving) {
169 QAngle moveang = vector_angles(Vector{vel[0], vel[1], vel[2]});
170 moveang.x = 0.f;
171 QAngle viewang{cmd->viewangles[0], cmd->viewangles[1],
172 cmd->viewangles[2]};
173 viewang.x = 0.f;
174 backmove = getfov(viewang, moveang) > 90.f;
175 }
176 if (!onground) {
177 lastground = false;
178 cmd->buttons &= ~IN_JUMP;
179 note_modified(cmd, flags, vel);
180 } else if (!lastground) {
181 lastground = true;
182 if (!backmove)
183 cmd->forwardmove = -0.1f;
184 note_modified(cmd, flags, vel);
185 }
186 }
187}
188
189// ---------------------------------------------------------------------------
190// CreateMove hook (cdecl — game virtuals use cdecl, not __thiscall)
191// ---------------------------------------------------------------------------
192
193using CreateMoveFn = bool (*)(void *, int, float, bool);
194static CreateMoveFn oCreateMove = nullptr;
195
196static bool hkdCreateMove(void *self, int seq, float frametime, bool active) {
197 bool ret = oCreateMove(self, seq, frametime, active);
198 do_move(seq);
199 return ret;
200}
201
202static void install_hook() {
203 void **vt = VTABLE(g_iface.client);
204 constexpr size_t SLOTS = 256;
205 void **newvt = new void *[SLOTS];
206 memcpy(newvt, vt, SLOTS * sizeof(void *));
207 oCreateMove = (CreateMoveFn)vt[21];
208 newvt[21] = (void *)&hkdCreateMove;
209 VTABLE(g_iface.client) = newvt;
210 logf("hook installed: CreateMove %p -> %p (orig %p)", vt[21],
211 (void *)&hkdCreateMove, (void *)oCreateMove);
212}
213
214// ---------------------------------------------------------------------------
215// setup thread
216// ---------------------------------------------------------------------------
217
218extern void find_modules();
219extern void resolve_netvars();
220extern bool find_input_global();
221extern void init_x11();
222extern void init_conmsg();
223extern void install_menu();
224
225static bool is_hl2_process() {
226 char buf[512];
227 ssize_t n = readlink("/proc/self/exe", buf, sizeof(buf) - 1);
228 if (n <= 0)
229 return false;
230 buf[n] = 0;
231 for (ssize_t i = n - 1; i >= 0; i--)
232 if (buf[i] == '/')
233 return strcmp(buf + i + 1, "hl2_linux") == 0;
234 return false;
235}
236
237static void *setup_thread(void *) {
238 for (int i = 0; i < 6000; i++) {
239 find_modules();
240 if (g_client.valid() && g_engine.valid())
241 break;
242 usleep(200 * 1000);
243 }
244 if (!g_client.valid() || !g_engine.valid()) {
245 logf("client.so/engine.so never appeared");
246 return nullptr;
247 }
248 logf("client.so: %d mapped range(s), %s", (int)g_client.ranges.size(),
249 g_client.path.c_str());
250 logf("engine.so: %d mapped range(s), %s", (int)g_engine.ranges.size(),
251 g_engine.path.c_str());
252
253 // resolve factories via dlopen handles
254 void *ch = dlopen(g_client.path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
255 void *eh = dlopen(g_engine.path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
256 auto cf = (CreateInterfaceFn)dlsym(ch, "CreateInterface");
257 auto ef = (CreateInterfaceFn)dlsym(eh, "CreateInterface");
258 if (!cf || !ef) {
259 logf("CreateInterface not found");
260 return nullptr;
261 }
262
263 for (int i = 0; i < 300; i++) {
264 if (!g_iface.client)
265 g_iface.client = cf("VClient017", nullptr);
266 if (!g_iface.entitylist)
267 g_iface.entitylist = cf("VClientEntityList003", nullptr);
268 if (!g_iface.engine)
269 g_iface.engine = ef("VEngineClient014", nullptr);
270 if (g_iface.client && g_iface.entitylist && g_iface.engine)
271 break;
272 usleep(100 * 1000);
273 }
274 if (!g_iface.client || !g_iface.entitylist || !g_iface.engine) {
275 logf("interfaces unresolved: VClient017=%p VClientEntityList003=%p "
276 "VEngineClient014=%p",
277 g_iface.client, g_iface.entitylist, g_iface.engine);
278 return nullptr;
279 }
280 logf("interfaces: VClient017=%p VClientEntityList003=%p "
281 "VEngineClient014=%p",
282 g_iface.client, g_iface.entitylist, g_iface.engine);
283
284 resolve_netvars();
285 if (!find_input_global())
286 return nullptr;
287
288 init_conmsg();
289 install_hook();
290 install_menu();
291 return nullptr;
292}
293
294// ---------------------------------------------------------------------------
295// constructor
296// ---------------------------------------------------------------------------
297
298__attribute__((constructor)) static void hl2hake_load() {
299 if (!is_hl2_process())
300 return;
301 logf("loaded (pid %d)", getpid());
302 pthread_t t;
303 if (pthread_create(&t, nullptr, setup_thread, nullptr) == 0)
304 pthread_detach(t);
305 else
306 logf("pthread_create failed");
307}
diff --git a/src/hl2h.h b/src/hl2h.h
new file mode 100644
index 0000000..6d3ebe7
--- /dev/null
+++ b/src/hl2h.h
@@ -0,0 +1,133 @@
1#ifndef HL2H_H
2#define HL2H_H
3
4#include <cstdint>
5#include <cstdio>
6#include <cstring>
7#include <string>
8#include <vector>
9
10#include <dlfcn.h>
11#include <pthread.h>
12#include <unistd.h>
13
14// ---------------------------------------------------------------------------
15// logging
16// ---------------------------------------------------------------------------
17
18void logf(const char *fmt, ...);
19
20// ---------------------------------------------------------------------------
21// module discovery
22// ---------------------------------------------------------------------------
23
24struct Module {
25 struct Range {
26 uintptr_t lo, hi;
27 bool file;
28 };
29 std::vector<Range> ranges;
30 std::string path;
31 bool valid() const { return !ranges.empty(); }
32 bool contains(uintptr_t p) const {
33 for (auto &r : ranges)
34 if (p >= r.lo && p < r.hi)
35 return true;
36 return false;
37 }
38};
39extern Module g_client, g_engine;
40
41// ---------------------------------------------------------------------------
42// game interfaces
43// ---------------------------------------------------------------------------
44
45using CreateInterfaceFn = void *(*)(const char *, int *);
46
47struct GameInterfaces {
48 void *client = nullptr;
49 void *entitylist = nullptr;
50 void *engine = nullptr;
51};
52extern GameInterfaces g_iface;
53
54#define VTABLE(obj) (*(void ***)(obj))
55template <typename Fn> inline Fn vfunc(void *obj, size_t idx) {
56 return reinterpret_cast<Fn>(VTABLE(obj)[idx]);
57}
58
59// ---------------------------------------------------------------------------
60// netvars
61// ---------------------------------------------------------------------------
62
63struct NetVars {
64 int flags = -1;
65 int velocity = -1;
66 int movetype = -1;
67 bool ok() const { return flags >= 0 && velocity >= 0 && movetype >= 0; }
68};
69extern NetVars g_net;
70
71// ---------------------------------------------------------------------------
72// CInput
73// ---------------------------------------------------------------------------
74
75extern uintptr_t g_input;
76
77// ---------------------------------------------------------------------------
78// CUserCmd
79// ---------------------------------------------------------------------------
80
81struct CUserCmd {
82 void *vtable;
83 int command_number;
84 int tick_count;
85 float viewangles[3];
86 float forwardmove;
87 float sidemove;
88 float upmove;
89 int buttons;
90 uint8_t impulse;
91 int weaponselect;
92 int weaponsubtype;
93 int random_seed;
94 short mousedx;
95 short mousedy;
96 bool hasbeenpredicted;
97};
98
99// ---------------------------------------------------------------------------
100// engine constants
101// ---------------------------------------------------------------------------
102
103enum { IN_ATTACK = 1 << 0, IN_JUMP = 1 << 1, IN_DUCK = 1 << 2, IN_USE = 1 << 5 };
104enum { FL_ONGROUND = 1 << 0, FL_INWATER = 1 << 9 };
105enum { MOVETYPE_NOCLIP = 8, MOVETYPE_LADDER = 9 };
106
107// ---------------------------------------------------------------------------
108// vector math
109// ---------------------------------------------------------------------------
110
111struct Vector { float x, y, z; };
112struct QAngle { float x, y, z; };
113QAngle vector_angles(const Vector &forward);
114Vector angle_forward_vector(const QAngle &angles);
115float getfov(const QAngle &my_view, const QAngle &aim_ang);
116
117// ---------------------------------------------------------------------------
118// cross-file declarations
119// ---------------------------------------------------------------------------
120
121void note_modified(const CUserCmd *cmd, int flags, const float *vel);
122CUserCmd *get_usercmd(int seq);
123void fix_verified_crc(int seq, uint32_t crc);
124uint32_t usercmd_checksum(const CUserCmd *c);
125bool find_input_global();
126void resolve_netvars();
127void find_modules();
128void install_menu();
129void init_conmsg();
130void init_x11();
131bool middle_mouse_down();
132
133#endif
diff --git a/src/menu.cpp b/src/menu.cpp
new file mode 100644
index 0000000..91a0948
--- /dev/null
+++ b/src/menu.cpp
@@ -0,0 +1,555 @@
1// menu.cpp — menu state, rendering, input, config, paint hook, X11 mouse
2#include "hl2h.h"
3#include <cstdarg>
4#include <cstdio>
5#include <cstring>
6
7// ---------------------------------------------------------------------------
8// ConMsg (tier0 console printer — prints to in-game console)
9// ---------------------------------------------------------------------------
10
11void (*g_conmsg)(const char *fmt, ...) = nullptr;
12
13void init_conmsg() {
14 g_conmsg = (decltype(g_conmsg))dlsym(RTLD_DEFAULT, "_Z6ConMsgPKcz");
15 if (g_conmsg)
16 return;
17 // fallback: dlopen libtier0.so from the engine directory
18 size_t slash = g_engine.path.find_last_of('/');
19 if (slash == std::string::npos)
20 return;
21 std::string tier0 = g_engine.path.substr(0, slash + 1) + "libtier0.so";
22 void *h = dlopen(tier0.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
23 if (!h)
24 h = dlopen(tier0.c_str(), RTLD_LAZY | RTLD_GLOBAL);
25 if (h)
26 g_conmsg = (decltype(g_conmsg))dlsym(h, "_Z6ConMsgPKcz");
27}
28
29// ---------------------------------------------------------------------------
30// X11 middle mouse (for climb feature)
31// ---------------------------------------------------------------------------
32
33static struct {
34 void *handle = nullptr;
35 void *dpy = nullptr;
36 int (*query)(void *, unsigned long, unsigned long *, unsigned long *, int *,
37 int *, int *, int *, unsigned int *) = nullptr;
38 int (*default_screen)(void *) = nullptr;
39 unsigned long (*root_window)(void *, int) = nullptr;
40 bool ok = false;
41} g_x11;
42
43static constexpr unsigned int BUTTON2_MASK = 1u << 9; // X11 Button2Mask
44
45void init_x11() {
46 g_x11.handle = dlopen("libX11.so.6", RTLD_LAZY | RTLD_GLOBAL);
47 if (!g_x11.handle)
48 return;
49 g_x11.query = (decltype(g_x11.query))dlsym(g_x11.handle, "XQueryPointer");
50 g_x11.default_screen =
51 (decltype(g_x11.default_screen))dlsym(g_x11.handle, "XDefaultScreen");
52 g_x11.root_window =
53 (decltype(g_x11.root_window))dlsym(g_x11.handle, "XRootWindow");
54 auto open_display =
55 (void *(*)(const char *))dlsym(g_x11.handle, "XOpenDisplay");
56 if (!g_x11.query || !g_x11.default_screen || !g_x11.root_window ||
57 !open_display)
58 return;
59 g_x11.dpy = open_display(nullptr);
60 if (g_x11.dpy)
61 g_x11.ok = true;
62}
63
64bool middle_mouse_down() {
65 if (!g_x11.ok)
66 return false;
67 unsigned long root =
68 g_x11.root_window(g_x11.dpy, g_x11.default_screen(g_x11.dpy));
69 unsigned long rret, cret;
70 int rx, ry, wx, wy;
71 unsigned int mask = 0;
72 if (!g_x11.query(g_x11.dpy, root, &rret, &cret, &rx, &ry, &wx, &wy, &mask))
73 return false;
74 return (mask & BUTTON2_MASK) != 0;
75}
76
77// ---------------------------------------------------------------------------
78// menu tree structure
79// ---------------------------------------------------------------------------
80
81enum NodeType { NODE_ROOT, NODE_FEATURE, NODE_TOGGLE, NODE_BIND };
82
83struct MenuNode {
84 NodeType type;
85 const char *label;
86 bool expanded; // for root/feature: whether children are visible
87 int child_count;
88 MenuNode *children[4]; // max children per node
89
90 // leaf data
91 bool *active_ptr; // NODE_TOGGLE: pointer to the active flag
92 int *bind_ptr; // NODE_BIND: pointer to the key binding (SDL scancode)
93 const char *bind_name; // NODE_BIND: current bind display name
94};
95
96// feature data
97struct FeatureState {
98 bool active;
99 int bind; // SDL scancode, 0 = none
100};
101
102static FeatureState g_climb{true, 0};
103static FeatureState g_bhop{true, 0};
104static FeatureState g_abh{true, 0};
105
106// tree
107static MenuNode g_root{NODE_ROOT, "HL2hake", true, 0, {}};
108static MenuNode g_climb_feat{NODE_FEATURE, "Climb", true, 2, {}};
109static MenuNode g_climb_active{NODE_TOGGLE, "Active", false, 0, {}, &g_climb.active};
110static MenuNode g_climb_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_climb.bind, "None"};
111static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", true, 1, {}};
112static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active};
113static MenuNode g_abh_feat{NODE_FEATURE, "ABH", true, 1, {}};
114static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active};
115
116static MenuNode *g_visible[16]; // flattened visible nodes for arrow navigation
117static int g_visible_count = 0;
118static int g_selected = 0;
119bool g_menu_open = false;
120
121// bind-select state
122static bool g_bind_mode = false; // true = waiting for key press
123
124static void build_tree() {
125 g_climb_feat.children[0] = &g_climb_active;
126 g_climb_feat.children[1] = &g_climb_bind;
127 g_bhop_feat.children[0] = &g_bhop_active;
128 g_abh_feat.children[0] = &g_abh_active;
129 g_root.children[0] = &g_climb_feat;
130 g_root.children[1] = &g_bhop_feat;
131 g_root.children[2] = &g_abh_feat;
132 g_root.child_count = 3;
133}
134
135static void flatten(MenuNode *node, int depth) {
136 if (g_visible_count >= 16)
137 return;
138 node->type = (depth == 0) ? NODE_ROOT
139 : (node->child_count > 0) ? NODE_FEATURE
140 : (node->active_ptr) ? NODE_TOGGLE
141 : NODE_BIND;
142 g_visible[g_visible_count++] = node;
143 if (node->expanded) {
144 for (int i = 0; i < node->child_count; i++)
145 flatten(node->children[i], depth + 1);
146 }
147}
148
149static void rebuild_visible() {
150 g_visible_count = 0;
151 flatten(&g_root, 0);
152 if (g_selected >= g_visible_count)
153 g_selected = g_visible_count - 1;
154 if (g_selected < 0)
155 g_selected = 0;
156}
157
158// ---------------------------------------------------------------------------
159// config persistence (INI format)
160// ---------------------------------------------------------------------------
161
162static std::string config_path() {
163 size_t slash = g_engine.path.find_last_of('/');
164 if (slash == std::string::npos)
165 return "hl2hake.cfg";
166 return g_engine.path.substr(0, slash + 1) + "hl2hake.cfg";
167}
168
169[[maybe_unused]] static const char *sdl_scancode_name(int sc) {
170 if (sc == 0)
171 return "None";
172 static char buf[32];
173 snprintf(buf, sizeof(buf), "Key%d", sc);
174 return buf;
175}
176
177[[maybe_unused]] static int parse_sdl_scancode(const char *s) {
178 if (strcmp(s, "None") == 0 || strcmp(s, "") == 0)
179 return 0;
180 if (strncmp(s, "Key", 3) == 0)
181 return atoi(s + 3);
182 return 0;
183}
184
185static void config_save() {
186 FILE *f = fopen(config_path().c_str(), "w");
187 if (!f)
188 return;
189 fprintf(f, "[climb]\nactive=%d\nbind=%d\n", g_climb.active, g_climb.bind);
190 fprintf(f, "[bhop]\nactive=%d\nbind=%d\n", g_bhop.active, g_bhop.bind);
191 fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind);
192 fclose(f);
193}
194
195static void config_load() {
196 FILE *f = fopen(config_path().c_str(), "r");
197 if (!f)
198 return;
199 char line[256];
200 FeatureState *cur = nullptr;
201 while (fgets(line, sizeof(line), f)) {
202 line[strcspn(line, "\r\n")] = 0;
203 if (line[0] == '[') {
204 if (strstr(line, "[climb]"))
205 cur = &g_climb;
206 else if (strstr(line, "[bhop]"))
207 cur = &g_bhop;
208 else if (strstr(line, "[abh]"))
209 cur = &g_abh;
210 else
211 cur = nullptr;
212 } else if (cur) {
213 char *eq = strchr(line, '=');
214 if (!eq)
215 continue;
216 *eq = 0;
217 const char *key = line, *val = eq + 1;
218 if (strcmp(key, "active") == 0)
219 cur->active = atoi(val) != 0;
220 else if (strcmp(key, "bind") == 0)
221 cur->bind = atoi(val);
222 }
223 }
224 fclose(f);
225}
226
227// ---------------------------------------------------------------------------
228// menu input handling
229// ---------------------------------------------------------------------------
230
231// SDL scancodes for navigation keys (matches the game's scancode set)
232static constexpr int SCAN_INSERT = 73;
233static constexpr int SCAN_UP = 82; // these are SDL scancodes
234static constexpr int SCAN_DOWN = 81;
235static constexpr int SCAN_LEFT = 80;
236static constexpr int SCAN_RIGHT = 79;
237static constexpr int SCAN_ENTER = 40;
238static constexpr int SCAN_ESCAPE = 41;
239
240// simplified scancode-to-name for common keys
241static const char *scancode_name(int sc) {
242 switch (sc) {
243 case 0: return "None";
244 case 4: return "A"; case 5: return "B"; case 6: return "C";
245 case 7: return "D"; case 8: return "E"; case 9: return "F";
246 case 10: return "G"; case 11: return "H"; case 12: return "I";
247 case 13: return "J"; case 14: return "K"; case 15: return "L";
248 case 16: return "M"; case 17: return "N"; case 18: return "O";
249 case 19: return "P"; case 20: return "Q"; case 21: return "R";
250 case 22: return "S"; case 23: return "T"; case 24: return "U";
251 case 25: return "V"; case 26: return "W"; case 27: return "X";
252 case 28: return "Y"; case 29: return "Z";
253 case 30: return "1"; case 31: return "2"; case 32: return "3";
254 case 33: return "4"; case 34: return "5"; case 35: return "6";
255 case 36: return "7"; case 37: return "8"; case 38: return "9";
256 case 39: return "0";
257 case 40: return "Enter"; case 41: return "Escape"; case 42: return "Backspace";
258 case 43: return "Tab"; case 44: return "Space";
259 case 79: return "Right"; case 80: return "Left";
260 case 81: return "Down"; case 82: return "Up";
261 case 87: return "F1"; case 88: return "F2"; case 89: return "F3";
262 case 90: return "F4"; case 91: return "F5"; case 92: return "F6";
263 case 93: return "F7"; case 94: return "F8";
264 default: return scancode_name(0); // "None" for unmapped
265 }
266}
267
268// get a snapshot of key states: returns array of currently-held scancodes
269static bool g_prev_key_state[512] = {};
270static bool g_key_down[512] = {}; // true for ONE frame on press
271
272// read SDL_GetKeyboardState — the game's own SDL is in-process
273using SDL_GetKeyboardState_fn = const uint8_t *(*)(int *);
274static SDL_GetKeyboardState_fn SDL_GetKeyboardState_real = nullptr;
275
276static void poll_keyboard() {
277 if (!SDL_GetKeyboardState_real)
278 SDL_GetKeyboardState_real =
279 (SDL_GetKeyboardState_fn)dlsym(RTLD_DEFAULT, "SDL_GetKeyboardState");
280 if (!SDL_GetKeyboardState_real)
281 return;
282 int numkeys = 0;
283 const uint8_t *state = SDL_GetKeyboardState_real(&numkeys);
284 if (!state)
285 return;
286 for (int i = 0; i < numkeys && i < 512; i++) {
287 g_key_down[i] = state[i] && !g_prev_key_state[i];
288 g_prev_key_state[i] = state[i];
289 }
290}
291
292static bool key_pressed(int sc) {
293 return sc >= 0 && sc < 512 && g_key_down[sc];
294}
295
296[[maybe_unused]] static bool key_held(int sc) {
297 return sc >= 0 && sc < 512 && g_prev_key_state[sc];
298}
299
300static void menu_handle_input() {
301 poll_keyboard();
302
303 if (!g_menu_open) {
304 if (key_pressed(SCAN_INSERT)) {
305 g_menu_open = true;
306 g_bind_mode = false;
307 rebuild_visible();
308 }
309 return;
310 }
311
312 // menu is open
313 if (g_bind_mode) {
314 // wait for any key press (except Insert, Up, Down, Escape, Enter)
315 for (int i = 0; i < 512; i++) {
316 if (!g_key_down[i])
317 continue;
318 if (i == SCAN_INSERT || i == SCAN_UP || i == SCAN_DOWN ||
319 i == SCAN_LEFT || i == SCAN_RIGHT) {
320 g_bind_mode = false;
321 return; // cancel bind select
322 }
323 if (i == SCAN_ESCAPE) {
324 // set bind to None
325 MenuNode *n = g_visible[g_selected];
326 if (n->bind_ptr)
327 *n->bind_ptr = 0;
328 g_bind_mode = false;
329 config_save();
330 rebuild_visible();
331 return;
332 }
333 if (i == SCAN_ENTER) {
334 g_bind_mode = false;
335 return; // cancel, keep old bind
336 }
337 // any other key = new bind
338 MenuNode *n = g_visible[g_selected];
339 if (n->bind_ptr) {
340 // clear same bind from other features
341 FeatureState *features[] = {&g_climb, &g_bhop, &g_abh};
342 for (auto *f : features)
343 if (&f->bind != n->bind_ptr && f->bind == i)
344 f->bind = 0;
345 *n->bind_ptr = i;
346 }
347 g_bind_mode = false;
348 config_save();
349 rebuild_visible();
350 return;
351 }
352 return; // no key pressed yet
353 }
354
355 // normal navigation
356 if (key_pressed(SCAN_INSERT) || key_pressed(SCAN_ESCAPE)) {
357 g_menu_open = false;
358 return;
359 }
360 if (key_pressed(SCAN_UP) && g_selected > 0) {
361 g_selected--;
362 return;
363 }
364 if (key_pressed(SCAN_DOWN) && g_selected < g_visible_count - 1) {
365 g_selected++;
366 return;
367 }
368 if (key_pressed(SCAN_ENTER)) {
369 MenuNode *n = g_visible[g_selected];
370 if (n->child_count > 0) {
371 n->expanded = !n->expanded;
372 rebuild_visible();
373 } else if (n->type == NODE_TOGGLE && n->active_ptr) {
374 *n->active_ptr = !(*n->active_ptr);
375 config_save();
376 } else if (n->type == NODE_BIND) {
377 g_bind_mode = true;
378 }
379 }
380}
381
382// ---------------------------------------------------------------------------
383// menu rendering (VGUI ISurface)
384// ---------------------------------------------------------------------------
385
386// VGUI surface interface
387using CreateInterfaceFn = void *(*)(const char *, int *);
388struct ISurface;
389
390// VGUI font handle (opaque)
391using HFont = unsigned long;
392
393// function pointers resolved at init
394static ISurface *g_surface = nullptr;
395static HFont g_font = 0;
396
397// surface vtable indices for 2007-era VGUI_SURFACE001
398// verified: slot 16 = DrawSetTextColor, slot 17 = DrawSetTextFont,
399// slot 19 = DrawSetTextPos, slot 22 = DrawPrintText,
400// slot 23 = DrawGetTextSize, slot 31 = CreateNewFont, slot 32 = SetFontGlyphSet
401// These are from the standard VGUI surface vtable layout; will be verified
402// against runtime if they fail.
403static constexpr int SURF_DrawSetTextColor = 16;
404static constexpr int SURF_DrawSetTextFont = 17;
405static constexpr int SURF_DrawSetTextPos = 19;
406static constexpr int SURF_DrawPrintText = 22;
407static constexpr int SURF_CreateNewFont = 31;
408static constexpr int SURF_SetFontGlyphSet = 32;
409
410static void surf_draw_set_text_color(int r, int g, int b, int a) {
411 using Fn = void (*)(void *, int, int, int, int);
412 vfunc<Fn>(g_surface, SURF_DrawSetTextColor)(g_surface, r, g, b, a);
413}
414static void surf_draw_set_text_font(HFont f) {
415 using Fn = void (*)(void *, HFont);
416 vfunc<Fn>(g_surface, SURF_DrawSetTextFont)(g_surface, f);
417}
418static void surf_draw_set_text_pos(int x, int y) {
419 using Fn = void (*)(void *, int, int);
420 vfunc<Fn>(g_surface, SURF_DrawSetTextPos)(g_surface, x, y);
421}
422static void surf_draw_print_text(const wchar_t *text, int count) {
423 using Fn = void (*)(void *, const wchar_t *, int);
424 vfunc<Fn>(g_surface, SURF_DrawPrintText)(g_surface, text, count);
425}
426static HFont surf_create_font() {
427 using Fn = HFont (*)(void *);
428 return vfunc<Fn>(g_surface, SURF_CreateNewFont)(g_surface);
429}
430static void surf_set_font_glyph_set(HFont f, const char *name, int tall,
431 int weight, int blur, int scanlines,
432 int flags) {
433 using Fn = void (*)(void *, HFont, const char *, int, int, int, int, int, int,
434 int);
435 vfunc<Fn>(g_surface, SURF_SetFontGlyphSet)(g_surface, f, name, tall, weight,
436 blur, scanlines, flags, 0, 0);
437}
438
439// wide string conversion for DrawPrintText
440static void draw_text(int x, int y, const char *text) {
441 wchar_t wtext[256];
442 mbstowcs(wtext, text, 255);
443 wtext[255] = 0;
444 int len = (int)wcslen(wtext);
445 surf_draw_set_text_pos(x, y);
446 surf_draw_print_text(wtext, len);
447}
448
449// ---------------------------------------------------------------------------
450// paint hook — draws menu overlay every frame
451// ---------------------------------------------------------------------------
452
453using PaintFn = void (*)(void *, int);
454static PaintFn oPaint = nullptr;
455
456static void hkdPaint(void *self, int mode) {
457 oPaint(self, mode);
458
459 // init font on first paint
460 static bool font_inited = false;
461 if (!font_inited) {
462 g_font = surf_create_font();
463 if (g_font) {
464 surf_set_font_glyph_set(g_font, "Consolas", 14, 400, 0, 0, 0);
465 }
466 font_inited = true;
467 }
468 if (!g_font)
469 return;
470
471 menu_handle_input();
472
473 if (!g_menu_open)
474 return;
475
476 surf_draw_set_text_font(g_font);
477
478 int y = 10;
479 rebuild_visible();
480 for (int i = 0; i < g_visible_count; i++) {
481 MenuNode *n = g_visible[i];
482 int indent = 0;
483
484 // count depth by counting how many parents are visible before this node
485 // (simple: first node = depth 0, others = depth 1 if label is Active/Bind)
486 if (n->type == NODE_TOGGLE || n->type == NODE_BIND)
487 indent = 20;
488
489 char buf[256];
490 if (n->type == NODE_TOGGLE) {
491 snprintf(buf, sizeof(buf), "%s: %s", n->label,
492 *n->active_ptr ? "ON" : "OFF");
493 } else if (n->type == NODE_BIND) {
494 snprintf(buf, sizeof(buf), "%s: %s", n->label,
495 scancode_name(*n->bind_ptr));
496 if (g_bind_mode && g_selected == i)
497 snprintf(buf, sizeof(buf), "%s: ...", n->label);
498 } else {
499 snprintf(buf, sizeof(buf), "%s", n->label);
500 }
501
502 if (i == g_selected) {
503 surf_draw_set_text_color(255, 255, 0, 255); // yellow highlight
504 } else if (n->type == NODE_TOGGLE) {
505 surf_draw_set_text_color(*n->active_ptr ? 0 : 255, 255, 0, 255);
506 } else {
507 surf_draw_set_text_color(255, 255, 255, 255);
508 }
509
510 draw_text(10 + indent, y, buf);
511 y += 18;
512 }
513
514 // reset text color
515 surf_draw_set_text_color(255, 255, 255, 255);
516}
517
518// ---------------------------------------------------------------------------
519// paint hook installation (called from setup_thread)
520// ---------------------------------------------------------------------------
521
522// VEngineVGui001 vtable indices for this build
523// (Paint is typically slot 6 — will be verified at runtime; if wrong,
524// we crash and can adjust. Common Source 2007 layout.)
525static constexpr int VGUI_Paint_SLOT = 6;
526
527static void install_paint_hook() {
528 // get IEngineVGui from engine
529 void *eh = dlopen(g_engine.path.c_str(),
530 RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
531 if (!eh)
532 return;
533 auto ef = (CreateInterfaceFn)dlsym(eh, "CreateInterface");
534 if (!ef)
535 return;
536 void *evgui = ef("VEngineVGui001", nullptr);
537 if (!evgui) {
538 logf("VEngineVGui001 not found");
539 return;
540 }
541
542 // hook Paint
543 void **vt = VTABLE(evgui);
544 oPaint = (PaintFn)vt[VGUI_Paint_SLOT];
545 vt[VGUI_Paint_SLOT] = (void *)&hkdPaint;
546 logf("paint hook installed: Paint %p -> %p", (void *)oPaint,
547 (void *)&hkdPaint);
548}
549
550// public init function called from setup_thread
551void install_menu() {
552 build_tree();
553 config_load();
554 install_paint_hook();
555}
diff --git a/src/modules.cpp b/src/modules.cpp
new file mode 100644
index 0000000..d20fe04
--- /dev/null
+++ b/src/modules.cpp
@@ -0,0 +1,69 @@
1// modules.cpp — /proc/self/maps parsing and module discovery
2#include "hl2h.h"
3
4static bool basename_is(const char *path, const char *base) {
5 size_t pl = strlen(path), bl = strlen(base);
6 if (pl < bl || strcmp(path + pl - bl, base) != 0)
7 return false;
8 return pl == bl || path[pl - bl - 1] == '/';
9}
10
11void find_modules() {
12 g_client = Module();
13 g_engine = Module();
14 FILE *f = fopen("/proc/self/maps", "r");
15 if (!f)
16 return;
17 char line[1024];
18 while (fgets(line, sizeof(line), f)) {
19 unsigned long lo, hi;
20 char perms[8];
21 int consumed = 0;
22 if (sscanf(line, "%lx-%lx %7s %*x %*x:%*x %*u%n", &lo, &hi, perms,
23 &consumed) < 3)
24 continue;
25 if (perms[0] == '-' && perms[1] == '-' && perms[2] == '-')
26 continue; // PROT_NONE
27
28 const char *path = line + consumed;
29 while (*path == ' ' || *path == '\t')
30 path++;
31 size_t pl = strlen(path);
32 while (pl && (path[pl - 1] == '\n' || path[pl - 1] == '\r'))
33 pl--;
34
35 Module *m = nullptr;
36 char clean[1024];
37 if (pl && pl < sizeof(clean)) {
38 memcpy(clean, path, pl);
39 clean[pl] = 0;
40 if (basename_is(clean, "client.so"))
41 m = &g_client;
42 else if (basename_is(clean, "engine.so"))
43 m = &g_engine;
44 } else {
45 // anon: absorb only as .bss tail after a file-backed range
46 if (g_client.valid() && g_client.ranges.back().file &&
47 lo == g_client.ranges.back().hi && hi - lo <= 0x1000000)
48 m = &g_client;
49 else if (g_engine.valid() && g_engine.ranges.back().file &&
50 lo == g_engine.ranges.back().hi && hi - lo <= 0x1000000)
51 m = &g_engine;
52 }
53 if (!m)
54 continue;
55 if (m->ranges.empty()) {
56 if (!pl)
57 continue;
58 m->path = clean;
59 }
60 bool file = pl != 0;
61 if (!m->ranges.empty() && lo == m->ranges.back().hi) {
62 m->ranges.back().hi = hi;
63 m->ranges.back().file = m->ranges.back().file && file;
64 } else {
65 m->ranges.push_back({lo, hi, file});
66 }
67 }
68 fclose(f);
69}
diff --git a/src/netvars.cpp b/src/netvars.cpp
new file mode 100644
index 0000000..41070c7
--- /dev/null
+++ b/src/netvars.cpp
@@ -0,0 +1,185 @@
1// netvars.cpp — ClientClass walk, RecvTable resolution, movetype from pred-TD
2#include "hl2h.h"
3
4// RecvTable layout: pProps@0, nProps@4, pDecoder@8, pNetTableName@0xC
5// RecvProp layout: pVarName@0, pArrayProp@0x18, pDataTable@0x28, offset@0x2C
6// ClientClass (2004-era): CreateFn@0, CreateEventFn@4, Name@8, RecvTable@0xC, Next@0x10
7
8static bool module_string(uintptr_t p, char *out, size_t outn) {
9 if (!g_client.contains(p))
10 return false;
11 const char *s = (const char *)p;
12 size_t i = 0;
13 while (i < outn - 1) {
14 unsigned char ch = (unsigned char)s[i];
15 if (ch == 0)
16 break;
17 if (ch < 0x20 || ch > 0x7E)
18 return false;
19 out[i] = (char)ch;
20 i++;
21 }
22 if (i == 0 || i >= outn - 1)
23 return false;
24 out[i] = 0;
25 return true;
26}
27
28static int g_dumped_tables = 0;
29
30static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) {
31 if (depth > 8)
32 return;
33 uintptr_t props = *(uintptr_t *)(table + 0x00);
34 int32_t nprops = *(int32_t *)(table + 0x04);
35 if (!props || !g_client.contains(props) || nprops <= 0 || nprops > 512)
36 return;
37
38 char tname[64];
39 if (!module_string(*(uintptr_t *)(table + 0x0C), tname, sizeof(tname)))
40 return;
41 if (strncmp(tname, "DT_", 3) != 0)
42 return;
43
44 bool dump = g_dumped_tables < 2 &&
45 (strcmp(tname, "DT_BaseEntity") == 0 ||
46 strcmp(tname, "DT_BasePlayer") == 0);
47 if (dump) {
48 g_dumped_tables++;
49 logf("dump %s: %d props @ table %#lx (base_off %#lx)", tname, nprops,
50 (unsigned long)table, (unsigned long)base_off);
51 }
52
53 for (int32_t i = 0; i < nprops; i++) {
54 uintptr_t prop = props + (size_t)i * 0x3C;
55 if (!g_client.contains(prop))
56 return;
57 uintptr_t pname = *(uintptr_t *)(prop + 0x00);
58 int32_t ptype = *(int32_t *)(prop + 0x04);
59 int32_t poff = *(int32_t *)(prop + 0x2C);
60 uintptr_t parray = *(uintptr_t *)(prop + 0x18);
61 uintptr_t ptable = *(uintptr_t *)(prop + 0x28);
62
63 char pname2[64];
64 if (!module_string(pname, pname2, sizeof(pname2)))
65 continue;
66
67 if (dump)
68 logf(" [%s] %s type=%d off=%d(0x%x) table=%s array=%s", tname, pname2,
69 ptype, poff, poff, ptable ? "yes" : "no", parray ? "yes" : "no");
70
71 if (ptable) {
72 collect_netvars(ptable, base_off + (uint32_t)poff, depth + 1);
73 continue;
74 }
75
76 if (strcmp(pname2, "m_fFlags") == 0 && g_net.flags < 0) {
77 g_net.flags = (int)(base_off + (uint32_t)poff);
78 } else if (strcmp(pname2, "m_vecVelocity") == 0 && g_net.velocity < 0) {
79 if (parray && g_client.contains(parray)) {
80 int32_t eoff = *(int32_t *)(parray + 0x2C);
81 g_net.velocity = (int)(base_off + (uint32_t)(eoff ? eoff : poff));
82 } else {
83 g_net.velocity = (int)(base_off + (uint32_t)poff);
84 }
85 }
86 if (g_net.ok())
87 return;
88 }
89}
90
91// prediction datamap scan for m_MoveType (FIELD_CHARACTER, fieldOffset in range)
92static bool resolve_movetype() {
93 static const char needle[] = "m_MoveType";
94 const size_t nlen = sizeof(needle);
95
96 uintptr_t str_va = 0;
97 for (auto &r : g_client.ranges) {
98 const uint8_t *base = (const uint8_t *)r.lo;
99 size_t span = r.hi - r.lo;
100 for (size_t i = 0; i + nlen <= span; i++) {
101 if (memcmp(base + i, needle, nlen) == 0) {
102 str_va = r.lo + i;
103 break;
104 }
105 }
106 if (str_va)
107 break;
108 }
109 if (!str_va) {
110 logf("m_MoveType string not found");
111 return false;
112 }
113
114 uint32_t offs[16];
115 int counts[16] = {};
116 int ncand = 0;
117 const uint32_t target = (uint32_t)str_va;
118 for (auto &r : g_client.ranges) {
119 const uint8_t *base = (const uint8_t *)r.lo;
120 size_t span = r.hi - r.lo;
121 for (size_t off = 4; off + 8 <= span; off += 4) {
122 if (*(const uint32_t *)(base + off) != target)
123 continue;
124 uint32_t ftype = *(const uint32_t *)(base + off - 4);
125 int32_t foff = *(const int32_t *)(base + off + 4);
126 if (ftype != 8 || foff < 0x40 || foff > 0x400)
127 continue;
128 int k;
129 for (k = 0; k < ncand; k++)
130 if (offs[k] == (uint32_t)foff)
131 break;
132 if (k == ncand && ncand < 16) {
133 offs[ncand] = (uint32_t)foff;
134 ncand++;
135 }
136 if (k < ncand)
137 counts[k]++;
138 }
139 }
140 if (ncand == 0) {
141 logf("m_MoveType typedescription not found");
142 return false;
143 }
144 int best = 0;
145 for (int k = 1; k < ncand; k++)
146 if (counts[k] > counts[best])
147 best = k;
148 g_net.movetype = (int)offs[best];
149 return true;
150}
151
152void resolve_netvars() {
153 using GetAllClassesFn = void *(*)(void *);
154 uintptr_t head =
155 (uintptr_t)vfunc<GetAllClassesFn>(g_iface.client, 8)(g_iface.client);
156 if (!g_client.contains(head)) {
157 logf("GetAllClasses returned %p, outside client module", (void *)head);
158 return;
159 }
160 int classes = 0;
161 for (uintptr_t cc = head; cc && g_client.contains(cc) && classes < 1024;
162 cc = *(uintptr_t *)(cc + 0x10)) {
163 classes++;
164 char name[64];
165 if (!module_string(*(uintptr_t *)(cc + 0x08), name, sizeof(name)))
166 break;
167 collect_netvars(*(uintptr_t *)(cc + 0x0C), 0, 0);
168 if (g_net.flags >= 0 && g_net.velocity >= 0)
169 break;
170 }
171 logf("client classes walked: %d", classes);
172
173 if (g_net.movetype < 0 && !resolve_movetype()) {
174 // movetype unresolved — bhop still works, just no noclip/ladder guard
175 logf("movetype guard disabled");
176 }
177
178 if (g_net.ok()) {
179 logf("netvars: m_fFlags=+0x%X m_vecVelocity[0]=+0x%X m_MoveType=+0x%X",
180 g_net.flags, g_net.velocity, g_net.movetype);
181 } else {
182 logf("netvar resolution incomplete: flags=%d velocity=%d movetype=%d",
183 g_net.flags, g_net.velocity, g_net.movetype);
184 }
185}