diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-06 16:09:55 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-06 16:09:55 +0100 |
| commit | 33f5ed9ef58f26c6b3b52cf605dd4311d658a60d (patch) | |
| tree | a97254872e465c9107b7ef19695ab56bb076d7e2 /src/studio.cpp | |
| parent | Items: log the usable ammo table once (diff) | |
| download | hl2h-33f5ed9ef58f26c6b3b52cf605dd4311d658a60d.tar.gz hl2h-33f5ed9ef58f26c6b3b52cf605dd4311d658a60d.zip | |
Fix audit findings: crash safety, correctness, rendering, performance
Crash/UB:
- gate every hook behind an init-stage flag until config/surface/input are ready
- publish module maps as immutable atomic snapshots instead of reallocating
shared vectors under unlocked readers
- validate the %ebp walk in the RandomSeed/RandomFloat hooks against the stack
- bounds-check every RecvTable/RecvProp/string dereference with module_range()
- preserve the Itanium vtable header when cloning (client, CInput, model render,
studio render)
- reject negative usercmd sequence numbers; guard config_load against a null
section target; cap the bone parent walk; check mprotect; drop PROT_EXEC on
the GOT patch; absorb server.so .bss tail
Correctness:
- filter dormant entities across ESP, aimbot, triggerbot, melee, items and
projectiles (slot self-tested against the local player)
- reset the server-health latch per map and only latch on valid model indices
- bone cache sized for the 128-target scan; set valid=false on every failure;
head_position keeps searching past an invalid bone
- vischeck steps past glass by probing until clear, and ignores entities owned
by the target (held weapons, bone followers)
- aimbot: maxs no longer disables it, AABB-aware FOV prefilter, lock hysteresis,
lead point re-checked for FOV and visibility
- spread: seed hook arms on the no-spread toggle alone; engine toggle only
controls the RandomFloat zeroing
- resolve m_hOwnerEntity/m_hOwner; NPC lead velocity from the server entity
- vector_angles pitch range, point_ground normal test, underwater jump+duck no
longer skips combat, semi-auto press/release edge for synthetic fire
- item/projectile markers and blast rings refresh origin per frame while
discovery stays at 10 Hz; trajectory uses the real m_DmgRadius and honours
the blast-radius toggle
Rendering/GUI:
- one overlay draw per engine frame (FRAME_RENDER_START boundary), fixing
alpha stacking and the speed-graph flicker
- clipped lines/outlines and ellipsised text instead of drawing past the
content pane or shifting over neighbours
- dropdown dismissal consumes the click; hover ignores hidden controls
- chams: reset AlphaModulate, reference shared materials once, restore prop
tint colour; fonts created once per handle
- projection uses Source's 4:3 aspect scaling and roll; screen size refreshed
each frame; FOV circle gated on the aim toggles, not the bind
Performance:
- TTL caches for NPC relation, friendly/neutral and damage class
- model names memoised per model index
- entity pointer -> index cache for the triggerbot (was a 4096-slot walk/tick)
Build:
- real Makefile with -MMD dependency tracking, debug/release objects and an
always-relink rule; build.sh kept as a thin wrapper
Diffstat (limited to 'src/studio.cpp')
| -rw-r--r-- | src/studio.cpp | 71 |
1 files changed, 56 insertions, 15 deletions
diff --git a/src/studio.cpp b/src/studio.cpp index 3ee6cc5..69a2e07 100644 --- a/src/studio.cpp +++ b/src/studio.cpp | |||
| @@ -68,9 +68,11 @@ const char *info_model_name(void *info) { | |||
| 68 | void *hdr = *(void **)info; | 68 | void *hdr = *(void **)info; |
| 69 | if (!hdr) | 69 | if (!hdr) |
| 70 | return nullptr; | 70 | return nullptr; |
| 71 | // studiohdr_t::name is char[64]; scanning further would read the header's | ||
| 72 | // length/eyeposition fields and could return a bogus "name". | ||
| 71 | const char *name = (const char *)hdr + 12; | 73 | const char *name = (const char *)hdr + 12; |
| 72 | size_t n = strnlen(name, 128); | 74 | size_t n = strnlen(name, 64); |
| 73 | if (n < 7 || n >= 128) | 75 | if (n < 7 || n >= 64) |
| 74 | return nullptr; | 76 | return nullptr; |
| 75 | for (size_t i = 0; i < n; i++) { | 77 | for (size_t i = 0; i < n; i++) { |
| 76 | unsigned char c = (unsigned char)name[i]; | 78 | unsigned char c = (unsigned char)name[i]; |
| @@ -127,8 +129,13 @@ void hook_draw_model(void *self, void *results, void *info, void *bone, | |||
| 127 | } | 129 | } |
| 128 | g_orig_draw_model(self, results, info, bone, flex, flex_delayed, | 130 | g_orig_draw_model(self, results, info, bone, flex, flex_delayed, |
| 129 | model_origin, flags); | 131 | model_origin, flags); |
| 130 | if (prop) | 132 | if (prop) { |
| 131 | g_prop_depth--; | 133 | g_prop_depth--; |
| 134 | // Don't leave the prop tint bound: models drawn without an explicit | ||
| 135 | // SetColorModulation would inherit it. | ||
| 136 | const float white[4] = {1.0f, 1.0f, 1.0f, 1.0f}; | ||
| 137 | g_orig_set_color(self, white); | ||
| 138 | } | ||
| 132 | } | 139 | } |
| 133 | 140 | ||
| 134 | void hook_draw_array(void *self, void *info, int count, void *instances, | 141 | void hook_draw_array(void *self, void *info, int count, void *instances, |
| @@ -158,8 +165,11 @@ void hook_draw_static_prop(void *self, void *info, void *model_to_world, | |||
| 158 | if (prop) | 165 | if (prop) |
| 159 | g_prop_depth++; | 166 | g_prop_depth++; |
| 160 | g_orig_draw_static(self, info, model_to_world, flags); | 167 | g_orig_draw_static(self, info, model_to_world, flags); |
| 161 | if (prop) | 168 | if (prop) { |
| 162 | g_prop_depth--; | 169 | g_prop_depth--; |
| 170 | const float white[4] = {1.0f, 1.0f, 1.0f, 1.0f}; | ||
| 171 | g_orig_set_color(self, white); | ||
| 172 | } | ||
| 163 | } | 173 | } |
| 164 | 174 | ||
| 165 | std::string loaded_module_path(const char *needle) { | 175 | std::string loaded_module_path(const char *needle) { |
| @@ -279,7 +289,8 @@ void chams_apply(void *mat, bool ignorez, int style, bool force_white, | |||
| 279 | vfunc<FlagFn>(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5); | 289 | vfunc<FlagFn>(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5); |
| 280 | } | 290 | } |
| 281 | 291 | ||
| 282 | void chams_reset(void *mat) { using FlagFn = void (*)(void *, int, bool); | 292 | void chams_reset(void *mat) { |
| 293 | using FlagFn = void (*)(void *, int, bool); | ||
| 283 | vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false); | 294 | vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false); |
| 284 | vfunc<FlagFn>(mat, 29)(mat, 1 << 10, false); | 295 | vfunc<FlagFn>(mat, 29)(mat, 1 << 10, false); |
| 285 | vfunc<FlagFn>(mat, 29)(mat, 1 << 28, false); | 296 | vfunc<FlagFn>(mat, 29)(mat, 1 << 28, false); |
| @@ -288,6 +299,11 @@ void chams_reset(void *mat) { using FlagFn = void (*)(void *, int, bool); | |||
| 288 | vfunc<FlagFn>(mat, 29)(mat, 1 << 6, false); | 299 | vfunc<FlagFn>(mat, 29)(mat, 1 << 6, false); |
| 289 | chams_color(mat, 1.0f, 1.0f, 1.0f); | 300 | chams_color(mat, 1.0f, 1.0f, 1.0f); |
| 290 | chams_alpha(mat, 1.0f); | 301 | chams_alpha(mat, 1.0f); |
| 302 | // AlphaModulate is applied per-entity, separate from $alpha, and was left at | ||
| 303 | // the last chams alpha. Styles 0/3/4/6 share one material, so a translucent | ||
| 304 | // pass permanently half-faded it for every later draw. | ||
| 305 | using AlphaFn = void (*)(void *, float); | ||
| 306 | vfunc<AlphaFn>(mat, 27)(mat, 1.0f); | ||
| 291 | } | 307 | } |
| 292 | 308 | ||
| 293 | // Model draws take their final alpha from IVRenderView::SetBlend; material | 309 | // Model draws take their final alpha from IVRenderView::SetBlend; material |
| @@ -310,6 +326,10 @@ void hkdModelExecute(void *self, const void *state, const void *info, | |||
| 310 | void *bone) { | 326 | void *bone) { |
| 311 | if (!g_orig_model_exec) | 327 | if (!g_orig_model_exec) |
| 312 | return; | 328 | return; |
| 329 | if (!init_live()) { | ||
| 330 | g_orig_model_exec(self, state, info, bone); | ||
| 331 | return; | ||
| 332 | } | ||
| 313 | if (feature_active(FEAT_CHAMS) && info && g_force_override) { | 333 | if (feature_active(FEAT_CHAMS) && info && g_force_override) { |
| 314 | // ModelRenderInfo_t::entity_index is at 0x30 on 32-bit. | 334 | // ModelRenderInfo_t::entity_index is at 0x30 on 32-bit. |
| 315 | int idx = *(const int *)((const char *)info + 0x30); | 335 | int idx = *(const int *)((const char *)info + 0x30); |
| @@ -497,17 +517,32 @@ void chams_preload() { | |||
| 497 | if (now - last < 1.5) | 517 | if (now - last < 1.5) |
| 498 | return; | 518 | return; |
| 499 | last = now; | 519 | last = now; |
| 520 | using RefFn = void (*)(void *); | ||
| 500 | for (int i = 0; i < 7; i++) { | 521 | for (int i = 0; i < 7; i++) { |
| 501 | if (g_style_mats[i]) | 522 | if (g_style_mats[i]) |
| 502 | continue; | 523 | continue; |
| 503 | void *m = world_find_material(chams_material_name(i)); | 524 | const char *name = chams_material_name(i); |
| 525 | // Styles 0/3/4/6 and 2/5 resolve to the same material. Reuse the pointer | ||
| 526 | // and reference it only once instead of incrementing the shared material | ||
| 527 | // up to four times. | ||
| 528 | void *m = nullptr; | ||
| 529 | for (int j = 0; j < 7; j++) { | ||
| 530 | if (g_style_mats[j] && strcmp(chams_material_name(j), name) == 0) { | ||
| 531 | m = g_style_mats[j]; | ||
| 532 | break; | ||
| 533 | } | ||
| 534 | } | ||
| 535 | bool fresh = m == nullptr; | ||
| 536 | if (!m) | ||
| 537 | m = world_find_material(name); | ||
| 504 | if (m) { | 538 | if (m) { |
| 505 | // FindMaterial can hand back an unreferenced material; the engine warns | 539 | // FindMaterial can hand back an unreferenced material; the engine warns |
| 506 | // ("bad reference count 0") and may reset its vars when it is bound. | 540 | // ("bad reference count 0") and may reset its vars when it is bound. |
| 507 | using RefFn = void (*)(void *); | 541 | if (fresh) |
| 508 | vfunc<RefFn>(m, 12)(m); // IMaterial::IncrementReferenceCount | 542 | vfunc<RefFn>(m, 12)(m); // IMaterial::IncrementReferenceCount |
| 509 | g_style_mats[i] = m; | 543 | g_style_mats[i] = m; |
| 510 | logf("chams: preloaded style %d %s = %p", i, chams_material_name(i), m); | 544 | logf("chams: preloaded style %d %s = %p%s", i, name, m, |
| 545 | fresh ? "" : " (shared)"); | ||
| 511 | } | 546 | } |
| 512 | } | 547 | } |
| 513 | if (!g_style_mats[7]) { | 548 | if (!g_style_mats[7]) { |
| @@ -535,14 +570,17 @@ void chams_init() { | |||
| 535 | } | 570 | } |
| 536 | g_force_override = (ForceOverrideFn)vt[1]; | 571 | g_force_override = (ForceOverrideFn)vt[1]; |
| 537 | g_orig_model_exec = (ModelExecFn)vt[19]; | 572 | g_orig_model_exec = (ModelExecFn)vt[19]; |
| 538 | if (!g_engine.contains((uintptr_t)g_force_override) || | 573 | if (!mod_engine().contains((uintptr_t)g_force_override) || |
| 539 | !g_engine.contains((uintptr_t)g_orig_model_exec)) { | 574 | !mod_engine().contains((uintptr_t)g_orig_model_exec)) { |
| 540 | logf("chams: implausible vtable entries override=%p draw=%p", | 575 | logf("chams: implausible vtable entries override=%p draw=%p", |
| 541 | (void *)g_force_override, (void *)g_orig_model_exec); | 576 | (void *)g_force_override, (void *)g_orig_model_exec); |
| 542 | return; | 577 | return; |
| 543 | } | 578 | } |
| 544 | static void *nvt[32]; | 579 | void **nvt = vtable_clone(g_iface.modelrender, 32); |
| 545 | memcpy(nvt, vt, sizeof(nvt)); | 580 | if (!nvt) { |
| 581 | logf("chams: vtable clone failed"); | ||
| 582 | return; | ||
| 583 | } | ||
| 546 | nvt[19] = (void *)&hkdModelExecute; | 584 | nvt[19] = (void *)&hkdModelExecute; |
| 547 | g_modelrender = g_iface.modelrender; | 585 | g_modelrender = g_iface.modelrender; |
| 548 | *(void ***)g_modelrender = nvt; | 586 | *(void ***)g_modelrender = nvt; |
| @@ -581,8 +619,11 @@ void studio_props_init() { | |||
| 581 | logf("studio: interface has no vtable"); | 619 | logf("studio: interface has no vtable"); |
| 582 | return; | 620 | return; |
| 583 | } | 621 | } |
| 584 | static void *vt[49]; | 622 | void **vt = vtable_clone(sr, 49); |
| 585 | memcpy(vt, old_vt, sizeof(vt)); | 623 | if (!vt) { |
| 624 | logf("studio: vtable clone failed"); | ||
| 625 | return; | ||
| 626 | } | ||
| 586 | 627 | ||
| 587 | g_orig_set_color = (SetColorFn)vt[27]; | 628 | g_orig_set_color = (SetColorFn)vt[27]; |
| 588 | g_orig_set_alpha = (SetAlphaFn)vt[28]; | 629 | g_orig_set_alpha = (SetAlphaFn)vt[28]; |
