diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-06 16:09:55 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-06 16:09:55 +0100 |
| commit | 33f5ed9ef58f26c6b3b52cf605dd4311d658a60d (patch) | |
| tree | a97254872e465c9107b7ef19695ab56bb076d7e2 /src/menu.cpp | |
| parent | Items: log the usable ammo table once (diff) | |
| download | hl2h-33f5ed9ef58f26c6b3b52cf605dd4311d658a60d.tar.gz hl2h-33f5ed9ef58f26c6b3b52cf605dd4311d658a60d.zip | |
Fix audit findings: crash safety, correctness, rendering, performance
Crash/UB:
- gate every hook behind an init-stage flag until config/surface/input are ready
- publish module maps as immutable atomic snapshots instead of reallocating
shared vectors under unlocked readers
- validate the %ebp walk in the RandomSeed/RandomFloat hooks against the stack
- bounds-check every RecvTable/RecvProp/string dereference with module_range()
- preserve the Itanium vtable header when cloning (client, CInput, model render,
studio render)
- reject negative usercmd sequence numbers; guard config_load against a null
section target; cap the bone parent walk; check mprotect; drop PROT_EXEC on
the GOT patch; absorb server.so .bss tail
Correctness:
- filter dormant entities across ESP, aimbot, triggerbot, melee, items and
projectiles (slot self-tested against the local player)
- reset the server-health latch per map and only latch on valid model indices
- bone cache sized for the 128-target scan; set valid=false on every failure;
head_position keeps searching past an invalid bone
- vischeck steps past glass by probing until clear, and ignores entities owned
by the target (held weapons, bone followers)
- aimbot: maxs no longer disables it, AABB-aware FOV prefilter, lock hysteresis,
lead point re-checked for FOV and visibility
- spread: seed hook arms on the no-spread toggle alone; engine toggle only
controls the RandomFloat zeroing
- resolve m_hOwnerEntity/m_hOwner; NPC lead velocity from the server entity
- vector_angles pitch range, point_ground normal test, underwater jump+duck no
longer skips combat, semi-auto press/release edge for synthetic fire
- item/projectile markers and blast rings refresh origin per frame while
discovery stays at 10 Hz; trajectory uses the real m_DmgRadius and honours
the blast-radius toggle
Rendering/GUI:
- one overlay draw per engine frame (FRAME_RENDER_START boundary), fixing
alpha stacking and the speed-graph flicker
- clipped lines/outlines and ellipsised text instead of drawing past the
content pane or shifting over neighbours
- dropdown dismissal consumes the click; hover ignores hidden controls
- chams: reset AlphaModulate, reference shared materials once, restore prop
tint colour; fonts created once per handle
- projection uses Source's 4:3 aspect scaling and roll; screen size refreshed
each frame; FOV circle gated on the aim toggles, not the bind
Performance:
- TTL caches for NPC relation, friendly/neutral and damage class
- model names memoised per model index
- entity pointer -> index cache for the triggerbot (was a 4096-slot walk/tick)
Build:
- real Makefile with -MMD dependency tracking, debug/release objects and an
always-relink rule; build.sh kept as a thin wrapper
Diffstat (limited to 'src/menu.cpp')
| -rw-r--r-- | src/menu.cpp | 99 |
1 files changed, 40 insertions, 59 deletions
diff --git a/src/menu.cpp b/src/menu.cpp index 1064365..f3a9bb9 100644 --- a/src/menu.cpp +++ b/src/menu.cpp | |||
| @@ -19,10 +19,10 @@ void init_conmsg() { | |||
| 19 | if (g_conmsg) | 19 | if (g_conmsg) |
| 20 | return; | 20 | return; |
| 21 | // fallback: dlopen libtier0.so from the engine directory | 21 | // fallback: dlopen libtier0.so from the engine directory |
| 22 | size_t slash = g_engine.path.find_last_of('/'); | 22 | size_t slash = mod_engine().path.find_last_of('/'); |
| 23 | if (slash == std::string::npos) | 23 | if (slash == std::string::npos) |
| 24 | return; | 24 | return; |
| 25 | std::string tier0 = g_engine.path.substr(0, slash + 1) + "libtier0.so"; | 25 | std::string tier0 = mod_engine().path.substr(0, slash + 1) + "libtier0.so"; |
| 26 | void *h = dlopen(tier0.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); | 26 | void *h = dlopen(tier0.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); |
| 27 | if (!h) | 27 | if (!h) |
| 28 | h = dlopen(tier0.c_str(), RTLD_LAZY | RTLD_GLOBAL); | 28 | h = dlopen(tier0.c_str(), RTLD_LAZY | RTLD_GLOBAL); |
| @@ -640,6 +640,11 @@ static bool config_load() { | |||
| 640 | g_esp_col_friendly[key[1] == 'r' ? 0 : key[1] == 'g' ? 1 : 2] = v; | 640 | g_esp_col_friendly[key[1] == 'r' ? 0 : key[1] == 'g' ? 1 : 2] = v; |
| 641 | } | 641 | } |
| 642 | } | 642 | } |
| 643 | // Scalar-only sections ([view], [movement], [chams_*]) intentionally | ||
| 644 | // leave cur == nullptr. A stray active=/bind= line in one of them must | ||
| 645 | // not be dereferenced (hand-edited or corrupt config). | ||
| 646 | if (!cur) | ||
| 647 | continue; | ||
| 643 | if (strcmp(key, "active") == 0) | 648 | if (strcmp(key, "active") == 0) |
| 644 | cur->active = atoi(val) != 0; | 649 | cur->active = atoi(val) != 0; |
| 645 | else if (strcmp(key, "bind") == 0) | 650 | else if (strcmp(key, "bind") == 0) |
| @@ -867,6 +872,11 @@ bool feature_active(int feature) { | |||
| 867 | return f->bind == 0 || key_held(f->bind); | 872 | return f->bind == 0 || key_held(f->bind); |
| 868 | } | 873 | } |
| 869 | 874 | ||
| 875 | bool feature_enabled(int feature) { | ||
| 876 | FeatureState *f = feature_state(feature); | ||
| 877 | return f && f->active; | ||
| 878 | } | ||
| 879 | |||
| 870 | bool feature_has_bind(int feature) { | 880 | bool feature_has_bind(int feature) { |
| 871 | FeatureState *f = feature_state(feature); | 881 | FeatureState *f = feature_state(feature); |
| 872 | return f && f->bind != 0; | 882 | return f && f->bind != 0; |
| @@ -924,81 +934,39 @@ bool aimbot_fov_circle() { return g_aim_fov_circle; } | |||
| 924 | struct ISurface; | 934 | struct ISurface; |
| 925 | using HFont = unsigned long; | 935 | using HFont = unsigned long; |
| 926 | 936 | ||
| 927 | ISurface *g_surface = nullptr; | ||
| 928 | static HFont g_font = 0; | 937 | static HFont g_font = 0; |
| 929 | static HFont g_font_small = 0; | 938 | static HFont g_font_small = 0; |
| 930 | 939 | ||
| 931 | // VGUI_Surface030 vtable slots (confirmed via binary disassembly of vguimatsurface.so): | 940 | // ISurface access lives in overlay.cpp; these wrappers keep the call sites in |
| 932 | // 8 = PushMakeCurrent(VPANEL, bool useInsets) — reads args at 0xc/0x10(%ebp) | 941 | // this file unchanged. |
| 933 | // 9 = PopMakeCurrent(VPANEL) — pops the make-current stack at this+0x264/0x270 | 942 | static int surf_get_embedded_panel() { return overlay_embedded_panel(); } |
| 934 | // 10 = DrawSetColor(int r, int g, int b, int a) — stores rgba at this+0x24 | ||
| 935 | // 12 = DrawFilledRect(int x0, int y0, int x1, int y1) — alpha check at this+0x27 | ||
| 936 | // 17 = DrawSetTextFont(HFont), 18 = DrawSetTextColor(int r,g,b,a) | ||
| 937 | // 20 = DrawSetTextPos(int x, int y), 22 = DrawPrintText(wchar_t*, len, drawType) | ||
| 938 | // 66 = CreateFont(), 67 = AddGlyphSetToFont(font, name, tall, weight, | ||
| 939 | // blur, scanlines, flags, rangeMin, rangeMax) | ||
| 940 | static constexpr int SURF_GetEmbeddedPanel = 6; | ||
| 941 | static constexpr int SURF_PushMakeCurrent = 8; | ||
| 942 | static constexpr int SURF_PopMakeCurrent = 9; | ||
| 943 | static constexpr int SURF_DrawSetColor = 10; | ||
| 944 | static constexpr int SURF_DrawFilledRect = 12; | ||
| 945 | static constexpr int SURF_DrawSetTextFont = 17; | ||
| 946 | static constexpr int SURF_DrawSetTextColor = 18; | ||
| 947 | static constexpr int SURF_DrawSetTextPos = 20; | ||
| 948 | static constexpr int SURF_DrawPrintText = 22; | ||
| 949 | static constexpr int SURF_CreateFont = 66; | ||
| 950 | static constexpr int SURF_AddGlyphSetToFont = 67; | ||
| 951 | |||
| 952 | static int surf_get_embedded_panel() { | ||
| 953 | using Fn = int (*)(void *); | ||
| 954 | return vfunc<Fn>(g_surface, SURF_GetEmbeddedPanel)(g_surface); | ||
| 955 | } | ||
| 956 | static void surf_push_make_current(int panel, bool use_insets) { | 943 | static void surf_push_make_current(int panel, bool use_insets) { |
| 957 | using Fn = void (*)(void *, int, bool); | 944 | overlay_push_make_current(panel, use_insets); |
| 958 | vfunc<Fn>(g_surface, SURF_PushMakeCurrent)(g_surface, panel, use_insets); | ||
| 959 | } | 945 | } |
| 960 | static void surf_pop_make_current(int panel) { | 946 | static void surf_pop_make_current(int panel) { |
| 961 | using Fn = void (*)(void *, int); | 947 | overlay_pop_make_current(panel); |
| 962 | vfunc<Fn>(g_surface, SURF_PopMakeCurrent)(g_surface, panel); | ||
| 963 | } | 948 | } |
| 964 | static void surf_draw_set_color(int r, int g, int b, int a) { | 949 | static void surf_draw_set_color(int r, int g, int b, int a) { |
| 965 | using Fn = void (*)(void *, int, int, int, int); | 950 | overlay_color(r, g, b, a); |
| 966 | vfunc<Fn>(g_surface, SURF_DrawSetColor)(g_surface, r, g, b, a); | ||
| 967 | } | 951 | } |
| 968 | static void surf_draw_filled_rect(int x0, int y0, int x1, int y1) { | 952 | static void surf_draw_filled_rect(int x0, int y0, int x1, int y1) { |
| 969 | using Fn = void (*)(void *, int, int, int, int); | 953 | overlay_fill(x0, y0, x1, y1); |
| 970 | vfunc<Fn>(g_surface, SURF_DrawFilledRect)(g_surface, x0, y0, x1, y1); | ||
| 971 | } | 954 | } |
| 972 | static void surf_draw_set_text_color(int r, int g, int b, int a) { | 955 | static void surf_draw_set_text_color(int r, int g, int b, int a) { |
| 973 | using Fn = void (*)(void *, int, int, int, int); | 956 | overlay_text_color(r, g, b, a); |
| 974 | vfunc<Fn>(g_surface, SURF_DrawSetTextColor)(g_surface, r, g, b, a); | ||
| 975 | } | ||
| 976 | static void surf_draw_set_text_font(HFont f) { | ||
| 977 | using Fn = void (*)(void *, HFont); | ||
| 978 | vfunc<Fn>(g_surface, SURF_DrawSetTextFont)(g_surface, f); | ||
| 979 | } | ||
| 980 | static void surf_draw_set_text_pos(int x, int y) { | ||
| 981 | using Fn = void (*)(void *, int, int); | ||
| 982 | vfunc<Fn>(g_surface, SURF_DrawSetTextPos)(g_surface, x, y); | ||
| 983 | } | 957 | } |
| 958 | static void surf_draw_set_text_font(HFont f) { overlay_text_font(f); } | ||
| 959 | static void surf_draw_set_text_pos(int x, int y) { overlay_text_pos(x, y); } | ||
| 984 | static void surf_draw_print_text(const wchar_t *text, int count) { | 960 | static void surf_draw_print_text(const wchar_t *text, int count) { |
| 985 | using Fn = void (*)(void *, const wchar_t *, int, int); | 961 | overlay_print_text(text, count); |
| 986 | vfunc<Fn>(g_surface, SURF_DrawPrintText)(g_surface, text, count, 0); // 0 = FONT_DRAW_DEFAULT | ||
| 987 | } | ||
| 988 | static HFont surf_create_font() { | ||
| 989 | using Fn = HFont (*)(void *); | ||
| 990 | return vfunc<Fn>(g_surface, SURF_CreateFont)(g_surface); | ||
| 991 | } | 962 | } |
| 963 | static HFont surf_create_font() { return overlay_create_font(); } | ||
| 992 | static bool surf_set_font_glyph_set(HFont f, const char *name, int tall, | 964 | static bool surf_set_font_glyph_set(HFont f, const char *name, int tall, |
| 993 | int weight, int blur, int scanlines, | 965 | int weight, int blur, int scanlines, |
| 994 | int flags, int range_min = 0, | 966 | int flags, int range_min = 0, |
| 995 | int range_max = 0) { | 967 | int range_max = 0) { |
| 996 | using Fn = bool (*)(void *, HFont, const char *, int, int, int, int, int, int, | 968 | return overlay_add_glyph_set(f, name, tall, weight, blur, scanlines, flags, |
| 997 | int); | 969 | range_min, range_max); |
| 998 | return vfunc<Fn>(g_surface, SURF_AddGlyphSetToFont)(g_surface, f, name, tall, | ||
| 999 | weight, blur, scanlines, | ||
| 1000 | flags, range_min, | ||
| 1001 | range_max); | ||
| 1002 | } | 970 | } |
| 1003 | 971 | ||
| 1004 | // wide string conversion for DrawPrintText | 972 | // wide string conversion for DrawPrintText |
| @@ -1071,6 +1039,11 @@ void fullbright_update() { | |||
| 1071 | // (see hkdDrawPrintText hook in hl2h.cpp): we wrap our draws in | 1039 | // (see hkdDrawPrintText hook in hl2h.cpp): we wrap our draws in |
| 1072 | // PushMakeCurrent/PopMakeCurrent so they are submitted with a clean clip. | 1040 | // PushMakeCurrent/PopMakeCurrent so they are submitted with a clean clip. |
| 1073 | void outer_draw() { | 1041 | void outer_draw() { |
| 1042 | // PaintTraverseEx runs for every VGUI panel and DrawPrintText for every text | ||
| 1043 | // draw, so collapse them to one draw per engine frame (see overlay.h). | ||
| 1044 | if (!overlay_should_draw()) | ||
| 1045 | return; | ||
| 1046 | |||
| 1074 | poll_keyboard(); | 1047 | poll_keyboard(); |
| 1075 | fullbright_update(); | 1048 | fullbright_update(); |
| 1076 | world_tint_update(); | 1049 | world_tint_update(); |
| @@ -1083,10 +1056,16 @@ void outer_draw() { | |||
| 1083 | bool esp_on = feature_active(FEAT_ESP); | 1056 | bool esp_on = feature_active(FEAT_ESP); |
| 1084 | bool items_on = feature_active(FEAT_ITEM_ESP); | 1057 | bool items_on = feature_active(FEAT_ITEM_ESP); |
| 1085 | bool projs_on = feature_active(FEAT_PROJECTILES); | 1058 | bool projs_on = feature_active(FEAT_PROJECTILES); |
| 1059 | // The FOV circle belongs to the aim features, not to ESP, and it is a visual | ||
| 1060 | // aid: use the enable toggle rather than the bind, otherwise it only appears | ||
| 1061 | // while the aim key is held (default bind is mouse5). | ||
| 1062 | bool aim_on = (feature_enabled(FEAT_AIMBOT) || | ||
| 1063 | feature_enabled(FEAT_SILENTAIM)) && | ||
| 1064 | aimbot_fov_circle(); | ||
| 1086 | bool toasts = gui_toasts_active(); | 1065 | bool toasts = gui_toasts_active(); |
| 1087 | if (!g_surface || | 1066 | if (!g_surface || |
| 1088 | (!gui_is_open() && !draw_test && !esp_on && !items_on && !projs_on && | 1067 | (!gui_is_open() && !draw_test && !esp_on && !items_on && !projs_on && |
| 1089 | !toasts)) | 1068 | !aim_on && !toasts)) |
| 1090 | return; | 1069 | return; |
| 1091 | 1070 | ||
| 1092 | // Level/menu -> game transitions can invalidate the VGUI font handle. | 1071 | // Level/menu -> game transitions can invalidate the VGUI font handle. |
| @@ -1147,6 +1126,8 @@ void outer_draw() { | |||
| 1147 | if (!panel) | 1126 | if (!panel) |
| 1148 | return; | 1127 | return; |
| 1149 | surf_push_make_current(panel, false); | 1128 | surf_push_make_current(panel, false); |
| 1129 | if (aim_on && overlay_font) | ||
| 1130 | esp_draw_fov_circle(); | ||
| 1150 | if (esp_on && overlay_font) | 1131 | if (esp_on && overlay_font) |
| 1151 | esp_draw(); | 1132 | esp_draw(); |
| 1152 | if (items_on && overlay_font) | 1133 | if (items_on && overlay_font) |
