From 210f3a12ee1d56750d593b4c404a0683f810aa3e Mon Sep 17 00:00:00 2001 From: salmonik Date: Sat, 12 Sep 2026 19:04:22 +0200 Subject: ISurface overlay working: PTEx draw path, -Werror, menu states, bind-driven features --- src/menu.cpp | 312 +++++++++++++++++++++++++++++++++++++---------------------- 1 file changed, 197 insertions(+), 115 deletions(-) (limited to 'src/menu.cpp') diff --git a/src/menu.cpp b/src/menu.cpp index 91a0948..c62db47 100644 --- a/src/menu.cpp +++ b/src/menu.cpp @@ -1,7 +1,8 @@ // menu.cpp — menu state, rendering, input, config, paint hook, X11 mouse #include "hl2h.h" -#include +#include #include +#include #include // --------------------------------------------------------------------------- @@ -104,14 +105,14 @@ static FeatureState g_bhop{true, 0}; static FeatureState g_abh{true, 0}; // tree -static MenuNode g_root{NODE_ROOT, "HL2hake", true, 0, {}}; -static MenuNode g_climb_feat{NODE_FEATURE, "Climb", true, 2, {}}; -static MenuNode g_climb_active{NODE_TOGGLE, "Active", false, 0, {}, &g_climb.active}; +static MenuNode g_root{NODE_ROOT, "HL2hake", true, 0, {}, nullptr, nullptr, nullptr}; +static MenuNode g_climb_feat{NODE_FEATURE, "Climb", true, 2, {}, nullptr, nullptr, nullptr}; +static MenuNode g_climb_active{NODE_TOGGLE, "Active", false, 0, {}, &g_climb.active, nullptr, nullptr}; static MenuNode g_climb_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_climb.bind, "None"}; -static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", true, 1, {}}; -static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active}; -static MenuNode g_abh_feat{NODE_FEATURE, "ABH", true, 1, {}}; -static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active}; +static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", true, 1, {}, nullptr, nullptr, nullptr}; +static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active, nullptr, nullptr}; +static MenuNode g_abh_feat{NODE_FEATURE, "ABH", true, 1, {}, nullptr, nullptr, nullptr}; +static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr}; static MenuNode *g_visible[16]; // flattened visible nodes for arrow navigation static int g_visible_count = 0; @@ -132,6 +133,8 @@ static void build_tree() { g_root.child_count = 3; } +static int g_depth[16]; // depth of each visible row (for indentation) + static void flatten(MenuNode *node, int depth) { if (g_visible_count >= 16) return; @@ -139,6 +142,7 @@ static void flatten(MenuNode *node, int depth) { : (node->child_count > 0) ? NODE_FEATURE : (node->active_ptr) ? NODE_TOGGLE : NODE_BIND; + g_depth[g_visible_count] = depth; g_visible[g_visible_count++] = node; if (node->expanded) { for (int i = 0; i < node->child_count; i++) @@ -297,11 +301,28 @@ static bool key_pressed(int sc) { return sc >= 0 && sc < 512 && g_prev_key_state[sc]; } +// feature gate queried from hl2h.cpp do_move(): feature must be Active AND +// the activation condition met. Climb activates on its bind key held (falls +// back to the historic middle-mouse behavior when unbound); other features +// activate on their bind key held, or are always-on when unbound. +bool feature_active(int feature) { + static FeatureState *feats[3] = {&g_climb, &g_bhop, &g_abh}; + if (feature < 0 || feature > 2) + return false; + FeatureState *f = feats[feature]; + if (!f->active) + return false; + if (f->bind == 0) + return feature == FEAT_CLIMB ? middle_mouse_down() : true; + return key_held(f->bind); +} + static void menu_handle_input() { poll_keyboard(); if (!g_menu_open) { if (key_pressed(SCAN_INSERT)) { + std::logf("insert pressed"); g_menu_open = true; g_bind_mode = false; rebuild_visible(); @@ -384,29 +405,53 @@ static void menu_handle_input() { // --------------------------------------------------------------------------- // VGUI surface interface -using CreateInterfaceFn = void *(*)(const char *, int *); struct ISurface; - -// VGUI font handle (opaque) using HFont = unsigned long; -// function pointers resolved at init -static ISurface *g_surface = nullptr; +ISurface *g_surface = nullptr; static HFont g_font = 0; -// surface vtable indices for 2007-era VGUI_SURFACE001 -// verified: slot 16 = DrawSetTextColor, slot 17 = DrawSetTextFont, -// slot 19 = DrawSetTextPos, slot 22 = DrawPrintText, -// slot 23 = DrawGetTextSize, slot 31 = CreateNewFont, slot 32 = SetFontGlyphSet -// These are from the standard VGUI surface vtable layout; will be verified -// against runtime if they fail. -static constexpr int SURF_DrawSetTextColor = 16; +// VGUI_Surface030 vtable slots (confirmed via binary disassembly of vguimatsurface.so): +// 8 = PushMakeCurrent(VPANEL, bool useInsets) — reads args at 0xc/0x10(%ebp) +// 9 = PopMakeCurrent(VPANEL) — pops the make-current stack at this+0x264/0x270 +// 10 = DrawSetColor(int r, int g, int b, int a) — stores rgba at this+0x24 +// 12 = DrawFilledRect(int x0, int y0, int x1, int y1) — alpha check at this+0x27 +// 17 = DrawSetTextFont(HFont), 18 = DrawSetTextColor(int r,g,b,a) +// 20 = DrawSetTextPos(int x, int y), 22 = DrawPrintText(wchar_t*, len, drawType) +// 66 = CreateFont(), 67 = AddGlyphSetToFont(font, name, tall, weight, +// blur, scanlines, flags, rangeMin, rangeMax) +static constexpr int SURF_GetEmbeddedPanel = 6; +static constexpr int SURF_PushMakeCurrent = 8; +static constexpr int SURF_PopMakeCurrent = 9; +static constexpr int SURF_DrawSetColor = 10; +static constexpr int SURF_DrawFilledRect = 12; static constexpr int SURF_DrawSetTextFont = 17; -static constexpr int SURF_DrawSetTextPos = 19; +static constexpr int SURF_DrawSetTextColor = 18; +static constexpr int SURF_DrawSetTextPos = 20; static constexpr int SURF_DrawPrintText = 22; -static constexpr int SURF_CreateNewFont = 31; -static constexpr int SURF_SetFontGlyphSet = 32; +static constexpr int SURF_CreateFont = 66; +static constexpr int SURF_AddGlyphSetToFont = 67; +static int surf_get_embedded_panel() { + using Fn = int (*)(void *); + return vfunc(g_surface, SURF_GetEmbeddedPanel)(g_surface); +} +static void surf_push_make_current(int panel, bool use_insets) { + using Fn = void (*)(void *, int, bool); + vfunc(g_surface, SURF_PushMakeCurrent)(g_surface, panel, use_insets); +} +static void surf_pop_make_current(int panel) { + using Fn = void (*)(void *, int); + vfunc(g_surface, SURF_PopMakeCurrent)(g_surface, panel); +} +static void surf_draw_set_color(int r, int g, int b, int a) { + using Fn = void (*)(void *, int, int, int, int); + vfunc(g_surface, SURF_DrawSetColor)(g_surface, r, g, b, a); +} +static void surf_draw_filled_rect(int x0, int y0, int x1, int y1) { + using Fn = void (*)(void *, int, int, int, int); + vfunc(g_surface, SURF_DrawFilledRect)(g_surface, x0, y0, x1, y1); +} static void surf_draw_set_text_color(int r, int g, int b, int a) { using Fn = void (*)(void *, int, int, int, int); vfunc(g_surface, SURF_DrawSetTextColor)(g_surface, r, g, b, a); @@ -420,20 +465,23 @@ static void surf_draw_set_text_pos(int x, int y) { vfunc(g_surface, SURF_DrawSetTextPos)(g_surface, x, y); } static void surf_draw_print_text(const wchar_t *text, int count) { - using Fn = void (*)(void *, const wchar_t *, int); - vfunc(g_surface, SURF_DrawPrintText)(g_surface, text, count); + using Fn = void (*)(void *, const wchar_t *, int, int); + vfunc(g_surface, SURF_DrawPrintText)(g_surface, text, count, 0); // 0 = FONT_DRAW_DEFAULT } static HFont surf_create_font() { using Fn = HFont (*)(void *); - return vfunc(g_surface, SURF_CreateNewFont)(g_surface); + return vfunc(g_surface, SURF_CreateFont)(g_surface); } -static void surf_set_font_glyph_set(HFont f, const char *name, int tall, - int weight, int blur, int scanlines, - int flags) { - using Fn = void (*)(void *, HFont, const char *, int, int, int, int, int, int, +static bool surf_set_font_glyph_set(HFont f, const char *name, int tall, + int weight, int blur, int scanlines, + int flags, int range_min = 0, + int range_max = 0) { + using Fn = bool (*)(void *, HFont, const char *, int, int, int, int, int, int, int); - vfunc(g_surface, SURF_SetFontGlyphSet)(g_surface, f, name, tall, weight, - blur, scanlines, flags, 0, 0); + return vfunc(g_surface, SURF_AddGlyphSetToFont)(g_surface, f, name, tall, + weight, blur, scanlines, + flags, range_min, + range_max); } // wide string conversion for DrawPrintText @@ -442,114 +490,148 @@ static void draw_text(int x, int y, const char *text) { mbstowcs(wtext, text, 255); wtext[255] = 0; int len = (int)wcslen(wtext); + surf_draw_set_text_font(g_font); + surf_draw_set_text_color(255, 255, 255, 255); surf_draw_set_text_pos(x, y); surf_draw_print_text(wtext, len); } -// --------------------------------------------------------------------------- -// paint hook — draws menu overlay every frame -// --------------------------------------------------------------------------- - -using PaintFn = void (*)(void *, int); -static PaintFn oPaint = nullptr; - -static void hkdPaint(void *self, int mode) { - oPaint(self, mode); - - // init font on first paint - static bool font_inited = false; - if (!font_inited) { +// Draw overlay. MUST be called from inside the ISurface paint path +// (see hkdDrawPrintText hook in hl2h.cpp): we wrap our draws in +// PushMakeCurrent/PopMakeCurrent so they are submitted with a clean clip. +void outer_draw() { + menu_handle_input(); + // HL2H_DRAW_TEST=1 draws a probe rect without needing the menu open + static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; + if (!g_surface || (!g_menu_open && !draw_test)) + return; + if (!g_font) { g_font = surf_create_font(); - if (g_font) { - surf_set_font_glyph_set(g_font, "Consolas", 14, 400, 0, 0, 0); + if (!g_font) { + logf("outer_draw: CreateFont failed"); + return; + } + // FONTFLAG_CUSTOM only; Liberation Sans is an installed real TTF (Verdana + // substitutes to Noto). Raw glyphs, ASCII range pinned. + if (!surf_set_font_glyph_set(g_font, "Liberation Sans", 16, 400, 0, 0, + 0x400, 0x20, 0xFF)) { + logf("outer_draw: SetFontGlyphSet failed"); + g_font = 0; + return; } - font_inited = true; } - if (!g_font) + int panel = surf_get_embedded_panel(); + if (!panel) return; - - menu_handle_input(); - - if (!g_menu_open) + surf_push_make_current(panel, false); + if (draw_test) { + surf_draw_set_color(255, 0, 0, 255); + surf_draw_filled_rect(20, 20, 180, 60); + surf_draw_set_color(255, 255, 255, 255); + draw_text(28, 36, "HL2H DRAW TEST"); + } + if (!g_menu_open) { + surf_pop_make_current(panel); return; - - surf_draw_set_text_font(g_font); - - int y = 10; - rebuild_visible(); + } for (int i = 0; i < g_visible_count; i++) { MenuNode *n = g_visible[i]; - int indent = 0; - - // count depth by counting how many parents are visible before this node - // (simple: first node = depth 0, others = depth 1 if label is Active/Bind) - if (n->type == NODE_TOGGLE || n->type == NODE_BIND) - indent = 20; - - char buf[256]; + char line[96]; + int x = 12 + g_depth[i] * 12; + snprintf(line, sizeof(line), "%s %s", + (i == g_selected) ? ">" : (n->child_count ? "-" : " "), n->label); + draw_text(x, 10 + i * 20, line); + // right-aligned state if (n->type == NODE_TOGGLE) { - snprintf(buf, sizeof(buf), "%s: %s", n->label, - *n->active_ptr ? "ON" : "OFF"); + draw_text(200, 10 + i * 20, *n->active_ptr ? "[on]" : "[off]"); } else if (n->type == NODE_BIND) { - snprintf(buf, sizeof(buf), "%s: %s", n->label, - scancode_name(*n->bind_ptr)); - if (g_bind_mode && g_selected == i) - snprintf(buf, sizeof(buf), "%s: ...", n->label); - } else { - snprintf(buf, sizeof(buf), "%s", n->label); - } - - if (i == g_selected) { - surf_draw_set_text_color(255, 255, 0, 255); // yellow highlight - } else if (n->type == NODE_TOGGLE) { - surf_draw_set_text_color(*n->active_ptr ? 0 : 255, 255, 0, 255); - } else { - surf_draw_set_text_color(255, 255, 255, 255); + if (g_bind_mode && i == g_selected) + draw_text(200, 10 + i * 20, "[press key]"); + else { + /* stable name for current bind */ + static char bn[3][32]; + static FeatureState *feats[] = {&g_climb, &g_bhop, &g_abh}; + int fi = 0; + for (int k = 0; k < 3; k++) + if (n->bind_ptr == &feats[k]->bind) + fi = k; + snprintf(bn[fi], 32, "[%s]", scancode_name(*n->bind_ptr)); + draw_text(200, 10 + i * 20, bn[fi]); + } } - - draw_text(10 + indent, y, buf); - y += 18; } + surf_pop_make_current(panel); +} - // reset text color - surf_draw_set_text_color(255, 255, 255, 255); +// Call an arbitrary ISurface vtable slot with up to 2 long args (cdecl thiscall +// on i386: this first, rest on stack). +long surf_call_slot(void *surf, int slot, long a1, long a2) { + void **vt = *(void ***)surf; + using Fn = long (*)(void *, long, long); + return ((Fn)vt[slot])(surf, a1, a2); } // --------------------------------------------------------------------------- -// paint hook installation (called from setup_thread) +// ISurface discovery — try known modules and all loaded .so files // --------------------------------------------------------------------------- -// VEngineVGui001 vtable indices for this build -// (Paint is typically slot 6 — will be verified at runtime; if wrong, -// we crash and can adjust. Common Source 2007 layout.) -static constexpr int VGUI_Paint_SLOT = 6; - -static void install_paint_hook() { - // get IEngineVGui from engine - void *eh = dlopen(g_engine.path.c_str(), - RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); - if (!eh) - return; - auto ef = (CreateInterfaceFn)dlsym(eh, "CreateInterface"); - if (!ef) - return; - void *evgui = ef("VEngineVGui001", nullptr); - if (!evgui) { - logf("VEngineVGui001 not found"); - return; +static ISurface *find_surface_interface() { + static const char *known[] = {"engine.so", "client.so", nullptr}; + static const char *iface_names[] = { + "VGUI_Surface030", "VGUI_Surface001", + "MatSystemSurface008", "MatSystemSurface001", + "VGUI_Surface002", "VGUI_SURFACE001", + nullptr, + }; + // try known modules + for (const char **p = known; *p; p++) { + void *h = dlopen(*p, RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); + if (!h) continue; + auto fn = (CreateInterfaceFn)dlsym(h, "CreateInterface"); + if (!fn) continue; + for (const char **n = iface_names; *n; n++) { + int ret = 0; + ISurface *s = (ISurface *)fn(*n, &ret); + if (s) { logf("ISurface found: %s via %s (ret=%d)", *n, *p, ret); return s; } + } } - - // hook Paint - void **vt = VTABLE(evgui); - oPaint = (PaintFn)vt[VGUI_Paint_SLOT]; - vt[VGUI_Paint_SLOT] = (void *)&hkdPaint; - logf("paint hook installed: Paint %p -> %p", (void *)oPaint, - (void *)&hkdPaint); + // 2) scan /proc/self/maps for every loaded .so, try CreateInterface on each + FILE *maps = fopen("/proc/self/maps", "r"); + if (!maps) return nullptr; + char line[1024]; + while (fgets(line, sizeof(line), maps)) { + char *p = strchr(line, '/'); + if (!p) continue; + size_t pl = strlen(p); + while (pl && (p[pl-1] == '\n' || p[pl-1] == '\r')) p[--pl] = 0; + if (!strstr(p, ".so")) continue; + void *h = dlopen(p, RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); + if (!h) continue; + auto fn = (CreateInterfaceFn)dlsym(h, "CreateInterface"); + if (!fn) continue; + for (const char **n = iface_names; *n; n++) { + int ret = 0; + ISurface *s = (ISurface *)fn(*n, &ret); + if (s) { + logf("ISurface found: %s via %s", *n, p); + fclose(maps); + return s; + } + } + } + fclose(maps); + return nullptr; } -// public init function called from setup_thread void install_menu() { build_tree(); config_load(); - install_paint_hook(); + // resolve ISurface once (used by the DrawPrintText hook in hl2h.cpp and + // by outer_draw). DrawPrintText hook is installed from hkdCreateMove. + g_surface = find_surface_interface(); + if (!g_surface) { + logf("ISurface not found; menu disabled"); + return; + } + logf("ISurface = %p", g_surface); } -- cgit v1.2.3