From 76c88403d18ef01e8f15b89fcd6d09f0825a1bfc Mon Sep 17 00:00:00 2001 From: salmonik Date: Wed, 4 Nov 2026 01:16:51 +0100 Subject: World tint: tint map-static props through IStudioRender Most of the crates are prop_static, which have no client entity, so writing m_clrRender only reached the three dynamic ones. Hook the StudioRender interface (VStudioRender025) on the interface object's vtable: substitute the prop colour in SetColorModulation/SetAlphaModulation while DrawModelStaticProp is drawing a models/props model, and set the modulation explicitly before the original draw in case the engine sets it just before the call. Vtable indices verified by disassembling CStudioRenderContext: 27 = three float stores from the argument pointer, 28 = one float, 30 = the DrawModelInfo_t-drawing entry with three arguments (the 2013 SDK header lists these slots five positions earlier in this build). --- src/esp.cpp | 2 + src/hl2h.h | 1 + src/studio.cpp | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 181 insertions(+) create mode 100644 src/studio.cpp (limited to 'src') diff --git a/src/esp.cpp b/src/esp.cpp index 9d041b0..67fbad2 100644 --- a/src/esp.cpp +++ b/src/esp.cpp @@ -1413,6 +1413,8 @@ void prop_tint_update() { static bool logged_missing = false; static bool logged_touched = false; bool on = feature_active(FEAT_WORLD_TINT) && g_tint_prop_on; + if (on) + studio_props_init(); if (!on && !applied) return; if (!g_iface.entitylist || g_net.clr_render < 0) { diff --git a/src/hl2h.h b/src/hl2h.h index 779a241..3ccf7b5 100644 --- a/src/hl2h.h +++ b/src/hl2h.h @@ -298,6 +298,7 @@ void draw_text_col(int x, int y, const char *s, int r, int g, int b); void esp_draw(); void esp_draw_items(); void prop_tint_update(); +void studio_props_init(); bool world_to_screen(const Vector &w, float &sx, float &sy); void set_silent_restore(const QAngle &angles); bool is_visible(const Vector &eye, const Vector &point, diff --git a/src/studio.cpp b/src/studio.cpp new file mode 100644 index 0000000..dd6f109 --- /dev/null +++ b/src/studio.cpp @@ -0,0 +1,178 @@ +// studio.cpp — colour static props at the StudioRender level. +// +// Dynamic props are tinted through their client entity's m_clrRender (see +// esp.cpp). Map-static props have no entity: the engine's static-prop manager +// calls IStudioRender::DrawModelStaticProp, and the colour modulation used for +// the draw comes from IStudioRender::SetColorModulation. Hook both on the +// interface object so the prop colour is substituted only while a prop model +// is being drawn. +// +// VStudioRender025 vtable (verified against studiorender.so's +// CStudioRenderContext vtable at .data.rel.ro): +// 27 = SetColorModulation(float const*) +// 28 = SetAlphaModulation(float) +// 30 = DrawModelStaticProp(const DrawModelInfo_t&, const matrix3x4_t&, int) +// (The 2013 SDK header lists these five slots earlier; this build's layout +// has additional methods before them.) + +#include "hl2h.h" +#include +#include +#include + +namespace { + +using SetColorFn = void (*)(void *, const float *); +using SetAlphaFn = void (*)(void *, float); +using DrawStaticFn = void (*)(void *, void *, void *, int); + +SetColorFn g_orig_set_color = nullptr; +SetAlphaFn g_orig_set_alpha = nullptr; +DrawStaticFn g_orig_draw_static = nullptr; + +bool g_install_tried = false; +bool g_installed = false; +int g_prop_depth = 0; + +bool prop_tint_on() { + return feature_active(FEAT_WORLD_TINT) && g_tint_prop_on; +} + +void prop_color(float out[4]) { + out[0] = (float)g_tint_prop_rgb[0] / 255.0f; + out[1] = (float)g_tint_prop_rgb[1] / 255.0f; + out[2] = (float)g_tint_prop_rgb[2] / 255.0f; + out[3] = 1.0f; +} + +// DrawModelInfo_t starts with studiohdr_t*; studiohdr_t::name is at +12 +// (id, version, checksum, then char name[64]). +const char *info_model_name(void *info) { + if (!info) + return nullptr; + void *hdr = *(void **)info; + if (!hdr) + return nullptr; + const char *name = (const char *)hdr + 12; + size_t n = strnlen(name, 128); + if (n < 7 || n >= 128) + return nullptr; + for (size_t i = 0; i < n; i++) { + unsigned char c = (unsigned char)name[i]; + if (c < 0x20 || c > 0x7e) + return nullptr; + } + return name; +} + +bool is_prop_model(const char *name) { + return name && strncmp(name, "models/props", 12) == 0; +} + +void hook_set_color(void *self, const float *color) { + if (g_prop_depth > 0 && prop_tint_on()) { + float c[4]; + prop_color(c); + g_orig_set_color(self, c); + return; + } + g_orig_set_color(self, color); +} + +void hook_set_alpha(void *self, float alpha) { + if (g_prop_depth > 0 && prop_tint_on()) { + g_orig_set_alpha(self, 1.0f); + return; + } + g_orig_set_alpha(self, alpha); +} + +void hook_draw_static_prop(void *self, void *info, void *model_to_world, + int flags) { + bool prop = prop_tint_on() && is_prop_model(info_model_name(info)); + if (prop) { + // The engine may have set the modulation just before this call; override + // it here as well as from the SetColorModulation hook (the static-prop + // renderer can call it either side of DrawModelStaticProp). + float c[4]; + prop_color(c); + g_orig_set_color(self, c); + g_orig_set_alpha(self, 1.0f); + g_prop_depth++; + } + g_orig_draw_static(self, info, model_to_world, flags); + if (prop) + g_prop_depth--; +} + +std::string loaded_module_path(const char *needle) { + FILE *maps = fopen("/proc/self/maps", "r"); + if (!maps) + return {}; + char line[1024]; + std::string found; + while (fgets(line, sizeof(line), maps)) { + char *p = strchr(line, '/'); + if (!p) + continue; + size_t pl = strlen(p); + while (pl && (p[pl - 1] == '\n' || p[pl - 1] == '\r')) + p[--pl] = 0; + if (!strstr(p, needle) || !strstr(p, ".so")) + continue; + found = p; + if (!strstr(p, "(deleted)")) + break; + } + fclose(maps); + return found; +} + +} // namespace + +void studio_props_init() { + if (g_install_tried) + return; + g_install_tried = true; + + std::string path = loaded_module_path("studiorender"); + void *h = nullptr; + if (!path.empty()) + h = dlopen(path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); + if (!h) + h = dlopen("studiorender.so", RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); + if (!h) { + logf("studio: studiorender.so not loaded"); + return; + } + auto cf = (CreateInterfaceFn)dlsym(h, "CreateInterface"); + if (!cf) { + logf("studio: studiorender CreateInterface missing"); + return; + } + void *sr = cf("VStudioRender025", nullptr); + if (!sr) { + logf("studio: VStudioRender025 not exposed"); + return; + } + + void **old_vt = *(void ***)sr; + if (!old_vt) { + logf("studio: interface has no vtable"); + return; + } + static void *vt[49]; + memcpy(vt, old_vt, sizeof(vt)); + + g_orig_set_color = (SetColorFn)vt[27]; + g_orig_set_alpha = (SetAlphaFn)vt[28]; + g_orig_draw_static = (DrawStaticFn)vt[30]; + vt[27] = (void *)&hook_set_color; + vt[28] = (void *)&hook_set_alpha; + vt[30] = (void *)&hook_draw_static_prop; + + *(void ***)sr = vt; + g_installed = true; + logf("studio: IStudioRender %p hooked (setcolor=%p drawstatic=%p)", sr, + (void *)g_orig_set_color, (void *)g_orig_draw_static); +} -- cgit v1.2.3