diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 17:51:20 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 17:51:20 +0100 |
| commit | 49df428d95a7c443d418d5b7e2a93af599b54f76 (patch) | |
| tree | 604e64048aacc5e2b86f441e03e5e5ab8600bef2 | |
| parent | Items: skip the CNetworkArray length header for m_iAmmo/m_hMyWeapons (diff) | |
| download | hl2h-49df428d95a7c443d418d5b7e2a93af599b54f76.tar.gz hl2h-49df428d95a7c443d418d5b7e2a93af599b54f76.zip | |
Add chams
- Hook IVModelRender::DrawModelExecute (VEngineModel016 slot 19) and force
models/debug/debugwhite for NPC models, identified by
ModelRenderInfo_t::entity_index.
- Two passes: MATERIAL_VAR_IGNOREZ through walls, then normal depth, with
IMaterial::ColorModulate for the configured colour; restore the shared
material's flag/colour afterwards.
- Material lookup via the IMaterialSystem list, FindMaterial fallback.
- New Chams feature/card with Through walls and colour; [chams] config.
- Remove the temporary ammo/projectile/fuse diagnostics.
| -rw-r--r-- | CONTEXT.md | 19 | ||||
| -rw-r--r-- | src/esp.cpp | 41 | ||||
| -rw-r--r-- | src/gui.cpp | 7 | ||||
| -rw-r--r-- | src/hl2h.cpp | 4 | ||||
| -rw-r--r-- | src/hl2h.h | 11 | ||||
| -rw-r--r-- | src/menu.cpp | 30 | ||||
| -rw-r--r-- | src/netvars.cpp | 31 | ||||
| -rw-r--r-- | src/server.cpp | 9 | ||||
| -rw-r--r-- | src/studio.cpp | 98 | ||||
| -rw-r--r-- | src/world.cpp | 47 |
10 files changed, 224 insertions, 73 deletions
| @@ -278,6 +278,25 @@ vocabulary / decisions. | |||
| 278 | visible corner. Each edge is clipped at the camera near plane and then to the | 278 | visible corner. Each edge is clipped at the camera near plane and then to the |
| 279 | screen, so a box straddling the camera still draws its visible portions. | 279 | screen, so a box straddling the camera still draws its visible portions. |
| 280 | 280 | ||
| 281 | ## Chams | ||
| 282 | |||
| 283 | - Hooks `IVModelRender::DrawModelExecute` (slot 19 in `VEngineModel016`). | ||
| 284 | The model's entity is `ModelRenderInfo_t::entity_index` (32-bit offset 0x30); | ||
| 285 | only entities for which `esp_is_npc_index` says the client class is an NPC | ||
| 286 | are affected. | ||
| 287 | - Uses `IVModelRender::ForcedMaterialOverride` (slot 1) with | ||
| 288 | `models/debug/debugwhite` and draws twice: first with | ||
| 289 | `MATERIAL_VAR_IGNOREZ` (1 << 15) set through `IMaterial::SetMaterialVarFlag` | ||
| 290 | (slot 29) for the through-wall pass, then without it. `IMaterial:: | ||
| 291 | ColorModulate` (slot 28) applies the configured colour. The shared material's | ||
| 292 | flag and colour are restored to (false, white) after the draw. | ||
| 293 | - Material lookup walks the `IMaterialSystem` list (`world_find_material`), with | ||
| 294 | `FindMaterial` (slot 71) as the fallback for a not-yet-resident material. | ||
| 295 | - Feature is off by default; `[chams] active/bind/walls/r/g/b`, with a | ||
| 296 | "Through walls" toggle and colour picker in the Visuals card. | ||
| 297 | - This is a flat material override, not stencil outlines; depth handling is | ||
| 298 | the simple ignorez/normal two-pass. | ||
| 299 | |||
| 281 | ## Bones / ESP | 300 | ## Bones / ESP |
| 282 | 301 | ||
| 283 | - `SetupBones(BONE_USED_BY_ANYTHING)` with engine `Time()`; studio header via | 302 | - `SetupBones(BONE_USED_BY_ANYTHING)` with engine `Time()`; studio header via |
diff --git a/src/esp.cpp b/src/esp.cpp index a12cf9c..b50dd87 100644 --- a/src/esp.cpp +++ b/src/esp.cpp | |||
| @@ -163,6 +163,16 @@ static bool is_npc_class(const char *name) { | |||
| 163 | strstr(name, "BaseHelicopter") != nullptr; | 163 | strstr(name, "BaseHelicopter") != nullptr; |
| 164 | } | 164 | } |
| 165 | 165 | ||
| 166 | // Used by the chams hook to decide whether a model-render entity is an NPC. | ||
| 167 | bool esp_is_npc_index(int idx) { | ||
| 168 | if (!g_iface.entitylist || idx <= 0 || idx > 8192) | ||
| 169 | return false; | ||
| 170 | void *net = entity_networkable(idx); | ||
| 171 | if (!net) | ||
| 172 | return false; | ||
| 173 | return is_npc_class(entity_class_name(net, nullptr)); | ||
| 174 | } | ||
| 175 | |||
| 166 | static bool model_is_friendly(const char *m) { | 176 | static bool model_is_friendly(const char *m) { |
| 167 | static const char *patterns[] = { | 177 | static const char *patterns[] = { |
| 168 | "citizen", "humans/", "male_", "female_", "barney", "alyx", | 178 | "citizen", "humans/", "male_", "female_", "barney", "alyx", |
| @@ -2796,23 +2806,6 @@ void esp_draw_items() { | |||
| 2796 | if (g_item_usable) | 2806 | if (g_item_usable) |
| 2797 | local_vitals(vit_hp, vit_max, vit_armor); | 2807 | local_vitals(vit_hp, vit_max, vit_armor); |
| 2798 | 2808 | ||
| 2799 | static bool ammo_logged = false; | ||
| 2800 | if (!ammo_logged) { | ||
| 2801 | ammo_logged = true; | ||
| 2802 | void *le = entitylist_get(local); | ||
| 2803 | char buf[512] = {}; | ||
| 2804 | int used = 0; | ||
| 2805 | for (int t = 0; t < 12 && used < (int)sizeof(buf) - 24; t++) { | ||
| 2806 | int w = snprintf(buf + used, sizeof(buf) - (size_t)used, "%d=%d/%d ", t, | ||
| 2807 | local_ammo(le, t), ammo_max(t)); | ||
| 2808 | if (w < 0) | ||
| 2809 | break; | ||
| 2810 | used += w; | ||
| 2811 | } | ||
| 2812 | logf("items: ammo current/max %s(usable=%d my_weapons=%d ammo=%d)", buf, | ||
| 2813 | (int)g_item_usable, g_net.my_weapons, g_net.ammo); | ||
| 2814 | } | ||
| 2815 | |||
| 2816 | for (int i = 0; i < g_nitems; i++) { | 2809 | for (int i = 0; i < g_nitems; i++) { |
| 2817 | const ItemTarget &t = g_item_targets[i]; | 2810 | const ItemTarget &t = g_item_targets[i]; |
| 2818 | if (!item_kind_enabled(t.kind)) | 2811 | if (!item_kind_enabled(t.kind)) |
| @@ -2830,12 +2823,6 @@ void esp_draw_items() { | |||
| 2830 | if (t.kind == IK_AMMO && t.ammo_type >= 0) { | 2823 | if (t.kind == IK_AMMO && t.ammo_type >= 0) { |
| 2831 | int maxa = ammo_max(t.ammo_type); | 2824 | int maxa = ammo_max(t.ammo_type); |
| 2832 | int cura = local_ammo(entitylist_get(local), t.ammo_type); | 2825 | int cura = local_ammo(entitylist_get(local), t.ammo_type); |
| 2833 | static int flt_logs = 0; | ||
| 2834 | if (flt_logs < 24) { | ||
| 2835 | flt_logs++; | ||
| 2836 | logf("items: ammo %s type=%d cur=%d max=%d", t.label, t.ammo_type, | ||
| 2837 | cura, maxa); | ||
| 2838 | } | ||
| 2839 | if (maxa > 0 && cura >= 0 && cura >= maxa) | 2826 | if (maxa > 0 && cura >= 0 && cura >= maxa) |
| 2840 | continue; | 2827 | continue; |
| 2841 | } | 2828 | } |
| @@ -3021,14 +3008,6 @@ static void projs_collect(int local_idx) { | |||
| 3021 | p.fuse = -1.0f; | 3008 | p.fuse = -1.0f; |
| 3022 | } | 3009 | } |
| 3023 | } | 3010 | } |
| 3024 | static int logs = 0; | ||
| 3025 | if (logs < 8) { | ||
| 3026 | logs++; | ||
| 3027 | logf("projectile: idx=%d cls=%s model=%s kind=%s det=%.2f cur=%.2f " | ||
| 3028 | "fuse=%.2f radius=%.0f", | ||
| 3029 | idx, cls ? cls : "?", model, proj_name(kind), det, curtime, p.fuse, | ||
| 3030 | p.radius); | ||
| 3031 | } | ||
| 3032 | } | 3011 | } |
| 3033 | } | 3012 | } |
| 3034 | 3013 | ||
diff --git a/src/gui.cpp b/src/gui.cpp index 2f832a2..a38f537 100644 --- a/src/gui.cpp +++ b/src/gui.cpp | |||
| @@ -423,6 +423,13 @@ static void build_cards() { | |||
| 423 | add_toggle(proj, "Blast radius", | 423 | add_toggle(proj, "Blast radius", |
| 424 | "Draw the projectile's damage radius on the ground.", &g_proj_radius); | 424 | "Draw the projectile's damage radius on the ground.", &g_proj_radius); |
| 425 | 425 | ||
| 426 | int chams = add_card(2, &g_chams, "Chams", | ||
| 427 | "Flat-colour NPC models.", true); | ||
| 428 | add_toggle(chams, "Through walls", | ||
| 429 | "Draw a second pass with the depth test disabled.", | ||
| 430 | &g_chams_walls); | ||
| 431 | add_color(chams, "Colour", "Chams colour.", g_chams_rgb); | ||
| 432 | |||
| 426 | int view = add_card(2, nullptr, "Camera / display", | 433 | int view = add_card(2, nullptr, "Camera / display", |
| 427 | "Camera FOV, viewmodel FOV and HDR brightness.", true); | 434 | "Camera FOV, viewmodel FOV and HDR brightness.", true); |
| 428 | add_slider(view, "Camera FOV", | 435 | add_slider(view, "Camera FOV", |
diff --git a/src/hl2h.cpp b/src/hl2h.cpp index 703a582..067b11f 100644 --- a/src/hl2h.cpp +++ b/src/hl2h.cpp | |||
| @@ -1148,6 +1148,7 @@ static void *setup_thread(void *) { | |||
| 1148 | g_iface.engineserver = ef("VEngineServer021", nullptr); | 1148 | g_iface.engineserver = ef("VEngineServer021", nullptr); |
| 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 | static const char *cvar_names[] = {"VEngineCvar004", "VEngineCvar007", | 1152 | static const char *cvar_names[] = {"VEngineCvar004", "VEngineCvar007", |
| 1152 | "VEngineCvar003", "IVEngineCvar004", | 1153 | "VEngineCvar003", "IVEngineCvar004", |
| 1153 | nullptr}; | 1154 | nullptr}; |
| @@ -1178,6 +1179,9 @@ static void *setup_thread(void *) { | |||
| 1178 | "enginetrace: %p (EngineTraceClient003)", | 1179 | "enginetrace: %p (EngineTraceClient003)", |
| 1179 | g_iface.engineserver, g_iface.debugoverlay, g_iface.enginetrace); | 1180 | g_iface.engineserver, g_iface.debugoverlay, g_iface.enginetrace); |
| 1180 | logf("cvar: %p (VEngineCvar004)", g_iface.cvar); | 1181 | logf("cvar: %p (VEngineCvar004)", g_iface.cvar); |
| 1182 | logf("modelrender: %p (VEngineModel016)", g_iface.modelrender); | ||
| 1183 | |||
| 1184 | chams_init(); | ||
| 1181 | 1185 | ||
| 1182 | resolve_netvars(); | 1186 | resolve_netvars(); |
| 1183 | if (!g_net.ok()) | 1187 | if (!g_net.ok()) |
| @@ -68,6 +68,7 @@ struct GameInterfaces { | |||
| 68 | void *debugoverlay = nullptr; // IVDebugOverlay003 (engine-accurate world->screen) | 68 | void *debugoverlay = nullptr; // IVDebugOverlay003 (engine-accurate world->screen) |
| 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 | }; | 72 | }; |
| 72 | extern GameInterfaces g_iface; | 73 | extern GameInterfaces g_iface; |
| 73 | 74 | ||
| @@ -189,7 +190,8 @@ enum FeatureId { | |||
| 189 | FEAT_FOG = 12, | 190 | FEAT_FOG = 12, |
| 190 | FEAT_SKYBOX = 13, | 191 | FEAT_SKYBOX = 13, |
| 191 | FEAT_PROJECTILES = 14, | 192 | FEAT_PROJECTILES = 14, |
| 192 | FEAT_COUNT = 15 | 193 | FEAT_CHAMS = 15, |
| 194 | FEAT_COUNT = 16 | ||
| 193 | }; | 195 | }; |
| 194 | enum EspOption { | 196 | enum EspOption { |
| 195 | ESP_OPT_SHOW_NEUTRAL = 0, | 197 | ESP_OPT_SHOW_NEUTRAL = 0, |
| @@ -226,8 +228,10 @@ struct FeatureState { | |||
| 226 | 228 | ||
| 227 | extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, | 229 | extern FeatureState g_bhop, g_abh, g_jumpbug, g_melee, g_spread, g_esp, |
| 228 | g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, | 230 | g_aimbot, g_triggerbot, g_silentaim, g_item_esp, g_fullbright, |
| 229 | g_world_tint, g_fog, g_skybox, g_projectiles; | 231 | g_world_tint, g_fog, g_skybox, g_projectiles, g_chams; |
| 230 | extern bool g_proj_fuse, g_proj_radius; | 232 | extern bool g_proj_fuse, g_proj_radius; |
| 233 | extern bool g_chams_walls; | ||
| 234 | extern int g_chams_rgb[3]; | ||
| 231 | extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; | 235 | extern bool g_tint_world_on, g_tint_sky_on, g_tint_prop_on; |
| 232 | extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; | 236 | extern int g_tint_world_rgb[3], g_tint_sky_rgb[3], g_tint_prop_rgb[3]; |
| 233 | extern bool g_fog_skybox, g_fog_disable; | 237 | extern bool g_fog_skybox, g_fog_disable; |
| @@ -329,6 +333,9 @@ bool aimbot_fov_circle(); | |||
| 329 | void aimbot_reset_lock(); | 333 | void aimbot_reset_lock(); |
| 330 | void silentaim_reset_lock(); | 334 | void silentaim_reset_lock(); |
| 331 | void *entitylist_get(int idx); | 335 | void *entitylist_get(int idx); |
| 336 | bool esp_is_npc_index(int idx); | ||
| 337 | void *world_find_material(const char *name); | ||
| 338 | void chams_init(); | ||
| 332 | int engine_get_local_player(); | 339 | int engine_get_local_player(); |
| 333 | void draw_text_col(int x, int y, const char *s, int r, int g, int b); | 340 | void draw_text_col(int x, int y, const char *s, int r, int g, int b); |
| 334 | void draw_text_small_col(int x, int y, const char *s, int r, int g, int b); | 341 | void draw_text_small_col(int x, int y, const char *s, int r, int g, int b); |
diff --git a/src/menu.cpp b/src/menu.cpp index 521bf43..77eb35f 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -63,6 +63,9 @@ FeatureState g_skybox{false, 0}; // skybox.png image override | |||
| 63 | FeatureState g_projectiles{true, 0}; // grenade / rocket ESP | 63 | FeatureState g_projectiles{true, 0}; // grenade / rocket ESP |
| 64 | bool g_proj_fuse = true; | 64 | bool g_proj_fuse = true; |
| 65 | bool g_proj_radius = true; | 65 | bool g_proj_radius = true; |
| 66 | FeatureState g_chams{false, 0}; // flat model material, optional through walls | ||
| 67 | bool g_chams_walls = true; | ||
| 68 | int g_chams_rgb[3] = {255, 60, 60}; | ||
| 66 | bool g_item_health = true; | 69 | bool g_item_health = true; |
| 67 | bool g_item_ammo = true; | 70 | bool g_item_ammo = true; |
| 68 | bool g_item_armor = true; | 71 | bool g_item_armor = true; |
| @@ -234,6 +237,9 @@ void config_save() { | |||
| 234 | fprintf(f, "[projectiles]\nactive=%d\nbind=%d\nfuse=%d\nradius=%d\n", | 237 | fprintf(f, "[projectiles]\nactive=%d\nbind=%d\nfuse=%d\nradius=%d\n", |
| 235 | g_projectiles.active, g_projectiles.bind, (int)g_proj_fuse, | 238 | g_projectiles.active, g_projectiles.bind, (int)g_proj_fuse, |
| 236 | (int)g_proj_radius); | 239 | (int)g_proj_radius); |
| 240 | fprintf(f, "[chams]\nactive=%d\nbind=%d\nwalls=%d\nr=%d\ng=%d\nb=%d\n", | ||
| 241 | g_chams.active, g_chams.bind, (int)g_chams_walls, g_chams_rgb[0], | ||
| 242 | g_chams_rgb[1], g_chams_rgb[2]); | ||
| 237 | fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" | 243 | fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" |
| 238 | "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n" | 244 | "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n" |
| 239 | "usable=%d\n", | 245 | "usable=%d\n", |
| @@ -284,7 +290,7 @@ static bool config_load() { | |||
| 284 | bool in_esp = false, in_bhop = false, in_aim = false, | 290 | bool in_esp = false, in_bhop = false, in_aim = false, |
| 285 | in_jumpbug = false, in_spread = false, in_items = false, | 291 | in_jumpbug = false, in_spread = false, in_items = false, |
| 286 | in_tint = false, in_fog = false, in_view = false, in_movement = false, | 292 | in_tint = false, in_fog = false, in_view = false, in_movement = false, |
| 287 | in_proj = false; | 293 | in_proj = false, in_chams = false; |
| 288 | while (fgets(line, sizeof(line), f)) { | 294 | while (fgets(line, sizeof(line), f)) { |
| 289 | line[strcspn(line, "\r\n")] = 0; | 295 | line[strcspn(line, "\r\n")] = 0; |
| 290 | if (line[0] == '[') { | 296 | if (line[0] == '[') { |
| @@ -312,6 +318,8 @@ static bool config_load() { | |||
| 312 | cur = &g_skybox; | 318 | cur = &g_skybox; |
| 313 | else if (strstr(line, "[projectiles]")) | 319 | else if (strstr(line, "[projectiles]")) |
| 314 | cur = &g_projectiles; | 320 | cur = &g_projectiles; |
| 321 | else if (strstr(line, "[chams]")) | ||
| 322 | cur = &g_chams; | ||
| 315 | else if (strstr(line, "[aimbot]")) | 323 | else if (strstr(line, "[aimbot]")) |
| 316 | cur = &g_aimbot; | 324 | cur = &g_aimbot; |
| 317 | else if (strstr(line, "[triggerbot]")) | 325 | else if (strstr(line, "[triggerbot]")) |
| @@ -335,6 +343,7 @@ static bool config_load() { | |||
| 335 | in_view = strstr(line, "[view]") != nullptr; | 343 | in_view = strstr(line, "[view]") != nullptr; |
| 336 | in_movement = strstr(line, "[movement]") != nullptr; | 344 | in_movement = strstr(line, "[movement]") != nullptr; |
| 337 | in_proj = (cur == &g_projectiles); | 345 | in_proj = (cur == &g_projectiles); |
| 346 | in_chams = (cur == &g_chams); | ||
| 338 | } else if (cur || in_view || in_movement) { | 347 | } else if (cur || in_view || in_movement) { |
| 339 | char *eq = strchr(line, '='); | 348 | char *eq = strchr(line, '='); |
| 340 | if (!eq) | 349 | if (!eq) |
| @@ -381,6 +390,17 @@ static bool config_load() { | |||
| 381 | else if (strcmp(key, "radius") == 0) | 390 | else if (strcmp(key, "radius") == 0) |
| 382 | g_proj_radius = atoi(val) != 0; | 391 | g_proj_radius = atoi(val) != 0; |
| 383 | } | 392 | } |
| 393 | if (in_chams) { | ||
| 394 | if (strcmp(key, "walls") == 0) | ||
| 395 | g_chams_walls = atoi(val) != 0; | ||
| 396 | else if (strcmp(key, "r") == 0 || strcmp(key, "g") == 0 || | ||
| 397 | strcmp(key, "b") == 0) { | ||
| 398 | int v = atoi(val); | ||
| 399 | if (v < 0) v = 0; | ||
| 400 | if (v > 255) v = 255; | ||
| 401 | g_chams_rgb[key[0] == 'r' ? 0 : key[0] == 'g' ? 1 : 2] = v; | ||
| 402 | } | ||
| 403 | } | ||
| 384 | if (in_aim) { | 404 | if (in_aim) { |
| 385 | if (strcmp(key, "smooth") == 0) { | 405 | if (strcmp(key, "smooth") == 0) { |
| 386 | int v = atoi(val); | 406 | int v = atoi(val); |
| @@ -605,6 +625,11 @@ void config_reset_defaults() { | |||
| 605 | g_projectiles = {true, 0}; | 625 | g_projectiles = {true, 0}; |
| 606 | g_proj_fuse = true; | 626 | g_proj_fuse = true; |
| 607 | g_proj_radius = true; | 627 | g_proj_radius = true; |
| 628 | g_chams = {false, 0}; | ||
| 629 | g_chams_walls = true; | ||
| 630 | g_chams_rgb[0] = 255; | ||
| 631 | g_chams_rgb[1] = 60; | ||
| 632 | g_chams_rgb[2] = 60; | ||
| 608 | g_item_health = true; | 633 | g_item_health = true; |
| 609 | g_item_ammo = true; | 634 | g_item_ammo = true; |
| 610 | g_item_armor = true; | 635 | g_item_armor = true; |
| @@ -738,7 +763,8 @@ static FeatureState *feature_state(int feature) { | |||
| 738 | static FeatureState *feats[FEAT_COUNT] = { | 763 | static FeatureState *feats[FEAT_COUNT] = { |
| 739 | &g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot, | 764 | &g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot, |
| 740 | &g_silentaim, &g_jumpbug, &g_melee, &g_spread, &g_item_esp, | 765 | &g_silentaim, &g_jumpbug, &g_melee, &g_spread, &g_item_esp, |
| 741 | &g_fullbright, &g_world_tint, &g_fog, &g_skybox, &g_projectiles}; | 766 | &g_fullbright, &g_world_tint, &g_fog, &g_skybox, &g_projectiles, |
| 767 | &g_chams}; | ||
| 742 | if (feature < 0 || feature >= FEAT_COUNT) | 768 | if (feature < 0 || feature >= FEAT_COUNT) |
| 743 | return nullptr; | 769 | return nullptr; |
| 744 | return feats[feature]; | 770 | return feats[feature]; |
diff --git a/src/netvars.cpp b/src/netvars.cpp index 6a34ee5..e22ff38 100644 --- a/src/netvars.cpp +++ b/src/netvars.cpp | |||
| @@ -61,15 +61,8 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 61 | uintptr_t ptable = *(uintptr_t *)(prop + 0x28); | 61 | uintptr_t ptable = *(uintptr_t *)(prop + 0x28); |
| 62 | 62 | ||
| 63 | char pname2[64]; | 63 | char pname2[64]; |
| 64 | if (!module_string(pname, pname2, sizeof(pname2))) { | 64 | if (!module_string(pname, pname2, sizeof(pname2))) |
| 65 | static int bad_names = 0; | ||
| 66 | if (bad_names < 8) { | ||
| 67 | bad_names++; | ||
| 68 | logf("netvar: unreadable prop name %#lx in %s", (unsigned long)pname, | ||
| 69 | tname); | ||
| 70 | } | ||
| 71 | continue; | 65 | continue; |
| 72 | } | ||
| 73 | 66 | ||
| 74 | if (dump) | 67 | if (dump) |
| 75 | logd(" [%s] %s type=%d off=%d(0x%x) table=%s array=%s", tname, pname2, | 68 | logd(" [%s] %s type=%d off=%d(0x%x) table=%s array=%s", tname, pname2, |
| @@ -90,13 +83,6 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 90 | g_net.my_weapons = off; | 83 | g_net.my_weapons = off; |
| 91 | logf("netvar: m_hMyWeapons prop=%s poff=%d elem=%p -> %d", pname2, | 84 | logf("netvar: m_hMyWeapons prop=%s poff=%d elem=%p -> %d", pname2, |
| 92 | poff, (void *)elem, off); | 85 | poff, (void *)elem, off); |
| 93 | } else { | ||
| 94 | static int bad = 0; | ||
| 95 | if (bad < 8) { | ||
| 96 | bad++; | ||
| 97 | logf("netvar: m_hMyWeapons prop=%s rejected poff=%d elem=%p off=%d", | ||
| 98 | pname2, poff, (void *)elem, off); | ||
| 99 | } | ||
| 100 | } | 86 | } |
| 101 | continue; | 87 | continue; |
| 102 | } | 88 | } |
| @@ -112,13 +98,6 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 112 | g_net.ammo = off; | 98 | g_net.ammo = off; |
| 113 | logf("netvar: m_iAmmo prop=%s poff=%d elem=%p -> %d", pname2, poff, | 99 | logf("netvar: m_iAmmo prop=%s poff=%d elem=%p -> %d", pname2, poff, |
| 114 | (void *)elem, off); | 100 | (void *)elem, off); |
| 115 | } else { | ||
| 116 | static int bad = 0; | ||
| 117 | if (bad < 8) { | ||
| 118 | bad++; | ||
| 119 | logf("netvar: m_iAmmo prop=%s rejected poff=%d elem=%p off=%d", | ||
| 120 | pname2, poff, (void *)elem, off); | ||
| 121 | } | ||
| 122 | } | 101 | } |
| 123 | continue; | 102 | continue; |
| 124 | } | 103 | } |
| @@ -126,14 +105,6 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 126 | collect_netvars(ptable, base_off + (uint32_t)poff, depth + 1); | 105 | collect_netvars(ptable, base_off + (uint32_t)poff, depth + 1); |
| 127 | continue; | 106 | continue; |
| 128 | } | 107 | } |
| 129 | if (parray) { | ||
| 130 | static int alogs = 0; | ||
| 131 | if (alogs < 16) { | ||
| 132 | alogs++; | ||
| 133 | logf("netvar array %s poff=%d eoff=%d base=%d", pname2, poff, | ||
| 134 | *(int32_t *)(parray + 0x2C), (int)base_off); | ||
| 135 | } | ||
| 136 | } | ||
| 137 | 108 | ||
| 138 | if (strcmp(pname2, "m_lifeState") == 0 && g_net.lifestate < 0) { | 109 | if (strcmp(pname2, "m_lifeState") == 0 && g_net.lifestate < 0) { |
| 139 | g_net.lifestate = (int)(base_off + (uint32_t)poff); | 110 | g_net.lifestate = (int)(base_off + (uint32_t)poff); |
diff --git a/src/server.cpp b/src/server.cpp index 09d198a..bae355e 100644 --- a/src/server.cpp +++ b/src/server.cpp | |||
| @@ -462,14 +462,7 @@ float server_grenade_detonate(int idx) { | |||
| 462 | server_entity_health(idx, nullptr, nullptr, &base); | 462 | server_entity_health(idx, nullptr, nullptr, &base); |
| 463 | if (!base) | 463 | if (!base) |
| 464 | return -1.0f; | 464 | return -1.0f; |
| 465 | float v = *(float *)((uintptr_t)base + g_srv_detonate); | 465 | return *(float *)((uintptr_t)base + g_srv_detonate); |
| 466 | static int logs = 0; | ||
| 467 | if (logs < 8) { | ||
| 468 | logs++; | ||
| 469 | logf("grenade fuse: idx=%d base=%p off=%d value=%.2f", idx, base, | ||
| 470 | g_srv_detonate, v); | ||
| 471 | } | ||
| 472 | return v; | ||
| 473 | } | 466 | } |
| 474 | 467 | ||
| 475 | float server_grenade_radius(int idx) { | 468 | float server_grenade_radius(int idx) { |
diff --git a/src/studio.cpp b/src/studio.cpp index 23be97f..1b7b77d 100644 --- a/src/studio.cpp +++ b/src/studio.cpp | |||
| @@ -181,8 +181,106 @@ std::string loaded_module_path(const char *needle) { | |||
| 181 | return found; | 181 | return found; |
| 182 | } | 182 | } |
| 183 | 183 | ||
| 184 | // --------------------------------------------------------------------------- | ||
| 185 | // chams: force a flat material on NPC draws | ||
| 186 | // --------------------------------------------------------------------------- | ||
| 187 | // | ||
| 188 | // IVModelRender::DrawModelExecute is called once per model. For NPC entities | ||
| 189 | // we force models/debug/debugwhite, optionally with MATERIAL_VAR_IGNOREZ for | ||
| 190 | // the through-wall pass, and draw twice. IVModelRender::ForcedMaterialOverride | ||
| 191 | // is slot 1; DrawModelExecute is slot 19 in VEngineModel016. | ||
| 192 | |||
| 193 | using ModelExecFn = void (*)(void *, const void *, const void *, void *); | ||
| 194 | using ForceOverrideFn = void (*)(void *, void *, int); | ||
| 195 | |||
| 196 | void *g_modelrender = nullptr; | ||
| 197 | ModelExecFn g_orig_model_exec = nullptr; | ||
| 198 | ForceOverrideFn g_force_override = nullptr; | ||
| 199 | void *g_chams_mat = nullptr; | ||
| 200 | bool g_chams_mat_tried = false; | ||
| 201 | |||
| 202 | void *chams_material() { | ||
| 203 | if (g_chams_mat_tried) | ||
| 204 | return g_chams_mat; | ||
| 205 | g_chams_mat_tried = true; | ||
| 206 | g_chams_mat = world_find_material("models/debug/debugwhite"); | ||
| 207 | if (g_chams_mat) | ||
| 208 | logf("chams: material models/debug/debugwhite = %p", g_chams_mat); | ||
| 209 | else | ||
| 210 | logf("chams: models/debug/debugwhite not found"); | ||
| 211 | return g_chams_mat; | ||
| 212 | } | ||
| 213 | |||
| 214 | void chams_apply(void *mat, bool ignorez) { | ||
| 215 | using ColorFn = void (*)(void *, float, float, float); | ||
| 216 | using FlagFn = void (*)(void *, int, bool); | ||
| 217 | vfunc<ColorFn>(mat, 28)(mat, (float)g_chams_rgb[0] / 255.0f, | ||
| 218 | (float)g_chams_rgb[1] / 255.0f, | ||
| 219 | (float)g_chams_rgb[2] / 255.0f); | ||
| 220 | vfunc<FlagFn>(mat, 29)(mat, 1 << 15 /* MATERIAL_VAR_IGNOREZ */, ignorez); | ||
| 221 | } | ||
| 222 | |||
| 223 | void hkdModelExecute(void *self, const void *state, const void *info, | ||
| 224 | void *bone) { | ||
| 225 | if (!g_orig_model_exec) | ||
| 226 | return; | ||
| 227 | if (feature_active(FEAT_CHAMS) && info && g_force_override) { | ||
| 228 | // ModelRenderInfo_t::entity_index is at 0x30 on 32-bit. | ||
| 229 | int idx = *(const int *)((const char *)info + 0x30); | ||
| 230 | if (idx > 0 && idx <= 8192 && esp_is_npc_index(idx)) { | ||
| 231 | void *mat = chams_material(); | ||
| 232 | if (mat) { | ||
| 233 | if (g_chams_walls) { | ||
| 234 | chams_apply(mat, true); | ||
| 235 | g_force_override(self, mat, 0 /* OVERRIDE_NORMAL */); | ||
| 236 | g_orig_model_exec(self, state, info, bone); | ||
| 237 | } | ||
| 238 | chams_apply(mat, false); | ||
| 239 | g_force_override(self, mat, 0); | ||
| 240 | g_orig_model_exec(self, state, info, bone); | ||
| 241 | g_force_override(self, nullptr, 0); | ||
| 242 | // debugwhite is shared: restore colour and depth behaviour. | ||
| 243 | using ColorFn = void (*)(void *, float, float, float); | ||
| 244 | using FlagFn = void (*)(void *, int, bool); | ||
| 245 | vfunc<FlagFn>(mat, 29)(mat, 1 << 15, false); | ||
| 246 | vfunc<ColorFn>(mat, 28)(mat, 1.0f, 1.0f, 1.0f); | ||
| 247 | return; | ||
| 248 | } | ||
| 249 | } | ||
| 250 | } | ||
| 251 | g_orig_model_exec(self, state, info, bone); | ||
| 252 | } | ||
| 253 | |||
| 184 | } // namespace | 254 | } // namespace |
| 185 | 255 | ||
| 256 | void chams_init() { | ||
| 257 | if (g_modelrender || !g_iface.modelrender) { | ||
| 258 | if (!g_iface.modelrender) | ||
| 259 | logf("chams: VEngineModel016 unavailable"); | ||
| 260 | return; | ||
| 261 | } | ||
| 262 | void **vt = *(void ***)g_iface.modelrender; | ||
| 263 | if (!vt) { | ||
| 264 | logf("chams: model render has no vtable"); | ||
| 265 | return; | ||
| 266 | } | ||
| 267 | g_force_override = (ForceOverrideFn)vt[1]; | ||
| 268 | g_orig_model_exec = (ModelExecFn)vt[19]; | ||
| 269 | if (!g_engine.contains((uintptr_t)g_force_override) || | ||
| 270 | !g_engine.contains((uintptr_t)g_orig_model_exec)) { | ||
| 271 | logf("chams: implausible vtable entries override=%p draw=%p", | ||
| 272 | (void *)g_force_override, (void *)g_orig_model_exec); | ||
| 273 | return; | ||
| 274 | } | ||
| 275 | static void *nvt[32]; | ||
| 276 | memcpy(nvt, vt, sizeof(nvt)); | ||
| 277 | nvt[19] = (void *)&hkdModelExecute; | ||
| 278 | g_modelrender = g_iface.modelrender; | ||
| 279 | *(void ***)g_modelrender = nvt; | ||
| 280 | logf("chams: IVModelRender %p hooked (draw=%p override=%p)", g_modelrender, | ||
| 281 | (void *)g_orig_model_exec, (void *)g_force_override); | ||
| 282 | } | ||
| 283 | |||
| 186 | void studio_props_init() { | 284 | void studio_props_init() { |
| 187 | if (g_install_tried) | 285 | if (g_install_tried) |
| 188 | return; | 286 | return; |
diff --git a/src/world.cpp b/src/world.cpp index 2fa1fc4..ab0c134 100644 --- a/src/world.cpp +++ b/src/world.cpp | |||
| @@ -999,3 +999,50 @@ void skybox_update() { | |||
| 999 | g_sky_retry_at = now + 1.0; | 999 | g_sky_retry_at = now + 1.0; |
| 1000 | } | 1000 | } |
| 1001 | } | 1001 | } |
| 1002 | |||
| 1003 | // Find a loaded material by its full name (e.g. "models/debug/debugwhite"). | ||
| 1004 | // Used by chams; walks the material list like the tint code and falls back to | ||
| 1005 | // FindMaterial if the material is not currently resident. | ||
| 1006 | void *world_find_material(const char *name) { | ||
| 1007 | if (!name || !*name) | ||
| 1008 | return nullptr; | ||
| 1009 | void *ms = material_system(); | ||
| 1010 | if (!ms) | ||
| 1011 | return nullptr; | ||
| 1012 | using FirstFn = unsigned short (*)(void *); | ||
| 1013 | using NextFn = unsigned short (*)(void *, unsigned short); | ||
| 1014 | using InvalidFn = unsigned short (*)(void *); | ||
| 1015 | using GetFn = void *(*)(void *, unsigned short); | ||
| 1016 | using CountFn = int (*)(void *); | ||
| 1017 | using NameFn = const char *(*)(void *); | ||
| 1018 | FirstFn first = (FirstFn)vfunc<FirstFn>(ms, 73); | ||
| 1019 | NextFn next = (NextFn)vfunc<NextFn>(ms, 74); | ||
| 1020 | InvalidFn invalid = (InvalidFn)vfunc<InvalidFn>(ms, 75); | ||
| 1021 | GetFn get = (GetFn)vfunc<GetFn>(ms, 76); | ||
| 1022 | CountFn count = (CountFn)vfunc<CountFn>(ms, 77); | ||
| 1023 | int total = count ? count(ms) : 0; | ||
| 1024 | if (total > 0 && total < 200000) { | ||
| 1025 | unsigned short h = first(ms); | ||
| 1026 | unsigned short inv = invalid(ms); | ||
| 1027 | for (int i = 0; i < total && h != inv; i++) { | ||
| 1028 | void *mat = get(ms, h); | ||
| 1029 | if (mat) { | ||
| 1030 | NameFn mat_name = (NameFn)vfunc<NameFn>(mat, 0); | ||
| 1031 | const char *n = mat_name(mat); | ||
| 1032 | if (n && strcmp(n, name) == 0) | ||
| 1033 | return mat; | ||
| 1034 | } | ||
| 1035 | h = next(ms, h); | ||
| 1036 | } | ||
| 1037 | } | ||
| 1038 | // Not resident yet: let the material system load it. FindMaterial is slot | ||
| 1039 | // 71 (IsMaterialLoaded is 72, FirstMaterial 73). | ||
| 1040 | using FindFn = void *(*)(void *, const char *, const char *, bool, | ||
| 1041 | const char *); | ||
| 1042 | void *mat = vfunc<FindFn>(ms, 71)(ms, name, "Model textures", false, nullptr); | ||
| 1043 | if (!mat) | ||
| 1044 | return nullptr; | ||
| 1045 | NameFn mat_name = (NameFn)vfunc<NameFn>(mat, 0); | ||
| 1046 | const char *n = mat_name(mat); | ||
| 1047 | return (n && strcmp(n, name) == 0) ? mat : nullptr; | ||
| 1048 | } | ||
