summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 01:16:51 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 01:16:51 +0100
commit76c88403d18ef01e8f15b89fcd6d09f0825a1bfc (patch)
tree04049208055fb0533af01a46a2a96c2b9ce491a5
parentWorld tint: colour dynamic props through m_clrRender (diff)
downloadhl2h-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 '')
-rwxr-xr-xbuild.sh2
-rw-r--r--src/esp.cpp2
-rw-r--r--src/hl2h.h1
-rw-r--r--src/studio.cpp178
4 files changed, 182 insertions, 1 deletions
diff --git a/build.sh b/build.sh
index b59f20e..b12f67b 100755
--- a/build.sh
+++ b/build.sh
@@ -3,6 +3,6 @@ set -e
3cd "$(dirname "$0")" 3cd "$(dirname "$0")"
4g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden -fno-omit-frame-pointer \ 4g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden -fno-omit-frame-pointer \
5 -o libhl2hake.so \ 5 -o libhl2hake.so \
6 src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/gui.cpp src/esp.cpp src/server.cpp src/bones.cpp src/vischeck.cpp src/world.cpp \ 6 src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp src/gui.cpp src/esp.cpp src/server.cpp src/bones.cpp src/vischeck.cpp src/world.cpp src/studio.cpp \
7 -ldl -Wl,--no-undefined 7 -ldl -Wl,--no-undefined
8file libhl2hake.so 8file libhl2hake.so
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) {
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);
298void esp_draw(); 298void esp_draw();
299void esp_draw_items(); 299void esp_draw_items();
300void prop_tint_update(); 300void prop_tint_update();
301void studio_props_init();
301bool world_to_screen(const Vector &w, float &sx, float &sy); 302bool world_to_screen(const Vector &w, float &sx, float &sy);
302void set_silent_restore(const QAngle &angles); 303void set_silent_restore(const QAngle &angles);
303bool is_visible(const Vector &eye, const Vector &point, 304bool 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
23namespace {
24
25using SetColorFn = void (*)(void *, const float *);
26using SetAlphaFn = void (*)(void *, float);
27using DrawStaticFn = void (*)(void *, void *, void *, int);
28
29SetColorFn g_orig_set_color = nullptr;
30SetAlphaFn g_orig_set_alpha = nullptr;
31DrawStaticFn g_orig_draw_static = nullptr;
32
33bool g_install_tried = false;
34bool g_installed = false;
35int g_prop_depth = 0;
36
37bool prop_tint_on() {
38 return feature_active(FEAT_WORLD_TINT) && g_tint_prop_on;
39}
40
41void 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]).
50const 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
68bool is_prop_model(const char *name) {
69 return name && strncmp(name, "models/props", 12) == 0;
70}
71
72void 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
82void 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
90void 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
108std::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
133void 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}