# HL2hake ISurface Drawing — Status Report ## Date: 2026-09-12, updated 2026-10-27 ## Result: WORKING Overlay drawing works in-game and in menus. Draw path: `PaintTraverseEx` hook (slot 114) on the copied ISurface vtable, wrapping our draws in `PushMakeCurrent(panel, false)` / `PopMakeCurrent(panel)` around ISurface slots 6/10/12/15/17/18/20/22/66/67. ## Confirmed ISurface VTable Slots (disassembly-verified, binary=SDK header+4) | Slot | Function | Evidence | |------|-------------------------------------------|----------| | 6 | `GetEmbeddedPanel()` | returns VPANEL used with PushMakeCurrent | | 8 | `PushMakeCurrent(VPANEL, bool)` | verified via disassembly | | 9 | `PopMakeCurrent(VPANEL)` | verified via disassembly | | 10 | `DrawSetColor(int r,g,b,a)` | stores RGBA at `this+0x24..0x27` | | 12 | `DrawFilledRect(int x0,y0,x1,y1)` | verified via disassembly | | 14 | `DrawOutlinedRect(int x0,y0,x1,y1)` | SDK header 10 + 4 | | 15 | `DrawLine(int x0,y0,x1,y1)` | SDK header 11 + 4 (skeleton ESP) | | 17 | `DrawSetTextFont(HFont)` | stores HFont at `this+0x290` | | 18 | `DrawSetTextColor(int r,g,b,a)` | stores RGBA at `this+0x28..0x2b` | | 20 | `DrawSetTextPos(int x,y)` | stores at `this+0x2c/0x30` | | 22 | `DrawPrintText(wchar_t*, int, drawType)` | reads font `0x290`, pos `0x2c/0x30` | | 66 | `CreateFont()` | SDK header 62; empirical (379) | | 67 | `AddGlyphSetToFont(font,name,tall,weight,blur,scanlines,flags,rangeMin,rangeMax)` | SDK header 62+4; disassembly (slot 67 = 0x6a300, passes 9 args to FontManager) | | 75 | `GetTextSize(HFont, const wchar_t*, int& wide, int& tall)` | SDK header 71+4; used for accurate GUI wrapping | | 87 | `SolveTraverse` | InternalSchemeSettingsTraverse profiling strings @ 0x73240 | | 88 | `PaintTraverse(VPANEL)` | tail-calls `ipanel->vt+0x1c8(panel,false)` @ 0x697e0 | | 114 | `PaintTraverseEx(VPANEL, bool paintPopups)` | IsVisible check `ipanel+0x40`, popup array `this+0x250/0x25c`, "Too many popups!" assert @ 0x6dd46 | Slot mapping rule: **binary slot = SDK-header slot + 4** (confirmed via CreateFont 62→66 and PaintTraverseEx 110→114). Vtable has 179 entries; slots 122–148 = second base class `IMatSystemSurface` (AttachToWindow=122 … DrawSetTextureRGBAEx2=148). ## Draw-Path Decision - **Primary: PaintTraverseEx (slot 114)** — fires every frame for the root panel. - **Fallback kept: DrawPrintText (slot 22)** — fires only when the game draws text (menus); also used as in-draw call site for the game's own text. - `g_in_draw` (hl2h.cpp) prevents recursion; both hooks call `outer_draw()` (menu.cpp) guarded by it. ## Fonts (the long fight) Font alignment problem: `Tahoma` placeholder fontconfig-substituted to Noto Sans → per-glyph baseline jumps (fallback-substitution glyph metrics). Fixes tried in order: Tahoma → DejaVu Sans (+ANTIALIAS 0x10) → no AA → **winner: "Liberation Sans" 16px, weight 400, flags 0x400 (FONTFLAG_CUSTOM), glyph range pinned 0x20–0xFF**. `FONTFLAG_CUSTOM` disables the Asian fallback-compat glyph path; pinned range forces one font pass → uniform metrics, no jumping letters. fc-match findings (system fontconfig): - `Tahoma` → NotoSans-Regular.ttf (substituted — unusable) - `DejaVu Sans` → native, but jumping letters persisted - `Verdana` → Noto Sans (substituted) - `Liberation Sans` → LiberationSans-Regular.ttf (real, installed) ✓ ## Surface Internals Observed - Paint-stack count: `this+0x270` (nonzero while painting) - Stack array: `this+0x264`, 0x1c-byte entries - Popup array: `this+0x250/0x25c` - Current font: `this+0x290`; text pos: `this+0x2c/0x30` ## Menu - Custom immediate-mode GUI; `Insert` is the only toggle. - Sidebar: Movement / Combat / Visuals / Weapons / System. Cards show Active + Bind and collapsible settings; System has config path, build info, Reload and two-click Reset. - Feature gating: `feature_active(int)` (menu.cpp) — active AND (bind==0 or key held), used by `do_move` and the aim features. - Layout (`x`, `y`, `category`, `expanded` bitmask) persists under `[gui]`. - Config: `$XDG_CONFIG_HOME/hl2hake/config` (fallback `~/.config/hl2hake/config`); writes are atomic (`.tmp` + rename). - Toasts are drawn by `gui.cpp` even when the menu is closed. ## Build / Run - `./build.sh`: g++ `-m32 -O2 -std=c++17 -fPIC -shared`, 10 TUs (`hl2h modules cinput netvars menu gui esp server bones vischeck`), `-ldl`, `-Wall -Wextra -Werror` (all warnings fatal). - `./play.sh -windowed +map d1_trainstation_01` (Steam runtime wrapper, `LD_PRELOAD=libhl2hake.so`). - Logs: `/tmp/hl2hake.log` (logf), stderr. - `HL2H_DRAW_TEST=1` env: draws red rect + "HL2H DRAW TEST" without opening the menu. `HL2H_BONE_DEBUG=1` draws head-hitbox markers. ## Lessons 1. **Never pipe-mask build failures**: `./build.sh | tail` hides g++ rc. Use `./build.sh > /tmp/build.log 2>&1; echo rc=$?`. This masked a dropped `static bool g_in_draw` declaration for a whole session. 2. PIC base recovery in vguimatsurface.so: string refs are `lea -disp(%reg)` after `call thunk; add $const,%reg`; base 0x271000 for slot-87/114 region. 3. Check binary freshness: `strings libhl2hake.so | grep -c ` — mtime can lie (build silently failed). ## File Locations - Project: `/home/salmon/Documents/rust/hl2h/` - Hook + movement/feature logic: `src/hl2h.cpp` - Menu + drawing + config + input: `src/menu.cpp` - ESP / skeleton drawing / aimbot / silent aim: `src/esp.cpp` - Bones + hitboxes: `src/bones.cpp` - Server-side entity/health access: `src/server.cpp` - World vischeck / entity trace: `src/vischeck.cpp` - Shared API: `src/hl2h.h` - Current project overview: `CONTEXT.md` - SDK headers: `source-sdk-2013/src/public/vgui/`