// menu.cpp — feature state, config, overlay draw + ISurface discovery #include "hl2h.h" #include #include #include #include #include #include #include // --------------------------------------------------------------------------- // ConMsg (tier0 console printer — prints to in-game console) // --------------------------------------------------------------------------- void (*g_conmsg)(const char *fmt, ...) = nullptr; void init_conmsg() { g_conmsg = (decltype(g_conmsg))dlsym(RTLD_DEFAULT, "_Z6ConMsgPKcz"); if (g_conmsg) return; // fallback: dlopen libtier0.so from the engine directory size_t slash = g_engine.path.find_last_of('/'); if (slash == std::string::npos) return; std::string tier0 = g_engine.path.substr(0, slash + 1) + "libtier0.so"; void *h = dlopen(tier0.c_str(), RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); if (!h) h = dlopen(tier0.c_str(), RTLD_LAZY | RTLD_GLOBAL); if (h) g_conmsg = (decltype(g_conmsg))dlsym(h, "_Z6ConMsgPKcz"); } // feature data (declared in hl2h.h; shared with the GUI) FeatureState g_bhop{true, 0}; bool g_bhop_autostrafe = true; FeatureState g_abh{true, 0}; FeatureState g_jumpbug{true, 0}; bool g_jumpbug_assist = false; FeatureState g_melee{true, 0}; FeatureState g_spread{true, 0}; bool g_spread_no_spread = true; FeatureState g_esp{true, 0}; FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in the GUI FeatureState g_triggerbot{true, 603}; // default Mouse4 FeatureState g_silentaim{true, 602}; // default Mouse3 FeatureState g_item_esp{true, 0}; // valuable item tags, always-on by default FeatureState g_fullbright{false, 0}; // mat_fullbright world lighting bool g_item_health = true; bool g_item_ammo = true; bool g_item_armor = true; bool g_item_weapons = true; bool g_item_crates = true; bool g_item_distance = true; int g_item_max_dist = 5000; // Aimbot bone checklist. Head defaults on (v1 behaviour). bool g_aim_bones[AIM_BONE_COUNT] = {true, false, false, false, false, false}; int g_aim_smooth = 5; // degrees per tick bool g_aim_vischeck = true; int g_aim_fov = 60; // angular distance from the crosshair bool g_aim_fov_circle = true; int g_silent_fov = 60; // ESP sub-options. Defaults match the pre-option behaviour: everything on. bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true}; // --------------------------------------------------------------------------- // config persistence (INI format) // --------------------------------------------------------------------------- static void mkdir_p(const std::string &path) { for (size_t i = 1; i <= path.size(); i++) { if (i != path.size() && path[i] != '/') continue; std::string p = path.substr(0, i); if (!p.empty() && p != "/") mkdir(p.c_str(), 0755); } } static std::string config_path() { const char *xdg = getenv("XDG_CONFIG_HOME"); std::string base; if (xdg && *xdg) { base = xdg; } else { const char *home = getenv("HOME"); base = (home && *home) ? std::string(home) + "/.config" : "."; } std::string dir = base + "/hl2hake"; mkdir_p(dir); return dir + "/config"; } // Register the bundled Liberation Sans with fontconfig so the VGUI overlay // does not depend on the host/game install having it. The font file lives in // assets/ next to the .so (or the system path as a fallback). void register_bundled_font() { static bool done = false; if (done) return; done = true; std::string path; Dl_info info; if (dladdr((void *)®ister_bundled_font, &info) && info.dli_fname) { char resolved[4096]; const char *lib = realpath(info.dli_fname, resolved); std::string p = lib ? resolved : info.dli_fname; size_t slash = p.find_last_of('/'); if (slash != std::string::npos) p.resize(slash + 1); else p = "./"; p += "assets/LiberationSans-Regular.ttf"; if (access(p.c_str(), R_OK) == 0) path = p; } if (path.empty()) { const char *sys = "/usr/share/fonts/liberation/LiberationSans-Regular.ttf"; if (access(sys, R_OK) == 0) path = sys; } if (path.empty()) { logf("font: bundled LiberationSans-Regular.ttf not found"); toastf(TOAST_WARN, "Bundled font missing; overlay may not render"); return; } void *fc = dlopen("libfontconfig.so.1", RTLD_LAZY | RTLD_GLOBAL); if (!fc) { logf("font: libfontconfig.so.1 not available"); toastf(TOAST_WARN, "fontconfig unavailable; overlay font not registered"); return; } using InitFn = int (*)(); using GetCurrentFn = void *(*)(); using AddFileFn = int (*)(void *, const char *); auto init = (InitFn)dlsym(fc, "FcInit"); auto get = (GetCurrentFn)dlsym(fc, "FcConfigGetCurrent"); auto add = (AddFileFn)dlsym(fc, "FcConfigAppFontAddFile"); if (!init || !get || !add) { logf("font: fontconfig symbols missing"); toastf(TOAST_WARN, "fontconfig symbols missing; overlay font not registered"); return; } init(); void *cfg = get(); if (cfg && add(cfg, path.c_str())) logf("font: registered %s", path.c_str()); else { logf("font: failed to register %s", path.c_str()); toastf(TOAST_WARN, "Could not register the overlay font"); } } void config_save() { std::string path = config_path(); std::string tmp = path + ".tmp"; FILE *f = fopen(tmp.c_str(), "w"); if (!f) { toastf(TOAST_ERROR, "Config save failed: %s", path.c_str()); return; } fprintf(f, "[bhop]\nactive=%d\nbind=%d\nautostrafe=%d\n", g_bhop.active, g_bhop.bind, g_bhop_autostrafe); fprintf(f, "[abh]\nactive=%d\nbind=%d\n", g_abh.active, g_abh.bind); fprintf(f, "[jumpbug]\nactive=%d\nbind=%d\nassist=%d\n", g_jumpbug.active, g_jumpbug.bind, (int)g_jumpbug_assist); fprintf(f, "[melee]\nactive=%d\nbind=%d\n", g_melee.active, g_melee.bind); fprintf(f, "[spread]\nactive=%d\nbind=%d\nno_spread=%d\n", g_spread.active, g_spread.bind, (int)g_spread_no_spread); fprintf(f, "[aimbot]\nactive=%d\nbind=%d\nsmooth=%d\nvischeck=%d\n" "fov=%d\nfov_circle=%d\n" "bone_head=%d\nbone_torso=%d\nbone_larm=%d\nbone_rarm=%d\n" "bone_lleg=%d\nbone_rleg=%d\n", g_aimbot.active, g_aimbot.bind, g_aim_smooth, (int)g_aim_vischeck, g_aim_fov, (int)g_aim_fov_circle, g_aim_bones[AIM_BONE_HEAD], g_aim_bones[AIM_BONE_TORSO], g_aim_bones[AIM_BONE_LARM], g_aim_bones[AIM_BONE_RARM], g_aim_bones[AIM_BONE_LLEG], g_aim_bones[AIM_BONE_RLEG]); fprintf(f, "[triggerbot]\nactive=%d\nbind=%d\n", g_triggerbot.active, g_triggerbot.bind); fprintf(f, "[silentaim]\nactive=%d\nbind=%d\nfov=%d\n", g_silentaim.active, g_silentaim.bind, g_silent_fov); fprintf(f, "[fullbright]\nactive=%d\nbind=%d\n", g_fullbright.active, g_fullbright.bind); fprintf(f, "[items]\nactive=%d\nbind=%d\nhealth=%d\nammo=%d\n" "armor=%d\nweapons=%d\ncrates=%d\ndistance=%d\nmax_dist=%d\n", g_item_esp.active, g_item_esp.bind, (int)g_item_health, (int)g_item_ammo, (int)g_item_armor, (int)g_item_weapons, (int)g_item_crates, (int)g_item_distance, g_item_max_dist); fprintf(f, "[esp]\nactive=%d\nbind=%d\nneutral=%d\nhealthbar=%d\n" "healthnum=%d\nname=%d\nskeleton=%d\nbox=%d\nspeedgraph=%d\n", g_esp.active, g_esp.bind, g_esp_opt[ESP_OPT_SHOW_NEUTRAL], g_esp_opt[ESP_OPT_SHOW_HEALTHBAR], g_esp_opt[ESP_OPT_SHOW_HEALTHNUM], g_esp_opt[ESP_OPT_SHOW_NAME], g_esp_opt[ESP_OPT_SHOW_SKELETON], g_esp_opt[ESP_OPT_SHOW_BOX], g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH]); gui_config_write(f); bool ok = ferror(f) == 0; if (fflush(f) != 0) ok = false; if (ok) (void)fsync(fileno(f)); // best effort; the rename is what matters if (fclose(f) != 0) ok = false; if (!ok || rename(tmp.c_str(), path.c_str()) != 0) { remove(tmp.c_str()); toastf(TOAST_ERROR, "Config save failed: %s", path.c_str()); } } static bool config_load() { FILE *f = fopen(config_path().c_str(), "r"); if (!f) return false; char line[256]; FeatureState *cur = nullptr; bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false, in_jumpbug = false, in_spread = false, in_items = false; while (fgets(line, sizeof(line), f)) { line[strcspn(line, "\r\n")] = 0; if (line[0] == '[') { if (strstr(line, "[bhop]")) cur = &g_bhop; else if (strstr(line, "[abh]")) cur = &g_abh; else if (strstr(line, "[jumpbug]")) cur = &g_jumpbug; else if (strstr(line, "[melee]")) cur = &g_melee; else if (strstr(line, "[spread]")) cur = &g_spread; else if (strstr(line, "[esp]")) cur = &g_esp; else if (strstr(line, "[items]")) cur = &g_item_esp; else if (strstr(line, "[fullbright]")) cur = &g_fullbright; else if (strstr(line, "[aimbot]")) cur = &g_aimbot; else if (strstr(line, "[triggerbot]")) cur = &g_triggerbot; else if (strstr(line, "[silentaim]")) cur = &g_silentaim; else cur = nullptr; in_esp = (cur == &g_esp); in_bhop = (cur == &g_bhop); in_aim = (cur == &g_aimbot); in_silent = (cur == &g_silentaim); in_jumpbug = (cur == &g_jumpbug); in_spread = (cur == &g_spread); in_items = (cur == &g_item_esp); } else if (cur) { char *eq = strchr(line, '='); if (!eq) continue; *eq = 0; const char *key = line, *val = eq + 1; if (in_bhop && strcmp(key, "autostrafe") == 0) { g_bhop_autostrafe = atoi(val) != 0; } if (in_jumpbug && strcmp(key, "assist") == 0) { g_jumpbug_assist = atoi(val) != 0; } if (in_spread && strcmp(key, "no_spread") == 0) { g_spread_no_spread = atoi(val) != 0; } if (in_items) { if (strcmp(key, "health") == 0) g_item_health = atoi(val) != 0; else if (strcmp(key, "ammo") == 0) g_item_ammo = atoi(val) != 0; else if (strcmp(key, "armor") == 0) g_item_armor = atoi(val) != 0; else if (strcmp(key, "weapons") == 0) g_item_weapons = atoi(val) != 0; else if (strcmp(key, "crates") == 0) g_item_crates = atoi(val) != 0; else if (strcmp(key, "distance") == 0) g_item_distance = atoi(val) != 0; else if (strcmp(key, "max_dist") == 0) { int v = atoi(val); if (v < 0) v = 0; // 0 = unlimited if (v > 10000) v = 10000; g_item_max_dist = v; } } if (in_aim) { if (strcmp(key, "smooth") == 0) { int v = atoi(val); if (v < 1) v = 1; if (v > 45) v = 45; g_aim_smooth = v; } else if (strcmp(key, "bone_head") == 0) { g_aim_bones[AIM_BONE_HEAD] = atoi(val) != 0; } else if (strcmp(key, "bone_torso") == 0) { g_aim_bones[AIM_BONE_TORSO] = atoi(val) != 0; } else if (strcmp(key, "bone_larm") == 0) { g_aim_bones[AIM_BONE_LARM] = atoi(val) != 0; } else if (strcmp(key, "bone_rarm") == 0) { g_aim_bones[AIM_BONE_RARM] = atoi(val) != 0; } else if (strcmp(key, "bone_lleg") == 0) { g_aim_bones[AIM_BONE_LLEG] = atoi(val) != 0; } else if (strcmp(key, "bone_rleg") == 0) { g_aim_bones[AIM_BONE_RLEG] = atoi(val) != 0; } else if (strcmp(key, "vischeck") == 0) { g_aim_vischeck = atoi(val) != 0; } else if (strcmp(key, "fov") == 0) { int v = atoi(val); if (v < 1) v = 1; if (v > 180) v = 180; g_aim_fov = v; } else if (strcmp(key, "fov_circle") == 0) { g_aim_fov_circle = atoi(val) != 0; } } if (in_silent && strcmp(key, "fov") == 0) { int v = atoi(val); if (v < 1) v = 1; if (v > 180) v = 180; g_silent_fov = v; } if (in_esp) { if (strcmp(key, "neutral") == 0) g_esp_opt[ESP_OPT_SHOW_NEUTRAL] = atoi(val) != 0; else if (strcmp(key, "healthbar") == 0) g_esp_opt[ESP_OPT_SHOW_HEALTHBAR] = atoi(val) != 0; else if (strcmp(key, "healthnum") == 0) g_esp_opt[ESP_OPT_SHOW_HEALTHNUM] = atoi(val) != 0; else if (strcmp(key, "name") == 0) g_esp_opt[ESP_OPT_SHOW_NAME] = atoi(val) != 0; else if (strcmp(key, "skeleton") == 0) g_esp_opt[ESP_OPT_SHOW_SKELETON] = atoi(val) != 0; else if (strcmp(key, "box") == 0) g_esp_opt[ESP_OPT_SHOW_BOX] = atoi(val) != 0; else if (strcmp(key, "speedgraph") == 0) g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH] = atoi(val) != 0; } if (strcmp(key, "active") == 0) cur->active = atoi(val) != 0; else if (strcmp(key, "bind") == 0) cur->bind = atoi(val); } } fclose(f); return true; } void config_reload() { if (config_load()) toastf(TOAST_INFO, "Config reloaded"); else toastf(TOAST_WARN, "Config not found; kept current settings"); } const char *config_path_cstr() { static std::string path; path = config_path(); return path.c_str(); } void config_reset_defaults() { g_bhop = {true, 0}; g_bhop_autostrafe = true; g_abh = {true, 0}; g_jumpbug = {true, 0}; g_jumpbug_assist = false; g_melee = {true, 0}; g_spread = {true, 0}; g_spread_no_spread = true; g_esp = {true, 0}; for (int i = 0; i < ESP_OPT_COUNT; i++) g_esp_opt[i] = true; g_item_esp = {true, 0}; g_fullbright = {false, 0}; g_item_health = true; g_item_ammo = true; g_item_armor = true; g_item_weapons = true; g_item_crates = true; g_item_distance = true; g_item_max_dist = 5000; g_aimbot = {true, 604}; g_triggerbot = {true, 603}; g_silentaim = {true, 602}; for (int i = 0; i < AIM_BONE_COUNT; i++) g_aim_bones[i] = false; g_aim_bones[AIM_BONE_HEAD] = true; g_aim_smooth = 5; g_aim_vischeck = true; g_aim_fov = 60; g_aim_fov_circle = true; g_silent_fov = 60; config_save(); } // simplified scancode-to-name for common keys const char *scancode_name(int sc) { switch (sc) { case 0: return "None"; case 4: return "A"; case 5: return "B"; case 6: return "C"; case 7: return "D"; case 8: return "E"; case 9: return "F"; case 10: return "G"; case 11: return "H"; case 12: return "I"; case 13: return "J"; case 14: return "K"; case 15: return "L"; case 16: return "M"; case 17: return "N"; case 18: return "O"; case 19: return "P"; case 20: return "Q"; case 21: return "R"; case 22: return "S"; case 23: return "T"; case 24: return "U"; case 25: return "V"; case 26: return "W"; case 27: return "X"; case 28: return "Y"; case 29: return "Z"; case 30: return "1"; case 31: return "2"; case 32: return "3"; case 33: return "4"; case 34: return "5"; case 35: return "6"; case 36: return "7"; case 37: return "8"; case 38: return "9"; case 39: return "0"; case 40: return "Enter"; case 41: return "Escape"; case 42: return "Backspace"; case 43: return "Tab"; case 44: return "Space"; case 45: return "Grave"; case 46: return "Minus"; case 47: return "Equals"; case 48: return "LBracket"; case 49: return "RBracket"; case 50: return "Backslash"; case 51: return "Semicolon"; case 52: return "Apostrophe"; case 53: return "Tilde"; case 54: return "Comma"; case 55: return "Period"; case 56: return "Slash"; case 57: return "CapsLock"; case 79: return "Right"; case 80: return "Left"; case 81: return "Down"; case 82: return "Up"; case 58: return "F1"; case 59: return "F2"; case 60: return "F3"; case 61: return "F4"; case 62: return "F5"; case 63: return "F6"; case 64: return "F7"; case 65: return "F8"; case 66: return "F9"; case 67: return "F10"; case 68: return "F11"; case 69: return "F12"; case 73: return "Insert"; case 74: return "Home"; case 75: return "PgUp"; case 76: return "Delete"; case 77: return "End"; case 78: return "PgDn"; case 600: return "MouseL"; case 601: return "MouseR"; case 602: return "MouseM"; case 603: return "Mouse4"; case 604: return "Mouse5"; default: { static char buf[16]; snprintf(buf, sizeof(buf), "Key%d", sc); return buf; } } } // held state for feature binds static bool g_prev_key_state[608] = {}; // read SDL_GetKeyboardState — the game's own SDL is in-process using SDL_GetKeyboardState_fn = const uint8_t *(*)(int *); static SDL_GetKeyboardState_fn SDL_GetKeyboardState_real = nullptr; // mouse buttons map to pseudo-scancodes 600..604 static constexpr int MOUSE_BASE = 600; // 600=L 601=R 602=M 603=X1 604=X2 using SDL_GetMouseState_fn = unsigned (*)(int *, int *); static SDL_GetMouseState_fn SDL_GetMouseState_real = nullptr; static void poll_mouse() { if (!SDL_GetMouseState_real) SDL_GetMouseState_real = (SDL_GetMouseState_fn)dlsym(RTLD_DEFAULT, "SDL_GetMouseState"); if (!SDL_GetMouseState_real) return; int mx, my; unsigned mask = SDL_GetMouseState_real(&mx, &my); for (int b = 0; b < 5; b++) { bool down = (mask & (1u << b)) != 0; // SDL buttons 1..5 = bits 0..4 int sc = MOUSE_BASE + b; g_prev_key_state[sc] = down; } } static void poll_keyboard() { if (!SDL_GetKeyboardState_real) SDL_GetKeyboardState_real = (SDL_GetKeyboardState_fn)dlsym(RTLD_DEFAULT, "SDL_GetKeyboardState"); if (!SDL_GetKeyboardState_real) return; int numkeys = 0; const uint8_t *state = SDL_GetKeyboardState_real(&numkeys); if (state) for (int i = 0; i < numkeys && i < MOUSE_BASE; i++) g_prev_key_state[i] = state[i] != 0; poll_mouse(); } static bool key_held(int sc) { return sc >= 0 && sc < MOUSE_BASE + 5 && g_prev_key_state[sc]; } // feature gate queried from hl2h.cpp do_move(): feature must be Active AND // its bind key held (always-on when unbound). static FeatureState *feature_state(int feature) { static FeatureState *feats[FEAT_COUNT] = { &g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot, &g_silentaim, &g_jumpbug, &g_melee, &g_spread, &g_item_esp, &g_fullbright}; if (feature < 0 || feature >= FEAT_COUNT) return nullptr; return feats[feature]; } bool feature_active(int feature) { FeatureState *f = feature_state(feature); if (!f || !f->active) return false; return f->bind == 0 || key_held(f->bind); } bool feature_has_bind(int feature) { FeatureState *f = feature_state(feature); return f && f->bind != 0; } bool feature_bind_held(int feature) { FeatureState *f = feature_state(feature); return f && f->bind != 0 && key_held(f->bind); } bool esp_option(int opt) { return opt >= 0 && opt < ESP_OPT_COUNT && g_esp_opt[opt]; } bool bhop_autostrafe_enabled() { return g_bhop_autostrafe; } bool jumpbug_assist_enabled() { return g_jumpbug_assist; } bool spread_no_spread_enabled() { return g_spread_no_spread; } unsigned aimbot_bone_mask() { unsigned m = 0; if (g_aim_bones[AIM_BONE_HEAD]) m |= 1u << 1; // HITGROUP_HEAD if (g_aim_bones[AIM_BONE_TORSO]) m |= (1u << 2) | (1u << 3); // chest + stomach if (g_aim_bones[AIM_BONE_LARM]) m |= 1u << 4; if (g_aim_bones[AIM_BONE_RARM]) m |= 1u << 5; if (g_aim_bones[AIM_BONE_LLEG]) m |= 1u << 6; if (g_aim_bones[AIM_BONE_RLEG]) m |= 1u << 7; return m; } int aimbot_smooth_step() { return g_aim_smooth; } bool aimbot_vischeck_enabled() { return g_aim_vischeck; } int aimbot_fov() { return g_aim_fov; } int silentaim_fov() { return g_silent_fov; } bool aimbot_fov_circle() { return g_aim_fov_circle; } // --------------------------------------------------------------------------- // overlay rendering (VGUI ISurface) // --------------------------------------------------------------------------- // VGUI surface interface struct ISurface; using HFont = unsigned long; ISurface *g_surface = nullptr; static HFont g_font = 0; // 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_DrawSetTextColor = 18; static constexpr int SURF_DrawSetTextPos = 20; static constexpr int SURF_DrawPrintText = 22; 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); } static void surf_draw_set_text_font(HFont f) { using Fn = void (*)(void *, HFont); vfunc(g_surface, SURF_DrawSetTextFont)(g_surface, f); } static void surf_draw_set_text_pos(int x, int y) { using Fn = void (*)(void *, int, int); 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, 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_CreateFont)(g_surface); } 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); return vfunc(g_surface, SURF_AddGlyphSetToFont)(g_surface, f, name, tall, weight, blur, scanlines, flags, range_min, range_max); } // wide string conversion for DrawPrintText static void draw_text(int x, int y, const char *text) { wchar_t wtext[256]; 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); } void draw_text_col(int x, int y, const char *text, int r, int g, int b) { wchar_t wtext[256]; mbstowcs(wtext, text, 255); wtext[255] = 0; surf_draw_set_text_font(g_font); surf_draw_set_text_color(r, g, b, 255); surf_draw_set_text_pos(x, y); surf_draw_print_text(wtext, (int)wcslen(wtext)); } // 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() { poll_keyboard(); fullbright_update(); gui_handle_input(); // HL2H_DRAW_TEST=1 draws a probe rect without needing the GUI open static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr; bool esp_on = feature_active(FEAT_ESP); bool items_on = feature_active(FEAT_ITEM_ESP); bool toasts = gui_toasts_active(); if (!g_surface || (!gui_is_open() && !draw_test && !esp_on && !items_on && !toasts)) return; // Level/menu -> game transitions can invalidate the VGUI font handle. // Recreate it when the local player entity changes (menu -> map, map load). static void *last_local = nullptr; bool font_reset = false; if (g_iface.entitylist) { int local = engine_get_local_player(); void *le = local > 0 ? entitylist_get(local) : nullptr; if (le != last_local) { last_local = le; font_reset = true; } } if (font_reset && g_font) { g_font = 0; logd("font: reset (local entity change)"); } bool overlay_font = true; if (!g_font) { static const char *families[] = { "Liberation Sans", "DejaVu Sans", "Noto Sans", "Tahoma", "Verdana", "Arial", nullptr}; bool font_ok = false; for (int fi = 0; families[fi] && !font_ok; fi++) { g_font = surf_create_font(); if (!g_font) continue; if (surf_set_font_glyph_set(g_font, families[fi], 16, 400, 0, 0, 0x400, 0x20, 0xFF)) { logd("outer_draw: font family %s", families[fi]); font_ok = true; } else { g_font = 0; } } if (!font_ok) { logf("outer_draw: no usable font"); toastf(TOAST_ERROR, "No usable overlay font"); overlay_font = false; } } int panel = surf_get_embedded_panel(); if (!panel) return; surf_push_make_current(panel, false); if (esp_on && overlay_font) esp_draw(); if (items_on && overlay_font) esp_draw_items(); if (draw_test && overlay_font) { 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 (gui_is_open()) gui_draw(); if (toasts) gui_draw_toasts(); surf_pop_make_current(panel); } // --------------------------------------------------------------------------- // ISurface discovery — try known modules and all loaded .so files // --------------------------------------------------------------------------- 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; } } } // 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; } void install_menu() { register_bundled_font(); logf("config: %s", config_path().c_str()); if (!config_load()) toastf(TOAST_INFO, "No config found; using defaults"); gui_config_init(); // 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"); toastf(TOAST_ERROR, "Overlay unavailable: ISurface not found"); return; } logf("ISurface = %p", g_surface); }