From 33f5ed9ef58f26c6b3b52cf605dd4311d658a60d Mon Sep 17 00:00:00 2001 From: salmonik Date: Fri, 6 Nov 2026 16:09:55 +0100 Subject: 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 --- src/gui.cpp | 259 ++++++++++++++++++++++++++++++++++++------------------------ 1 file changed, 156 insertions(+), 103 deletions(-) (limited to 'src/gui.cpp') diff --git a/src/gui.cpp b/src/gui.cpp index 454e2cb..8a71ecb 100644 --- a/src/gui.cpp +++ b/src/gui.cpp @@ -19,20 +19,8 @@ // ISurface drawing (same binary slots as menu.cpp / esp.cpp) // --------------------------------------------------------------------------- -enum { - GS_DrawSetColor = 10, - GS_DrawFilledRect = 12, - GS_DrawOutlinedRect = 14, - GS_DrawLine = 15, - GS_DrawSetTextFont = 17, - GS_DrawSetTextColor = 18, - GS_DrawSetTextPos = 20, - GS_DrawPrintText = 22, - GS_GetScreenSize = 38, - GS_CreateFont = 66, - GS_AddGlyphSet = 67, - GS_GetTextSize = 75 -}; +// ISurface access lives in overlay.cpp. These thin wrappers keep the GUI code +// readable without repeating the vtable indices in this file. static unsigned long g_font_body = 0; static unsigned long g_font_title = 0; @@ -43,57 +31,27 @@ static void *g_font_local = (void *)-1; static bool g_font_fail_logged = false; static int g_scr_w = 0, g_scr_h = 0; -static void s_color(int r, int g, int b, int a) { - using Fn = void (*)(void *, int, int, int, int); - vfunc(g_surface, GS_DrawSetColor)(g_surface, r, g, b, a); -} +static void s_color(int r, int g, int b, int a) { overlay_color(r, g, b, a); } static void s_fill(int x0, int y0, int x1, int y1) { - using Fn = void (*)(void *, int, int, int, int); - vfunc(g_surface, GS_DrawFilledRect)(g_surface, x0, y0, x1, y1); -} -static void s_outline(int x0, int y0, int x1, int y1) { - using Fn = void (*)(void *, int, int, int, int); - vfunc(g_surface, GS_DrawOutlinedRect)(g_surface, x0, y0, x1, y1); + overlay_fill(x0, y0, x1, y1); } static void s_line(int x0, int y0, int x1, int y1) { - using Fn = void (*)(void *, int, int, int, int); - vfunc(g_surface, GS_DrawLine)(g_surface, x0, y0, x1, y1); -} -static void s_text_font(unsigned long f) { - using Fn = void (*)(void *, unsigned long); - vfunc(g_surface, GS_DrawSetTextFont)(g_surface, f); + overlay_line(x0, y0, x1, y1); } +static void s_text_font(HFont f) { overlay_text_font(f); } static void s_text_color(int r, int g, int b, int a) { - using Fn = void (*)(void *, int, int, int, int); - vfunc(g_surface, GS_DrawSetTextColor)(g_surface, r, g, b, a); -} -static void s_text_pos(int x, int y) { - using Fn = void (*)(void *, int, int); - vfunc(g_surface, GS_DrawSetTextPos)(g_surface, x, y); + overlay_text_color(r, g, b, a); } -static void s_print(const wchar_t *text, int n) { - using Fn = void (*)(void *, const wchar_t *, int, int); - vfunc(g_surface, GS_DrawPrintText)(g_surface, text, n, 0); -} -static unsigned long s_create_font() { - using Fn = unsigned long (*)(void *); - return vfunc(g_surface, GS_CreateFont)(g_surface); -} -static bool s_set_font(unsigned long font, const char *name, int tall, int weight, +static void s_text_pos(int x, int y) { overlay_text_pos(x, y); } +static void s_print(const wchar_t *text, int n) { overlay_print_text(text, n); } +static HFont s_create_font() { return overlay_create_font(); } +static bool s_set_font(HFont font, const char *name, int tall, int weight, int flags, int lo, int hi) { - using Fn = bool (*)(void *, unsigned long, const char *, int, int, int, int, - int, int, int); - return vfunc(g_surface, GS_AddGlyphSet)(g_surface, font, name, tall, - weight, 0, 0, flags, lo, hi); -} -static void s_screen(int &w, int &h) { - using Fn = void (*)(void *, int &, int &); - vfunc(g_surface, GS_GetScreenSize)(g_surface, w, h); + return overlay_add_glyph_set(font, name, tall, weight, 0, 0, flags, lo, hi); } -static void s_text_size(unsigned long font, const wchar_t *text, int &w, - int &h) { - using Fn = void (*)(void *, unsigned long, const wchar_t *, int &, int &); - vfunc(g_surface, GS_GetTextSize)(g_surface, font, text, w, h); +static void s_screen(int &w, int &h) { overlay_screen_size(w, h); } +static void s_text_size(HFont font, const wchar_t *text, int &w, int &h) { + overlay_text_size(font, text, w, h); } // --------------------------------------------------------------------------- @@ -137,9 +95,12 @@ static void gui_init_sdl() { g_sdl_init_tried = true; sd_get_keys = (GetKeysFn)dlsym(RTLD_DEFAULT, "SDL_GetKeyboardState"); sd_get_mouse = (GetMouseFn)dlsym(RTLD_DEFAULT, "SDL_GetMouseState"); - sd_show_cursor = (ShowCursorFn)dlsym(RTLD_DEFAULT, "SDL_ShowCursor"); - sd_set_relative = (SetRelativeFn)dlsym(RTLD_DEFAULT, "SDL_SetRelativeMouseMode"); - sd_get_relative = (GetRelativeFn)dlsym(RTLD_DEFAULT, "SDL_GetRelativeMouseMode"); + // RTLD_NEXT, not RTLD_DEFAULT: this object is LD_PRELOADed first, so + // RTLD_DEFAULT resolves to our own exported wrapper and the "real" pointer + // would point back into this file. + sd_show_cursor = (ShowCursorFn)dlsym(RTLD_NEXT, "SDL_ShowCursor"); + sd_set_relative = (SetRelativeFn)dlsym(RTLD_NEXT, "SDL_SetRelativeMouseMode"); + sd_get_relative = (GetRelativeFn)dlsym(RTLD_NEXT, "SDL_GetRelativeMouseMode"); sd_add_watch = (AddWatchFn)dlsym(RTLD_DEFAULT, "SDL_AddEventWatch"); sd_get_ticks = (GetTicksFn)dlsym(RTLD_DEFAULT, "SDL_GetTicks"); g_sdl_ready = sd_get_keys && sd_get_mouse; @@ -607,6 +568,10 @@ static int g_mouse_press = 0; // 1..5 on the frame a button goes down static unsigned char g_prev_keys[512] = {}; static bool g_prev_cursor = true; static bool g_prev_relative = true; +// Last state the game asked for while the GUI held the mouse; restored on +// close instead of the (possibly stale) pre-open snapshot. +static bool g_game_wants_cursor = true; +static bool g_game_wants_relative = false; static double g_hover_since = 0.0; static const char *g_hover_desc = nullptr; static const char *g_hover_prev = nullptr; @@ -688,10 +653,12 @@ static unsigned long create_gui_font(int tall) { static const char *families[] = {"Liberation Sans", "DejaVu Sans", "Noto Sans", "Tahoma", "Verdana", "Arial", nullptr}; + // Create one handle and add glyph sets to it. Creating a handle per family + // leaked one HFont per failed family, and ISurface has no DestroyFont. + unsigned long f = s_create_font(); + if (!f) + return 0; for (int i = 0; families[i]; i++) { - unsigned long f = s_create_font(); - if (!f) - break; if (s_set_font(f, families[i], tall, 400, 0x400, 0x20, 0xFF) && font_valid(f)) { logd("gui: font %dpx family %s", tall, families[i]); @@ -710,8 +677,13 @@ static void ensure_fonts() { } if (le != g_font_local) { g_font_local = le; - gui_reset_fonts(); - logd("gui: fonts reset (local entity change)"); + // VGUI fonts are global; a level change alone does not invalidate them. + // Dropping valid handles here leaked a fresh pair on every death, respawn + // and map load. Only reset when the manager actually lost them. + if (!font_valid(g_font_body) || !font_valid(g_font_title)) { + gui_reset_fonts(); + logd("gui: fonts reset (invalid after local entity change)"); + } } bool scale_changed = fabsf(g_font_scale - g_scale) >= 0.001f; @@ -871,20 +843,30 @@ static void draw_text_raw(int x, int y, const char *s, unsigned long font, static void text_in(const Rect &clip, int x, int y, const char *s, unsigned long font, int tall, int max_w, const Col &c) { - (void)max_w; // callers must size the layout; text is never truncated if (!s || !font) return; if (y < clip.y || y + tall > clip.y + clip.h) return; if (x >= clip.x + clip.w) return; - int w = text_w(s, tall); - if (x + w > clip.x + clip.w) - x = clip.x + clip.w - w; - if (x < clip.x) - x = clip.x; + // Available width: the clip edge, further limited by the layout's column + // width. Overflowing text used to be shifted left over its neighbour; now it + // is truncated with an ellipsis instead. + int avail = clip.x + clip.w - x; + if (max_w > 0 && max_w < avail) + avail = max_w; + if (avail <= 0) + return; + std::string out = s; + if (text_w(out.c_str(), tall) > avail) { + while (!out.empty() && text_w((out + "...").c_str(), tall) > avail) + out.pop_back(); + if (out.empty()) + return; + out += "..."; + } s_text_color(c.r, c.g, c.b, c.a); - draw_text_raw(x, y, s, font, tall); + draw_text_raw(x, y, out.c_str(), font, tall); } static Rect clip_rect(const Rect &r, const Rect &clip) { @@ -911,13 +893,49 @@ static void fill(const Rect &r, const Col &c, const Rect *clip = nullptr) { static void outline(const Rect &r, const Col &c, const Rect *clip = nullptr) { if (r.w <= 0 || r.h <= 0) return; - Rect q = clip ? clip_rect(r, *clip) : r; - if (q.w <= 0 || q.h <= 0) - return; - s_color(c.r, c.g, c.b, c.a); - s_outline(q.x, q.y, q.x + q.w, q.y + q.h); -} -static void line(int x0, int y0, int x1, int y1, const Col &c) { + // Draw the four edges as thin filled rects. Outlining the *clamped* rect used + // to paint a false border along the clip edge whenever a card was partially + // scrolled out of the content pane. + int t = S(1.0f); + if (t < 1) t = 1; + fill(Rect{r.x, r.y, r.w, t}, c, clip); + fill(Rect{r.x, r.y + r.h - t, r.w, t}, c, clip); + fill(Rect{r.x, r.y, t, r.h}, c, clip); + fill(Rect{r.x + r.w - t, r.y, t, r.h}, c, clip); +} +// Liang–Barsky line clip. `clip` is optional so the existing call sites that +// draw inside an already-safe area keep working unchanged. +static void line(int x0, int y0, int x1, int y1, const Col &c, + const Rect *clip = nullptr) { + if (clip) { + float dx = (float)(x1 - x0), dy = (float)(y1 - y0); + float t0 = 0.0f, t1 = 1.0f; + float p[4] = {-dx, dx, -dy, dy}; + float q[4] = {(float)(x0 - clip->x), + (float)(clip->x + clip->w - x0), + (float)(y0 - clip->y), + (float)(clip->y + clip->h - y0)}; + for (int i = 0; i < 4; i++) { + if (fabsf(p[i]) < 1e-6f) { + if (q[i] < 0.0f) + return; // parallel and outside + continue; + } + float t = q[i] / p[i]; + if (p[i] < 0.0f) { + if (t > t1) return; + if (t > t0) t0 = t; + } else { + if (t < t0) return; + if (t < t1) t1 = t; + } + } + int nx0 = (int)((float)x0 + t0 * dx + 0.5f); + int ny0 = (int)((float)y0 + t0 * dy + 0.5f); + int nx1 = (int)((float)x0 + t1 * dx + 0.5f); + int ny1 = (int)((float)y0 + t1 * dy + 0.5f); + x0 = nx0; y0 = ny0; x1 = nx1; y1 = ny1; + } s_color(c.r, c.g, c.b, c.a); s_line(x0, y0, x1, y1); } @@ -1298,7 +1316,7 @@ static void draw_picker(const Rect &clip, const Rect &cr, const Ctrl &ct, outline(p.alpha, Col{0, 0, 0, 255}, &clip); int amx = p.alpha.x + (int)((float)pa / 255.0f * (float)p.alpha.w); line(amx, p.alpha.y - S(2.0f), amx, p.alpha.y + p.alpha.h + S(2.0f), - Col{255, 255, 255, 255}); + Col{255, 255, 255, 255}, &clip); } // Selection markers. @@ -1311,7 +1329,7 @@ static void draw_picker(const Rect &clip, const Rect &cr, const Ctrl &ct, int hxm = p.hue.x + (int)((ct.hue / 360.0f) * (float)p.hue.w); line(hxm, p.hue.y - S(2.0f), hxm, p.hue.y + p.hue.h + S(2.0f), - Col{255, 255, 255, 255}); + Col{255, 255, 255, 255}, &clip); // Preview swatch and hex value. draw_checker(p.preview, &clip); @@ -1351,6 +1369,23 @@ static Rect card_rect(int index, int y) { return Rect{x, y, w, card_h(c)}; } +// Slider track inside a card: the single source of truth for drawing, +// hit-testing and dragging. The drag path used to recompute this by hand and +// omitted the card's +S(10) left margin, so the value jumped on mouse-down. +static Rect slider_track(const Rect &cr, int y, int rh) { + int w = cr.w - S(16.0f) - S(230.0f) - S(64.0f); + if (w < S(30.0f)) w = S(30.0f); + return Rect{cr.x + S(16.0f) + S(230.0f), y + (rh - S(8.0f)) / 2, w, + S(8.0f)}; +} + +static int card_for_ctrl(int ci) { + for (int i = 0; i < g_ncards; i++) + if (ci >= g_cards[i].c0 && ci < g_cards[i].c0 + g_cards[i].n) + return i; + return -1; +} + static bool card_in_category(int i) { return g_cards[i].cat == g_cat; } static int content_height() { @@ -1379,10 +1414,13 @@ static void close_gui() { g_reset_armed = false; input_clear_mouse_states(); if (g_sdl_ready) { + // Restore what the game actually wants now, not the pre-open snapshot: the + // game keeps requesting relative mode/cursor state while the GUI is open + // (menu -> map transition), and those requests were being dropped. if (sd_set_relative) - sd_set_relative(g_prev_relative ? 1 : 0); + sd_set_relative(g_game_wants_relative ? 1 : 0); if (sd_show_cursor) - sd_show_cursor(g_prev_cursor ? 1 : 0); + sd_show_cursor(g_game_wants_cursor ? 1 : 0); } config_save(); logf("gui: closed"); @@ -1410,6 +1448,9 @@ static void open_gui() { g_prev_relative = sd_get_relative() != 0; if (sd_show_cursor) g_prev_cursor = sd_show_cursor(-1) != 0; + // Until the game asks for something else, restore what it had before. + g_game_wants_relative = g_prev_relative; + g_game_wants_cursor = g_prev_cursor; if (sd_set_relative) sd_set_relative(0); if (sd_show_cursor) @@ -1519,6 +1560,10 @@ static void handle_mouse_click() { } } g_dropdown_ctrl = -1; + // Consume the dismissal click: it must not also activate whatever control + // happened to be underneath the popup. + g_mouse_click = false; + return; } // title bar: drag @@ -1624,9 +1669,7 @@ static void handle_mouse_click() { return; } if (ct.kind == CK_SLIDER) { - Rect track{cr.x + S(16.0f) + S(230.0f), - by + (rh - S(8.0f)) / 2, - cr.w - S(16.0f) - S(230.0f) - S(64.0f), S(8.0f)}; + Rect track = slider_track(cr, by, rh); if (mx >= track.x && mx < track.x + track.w) { float t = (float)(mx - track.x) / (float)track.w; int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); @@ -1717,6 +1760,11 @@ static void handle_mouse_click() { static void handle_hover() { g_hover_desc = nullptr; g_hover_slider = -1; + // An open popup/picker or an active drag owns the cursor; arming hover on the + // controls underneath let Left/Right arrows adjust a hidden slider. + if (g_dropdown_ctrl >= 0 || g_picker_ctrl >= 0 || g_picker_drag || + g_slider_drag >= 0 || g_dragging || g_scroll_drag) + return; int mx = g_mouse_x, my = g_mouse_y; Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; @@ -1849,14 +1897,15 @@ void gui_handle_input() { config_save(); } else { Ctrl &ct = g_ctrls[g_slider_drag]; - Rect content{g_win_x + S(190.0f), g_win_y + S(38.0f), - g_win_w - S(190.0f), g_win_h - S(38.0f) - S(30.0f)}; - int x0 = g_win_x + S(190.0f) + S(16.0f) + S(230.0f); - int x1 = g_win_x + g_win_w - S(28.0f) - S(64.0f); - (void)content; - float t = (float)(g_mouse_x - x0) / (float)(x1 - x0); - int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); - ctrl_set(ct, v); + int ci = card_for_ctrl(g_slider_drag); + if (ci >= 0) { + Rect track = slider_track(card_rect(ci, 0), 0, 0); + float t = (float)(g_mouse_x - track.x) / (float)track.w; + if (t < 0.0f) t = 0.0f; + if (t > 1.0f) t = 1.0f; + int v = ct.min + (int)(t * (ct.max - ct.min) + 0.5f); + ctrl_set(ct, v); + } } } @@ -1938,18 +1987,15 @@ static void draw_checkbox(int x, int y, bool on, fill(box, on ? C_ACCENT : Col{30, 34, 42, 255}, clip); outline(box, C_BORDER, clip); if (on) { - line(x + S(3.0f), y + S(8.0f), x + S(7.0f), y + S(12.0f), C_BG); - line(x + S(7.0f), y + S(12.0f), x + S(13.0f), y + S(4.0f), C_BG); + line(x + S(3.0f), y + S(8.0f), x + S(7.0f), y + S(12.0f), C_BG, clip); + line(x + S(7.0f), y + S(12.0f), x + S(13.0f), y + S(4.0f), C_BG, clip); } } static void draw_slider(const Rect &clip, const Rect &cr, int y, int rh, const Ctrl &ct) { - int track_x = cr.x + S(16.0f) + S(230.0f); - int track_w = cr.w - S(16.0f) - S(230.0f) - S(64.0f); - if (track_w < S(30.0f)) track_w = S(30.0f); - int track_y = y + (rh - S(8.0f)) / 2; - Rect track{track_x, track_y, track_w, S(8.0f)}; + Rect track = slider_track(cr, y, rh); + int track_w = track.w; fill(track, Col{38, 42, 50, 255}, &clip); int value = ctrl_val(ct); float t = (ct.max > ct.min) @@ -2416,6 +2462,8 @@ extern "C" __attribute__((visibility("default"))) int SDL_ShowCursor(int toggle) if (!real_show_cursor) real_show_cursor = (int (*)(int))dlsym(RTLD_NEXT, "SDL_ShowCursor"); if (g_open && toggle != -1) { + // Remember what the game asked for so close_gui() can restore it. + g_game_wants_cursor = toggle != 0; if (real_show_cursor) real_show_cursor(1); return 1; @@ -2429,6 +2477,7 @@ SDL_SetRelativeMouseMode(int enabled) { real_set_relative = (int (*)(int))dlsym(RTLD_NEXT, "SDL_SetRelativeMouseMode"); if (g_open) { + g_game_wants_relative = enabled != 0; if (real_set_relative) real_set_relative(0); return 0; @@ -2520,7 +2569,11 @@ SDL_PollEvent(void *event) { unsigned int type = *(unsigned int *)event; if (type != 0x400) // SDL_MOUSEMOTION return r; - // Drop motion while the GUI is open: the game must not accumulate look - // deltas. The GUI uses absolute SDL_GetMouseState polling. + // Keep the event (VGUI hover and SDL's internal cursor state need it) but + // zero the relative deltas so the game does not accumulate look movement. + // SDL_MouseMotionEvent: xrel at +28, yrel at +32. + *(int *)((char *)event + 28) = 0; + *(int *)((char *)event + 32) = 0; + return r; } } -- cgit v1.2.3