// 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 = mod_engine().path.find_last_of('/'); if (slash == std::string::npos) return; std::string tier0 = mod_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 FeatureState g_world_tint{false, 0}; // material colour modulation bool g_tint_world_on = true; bool g_tint_sky_on = true; bool g_tint_prop_on = true; int g_tint_world_rgb[3] = {255, 255, 255}; int g_tint_sky_rgb[3] = {255, 255, 255}; int g_tint_prop_rgb[3] = {255, 255, 255}; FeatureState g_fog{false, 0}; // fog_override colour/distance bool g_fog_skybox = true; bool g_fog_disable = false; int g_fog_rgb[3] = {160, 170, 190}; int g_fog_start = 200; int g_fog_end = 3000; FeatureState g_skybox{false, 0}; // skybox.png image override FeatureState g_projectiles{true, 0}; // grenade / rocket ESP bool g_proj_fuse = true; bool g_proj_radius = true; bool g_proj_traj = true; FeatureState g_chams{false, 0}; ChamsGroup g_chams_hostile{true, 3, {255, 60, 60, 255}, true, 3, {255, 60, 60, 255}}; ChamsGroup g_chams_neutral{true, 3, {220, 220, 220, 255}, false, 3, {220, 220, 220, 255}}; ChamsGroup g_chams_friendly{true, 3, {60, 255, 60, 255}, false, 3, {60, 255, 60, 255}}; bool g_chams_viewmodel = false; int g_chams_vm_mat = 3; int g_chams_vm_rgb[4] = {200, 200, 255, 255}; 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; bool g_item_usable = 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; bool g_aim_punch = true; // compensate server aim punch bool g_spread_engine = true; // zero the pellet random draws, not just the seed bool g_auto_crouchjump = false; bool g_edge_jump = false; int g_fov_camera = 75; // HL2's stock camera FOV int g_fov_viewmodel = 54; // HL2's stock viewmodel FOV int g_tonemap = 0; // mat_force_tonemap_scale x10, 0 = automatic int g_esp_col_hostile[3] = {255, 60, 60}; int g_esp_col_neutral[3] = {220, 220, 220}; int g_esp_col_friendly[3] = {60, 255, 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, 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; } settings_save(f); 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; settings_load(f); 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(); } const char *config_file_cstr(const char *name) { static std::string path; std::string p = config_path(); size_t slash = p.find_last_of('/'); std::string dir = slash == std::string::npos ? std::string(".") : p.substr(0, slash); path = dir + "/" + (name ? name : ""); return path.c_str(); } void config_reset_defaults() { settings_reset_defaults(); 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, &g_world_tint, &g_fog, &g_skybox, &g_projectiles, &g_chams}; 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_enabled(int feature) { FeatureState *f = feature_state(feature); return f && f->active; } 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; } bool aim_punch_enabled() { return g_aim_punch; } bool spread_engine_enabled() { return g_spread_engine; } bool aimbot_fov_circle() { return g_aim_fov_circle; } // --------------------------------------------------------------------------- // overlay rendering (VGUI ISurface) // --------------------------------------------------------------------------- // VGUI surface interface struct ISurface; using HFont = unsigned long; static HFont g_font = 0; static HFont g_font_small = 0; // ISurface access lives in overlay.cpp; these wrappers keep the call sites in // this file unchanged. static int surf_get_embedded_panel() { return overlay_embedded_panel(); } static void surf_push_make_current(int panel, bool use_insets) { overlay_push_make_current(panel, use_insets); } static void surf_pop_make_current(int panel) { overlay_pop_make_current(panel); } static void surf_draw_set_color(int r, int g, int b, int a) { overlay_color(r, g, b, a); } static void surf_draw_filled_rect(int x0, int y0, int x1, int y1) { overlay_fill(x0, y0, x1, y1); } static void surf_draw_set_text_color(int r, int g, int b, int a) { overlay_text_color(r, g, b, a); } static void surf_draw_set_text_font(HFont f) { overlay_text_font(f); } static void surf_draw_set_text_pos(int x, int y) { overlay_text_pos(x, y); } static void surf_draw_print_text(const wchar_t *text, int count) { overlay_print_text(text, count); } static HFont surf_create_font() { return overlay_create_font(); } 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) { return overlay_add_glyph_set(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)); } void draw_text_small_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_small ? g_font_small : 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)); } // --------------------------------------------------------------------------- // mat_fullbright // --------------------------------------------------------------------------- static int g_fullbright_applied = -1; static bool g_fullbright_fail_logged = false; void fullbright_invalidate() { g_fullbright_applied = -1; } void fullbright_update() { bool want = feature_active(FEAT_FULLBRIGHT); if ((int)want == g_fullbright_applied) return; // Avoid touching the engine console before the first map/menu frame. The // engine may still be initializing its command buffer. if (!want && g_fullbright_applied < 0) { g_fullbright_applied = 0; return; } bool ok = cvar_set_int("mat_fullbright", want ? 1 : 0); g_fullbright_applied = want; if (ok) { if (want) logf("fullbright: on"); else logd("fullbright: off"); return; } if (want && !g_fullbright_fail_logged) { g_fullbright_fail_logged = true; logf("fullbright: mat_fullbright cvar unavailable"); toastf(TOAST_ERROR, "Fullbright unavailable: cvar interface missing"); } } // 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() { // PaintTraverseEx runs for every VGUI panel and DrawPrintText for every text // draw, so collapse them to one draw per engine frame (see overlay.h). if (!overlay_should_draw()) return; poll_keyboard(); fullbright_update(); world_tint_update(); prop_tint_update(); fog_update(); chams_preload(); 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 projs_on = feature_active(FEAT_PROJECTILES); // The FOV circle belongs to the aim features, not to ESP, and it is a visual // aid: use the enable toggle rather than the bind, otherwise it only appears // while the aim key is held (default bind is mouse5). bool aim_on = (feature_enabled(FEAT_AIMBOT) || feature_enabled(FEAT_SILENTAIM)) && aimbot_fov_circle(); bool toasts = gui_toasts_active(); if (!g_surface || (!gui_is_open() && !draw_test && !esp_on && !items_on && !projs_on && !aim_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; world_tint_invalidate(); fog_invalidate(); skybox_invalidate(); } } if (font_reset && g_font) { g_font = 0; g_font_small = 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; const char *chosen = nullptr; 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; chosen = families[fi]; } else { g_font = 0; } } if (font_ok && chosen && !g_font_small) { g_font_small = surf_create_font(); if (g_font_small && !surf_set_font_glyph_set(g_font_small, chosen, 10, 400, 0, 0, 0x400, 0x20, 0xFF)) g_font_small = 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 (aim_on && overlay_font) esp_draw_fov_circle(); if (esp_on && overlay_font) esp_draw(); if (items_on && overlay_font) esp_draw_items(); if (projs_on && overlay_font) esp_draw_projectiles(); 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); }