From 33f5ed9ef58f26c6b3b52cf605dd4311d658a60d Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 6 Nov 2026 16:09:55 +0100 Subject: 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 --- src/studio.cpp | 71 +++++++++++++++++++++++++++++++++++++++++++++------------- 1 file changed, 56 insertions(+), 15 deletions(-) (limited to 'src/studio.cpp') 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) { void *hdr = *(void **)info; if (!hdr) return nullptr; + // studiohdr_t::name is char[64]; scanning further would read the header's + // length/eyeposition fields and could return a bogus "name". const char *name = (const char *)hdr + 12; - size_t n = strnlen(name, 128); - if (n < 7 || n >= 128) + size_t n = strnlen(name, 64); + if (n < 7 || n >= 64) return nullptr; for (size_t i = 0; i < n; i++) { unsigned char c = (unsigned char)name[i]; @@ -127,8 +129,13 @@ void hook_draw_model(void *self, void *results, void *info, void *bone, } g_orig_draw_model(self, results, info, bone, flex, flex_delayed, model_origin, flags); - if (prop) + if (prop) { g_prop_depth--; + // Don't leave the prop tint bound: models drawn without an explicit + // SetColorModulation would inherit it. + const float white[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + g_orig_set_color(self, white); + } } 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, if (prop) g_prop_depth++; g_orig_draw_static(self, info, model_to_world, flags); - if (prop) + if (prop) { g_prop_depth--; + const float white[4] = {1.0f, 1.0f, 1.0f, 1.0f}; + g_orig_set_color(self, white); + } } std::string loaded_module_path(const char *needle) { @@ -279,7 +289,8 @@ void chams_apply(void *mat, bool ignorez, int style, bool force_white, vfunc(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5); } -void chams_reset(void *mat) { using FlagFn = void (*)(void *, int, bool); +void chams_reset(void *mat) { + using FlagFn = void (*)(void *, int, bool); vfunc(mat, 29)(mat, 1 << 15, false); vfunc(mat, 29)(mat, 1 << 10, false); vfunc(mat, 29)(mat, 1 << 28, false); @@ -288,6 +299,11 @@ void chams_reset(void *mat) { using FlagFn = void (*)(void *, int, bool); vfunc(mat, 29)(mat, 1 << 6, false); chams_color(mat, 1.0f, 1.0f, 1.0f); chams_alpha(mat, 1.0f); + // AlphaModulate is applied per-entity, separate from $alpha, and was left at + // the last chams alpha. Styles 0/3/4/6 share one material, so a translucent + // pass permanently half-faded it for every later draw. + using AlphaFn = void (*)(void *, float); + vfunc(mat, 27)(mat, 1.0f); } // Model draws take their final alpha from IVRenderView::SetBlend; material @@ -310,6 +326,10 @@ void hkdModelExecute(void *self, const void *state, const void *info, void *bone) { if (!g_orig_model_exec) return; + if (!init_live()) { + g_orig_model_exec(self, state, info, bone); + return; + } if (feature_active(FEAT_CHAMS) && info && g_force_override) { // ModelRenderInfo_t::entity_index is at 0x30 on 32-bit. int idx = *(const int *)((const char *)info + 0x30); @@ -497,17 +517,32 @@ void chams_preload() { if (now - last < 1.5) return; last = now; + using RefFn = void (*)(void *); for (int i = 0; i < 7; i++) { if (g_style_mats[i]) continue; - void *m = world_find_material(chams_material_name(i)); + const char *name = chams_material_name(i); + // Styles 0/3/4/6 and 2/5 resolve to the same material. Reuse the pointer + // and reference it only once instead of incrementing the shared material + // up to four times. + void *m = nullptr; + for (int j = 0; j < 7; j++) { + if (g_style_mats[j] && strcmp(chams_material_name(j), name) == 0) { + m = g_style_mats[j]; + break; + } + } + bool fresh = m == nullptr; + if (!m) + m = world_find_material(name); if (m) { // FindMaterial can hand back an unreferenced material; the engine warns // ("bad reference count 0") and may reset its vars when it is bound. - using RefFn = void (*)(void *); - vfunc(m, 12)(m); // IMaterial::IncrementReferenceCount + if (fresh) + vfunc(m, 12)(m); // IMaterial::IncrementReferenceCount g_style_mats[i] = m; - logf("chams: preloaded style %d %s = %p", i, chams_material_name(i), m); + logf("chams: preloaded style %d %s = %p%s", i, name, m, + fresh ? "" : " (shared)"); } } if (!g_style_mats[7]) { @@ -535,14 +570,17 @@ void chams_init() { } g_force_override = (ForceOverrideFn)vt[1]; g_orig_model_exec = (ModelExecFn)vt[19]; - if (!g_engine.contains((uintptr_t)g_force_override) || - !g_engine.contains((uintptr_t)g_orig_model_exec)) { + if (!mod_engine().contains((uintptr_t)g_force_override) || + !mod_engine().contains((uintptr_t)g_orig_model_exec)) { logf("chams: implausible vtable entries override=%p draw=%p", (void *)g_force_override, (void *)g_orig_model_exec); return; } - static void *nvt[32]; - memcpy(nvt, vt, sizeof(nvt)); + void **nvt = vtable_clone(g_iface.modelrender, 32); + if (!nvt) { + logf("chams: vtable clone failed"); + return; + } nvt[19] = (void *)&hkdModelExecute; g_modelrender = g_iface.modelrender; *(void ***)g_modelrender = nvt; @@ -581,8 +619,11 @@ void studio_props_init() { logf("studio: interface has no vtable"); return; } - static void *vt[49]; - memcpy(vt, old_vt, sizeof(vt)); + void **vt = vtable_clone(sr, 49); + if (!vt) { + logf("studio: vtable clone failed"); + return; + } g_orig_set_color = (SetColorFn)vt[27]; g_orig_set_alpha = (SetAlphaFn)vt[28]; -- cgit v1.2.3