summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rwxr-xr-xbuild.sh2
-rw-r--r--src/bones.cpp288
-rw-r--r--src/esp.cpp29
-rw-r--r--src/hl2h.h8
4 files changed, 326 insertions, 1 deletions
diff --git a/build.sh b/build.sh
index d1538ad..8be25d4 100755
--- a/build.sh
+++ b/build.sh
@@ -3,6 +3,6 @@ set -e
3cd "$(dirname "$0")" 3cd "$(dirname "$0")"
4g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden \ 4g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden \
5 -o libhl2hake.so \ 5 -o libhl2hake.so \
6 src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/esp.cpp src/server.cpp \ 6 src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/esp.cpp src/server.cpp src/bones.cpp \
7 -ldl 7 -ldl
8file libhl2hake.so 8file libhl2hake.so
diff --git a/src/bones.cpp b/src/bones.cpp
new file mode 100644
index 0000000..2ff282b
--- /dev/null
+++ b/src/bones.cpp
@@ -0,0 +1,288 @@
1// bones.cpp — client-side studio bone/hitbox access.
2//
3// C_BaseAnimating implements IClientRenderable::SetupBones(), which fills an
4// array of world-space bone matrices. The studio header (from
5// IVModelInfoClient::GetStudiomodel) gives bone names/parents and hitboxes.
6// Everything here is client-side; no server access is involved.
7
8#include "hl2h.h"
9#include <cstdint>
10#include <cstring>
11#include <ctime>
12#include <strings.h>
13
14namespace {
15
16constexpr int MAX_BONES = 256;
17constexpr int BONE_USED_BY_ANYTHING = 0x0007FF00;
18constexpr uint32_t STUDIO_ID = 0x54534449; // ('T'<<24)+('S'<<16)+('D'<<8)+'I'
19
20struct Matrix3x4 {
21 float m[3][4];
22};
23
24// Partial mirrors of the Source 2013 studio structures (32-bit). Only the
25// fields we use are named; layout must still match exactly.
26struct StudioHdr {
27 int id;
28 int version;
29 int checksum;
30 char name[64];
31 int length;
32 Vector eyeposition;
33 Vector illumposition;
34 Vector hull_min;
35 Vector hull_max;
36 Vector view_bbmin;
37 Vector view_bbmax;
38 int flags;
39 int numbones;
40 int boneindex;
41 int numbonecontrollers;
42 int bonecontrollerindex;
43 int numhitboxsets;
44 int hitboxsetindex;
45};
46
47struct StudioBone {
48 int sznameindex;
49 int parent;
50 int bonecontroller[6];
51 Vector pos;
52 float quat[4];
53 Vector rot;
54 Vector posscale;
55 Vector rotscale;
56 float poseToBone[12];
57 float qAlignment[4];
58 int flags;
59 int proctype;
60 int procindex;
61 int physicsbone;
62 int surfacepropidx;
63 int contents;
64 int unused[8];
65};
66
67struct HitboxSet {
68 int sznameindex;
69 int numhitboxes;
70 int hitboxindex;
71};
72
73struct Hitbox {
74 int bone;
75 int group;
76 Vector bbmin;
77 Vector bbmax;
78 int szhitboxnameindex;
79 int unused[8];
80};
81
82static_assert(sizeof(Vector) == 12, "Vector layout");
83static_assert(sizeof(Matrix3x4) == 48, "matrix3x4 layout");
84static_assert(sizeof(StudioHdr) == 0xB4, "studiohdr prefix layout");
85static_assert(sizeof(StudioBone) == 0xD8, "mstudiobone_t layout");
86static_assert(sizeof(HitboxSet) == 12, "mstudiohitboxset_t layout");
87static_assert(sizeof(Hitbox) == 0x44, "mstudiobbox_t layout");
88
89struct BoneEntry {
90 int idx;
91 void *ent;
92 double stamp;
93 bool valid;
94 const StudioHdr *hdr;
95 int count;
96 Matrix3x4 mats[MAX_BONES];
97};
98
99static BoneEntry g_entries[64];
100static int g_next = 0;
101static int g_logged = 0;
102static bool g_renderable_warned = false;
103
104static double bone_now() {
105 timespec ts;
106 clock_gettime(CLOCK_MONOTONIC, &ts);
107 return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
108}
109
110static BoneEntry *find_entry(int idx) {
111 for (int i = 0; i < 64; i++)
112 if (g_entries[i].ent && g_entries[i].idx == idx)
113 return &g_entries[i];
114 return nullptr;
115}
116
117static BoneEntry *alloc_entry(int idx, void *ent) {
118 BoneEntry *e = &g_entries[g_next++ % 64];
119 memset(e, 0, sizeof(*e));
120 e->idx = idx;
121 e->ent = ent;
122 return e;
123}
124
125static const StudioBone *bone_at(const StudioHdr *h, int i) {
126 return (const StudioBone *)((const uint8_t *)h + h->boneindex) + i;
127}
128
129static const char *bone_name_raw(const StudioHdr *h, int i) {
130 if (!h || i < 0 || i >= h->numbones)
131 return nullptr;
132 const StudioBone *b = bone_at(h, i);
133 if (b->sznameindex <= 0 || b->sznameindex > 0x10000)
134 return nullptr;
135 const char *n = (const char *)b + b->sznameindex;
136 return n[0] ? n : nullptr;
137}
138
139} // namespace
140
141bool bones_update(int idx, void *ent) {
142 if (!ent || !g_iface.modelinfo || !g_iface.engine || g_net.modelindex < 0)
143 return false;
144
145 BoneEntry *e = find_entry(idx);
146 double now = bone_now();
147 if (e && e->ent == ent && e->valid && now - e->stamp < 0.03)
148 return true;
149 if (!e)
150 e = alloc_entry(idx, ent);
151 if (e->ent != ent) {
152 e->ent = ent;
153 e->valid = false;
154 }
155
156 int modelindex = *(int *)((uintptr_t)ent + g_net.modelindex);
157 if (modelindex <= 0) {
158 e->valid = false;
159 return false;
160 }
161
162 using GetModelFn = const void *(*)(void *);
163 using GetModelIdxFn = const void *(*)(void *, int);
164 using GetStudioFn = const StudioHdr *(*)(void *, const void *);
165 using SetupFn = bool (*)(void *, Matrix3x4 *, int, int, float);
166 using GetRenderableFn = void *(*)(void *);
167 using TimeFn = float (*)(void *);
168
169 const void *expected =
170 vfunc<GetModelIdxFn>(g_iface.modelinfo, 2)(g_iface.modelinfo, modelindex);
171 if (!expected)
172 return false;
173
174 // IClientUnknown::GetClientRenderable = primary vtable slot 6.
175 void *r = vfunc<GetRenderableFn>(ent, 6)(ent);
176 if (!r)
177 return false;
178 // Validate the interface before using SetupBones on it.
179 const void *rmodel = vfunc<GetModelFn>(r, 9)(r);
180 if (rmodel != expected) {
181 if (!g_renderable_warned) {
182 g_renderable_warned = true;
183 logf("bones: renderable/model mismatch (renderable=%p model=%p "
184 "expected=%p)",
185 r, rmodel, expected);
186 }
187 e->valid = false;
188 return false;
189 }
190
191 const StudioHdr *hdr =
192 vfunc<GetStudioFn>(g_iface.modelinfo, 29)(g_iface.modelinfo, expected);
193 if (!hdr || hdr->id != STUDIO_ID || hdr->numbones <= 0 ||
194 hdr->numbones > MAX_BONES || hdr->boneindex <= 0) {
195 e->valid = false;
196 return false;
197 }
198
199 float curtime = vfunc<TimeFn>(g_iface.engine, 14)(g_iface.engine);
200 // IClientRenderable::SetupBones = slot 16.
201 if (!vfunc<SetupFn>(r, 16)(r, e->mats, MAX_BONES, BONE_USED_BY_ANYTHING,
202 curtime)) {
203 e->valid = false;
204 return false;
205 }
206
207 e->hdr = hdr;
208 e->count = hdr->numbones;
209 e->valid = true;
210 e->stamp = now;
211 if (g_logged < 4) {
212 g_logged++;
213 logf("bones: idx=%d count=%d bone0=%s", idx, e->count,
214 bone_name_raw(hdr, 0) ? bone_name_raw(hdr, 0) : "?");
215 }
216 return true;
217}
218
219int bones_count(int idx) {
220 BoneEntry *e = find_entry(idx);
221 return (e && e->valid) ? e->count : 0;
222}
223
224int bone_find(int idx, const char *name) {
225 BoneEntry *e = find_entry(idx);
226 if (!e || !e->valid || !name)
227 return -1;
228 for (int i = 0; i < e->count; i++) {
229 const char *n = bone_name_raw(e->hdr, i);
230 if (n && strcasecmp(n, name) == 0)
231 return i;
232 }
233 return -1;
234}
235
236int bone_parent(int idx, int bone) {
237 BoneEntry *e = find_entry(idx);
238 if (!e || !e->valid || bone < 0 || bone >= e->count)
239 return -1;
240 return bone_at(e->hdr, bone)->parent;
241}
242
243bool bone_pos(int idx, int bone, Vector &out) {
244 BoneEntry *e = find_entry(idx);
245 if (!e || !e->valid || bone < 0 || bone >= e->count)
246 return false;
247 const Matrix3x4 &m = e->mats[bone];
248 out = {m.m[0][3], m.m[1][3], m.m[2][3]};
249 return true;
250}
251
252bool hitbox_center(int idx, const char *name, Vector &out) {
253 BoneEntry *e = find_entry(idx);
254 if (!e || !e->valid || !name || !e->hdr)
255 return false;
256 const StudioHdr *h = e->hdr;
257 if (h->numhitboxsets <= 0 || h->hitboxsetindex <= 0)
258 return false;
259 const HitboxSet *set =
260 (const HitboxSet *)((const uint8_t *)h + h->hitboxsetindex);
261 if (set->numhitboxes <= 0 || set->numhitboxes > 512 ||
262 set->hitboxindex <= 0)
263 return false;
264 const Hitbox *boxes =
265 (const Hitbox *)((const uint8_t *)set + set->hitboxindex);
266 for (int i = 0; i < set->numhitboxes; i++) {
267 const Hitbox &b = boxes[i];
268 if (b.szhitboxnameindex <= 0)
269 continue;
270 const char *hn = (const char *)&b + b.szhitboxnameindex;
271 if (strcasecmp(hn, name) != 0)
272 continue;
273 if (b.bone < 0 || b.bone >= e->count)
274 return false;
275 Vector local{(b.bbmin.x + b.bbmax.x) * 0.5f,
276 (b.bbmin.y + b.bbmax.y) * 0.5f,
277 (b.bbmin.z + b.bbmax.z) * 0.5f};
278 const Matrix3x4 &m = e->mats[b.bone];
279 out.x = local.x * m.m[0][0] + local.y * m.m[0][1] + local.z * m.m[0][2] +
280 m.m[0][3];
281 out.y = local.x * m.m[1][0] + local.y * m.m[1][1] + local.z * m.m[1][2] +
282 m.m[1][3];
283 out.z = local.x * m.m[2][0] + local.y * m.m[2][1] + local.z * m.m[2][2] +
284 m.m[2][3];
285 return true;
286 }
287 return false;
288}
diff --git a/src/esp.cpp b/src/esp.cpp
index 4462f07..b384bc4 100644
--- a/src/esp.cpp
+++ b/src/esp.cpp
@@ -3,6 +3,7 @@
3#include <cmath> 3#include <cmath>
4#include <cstring> 4#include <cstring>
5#include <cstddef> 5#include <cstddef>
6#include <cstdlib>
6#include <ctime> 7#include <ctime>
7 8
8static constexpr float PI_F = 3.14159265358979323846f; 9static constexpr float PI_F = 3.14159265358979323846f;
@@ -16,6 +17,7 @@ enum SurfSlot {
16enum { LIFE_ALIVE = 0 }; 17enum { LIFE_ALIVE = 0 };
17 18
18struct EspTarget { 19struct EspTarget {
20 int idx;
19 float sx, sy; // screen pos of feet 21 float sx, sy; // screen pos of feet
20 float h; // box height px 22 float h; // box height px
21 float health, maxhealth; 23 float health, maxhealth;
@@ -242,6 +244,7 @@ static void esp_collect(int local_idx) {
242 if (sx < -(float)g_scr_w || sx > 2.0f * (float)g_scr_w) continue; 244 if (sx < -(float)g_scr_w || sx > 2.0f * (float)g_scr_w) continue;
243 245
244 EspTarget &t = g_targets[g_ntargets++]; 246 EspTarget &t = g_targets[g_ntargets++];
247 t.idx = idx;
245 t.sx = sx; t.sy = sy; t.h = h; 248 t.sx = sx; t.sy = sy; t.h = h;
246 t.health = hp > 0 ? (float)hp : 0.0f; 249 t.health = hp > 0 ? (float)hp : 0.0f;
247 t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f; 250 t.maxhealth = maxhp > 0 ? (float)maxhp : 100.0f;
@@ -319,4 +322,30 @@ void esp_draw() {
319 if (esp_option(ESP_OPT_SHOW_NAME)) 322 if (esp_option(ESP_OPT_SHOW_NAME))
320 draw_text_col(left, top - 14, t.name, 255, 255, 255); 323 draw_text_col(left, top - 14, t.name, 255, 255, 255);
321 } 324 }
325
326 // Groundwork probe: HL2H_BONE_DEBUG=1 draws a white marker at each NPC's
327 // "head" hitbox center (from SetupBones + studio hitboxes).
328 static bool bone_debug = getenv("HL2H_BONE_DEBUG") != nullptr;
329 if (bone_debug) {
330 static bool head_logged = false;
331 for (int i = 0; i < g_ntargets; i++) {
332 const EspTarget &t = g_targets[i];
333 void *ent = entitylist_get(t.idx);
334 if (!ent || !bones_update(t.idx, ent))
335 continue;
336 Vector head;
337 if (!hitbox_center(t.idx, "head", head))
338 continue;
339 float hx, hy;
340 if (!world_to_screen(head, hx, hy))
341 continue;
342 surf_color(255, 255, 255, 255);
343 surf_rect(1, (int)hx - 2, (int)hy - 2, (int)hx + 3, (int)hy + 3);
344 if (!head_logged) {
345 head_logged = true;
346 logf("bone debug: idx=%d bones=%d head=(%.1f %.1f %.1f)", t.idx,
347 bones_count(t.idx), head.x, head.y, head.z);
348 }
349 }
350 }
322} 351}
diff --git a/src/hl2h.h b/src/hl2h.h
index 0986017..d57105d 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -168,6 +168,14 @@ void draw_text_col(int x, int y, const char *s, int r, int g, int b);
168void esp_draw(); 168void esp_draw();
169bool world_to_screen(const Vector &w, float &sx, float &sy); 169bool world_to_screen(const Vector &w, float &sx, float &sy);
170 170
171// bones.cpp — client-side animated skeleton / hitbox access (see there)
172bool bones_update(int idx, void *ent);
173int bones_count(int idx);
174int bone_find(int idx, const char *name);
175int bone_parent(int idx, int bone);
176bool bone_pos(int idx, int bone, Vector &out);
177bool hitbox_center(int idx, const char *hitbox_name, Vector &out);
178
171void outer_draw(); 179void outer_draw();
172// raw ISurface slot calls used by the PaintTraverseEx hook (hl2h.cpp) 180// raw ISurface slot calls used by the PaintTraverseEx hook (hl2h.cpp)
173using SurfFn = void (*)(void *, ...); 181using SurfFn = void (*)(void *, ...);