From 49df428d95a7c443d418d5b7e2a93af599b54f76 Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 17:51:20 +0100 Subject: Add chams - Hook IVModelRender::DrawModelExecute (VEngineModel016 slot 19) and force models/debug/debugwhite for NPC models, identified by ModelRenderInfo_t::entity_index. - Two passes: MATERIAL_VAR_IGNOREZ through walls, then normal depth, with IMaterial::ColorModulate for the configured colour; restore the shared material's flag/colour afterwards. - Material lookup via the IMaterialSystem list, FindMaterial fallback. - New Chams feature/card with Through walls and colour; [chams] config. - Remove the temporary ammo/projectile/fuse diagnostics. --- src/studio.cpp | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 98 insertions(+) (limited to 'src/studio.cpp') diff --git a/src/studio.cpp b/src/studio.cpp index 23be97f..1b7b77d 100644 --- a/src/studio.cpp +++ b/src/studio.cpp @@ -181,8 +181,106 @@ std::string loaded_module_path(const char *needle) { return found; } +// --------------------------------------------------------------------------- +// chams: force a flat material on NPC draws +// --------------------------------------------------------------------------- +// +// IVModelRender::DrawModelExecute is called once per model. For NPC entities +// we force models/debug/debugwhite, optionally with MATERIAL_VAR_IGNOREZ for +// the through-wall pass, and draw twice. IVModelRender::ForcedMaterialOverride +// is slot 1; DrawModelExecute is slot 19 in VEngineModel016. + +using ModelExecFn = void (*)(void *, const void *, const void *, void *); +using ForceOverrideFn = void (*)(void *, void *, int); + +void *g_modelrender = nullptr; +ModelExecFn g_orig_model_exec = nullptr; +ForceOverrideFn g_force_override = nullptr; +void *g_chams_mat = nullptr; +bool g_chams_mat_tried = false; + +void *chams_material() { + if (g_chams_mat_tried) + return g_chams_mat; + g_chams_mat_tried = true; + g_chams_mat = world_find_material("models/debug/debugwhite"); + if (g_chams_mat) + logf("chams: material models/debug/debugwhite = %p", g_chams_mat); + else + logf("chams: models/debug/debugwhite not found"); + return g_chams_mat; +} + +void chams_apply(void *mat, bool ignorez) { + using ColorFn = void (*)(void *, float, float, float); + using FlagFn = void (*)(void *, int, bool); + vfunc(mat, 28)(mat, (float)g_chams_rgb[0] / 255.0f, + (float)g_chams_rgb[1] / 255.0f, + (float)g_chams_rgb[2] / 255.0f); + vfunc(mat, 29)(mat, 1 << 15 /* MATERIAL_VAR_IGNOREZ */, ignorez); +} + +void hkdModelExecute(void *self, const void *state, const void *info, + void *bone) { + if (!g_orig_model_exec) + 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); + if (idx > 0 && idx <= 8192 && esp_is_npc_index(idx)) { + void *mat = chams_material(); + if (mat) { + if (g_chams_walls) { + chams_apply(mat, true); + g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); + g_orig_model_exec(self, state, info, bone); + } + chams_apply(mat, false); + g_force_override(self, mat, 0); + g_orig_model_exec(self, state, info, bone); + g_force_override(self, nullptr, 0); + // debugwhite is shared: restore colour and depth behaviour. + using ColorFn = void (*)(void *, float, float, float); + using FlagFn = void (*)(void *, int, bool); + vfunc(mat, 29)(mat, 1 << 15, false); + vfunc(mat, 28)(mat, 1.0f, 1.0f, 1.0f); + return; + } + } + } + g_orig_model_exec(self, state, info, bone); +} + } // namespace +void chams_init() { + if (g_modelrender || !g_iface.modelrender) { + if (!g_iface.modelrender) + logf("chams: VEngineModel016 unavailable"); + return; + } + void **vt = *(void ***)g_iface.modelrender; + if (!vt) { + logf("chams: model render has no vtable"); + return; + } + 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)) { + 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)); + nvt[19] = (void *)&hkdModelExecute; + g_modelrender = g_iface.modelrender; + *(void ***)g_modelrender = nvt; + logf("chams: IVModelRender %p hooked (draw=%p override=%p)", g_modelrender, + (void *)g_orig_model_exec, (void *)g_force_override); +} + void studio_props_init() { if (g_install_tried) return; -- cgit v1.2.3