diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 01:16:51 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 01:16:51 +0100 |
| commit | 76c88403d18ef01e8f15b89fcd6d09f0825a1bfc (patch) | |
| tree | 04049208055fb0533af01a46a2a96c2b9ce491a5 /src | |
| parent | World tint: colour dynamic props through m_clrRender (diff) | |
| download | hl2h-76c88403d18ef01e8f15b89fcd6d09f0825a1bfc.tar.gz hl2h-76c88403d18ef01e8f15b89fcd6d09f0825a1bfc.zip | |
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).
Diffstat (limited to '')
| -rw-r--r-- | src/esp.cpp | 2 | ||||
| -rw-r--r-- | src/hl2h.h | 1 | ||||
| -rw-r--r-- | src/studio.cpp | 178 |
3 files changed, 181 insertions, 0 deletions
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() { | |||
| 1413 | static bool logged_missing = false; | 1413 | static bool logged_missing = false; |
| 1414 | static bool logged_touched = false; | 1414 | static bool logged_touched = false; |
| 1415 | bool on = feature_active(FEAT_WORLD_TINT) && g_tint_prop_on; | 1415 | bool on = feature_active(FEAT_WORLD_TINT) && g_tint_prop_on; |
| 1416 | if (on) | ||
| 1417 | studio_props_init(); | ||
| 1416 | if (!on && !applied) | 1418 | if (!on && !applied) |
| 1417 | return; | 1419 | return; |
| 1418 | if (!g_iface.entitylist || g_net.clr_render < 0) { | 1420 | if (!g_iface.entitylist || g_net.clr_render < 0) { |
| @@ -298,6 +298,7 @@ void draw_text_col(int x, int y, const char *s, int r, int g, int b); | |||
| 298 | void esp_draw(); | 298 | void esp_draw(); |
| 299 | void esp_draw_items(); | 299 | void esp_draw_items(); |
| 300 | void prop_tint_update(); | 300 | void prop_tint_update(); |
| 301 | void studio_props_init(); | ||
| 301 | bool world_to_screen(const Vector &w, float &sx, float &sy); | 302 | bool world_to_screen(const Vector &w, float &sx, float &sy); |
| 302 | void set_silent_restore(const QAngle &angles); | 303 | void set_silent_restore(const QAngle &angles); |
| 303 | bool is_visible(const Vector &eye, const Vector &point, | 304 | 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 @@ | |||
| 1 | // studio.cpp — colour static props at the StudioRender level. | ||
| 2 | // | ||
| 3 | // Dynamic props are tinted through their client entity's m_clrRender (see | ||
| 4 | // esp.cpp). Map-static props have no entity: the engine's static-prop manager | ||
| 5 | // calls IStudioRender::DrawModelStaticProp, and the colour modulation used for | ||
| 6 | // the draw comes from IStudioRender::SetColorModulation. Hook both on the | ||
| 7 | // interface object so the prop colour is substituted only while a prop model | ||
| 8 | // is being drawn. | ||
| 9 | // | ||
| 10 | // VStudioRender025 vtable (verified against studiorender.so's | ||
| 11 | // CStudioRenderContext vtable at .data.rel.ro): | ||
| 12 | // 27 = SetColorModulation(float const*) | ||
| 13 | // 28 = SetAlphaModulation(float) | ||
| 14 | // 30 = DrawModelStaticProp(const DrawModelInfo_t&, const matrix3x4_t&, int) | ||
| 15 | // (The 2013 SDK header lists these five slots earlier; this build's layout | ||
| 16 | // has additional methods before them.) | ||
| 17 | |||
| 18 | #include "hl2h.h" | ||
| 19 | #include <cstdio> | ||
| 20 | #include <cstring> | ||
| 21 | #include <dlfcn.h> | ||
| 22 | |||
| 23 | namespace { | ||
| 24 | |||
| 25 | using SetColorFn = void (*)(void *, const float *); | ||
| 26 | using SetAlphaFn = void (*)(void *, float); | ||
| 27 | using DrawStaticFn = void (*)(void *, void *, void *, int); | ||
| 28 | |||
| 29 | SetColorFn g_orig_set_color = nullptr; | ||
| 30 | SetAlphaFn g_orig_set_alpha = nullptr; | ||
| 31 | DrawStaticFn g_orig_draw_static = nullptr; | ||
| 32 | |||
| 33 | bool g_install_tried = false; | ||
| 34 | bool g_installed = false; | ||
| 35 | int g_prop_depth = 0; | ||
| 36 | |||
| 37 | bool prop_tint_on() { | ||
| 38 | return feature_active(FEAT_WORLD_TINT) && g_tint_prop_on; | ||
| 39 | } | ||
| 40 | |||
| 41 | void prop_color(float out[4]) { | ||
| 42 | out[0] = (float)g_tint_prop_rgb[0] / 255.0f; | ||
| 43 | out[1] = (float)g_tint_prop_rgb[1] / 255.0f; | ||
| 44 | out[2] = (float)g_tint_prop_rgb[2] / 255.0f; | ||
| 45 | out[3] = 1.0f; | ||
| 46 | } | ||
| 47 | |||
| 48 | // DrawModelInfo_t starts with studiohdr_t*; studiohdr_t::name is at +12 | ||
| 49 | // (id, version, checksum, then char name[64]). | ||
| 50 | const char *info_model_name(void *info) { | ||
| 51 | if (!info) | ||
| 52 | return nullptr; | ||
| 53 | void *hdr = *(void **)info; | ||
| 54 | if (!hdr) | ||
| 55 | return nullptr; | ||
| 56 | const char *name = (const char *)hdr + 12; | ||
| 57 | size_t n = strnlen(name, 128); | ||
| 58 | if (n < 7 || n >= 128) | ||
| 59 | return nullptr; | ||
| 60 | for (size_t i = 0; i < n; i++) { | ||
| 61 | unsigned char c = (unsigned char)name[i]; | ||
| 62 | if (c < 0x20 || c > 0x7e) | ||
| 63 | return nullptr; | ||
| 64 | } | ||
| 65 | return name; | ||
| 66 | } | ||
| 67 | |||
| 68 | bool is_prop_model(const char *name) { | ||
| 69 | return name && strncmp(name, "models/props", 12) == 0; | ||
| 70 | } | ||
| 71 | |||
| 72 | void hook_set_color(void *self, const float *color) { | ||
| 73 | if (g_prop_depth > 0 && prop_tint_on()) { | ||
| 74 | float c[4]; | ||
| 75 | prop_color(c); | ||
| 76 | g_orig_set_color(self, c); | ||
| 77 | return; | ||
| 78 | } | ||
| 79 | g_orig_set_color(self, color); | ||
| 80 | } | ||
| 81 | |||
| 82 | void hook_set_alpha(void *self, float alpha) { | ||
| 83 | if (g_prop_depth > 0 && prop_tint_on()) { | ||
| 84 | g_orig_set_alpha(self, 1.0f); | ||
| 85 | return; | ||
| 86 | } | ||
| 87 | g_orig_set_alpha(self, alpha); | ||
| 88 | } | ||
| 89 | |||
| 90 | void hook_draw_static_prop(void *self, void *info, void *model_to_world, | ||
| 91 | int flags) { | ||
| 92 | bool prop = prop_tint_on() && is_prop_model(info_model_name(info)); | ||
| 93 | if (prop) { | ||
| 94 | // The engine may have set the modulation just before this call; override | ||
| 95 | // it here as well as from the SetColorModulation hook (the static-prop | ||
| 96 | // renderer can call it either side of DrawModelStaticProp). | ||
| 97 | float c[4]; | ||
| 98 | prop_color(c); | ||
| 99 | g_orig_set_color(self, c); | ||
| 100 | g_orig_set_alpha(self, 1.0f); | ||
| 101 | g_prop_depth++; | ||
| 102 | } | ||
| 103 | g_orig_draw_static(self, info, model_to_world, flags); | ||
| 104 | if (prop) | ||
| 105 | g_prop_depth--; | ||
| 106 | } | ||
| 107 | |||
| 108 | std::string loaded_module_path(const char *needle) { | ||
| 109 | FILE *maps = fopen("/proc/self/maps", "r"); | ||
| 110 | if (!maps) | ||
| 111 | return {}; | ||
| 112 | char line[1024]; | ||
| 113 | std::string found; | ||
| 114 | while (fgets(line, sizeof(line), maps)) { | ||
| 115 | char *p = strchr(line, '/'); | ||
| 116 | if (!p) | ||
| 117 | continue; | ||
| 118 | size_t pl = strlen(p); | ||
| 119 | while (pl && (p[pl - 1] == '\n' || p[pl - 1] == '\r')) | ||
| 120 | p[--pl] = 0; | ||
| 121 | if (!strstr(p, needle) || !strstr(p, ".so")) | ||
| 122 | continue; | ||
| 123 | found = p; | ||
| 124 | if (!strstr(p, "(deleted)")) | ||
| 125 | break; | ||
| 126 | } | ||
| 127 | fclose(maps); | ||
| 128 | return found; | ||
| 129 | } | ||
| 130 | |||
| 131 | } // namespace | ||
| 132 | |||
| 133 | void studio_props_init() { | ||
| 134 | if (g_install_tried) | ||
| 135 | return; | ||
| 136 | g_install_tried = true; | ||
| 137 | |||
| 138 | std::string path = loaded_module_path("studiorender"); | ||
| 139 | void *h = nullptr; | ||
| 140 | if (!path.empty()) | ||
| 141 | h = dlopen(path.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); | ||
| 142 | if (!h) | ||
| 143 | h = dlopen("studiorender.so", RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); | ||
| 144 | if (!h) { | ||
| 145 | logf("studio: studiorender.so not loaded"); | ||
| 146 | return; | ||
| 147 | } | ||
| 148 | auto cf = (CreateInterfaceFn)dlsym(h, "CreateInterface"); | ||
| 149 | if (!cf) { | ||
| 150 | logf("studio: studiorender CreateInterface missing"); | ||
| 151 | return; | ||
| 152 | } | ||
| 153 | void *sr = cf("VStudioRender025", nullptr); | ||
| 154 | if (!sr) { | ||
| 155 | logf("studio: VStudioRender025 not exposed"); | ||
| 156 | return; | ||
| 157 | } | ||
| 158 | |||
| 159 | void **old_vt = *(void ***)sr; | ||
| 160 | if (!old_vt) { | ||
| 161 | logf("studio: interface has no vtable"); | ||
| 162 | return; | ||
| 163 | } | ||
| 164 | static void *vt[49]; | ||
| 165 | memcpy(vt, old_vt, sizeof(vt)); | ||
| 166 | |||
| 167 | g_orig_set_color = (SetColorFn)vt[27]; | ||
| 168 | g_orig_set_alpha = (SetAlphaFn)vt[28]; | ||
| 169 | g_orig_draw_static = (DrawStaticFn)vt[30]; | ||
| 170 | vt[27] = (void *)&hook_set_color; | ||
| 171 | vt[28] = (void *)&hook_set_alpha; | ||
| 172 | vt[30] = (void *)&hook_draw_static_prop; | ||
| 173 | |||
| 174 | *(void ***)sr = vt; | ||
| 175 | g_installed = true; | ||
| 176 | logf("studio: IStudioRender %p hooked (setcolor=%p drawstatic=%p)", sr, | ||
| 177 | (void *)g_orig_set_color, (void *)g_orig_draw_static); | ||
| 178 | } | ||
