1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
|
# HL2hake ISurface Drawing — Status Report
## Date: 2026-09-12, updated 2026-10-26
## 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) |
| 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
- Starts fully collapsed on first open; `Insert` toggles it.
- Tree: HL2hake -> Bunnyhop (Active, Auto-strafe), ABH (Active), ESP (Active,
Show neutral/healthbar/healthnum/name/skeleton/box), Aimbot (Active, Bind,
Vischeck, Bones, Smoothness), Triggerbot (Active, Bind), Silent aim
(Active, Bind). Children indented by `g_depth[]` (+12px/level).
- Right-aligned state at x=270: `[on]`/`[off]` for toggles, `[keyname]` for
binds, `[press key]` while binding, `N deg/tick` for values.
- Feature gating: `feature_active(int)` (menu.cpp) — active AND
(bind==0 or key held), used by `do_move` and the aim features.
- Config: `$XDG_CONFIG_HOME/hl2hake/config` (fallback `~/.config/hl2hake/config`).
## Build / Run
- `./build.sh`: g++ `-m32 -O2 -std=c++17 -fPIC -shared`, 9 TUs
(`hl2h modules cinput netvars menu 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 <newstr>`
— 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/`
|