summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorsalmonik <salmonik@tuta.io>2026-11-04 22:09:14 +0100
committersalmonik <salmonik@tuta.io>2026-11-04 22:09:14 +0100
commit0cbcbfc14997eff39e0c76acf30c48b851ed1f62 (patch)
tree19a5b0996bdf9e096f79e9636ebb48b57e10e643
parentChams: real additive material, addref cached materials (diff)
downloadhl2h-0cbcbfc14997eff39e0c76acf30c48b851ed1f62.tar.gz
hl2h-0cbcbfc14997eff39e0c76acf30c48b851ed1f62.zip
Chams: working alpha via RenderView blend and a custom VMT
Testing on :99 showed the material $alpha path is a dead end: UnlitGeneric takes translucency from the base texture/vertex alpha, and the debug materials have neither. Follow Amalgam: apply alpha with IVRenderView::SetBlend (VEngineRenderView014 slot 4) around the model draw, saving/restoring it. To have something translucent to blend, write a tiny UnlitGeneric VMT with $model 1 + $translucent 1 + a white base texture to <game>/<mod>/materials/hl2hake/translucent.vmt (plus a custom/ copy). It is written by a preload constructor so it exists before the material system scans its search paths, then loaded once with FindMaterial. Styles 0/3/4/6 use it when the colour alpha is below 255. Also: - Visible wireframe + through-wall: fill the visible silhouette first so the through-wall pass cannot show in the wireframe gaps, then draw a brightened contour. Style 6 is now "Solid + contour" (recolour + visible edges). - find_modules builds local snapshots and publishes them under a mutex; the setup thread and the server retry could reset the shared vectors live and crash at startup. Verified on :99: Solid + alpha 64 renders a see-through viewmodel, Solid + contour shows a blue fill with bright blue contours, Additive is a translucent coloured glow and Fullbright is a white additive glow.
-rw-r--r--CONTEXT.md46
-rw-r--r--src/gui.cpp2
-rw-r--r--src/hl2h.cpp1
-rw-r--r--src/hl2h.h1
-rw-r--r--src/modules.cpp35
-rw-r--r--src/studio.cpp161
6 files changed, 210 insertions, 36 deletions
diff --git a/CONTEXT.md b/CONTEXT.md
index 5b96c33..1e860bf 100644
--- a/CONTEXT.md
+++ b/CONTEXT.md
@@ -27,7 +27,10 @@ covers ESP, aimbot, triggerbot and silent aim.
27## Runtime architecture 27## Runtime architecture
28 28
29- Modules from `/proc/self/maps`: `client.so`, `engine.so`, `server.so`. 29- Modules from `/proc/self/maps`: `client.so`, `engine.so`, `server.so`.
30 PROT_NONE ranges excluded; file-backed bss tails merged. 30 PROT_NONE ranges excluded; file-backed bss tails merged. The scan builds
31 local Module snapshots and publishes them under a mutex, because the setup
32 thread and the server-netvar retry can call it concurrently (resetting the
33 shared vectors live used to crash at startup).
31- Calling convention: cdecl (i386); `this` is the first stack argument. 34- Calling convention: cdecl (i386); `this` is the first stack argument.
32- Hooks (copy-and-swap vtable, 256 slots): 35- Hooks (copy-and-swap vtable, 256 slots):
33 - `CHLClient` slot 21 `CreateMove`; slot 35 `FrameStageNotify`. 36 - `CHLClient` slot 21 `CreateMove`; slot 35 `FrameStageNotify`.
@@ -294,30 +297,43 @@ vocabulary / decisions.
294 alpha only. The through-wall pass uses `MATERIAL_VAR_IGNOREZ` (1 << 15) 297 alpha only. The through-wall pass uses `MATERIAL_VAR_IGNOREZ` (1 << 15)
295 through `IMaterial::SetMaterialVarFlag` (slot 29); colours use 298 through `IMaterial::SetMaterialVarFlag` (slot 29); colours use
296 `$color`/`$color2` (IMaterialVar slot 11, SetVecValue 3-float slot 11) and 299 `$color`/`$color2` (IMaterialVar slot 11, SetVecValue 3-float slot 11) and
297 `ColorModulate` (slot 28). `$alpha` is set through IMaterialVar slot 3 300 `ColorModulate` (slot 28). Alpha is applied with
298 (SetFloatValue). Shared materials are restored after each draw. 301 `IVRenderView::SetBlend` (`VEngineRenderView014` slot 4, saved/restored
302 around the passes) — model draws take their final alpha from the render
303 view, not from material `$alpha`. `$alpha`/`AlphaModulate` are still set for
304 completeness. Shared materials are restored after each draw.
299- The GUI colour picker (`add_color_alpha`) has an alpha bar, checkerboard 305- The GUI colour picker (`add_color_alpha`) has an alpha bar, checkerboard
300 preview and `#RRGGBBAA` readout; plain `add_color` keeps the old opaque 306 preview and `#RRGGBBAA` readout; plain `add_color` keeps the old opaque
301 three-component behaviour. Chams colours are four ints (r, g, b, a). 307 three-component behaviour. Chams colours are four ints (r, g, b, a).
302- **Alpha is stored but has no visual effect yet.** The usable model 308- Alpha needs a translucent model material, so the mod writes a tiny VMT:
303 materials are opaque: `debug/debugtranslucentsinglecolor` has no 309 `<game>/<mod>/materials/hl2hake/translucent.vmt` (and a copy under
304 `$translucent`, and toggling `MATERIAL_VAR_TRANSLUCENT` (1 << 21) or 310 `custom/hl2hake/...`): `UnlitGeneric` with `$basetexture sprites/white`,
305 rebuilding the state snapshot does not change the static shader combo. 311 `$color`, `$model 1`, `$translucent 1`, `$nocull 1`. It is written from a
306 `debug/debugtranslucentvertexcolor`/`debug/debughitbox` are translucent but 312 preload constructor (so it exists before the material system scans) and
307 need vertex colours and render nothing on skinned models. A custom VMT with 313 loaded with `FindMaterial`; styles 0/3/4/6 switch to it when the colour
308 `$model 1` + `$translucent 1` (or a white basetexture) is needed to make the 314 alpha is below 255. The base-game `debugtranslucentsinglecolor` has no
309 alpha control real. 315 `$translucent` and cannot do alpha; `debugtranslucentvertexcolor` and
316 `debug/debughitbox` need vertex colours and draw nothing on skinned models.
310- Styles: 0 Flat (`debug/debugtranslucentsinglecolor` + FLAT), 1 Wireframe 317- Styles: 0 Flat (`debug/debugtranslucentsinglecolor` + FLAT), 1 Wireframe
311 (`debug/debugwireframe`), 2 Additive 318 (`debug/debugwireframe`, brightened tint), 2 Additive
312 (`debug/debugtranslucentmodelhulls`, whose VMT has `$additive 1` + `$color`; 319 (`debug/debugtranslucentmodelhulls`, whose VMT has `$additive 1` + `$color`;
313 the ADDITIVE flag on the solid material did nothing), 3 Solid, 4 White 320 the ADDITIVE flag on the solid material did nothing), 3 Solid, 4 White
314 (solid, colour forced white), 5 Fullbright (the additive material, colour 321 (solid, colour forced white), 5 Fullbright (the additive material, colour
315 forced white, giving a white-hot glow), 6 Glow outline (solid plus a 322 forced white, giving a white-hot glow), 6 Solid + contour (solid fill plus
316 `debug/debugwireframe` contour pass drawn with ZNEARER (1 << 10) and a 323 a `debug/debugwireframe` contour pass drawn with ZNEARER (1 << 10) and a
317 brightened colour so the base cannot bury it). 324 brightened colour, so the entity is recoloured while its edges stay
325 readable). Style 6 is the requested "recolour the whole entity but show its
326 contours" look.
318 `vgui/white` is vertex-colour based and has no `$model`/`$basetexture`, so it 327 `vgui/white` is vertex-colour based and has no `$model`/`$basetexture`, so it
319 is never used for chams; `models/debug/debugwhite` is EP2-only and the old 328 is never used for chams; `models/debug/debugwhite` is EP2-only and the old
320 VGUI additive material was invisible on NPCs. 329 VGUI additive material was invisible on NPCs.
330- Through-wall + visible wireframe: a wireframe visible pass leaves gaps, so
331 the through-wall fill showed inside the visible silhouette. When both passes
332 are on and the visible style is Wireframe, a solid fill of the visible
333 colour is drawn first, then the brightened wireframe contour. (The proper
334 alternative is stencil: visible writes stencil 1, through-wall tests
335 stencil == 0, as Amalgam does; the fill is simpler and gives the
336 solid+contour look.)
321- Style materials are preloaded from `outer_draw` (throttled) and cached; the 337- Style materials are preloaded from `outer_draw` (throttled) and cached; the
322 render hook never calls FindMaterial itself. A missing style uses solid. 338 render hook never calls FindMaterial itself. A missing style uses solid.
323 `FindMaterial` can return an unreferenced material, so each cached material 339 `FindMaterial` can return an unreferenced material, so each cached material
diff --git a/src/gui.cpp b/src/gui.cpp
index 89888da..fe82762 100644
--- a/src/gui.cpp
+++ b/src/gui.cpp
@@ -459,7 +459,7 @@ static void build_cards() {
459 "Per-relationship model colours.", true); 459 "Per-relationship model colours.", true);
460 static const char *const chams_mat_names[] = { 460 static const char *const chams_mat_names[] = {
461 "Flat", "Wireframe", "Additive glow", "Solid", "White", "Fullbright", 461 "Flat", "Wireframe", "Additive glow", "Solid", "White", "Fullbright",
462 "Glow outline"}; 462 "Solid + contour"};
463 auto add_chams_group = [&](const char *title, ChamsGroup &g) { 463 auto add_chams_group = [&](const char *title, ChamsGroup &g) {
464 add_section(chams, title); 464 add_section(chams, title);
465 add_toggle(chams, "Visible", "Draw the normal-depth pass.", 465 add_toggle(chams, "Visible", "Draw the normal-depth pass.",
diff --git a/src/hl2h.cpp b/src/hl2h.cpp
index 067b11f..ddfc02a 100644
--- a/src/hl2h.cpp
+++ b/src/hl2h.cpp
@@ -1149,6 +1149,7 @@ static void *setup_thread(void *) {
1149 g_iface.debugoverlay = ef("VDebugOverlay003", nullptr); 1149 g_iface.debugoverlay = ef("VDebugOverlay003", nullptr);
1150 g_iface.enginetrace = ef("EngineTraceClient003", nullptr); 1150 g_iface.enginetrace = ef("EngineTraceClient003", nullptr);
1151 g_iface.modelrender = ef("VEngineModel016", nullptr); 1151 g_iface.modelrender = ef("VEngineModel016", nullptr);
1152 g_iface.renderview = ef("VEngineRenderView014", nullptr);
1152 static const char *cvar_names[] = {"VEngineCvar004", "VEngineCvar007", 1153 static const char *cvar_names[] = {"VEngineCvar004", "VEngineCvar007",
1153 "VEngineCvar003", "IVEngineCvar004", 1154 "VEngineCvar003", "IVEngineCvar004",
1154 nullptr}; 1155 nullptr};
diff --git a/src/hl2h.h b/src/hl2h.h
index f7274f1..34bf04f 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -69,6 +69,7 @@ struct GameInterfaces {
69 void *enginetrace = nullptr; // EngineTraceClient003 (world vischeck) 69 void *enginetrace = nullptr; // EngineTraceClient003 (world vischeck)
70 void *cvar = nullptr; // VEngineCvar004 (mat_fullbright etc.) 70 void *cvar = nullptr; // VEngineCvar004 (mat_fullbright etc.)
71 void *modelrender = nullptr; // VEngineModel016 (chams) 71 void *modelrender = nullptr; // VEngineModel016 (chams)
72 void *renderview = nullptr; // VEngineRenderView014 (alpha/color modulation)
72}; 73};
73extern GameInterfaces g_iface; 74extern GameInterfaces g_iface;
74 75
diff --git a/src/modules.cpp b/src/modules.cpp
index b832694..7a82028 100644
--- a/src/modules.cpp
+++ b/src/modules.cpp
@@ -1,8 +1,11 @@
1// modules.cpp — /proc/self/maps parsing and module discovery 1// modules.cpp — /proc/self/maps parsing and module discovery
2#include "hl2h.h" 2#include "hl2h.h"
3#include <elf.h> 3#include <elf.h>
4#include <mutex>
4#include <vector> 5#include <vector>
5 6
7static std::mutex g_modules_mutex;
8
6static bool basename_is(const char *path, const char *base) { 9static bool basename_is(const char *path, const char *base) {
7 size_t pl = strlen(path), bl = strlen(base); 10 size_t pl = strlen(path), bl = strlen(base);
8 if (pl < bl || strcmp(path + pl - bl, base) != 0) 11 if (pl < bl || strcmp(path + pl - bl, base) != 0)
@@ -11,9 +14,7 @@ static bool basename_is(const char *path, const char *base) {
11} 14}
12 15
13void find_modules() { 16void find_modules() {
14 g_client = Module(); 17 Module client, engine, server;
15 g_engine = Module();
16 g_server = Module();
17 FILE *f = fopen("/proc/self/maps", "r"); 18 FILE *f = fopen("/proc/self/maps", "r");
18 if (!f) 19 if (!f)
19 return; 20 return;
@@ -41,19 +42,19 @@ void find_modules() {
41 memcpy(clean, path, pl); 42 memcpy(clean, path, pl);
42 clean[pl] = 0; 43 clean[pl] = 0;
43 if (basename_is(clean, "client.so")) 44 if (basename_is(clean, "client.so"))
44 m = &g_client; 45 m = &client;
45 else if (basename_is(clean, "engine.so")) 46 else if (basename_is(clean, "engine.so"))
46 m = &g_engine; 47 m = &engine;
47 else if (basename_is(clean, "server.so")) 48 else if (basename_is(clean, "server.so"))
48 m = &g_server; 49 m = &server;
49 } else { 50 } else {
50 // anon: absorb only as .bss tail after a file-backed range 51 // anon: absorb only as .bss tail after a file-backed range
51 if (g_client.valid() && g_client.ranges.back().file && 52 if (client.valid() && client.ranges.back().file &&
52 lo == g_client.ranges.back().hi && hi - lo <= 0x1000000) 53 lo == client.ranges.back().hi && hi - lo <= 0x1000000)
53 m = &g_client; 54 m = &client;
54 else if (g_engine.valid() && g_engine.ranges.back().file && 55 else if (engine.valid() && engine.ranges.back().file &&
55 lo == g_engine.ranges.back().hi && hi - lo <= 0x1000000) 56 lo == engine.ranges.back().hi && hi - lo <= 0x1000000)
56 m = &g_engine; 57 m = &engine;
57 } 58 }
58 if (!m) 59 if (!m)
59 continue; 60 continue;
@@ -71,6 +72,16 @@ void find_modules() {
71 } 72 }
72 } 73 }
73 fclose(f); 74 fclose(f);
75 // setup_thread and resolve_server_netvars can both call this; the old code
76 // reset the shared Module globals and reallocated their vectors live, which
77 // crashed under the startup thread. Publish complete snapshots instead.
78 std::lock_guard<std::mutex> lock(g_modules_mutex);
79 if (client.valid())
80 g_client = std::move(client);
81 if (engine.valid())
82 g_engine = std::move(engine);
83 if (server.valid())
84 g_server = std::move(server);
74} 85}
75 86
76// --------------------------------------------------------------------------- 87// ---------------------------------------------------------------------------
diff --git a/src/studio.cpp b/src/studio.cpp
index cf145d1..3ee6cc5 100644
--- a/src/studio.cpp
+++ b/src/studio.cpp
@@ -19,7 +19,10 @@
19#include <cstdio> 19#include <cstdio>
20#include <cstring> 20#include <cstring>
21#include <dlfcn.h> 21#include <dlfcn.h>
22#include <string>
23#include <sys/stat.h>
22#include <time.h> 24#include <time.h>
25#include <unistd.h>
23 26
24namespace { 27namespace {
25 28
@@ -197,7 +200,7 @@ using ForceOverrideFn = void (*)(void *, void *, int);
197void *g_modelrender = nullptr; 200void *g_modelrender = nullptr;
198ModelExecFn g_orig_model_exec = nullptr; 201ModelExecFn g_orig_model_exec = nullptr;
199ForceOverrideFn g_force_override = nullptr; 202ForceOverrideFn g_force_override = nullptr;
200void *g_style_mats[7] = {}; 203void *g_style_mats[8] = {};
201 204
202const char *chams_material_name(int idx) { 205const char *chams_material_name(int idx) {
203 switch (idx) { 206 switch (idx) {
@@ -257,6 +260,12 @@ void chams_apply(void *mat, bool ignorez, int style, bool force_white,
257 float b = force_white ? 1.0f : (float)rgb[2] / 255.0f; 260 float b = force_white ? 1.0f : (float)rgb[2] / 255.0f;
258 chams_color(mat, r, g, b); 261 chams_color(mat, r, g, b);
259 chams_alpha(mat, (float)rgb[3] / 255.0f); 262 chams_alpha(mat, (float)rgb[3] / 255.0f);
263 {
264 // Entity-style alpha fade; UnlitGeneric takes translucency from the base
265 // texture/vertex alpha, so $alpha alone is not enough.
266 using AlphaFn = void (*)(void *, float);
267 vfunc<AlphaFn>(mat, 27)(mat, (float)rgb[3] / 255.0f);
268 }
260 // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright 269 // 0 Flat, 1 Wireframe, 2 Additive, 3 Solid, 4 White, 5 Fullbright
261 // (additive white glow), 6 Glow outline (solid base; the wireframe overlay 270 // (additive white glow), 6 Glow outline (solid base; the wireframe overlay
262 // is a second draw with ZNEARER so its contour is not hidden by the base). 271 // is a second draw with ZNEARER so its contour is not hidden by the base).
@@ -270,8 +279,7 @@ void chams_apply(void *mat, bool ignorez, int style, bool force_white,
270 vfunc<FlagFn>(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5); 279 vfunc<FlagFn>(mat, 29)(mat, 1 << 6 /* SELFILLUM */, style == 5);
271} 280}
272 281
273void chams_reset(void *mat) { 282void chams_reset(void *mat) { using FlagFn = void (*)(void *, int, bool);
274 using FlagFn = void (*)(void *, int, bool);
275 vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false); 283 vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false);
276 vfunc<FlagFn>(mat, 29)(mat, 1 << 10, false); 284 vfunc<FlagFn>(mat, 29)(mat, 1 << 10, false);
277 vfunc<FlagFn>(mat, 29)(mat, 1 << 28, false); 285 vfunc<FlagFn>(mat, 29)(mat, 1 << 28, false);
@@ -282,6 +290,22 @@ void chams_reset(void *mat) {
282 chams_alpha(mat, 1.0f); 290 chams_alpha(mat, 1.0f);
283} 291}
284 292
293// Model draws take their final alpha from IVRenderView::SetBlend; material
294// $alpha alone is ignored by UnlitGeneric. Save/restore it around our passes.
295float chams_get_blend() {
296 if (!g_iface.renderview)
297 return 1.0f;
298 using GetFn = float (*)(void *);
299 return vfunc<GetFn>(g_iface.renderview, 5)(g_iface.renderview);
300}
301
302void chams_set_blend(float a) {
303 if (!g_iface.renderview)
304 return;
305 using SetFn = void (*)(void *, float);
306 vfunc<SetFn>(g_iface.renderview, 4)(g_iface.renderview, a);
307}
308
285void hkdModelExecute(void *self, const void *state, const void *info, 309void hkdModelExecute(void *self, const void *state, const void *info,
286 void *bone) { 310 void *bone) {
287 if (!g_orig_model_exec) 311 if (!g_orig_model_exec)
@@ -304,21 +328,35 @@ void hkdModelExecute(void *self, const void *state, const void *info,
304 if (style < 0) style = 0; 328 if (style < 0) style = 0;
305 if (style > 6) style = 6; 329 if (style > 6) style = 6;
306 void *mat = style_material(style); 330 void *mat = style_material(style);
331 bool alpha = rgb[3] < 255;
332 if (alpha && g_style_mats[7] &&
333 (style == 0 || style == 3 || style == 4 || style == 6))
334 mat = g_style_mats[7];
307 if (!mat) 335 if (!mat)
308 return false; 336 return false;
309 bool white = style == 4 || style == 5; 337 bool white = style == 4 || style == 5;
338 // Wireframe lines use a brighter tint so they stay readable over a
339 // solid fill.
340 int wrgb[4];
310 const int *use_rgb = rgb; 341 const int *use_rgb = rgb;
342 if (style == 1) {
343 for (int i = 0; i < 3; i++)
344 wrgb[i] = (rgb[i] + 255) / 2;
345 wrgb[3] = rgb[3];
346 use_rgb = wrgb;
347 }
311 chams_apply(mat, ignorez, style, white, use_rgb, false); 348 chams_apply(mat, ignorez, style, white, use_rgb, false);
349 chams_set_blend((float)use_rgb[3] / 255.0f);
312 g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); 350 g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */);
313 g_orig_model_exec(self, state, info, bone); 351 g_orig_model_exec(self, state, info, bone);
314 if (style == 6) { 352 if (style == 6) {
315 // Solid base plus a wireframe contour pass for depth. Brighten 353 // Solid base plus a wireframe contour pass for depth.
316 // the contour and bias it forward so the base cannot bury it.
317 void *wire = g_style_mats[1]; 354 void *wire = g_style_mats[1];
318 if (wire && wire != mat) { 355 if (wire && wire != mat) {
319 int wrgb[4] = {(rgb[0] + 255) / 2, (rgb[1] + 255) / 2, 356 int crgb[4] = {(rgb[0] + 255) / 2, (rgb[1] + 255) / 2,
320 (rgb[2] + 255) / 2, rgb[3]}; 357 (rgb[2] + 255) / 2, rgb[3]};
321 chams_apply(wire, ignorez, 1, false, wrgb, true); 358 chams_apply(wire, ignorez, 1, false, crgb, true);
359 chams_set_blend((float)crgb[3] / 255.0f);
322 g_force_override(self, wire, 0); 360 g_force_override(self, wire, 0);
323 g_orig_model_exec(self, state, info, bone); 361 g_orig_model_exec(self, state, info, bone);
324 chams_reset(wire); 362 chams_reset(wire);
@@ -327,17 +365,24 @@ void hkdModelExecute(void *self, const void *state, const void *info,
327 chams_reset(mat); 365 chams_reset(mat);
328 return true; 366 return true;
329 }; 367 };
368 float old_blend = chams_get_blend();
330 bool did = false; 369 bool did = false;
331 if (is_vm) { 370 if (is_vm) {
332 did = draw_pass(g_chams_vm_mat, g_chams_vm_rgb, false); 371 did = draw_pass(g_chams_vm_mat, g_chams_vm_rgb, false);
333 } else { 372 } else {
334 if (grp->inv_on) 373 if (grp->inv_on)
335 did |= draw_pass(grp->inv_mat, grp->inv_rgb, true); 374 did |= draw_pass(grp->inv_mat, grp->inv_rgb, true);
336 if (grp->vis_on) 375 if (grp->vis_on) {
376 // A wireframe visible pass leaves gaps, so the through-wall fill
377 // would show inside the visible silhouette. Fill it first.
378 if (grp->inv_on && grp->vis_mat == 1)
379 did |= draw_pass(3, grp->vis_rgb, false);
337 did |= draw_pass(grp->vis_mat, grp->vis_rgb, false); 380 did |= draw_pass(grp->vis_mat, grp->vis_rgb, false);
381 }
338 } 382 }
339 if (did) { 383 if (did) {
340 g_force_override(self, nullptr, 0); 384 g_force_override(self, nullptr, 0);
385 chams_set_blend(old_blend);
341 return; 386 return;
342 } 387 }
343 } 388 }
@@ -354,6 +399,96 @@ static double chams_now() {
354 return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9; 399 return (double)ts.tv_sec + (double)ts.tv_nsec * 1e-9;
355} 400}
356 401
402// The base-game model debug materials are opaque. Drop a tiny VMT with
403// $model + $translucent into the game's custom/materials folders so the alpha
404// channel in the colour picker has something to drive. Written from a
405// constructor too, so it exists before the material system scans its paths.
406// Only our own hl2hake folder is touched.
407static void chams_mkdirs(const std::string &path) {
408 std::string cur;
409 for (size_t i = 0; i < path.size(); i++) {
410 cur += path[i];
411 if (path[i] == '/')
412 mkdir(cur.c_str(), 0755);
413 }
414 mkdir(path.c_str(), 0755);
415}
416
417static bool chams_write_vmt_at(const std::string &dir) {
418 chams_mkdirs(dir);
419 std::string path = dir + "/translucent.vmt";
420 FILE *f = fopen(path.c_str(), "w");
421 if (!f)
422 return false;
423 fputs("\"UnlitGeneric\"\n"
424 "{\n"
425 " \"$basetexture\" \"sprites/white\"\n"
426 " \"$color\" \"[1 1 1]\"\n"
427 " \"$alpha\" \"1\"\n"
428 " \"$translucent\" \"1\"\n"
429 " \"$model\" \"1\"\n"
430 " \"$nocull\" \"1\"\n"
431 " \"%noToolTexture\" \"1\"\n"
432 "}\n",
433 f);
434 fclose(f);
435 return true;
436}
437
438static std::string chams_game_arg() {
439 FILE *f = fopen("/proc/self/cmdline", "rb");
440 if (!f)
441 return std::string();
442 std::string data;
443 int c;
444 while ((c = fgetc(f)) != EOF)
445 data += (char)c;
446 fclose(f);
447 size_t pos = 0;
448 while (pos < data.size()) {
449 size_t end = data.find('\0', pos);
450 if (end == std::string::npos)
451 end = data.size();
452 if (data.compare(pos, end - pos, "-game") == 0) {
453 size_t n = end + 1;
454 size_t e2 = data.find('\0', n);
455 if (e2 == std::string::npos)
456 e2 = data.size();
457 return data.substr(n, e2 - n);
458 }
459 pos = end + 1;
460 }
461 return std::string();
462}
463
464static void chams_write_translucent_vmt() {
465 static bool done = false;
466 if (done)
467 return;
468 char cwd[4096];
469 ssize_t n = readlink("/proc/self/cwd", cwd, sizeof(cwd) - 1);
470 if (n <= 0)
471 return;
472 cwd[n] = 0;
473 std::string root(cwd);
474 std::string mod = chams_game_arg();
475 if (mod.empty())
476 mod = "hl2";
477 std::string base = (!mod.empty() && mod[0] == '/') ? mod : root + "/" + mod;
478 // Only write when this really is a Source game directory (the wrapper
479 // processes also preload us, from the repo directory).
480 if (access((base + "/gameinfo.txt").c_str(), R_OK) != 0)
481 return;
482 bool ok = false;
483 ok |= chams_write_vmt_at(base + "/custom/hl2hake/materials/hl2hake");
484 ok |= chams_write_vmt_at(base + "/materials/hl2hake");
485 done = ok;
486}
487
488__attribute__((constructor)) static void chams_early_vmt() {
489 chams_write_translucent_vmt();
490}
491
357// Called from outer_draw (outside the model draw) so FindMaterial can load 492// Called from outer_draw (outside the model draw) so FindMaterial can load
358// the style VMTs. The render hook only reads the cached pointers. 493// the style VMTs. The render hook only reads the cached pointers.
359void chams_preload() { 494void chams_preload() {
@@ -375,6 +510,16 @@ void chams_preload() {
375 logf("chams: preloaded style %d %s = %p", i, chams_material_name(i), m); 510 logf("chams: preloaded style %d %s = %p", i, chams_material_name(i), m);
376 } 511 }
377 } 512 }
513 if (!g_style_mats[7]) {
514 chams_write_translucent_vmt();
515 void *m = world_find_material("hl2hake/translucent");
516 if (m) {
517 using RefFn = void (*)(void *);
518 vfunc<RefFn>(m, 12)(m);
519 g_style_mats[7] = m;
520 logf("chams: preloaded translucent material = %p", m);
521 }
522 }
378} 523}
379 524
380void chams_init() { 525void chams_init() {