From 8a5efe20e6e077e10592aa427d704836d2a08dd1 Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 6 Nov 2026 16:17:02 +0100 Subject: Chams: leave engine-forced material passes alone Shadow-depth and reflection passes call DrawModelExecute with a material already forced via IVModelRender::ForcedMaterialOverride. Chams could not tell that apart from a free draw, substituted its own material (putting chams into the shadow map) and then cleared the override to null, which broke the shadow material for every model drawn afterwards. Hook slot 1 as a tracker so the current forced material is known, and skip the chams substitution whenever one is active. --- src/studio.cpp | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) (limited to 'src/studio.cpp') diff --git a/src/studio.cpp b/src/studio.cpp index 69a2e07..8b0a710 100644 --- a/src/studio.cpp +++ b/src/studio.cpp @@ -212,6 +212,20 @@ ModelExecFn g_orig_model_exec = nullptr; ForceOverrideFn g_force_override = nullptr; void *g_style_mats[8] = {}; +// The engine forces a material for special passes (shadow depth, reflections) +// and clears it again. Without tracking that, the chams pass could not tell +// "no override" from "engine override in progress": it hijacked the shadow pass +// with chams materials and then cleared the engine's override for every model +// drawn afterwards. +static ForceOverrideFn g_real_force_override = nullptr; +static void *g_current_override = nullptr; + +static void hkdForceOverride(void *self, void *mat, int type) { + g_current_override = mat; + if (g_real_force_override) + g_real_force_override(self, mat, type); +} + const char *chams_material_name(int idx) { switch (idx) { case 1: return "debug/debugwireframe"; @@ -330,6 +344,12 @@ void hkdModelExecute(void *self, const void *state, const void *info, g_orig_model_exec(self, state, info, bone); return; } + // Shadow-depth/reflection passes arrive with a material already forced. Let + // them draw normally instead of substituting chams. + if (g_current_override) { + 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); @@ -568,7 +588,8 @@ void chams_init() { logf("chams: model render has no vtable"); return; } - g_force_override = (ForceOverrideFn)vt[1]; + g_real_force_override = (ForceOverrideFn)vt[1]; + g_force_override = g_real_force_override; g_orig_model_exec = (ModelExecFn)vt[19]; if (!mod_engine().contains((uintptr_t)g_force_override) || !mod_engine().contains((uintptr_t)g_orig_model_exec)) { @@ -581,6 +602,7 @@ void chams_init() { logf("chams: vtable clone failed"); return; } + nvt[1] = (void *)&hkdForceOverride; // track engine-forced passes nvt[19] = (void *)&hkdModelExecute; g_modelrender = g_iface.modelrender; *(void ***)g_modelrender = nvt; -- cgit v1.2.3