// menu.cpp — menu state, rendering, input, config, paint hook, X11 mouse #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"); } // --------------------------------------------------------------------------- // menu tree structure // --------------------------------------------------------------------------- enum NodeType { NODE_ROOT, NODE_FEATURE, NODE_TOGGLE, NODE_BIND, NODE_VALUE }; struct MenuNode { NodeType type; const char *label; bool expanded; // for root/feature: whether children are visible int child_count; MenuNode *children[8]; // max children per node // leaf data bool *active_ptr; // NODE_TOGGLE: pointer to the active flag int *bind_ptr; // NODE_BIND: pointer to the key binding (SDL scancode) const char *bind_name; // NODE_BIND: current bind display name int *value_ptr = nullptr; // NODE_VALUE: pointer to the value int value_min = 0; int value_max = 0; int value_step = 0; }; // feature data struct FeatureState { bool active; int bind; // SDL scancode, 0 = none }; static FeatureState g_bhop{true, 0}; static bool g_bhop_autostrafe = true; static FeatureState g_abh{true, 0}; static FeatureState g_jumpbug{true, 0}; static bool g_jumpbug_assist = false; static FeatureState g_esp{true, 0}; static FeatureState g_aimbot{true, 604}; // default Mouse5, rebindable in menu static FeatureState g_triggerbot{true, 603}; // default Mouse4 static FeatureState g_silentaim{true, 602}; // default Mouse3 // Aimbot bone checklist. Head defaults on (v1 behaviour). enum { AIM_BONE_HEAD = 0, AIM_BONE_TORSO, AIM_BONE_LARM, AIM_BONE_RARM, AIM_BONE_LLEG, AIM_BONE_RLEG, AIM_BONE_COUNT }; static bool g_aim_bones[AIM_BONE_COUNT] = {true, false, false, false, false, false}; static int g_aim_smooth = 5; // degrees per tick static bool g_aim_vischeck = true; static int g_aim_fov = 60; // angular distance from the crosshair static bool g_aim_fov_circle = true; static int g_silent_fov = 60; // ESP sub-options (menu "ESP" children). Defaults match the pre-option // behaviour: everything on. static bool g_esp_opt[ESP_OPT_COUNT] = {true, true, true, true, true, true, true}; // tree static MenuNode g_root{NODE_ROOT, "HL2hake", false, 0, {}, nullptr, nullptr, nullptr}; static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", false, 1, {}, nullptr, nullptr, nullptr}; static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active, nullptr, nullptr}; static MenuNode g_bhop_autostrafe_node{NODE_TOGGLE, "Uni-directional auto-strafe", false, 0, {}, &g_bhop_autostrafe, nullptr, nullptr}; static MenuNode g_abh_feat{NODE_FEATURE, "ABH", false, 1, {}, nullptr, nullptr, nullptr}; static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr}; static MenuNode g_jumpbug_feat{NODE_FEATURE, "Jumpbug", false, 1, {}, nullptr, nullptr, nullptr}; static MenuNode g_jumpbug_active{NODE_TOGGLE, "Active", false, 0, {}, &g_jumpbug.active, nullptr, nullptr}; static MenuNode g_jumpbug_assist_node{NODE_TOGGLE, "Assist timing", false, 0, {}, &g_jumpbug_assist, nullptr, nullptr}; static MenuNode g_esp_feat{NODE_FEATURE, "ESP", false, 1, {}, nullptr, nullptr, nullptr}; static MenuNode g_esp_active{NODE_TOGGLE, "Active", false, 0, {}, &g_esp.active, nullptr, nullptr}; static MenuNode g_esp_opt_nodes[ESP_OPT_COUNT] = { {NODE_TOGGLE, "Show neutral", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_NEUTRAL], nullptr, nullptr}, {NODE_TOGGLE, "Show healthbar", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_HEALTHBAR], nullptr, nullptr}, {NODE_TOGGLE, "Show healthnum", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_HEALTHNUM], nullptr, nullptr}, {NODE_TOGGLE, "Show name", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_NAME], nullptr, nullptr}, {NODE_TOGGLE, "Show skeleton", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_SKELETON], nullptr, nullptr}, {NODE_TOGGLE, "Show box", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_BOX], nullptr, nullptr}, {NODE_TOGGLE, "Show speed graph", false, 0, {}, &g_esp_opt[ESP_OPT_SHOW_SPEEDGRAPH], nullptr, nullptr}, }; static MenuNode g_aimbot_feat{NODE_FEATURE, "Aimbot", false, 1, {}, nullptr, nullptr, nullptr}; static MenuNode g_aimbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_aimbot.active, nullptr, nullptr}; static MenuNode g_aimbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_aimbot.bind, nullptr}; static MenuNode g_aimbot_bones_feat{NODE_FEATURE, "Bones", false, 0, {}, nullptr, nullptr, nullptr}; static MenuNode g_aimbot_vischeck_node{NODE_TOGGLE, "Vischeck", false, 0, {}, &g_aim_vischeck, nullptr, nullptr}; static MenuNode g_aimbot_bone_nodes[AIM_BONE_COUNT] = { {NODE_TOGGLE, "Head", false, 0, {}, &g_aim_bones[AIM_BONE_HEAD], nullptr, nullptr}, {NODE_TOGGLE, "Torso", false, 0, {}, &g_aim_bones[AIM_BONE_TORSO], nullptr, nullptr}, {NODE_TOGGLE, "Left arm", false, 0, {}, &g_aim_bones[AIM_BONE_LARM], nullptr, nullptr}, {NODE_TOGGLE, "Right arm", false, 0, {}, &g_aim_bones[AIM_BONE_RARM], nullptr, nullptr}, {NODE_TOGGLE, "Left leg", false, 0, {}, &g_aim_bones[AIM_BONE_LLEG], nullptr, nullptr}, {NODE_TOGGLE, "Right leg", false, 0, {}, &g_aim_bones[AIM_BONE_RLEG], nullptr, nullptr}, }; static MenuNode g_aimbot_smooth_node{NODE_VALUE, "Smoothness", false, 0, {}, nullptr, nullptr, nullptr, &g_aim_smooth, 1, 45, 1}; static MenuNode g_aimbot_fov_node{NODE_VALUE, "FOV", false, 0, {}, nullptr, nullptr, nullptr, &g_aim_fov, 1, 180, 1}; static MenuNode g_aimbot_fov_circle_node{NODE_TOGGLE, "Show FOV circle", false, 0, {}, &g_aim_fov_circle, nullptr, nullptr}; static MenuNode g_triggerbot_feat{NODE_FEATURE, "Triggerbot", false, 1, {}, nullptr, nullptr, nullptr}; static MenuNode g_triggerbot_active{NODE_TOGGLE, "Active", false, 0, {}, &g_triggerbot.active, nullptr, nullptr}; static MenuNode g_triggerbot_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_triggerbot.bind, nullptr}; static MenuNode g_silentaim_feat{NODE_FEATURE, "Silent aim", false, 1, {}, nullptr, nullptr, nullptr}; static MenuNode g_silentaim_active{NODE_TOGGLE, "Active", false, 0, {}, &g_silentaim.active, nullptr, nullptr}; static MenuNode g_silentaim_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_silentaim.bind, nullptr}; static MenuNode g_silentaim_fov_node{NODE_VALUE, "FOV", false, 0, {}, nullptr, nullptr, nullptr, &g_silent_fov, 1, 180, 1}; static MenuNode *g_visible[48]; // flattened visible nodes for arrow navigation static int g_visible_count = 0; static int g_selected = 0; bool g_menu_open = false; static bool g_bind_mode = false; static void build_tree() { g_bhop_feat.children[0] = &g_bhop_active; g_bhop_feat.children[1] = &g_bhop_autostrafe_node; g_bhop_feat.child_count = 2; g_abh_feat.children[0] = &g_abh_active; g_abh_feat.child_count = 1; g_jumpbug_feat.children[0] = &g_jumpbug_active; g_jumpbug_feat.children[1] = &g_jumpbug_assist_node; g_jumpbug_feat.child_count = 2; g_esp_feat.children[0] = &g_esp_active; g_aimbot_feat.children[0] = &g_aimbot_active; g_aimbot_feat.children[1] = &g_aimbot_bind; g_aimbot_feat.children[2] = &g_aimbot_vischeck_node; g_aimbot_feat.children[3] = &g_aimbot_bones_feat; g_aimbot_feat.children[4] = &g_aimbot_smooth_node; g_aimbot_feat.children[5] = &g_aimbot_fov_node; g_aimbot_feat.children[6] = &g_aimbot_fov_circle_node; g_aimbot_feat.child_count = 7; for (int i = 0; i < AIM_BONE_COUNT; i++) g_aimbot_bones_feat.children[i] = &g_aimbot_bone_nodes[i]; g_aimbot_bones_feat.child_count = AIM_BONE_COUNT; g_triggerbot_feat.children[0] = &g_triggerbot_active; g_triggerbot_feat.children[1] = &g_triggerbot_bind; g_triggerbot_feat.child_count = 2; g_silentaim_feat.children[0] = &g_silentaim_active; g_silentaim_feat.children[1] = &g_silentaim_bind; g_silentaim_feat.children[2] = &g_silentaim_fov_node; g_silentaim_feat.child_count = 3; for (int i = 0; i < ESP_OPT_COUNT; i++) g_esp_feat.children[i + 1] = &g_esp_opt_nodes[i]; g_esp_feat.child_count = ESP_OPT_COUNT + 1; g_root.children[0] = &g_bhop_feat; g_root.children[1] = &g_abh_feat; g_root.children[2] = &g_jumpbug_feat; g_root.children[3] = &g_esp_feat; g_root.children[4] = &g_aimbot_feat; g_root.children[5] = &g_triggerbot_feat; g_root.children[6] = &g_silentaim_feat; g_root.child_count = 7; } static int g_depth[48]; // depth of each visible row (for indentation) static void flatten(MenuNode *node, int depth) { if (g_visible_count >= 48) return; node->type = (depth == 0) ? NODE_ROOT : (node->child_count > 0) ? NODE_FEATURE : (node->active_ptr) ? NODE_TOGGLE : (node->value_ptr) ? NODE_VALUE : 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++) flatten(node->children[i], depth + 1); } } static void rebuild_visible() { g_visible_count = 0; flatten(&g_root, 0); if (g_selected >= g_visible_count) g_selected = g_visible_count - 1; if (g_selected < 0) g_selected = 0; } // --------------------------------------------------------------------------- // 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"); return; } void *fc = dlopen("libfontconfig.so.1", RTLD_LAZY | RTLD_GLOBAL); if (!fc) { logf("font: libfontconfig.so.1 not available"); 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"); 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()); } static void config_save() { FILE *f = fopen(config_path().c_str(), "w"); if (!f) 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\nassist=%d\n", g_jumpbug.active, (int)g_jumpbug_assist); 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, "[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]); fclose(f); } static void config_load() { FILE *f = fopen(config_path().c_str(), "r"); if (!f) return; char line[256]; FeatureState *cur = nullptr; bool in_esp = false, in_bhop = false, in_aim = false, in_silent = false, in_jumpbug = 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, "[esp]")) cur = &g_esp; 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); } 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_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); } // --------------------------------------------------------------------------- // menu input handling // --------------------------------------------------------------------------- // SDL scancodes for navigation keys (matches the game's scancode set) static constexpr int SCAN_INSERT = 73; static constexpr int SCAN_UP = 82; // these are SDL scancodes static constexpr int SCAN_DOWN = 81; static constexpr int SCAN_LEFT = 80; static constexpr int SCAN_RIGHT = 79; static constexpr int SCAN_ENTER = 40; static constexpr int SCAN_ESCAPE = 41; // simplified scancode-to-name for common keys static 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 79: return "Right"; case 80: return "Left"; case 81: return "Down"; case 82: return "Up"; case 87: return "F1"; case 88: return "F2"; case 89: return "F3"; case 90: return "F4"; case 91: return "F5"; case 92: return "F6"; case 93: return "F7"; case 94: return "F8"; case 600: return "MouseL"; case 601: return "MouseR"; case 602: return "MouseM"; case 603: return "Mouse4"; case 604: return "Mouse5"; default: return scancode_name(0); // "None" for unmapped } } // get a snapshot of key states: returns array of currently-held scancodes static bool g_prev_key_state[608] = {}; static bool g_key_down[608] = {}; // true for ONE frame on press // 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_key_down[sc] = down && !g_prev_key_state[sc]; 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_key_down[i] = state[i] && !g_prev_key_state[i]; g_prev_key_state[i] = state[i]; } poll_mouse(); } static bool key_pressed(int sc) { return sc >= 0 && sc < MOUSE_BASE + 5 && g_key_down[sc]; } [[maybe_unused]] 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[7] = {&g_bhop, &g_abh, &g_esp, &g_aimbot, &g_triggerbot, &g_silentaim, &g_jumpbug}; if (feature < 0 || feature > 6) 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; } 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; } 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(); } return; } // menu is open if (g_bind_mode) { // wait for any key press (except Insert, Up, Down, Escape, Enter) for (int i = 0; i < MOUSE_BASE + 5; i++) { if (!g_key_down[i]) continue; if (i == SCAN_INSERT || i == SCAN_UP || i == SCAN_DOWN || i == SCAN_LEFT || i == SCAN_RIGHT) { g_bind_mode = false; return; // cancel bind select } if (i == SCAN_ESCAPE) { // set bind to None MenuNode *n = g_visible[g_selected]; if (n->bind_ptr) *n->bind_ptr = 0; g_bind_mode = false; config_save(); rebuild_visible(); return; } if (i == SCAN_ENTER) { g_bind_mode = false; return; // cancel, keep old bind } // any other key = new bind (features may share the same bind) MenuNode *n = g_visible[g_selected]; if (n->bind_ptr) *n->bind_ptr = i; g_bind_mode = false; config_save(); rebuild_visible(); return; } return; // no key pressed yet } // normal navigation if (key_pressed(SCAN_INSERT) || key_pressed(SCAN_ESCAPE)) { g_menu_open = false; return; } if (key_pressed(SCAN_UP) && g_selected > 0) { g_selected--; return; } if (key_pressed(SCAN_DOWN) && g_selected < g_visible_count - 1) { g_selected++; return; } if (key_pressed(SCAN_LEFT) || key_pressed(SCAN_RIGHT)) { MenuNode *n = g_visible[g_selected]; if (n->type == NODE_VALUE && n->value_ptr) { int dir = key_pressed(SCAN_RIGHT) ? 1 : -1; int step = n->value_step ? n->value_step : 1; int v = *n->value_ptr + dir * step; if (v < n->value_min) v = n->value_min; if (v > n->value_max) v = n->value_max; *n->value_ptr = v; config_save(); } return; } if (key_pressed(SCAN_ENTER)) { MenuNode *n = g_visible[g_selected]; if (n->child_count > 0) { n->expanded = !n->expanded; rebuild_visible(); } else if (n->type == NODE_TOGGLE && n->active_ptr) { *n->active_ptr = !(*n->active_ptr); config_save(); } else if (n->type == NODE_BIND) { g_bind_mode = true; } } } // --------------------------------------------------------------------------- // menu 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() { 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; bool esp_on = feature_active(FEAT_ESP); if (!g_surface || (!g_menu_open && !draw_test && !esp_on)) 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; logf("font: reset (local entity change)"); } 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)) { logf("outer_draw: font family %s", families[fi]); font_ok = true; } else { g_font = 0; } } if (!font_ok) { logf("outer_draw: no usable font"); return; } } int panel = surf_get_embedded_panel(); if (!panel) return; surf_push_make_current(panel, false); if (esp_on) esp_draw(); 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; } for (int i = 0; i < g_visible_count; i++) { MenuNode *n = g_visible[i]; 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) { draw_text(270, 10 + i * 20, *n->active_ptr ? "[on]" : "[off]"); } else if (n->type == NODE_BIND) { if (g_bind_mode && i == g_selected) draw_text(270, 10 + i * 20, "[press key]"); else { /* stable name for current bind */ static char bn[5][32]; static FeatureState *feats[] = {&g_bhop, &g_abh, &g_aimbot, &g_triggerbot, &g_silentaim}; int fi = 0; for (int k = 0; k < 5; k++) if (n->bind_ptr == &feats[k]->bind) fi = k; snprintf(bn[fi], 32, "[%s]", scancode_name(*n->bind_ptr)); draw_text(270, 10 + i * 20, bn[fi]); } } else if (n->type == NODE_VALUE) { char vb[32]; snprintf(vb, sizeof(vb), "%d deg/tick", *n->value_ptr); draw_text(270, 10 + i * 20, vb); } } 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() { build_tree(); register_bundled_font(); logf("config: %s", config_path().c_str()); config_load(); // 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); }