summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--.gitignore6
-rwxr-xr-xbuild.sh4
-rw-r--r--src/ISURFACE_STATUS.md104
-rw-r--r--src/hl2h.cpp74
-rw-r--r--src/hl2h.h18
-rw-r--r--src/menu.cpp312
-rw-r--r--vgui_surface030_vtable_identification.md227
7 files changed, 621 insertions, 124 deletions
diff --git a/.gitignore b/.gitignore
index 73aa73c..1239075 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,7 @@
1libhl2hake.so 1libhl2hake.so
2a.out
3crash
4# vendored SDK + reference cheats (read-only material)
5source-sdk-2013/
6Amalgam/
7projectz/
diff --git a/build.sh b/build.sh
index 33ff3c8..daee3f8 100755
--- a/build.sh
+++ b/build.sh
@@ -1,9 +1,7 @@
1#!/bin/sh 1#!/bin/sh
2set -e 2set -e
3cd "$(dirname "$0")" 3cd "$(dirname "$0")"
4g++ -m32 -O2 -std=c++17 -Wall -Wextra -fPIC -shared -fvisibility=hidden \ 4g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared -fvisibility=hidden \
5 -fno-exceptions -fno-rtti -static-libstdc++ -static-libgcc -pthread \
6 -o libhl2hake.so \
7 src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp \ 5 src/hl2h.cpp src/modules.cpp src/cinput.cpp src/netvars.cpp src/menu.cpp \
8 -ldl 6 -ldl
9file libhl2hake.so 7file libhl2hake.so
diff --git a/src/ISURFACE_STATUS.md b/src/ISURFACE_STATUS.md
new file mode 100644
index 0000000..32a27bb
--- /dev/null
+++ b/src/ISURFACE_STATUS.md
@@ -0,0 +1,104 @@
1# HL2hake ISurface Drawing — Status Report
2
3## Date: 2026-09-12 (final round)
4
5## Result: WORKING
6
7Overlay drawing works in-game and in menus. Draw path: `PaintTraverseEx`
8hook (slot 114) on the copied ISurface vtable, wrapping our draws in
9`PushMakeCurrent(panel, false)` / `PopMakeCurrent(panel)` around ISurface
10slots 6/10/12/17/18/20/22/66/67.
11
12## Confirmed ISurface VTable Slots (disassembly-verified, binary=SDK header+4)
13
14| Slot | Function | Evidence |
15|------|-------------------------------------------|----------|
16| 6 | `GetEmbeddedPanel()` | returns VPANEL used with PushMakeCurrent |
17| 8 | `PushMakeCurrent(VPANEL, bool)` | verified via disassembly |
18| 9 | `PopMakeCurrent(VPANEL)` | verified via disassembly |
19| 10 | `DrawSetColor(int r,g,b,a)` | stores RGBA at `this+0x24..0x27` |
20| 12 | `DrawFilledRect(int x0,y0,x1,y1)` | verified via disassembly |
21| 17 | `DrawSetTextFont(HFont)` | stores HFont at `this+0x290` |
22| 18 | `DrawSetTextColor(int r,g,b,a)` | stores RGBA at `this+0x28..0x2b` |
23| 20 | `DrawSetTextPos(int x,y)` | stores at `this+0x2c/0x30` |
24| 22 | `DrawPrintText(wchar_t*, int, drawType)` | reads font `0x290`, pos `0x2c/0x30` |
25| 66 | `CreateFont()` | SDK header 62; empirical (379) |
26| 67 | `AddGlyphSetToFont(font,name,tall,weight,blur,scanlines,flags,rangeMin,rangeMax)` | SDK header 62+4; disassembly (slot 67 = 0x6a300, passes 9 args to FontManager) |
27| 87 | `SolveTraverse` | InternalSchemeSettingsTraverse profiling strings @ 0x73240 |
28| 88 | `PaintTraverse(VPANEL)` | tail-calls `ipanel->vt+0x1c8(panel,false)` @ 0x697e0 |
29| 114 | `PaintTraverseEx(VPANEL, bool paintPopups)` | IsVisible check `ipanel+0x40`, popup array `this+0x250/0x25c`, "Too many popups!" assert @ 0x6dd46 |
30
31Slot mapping rule: **binary slot = SDK-header slot + 4** (confirmed via
32CreateFont 62→66 and PaintTraverseEx 110→114). Vtable has 179 entries;
33slots 122–148 = second base class `IMatSystemSurface`
34(AttachToWindow=122 … DrawSetTextureRGBAEx2=148).
35
36## Draw-Path Decision
37
38- **Primary: PaintTraverseEx (slot 114)** — fires every frame for the root panel.
39- **Fallback kept: DrawPrintText (slot 22)** — fires only when the game draws
40 text (menus); also used as in-draw call site for the game's own text.
41- `g_in_draw` (hl2h.cpp) prevents recursion; both hooks call `outer_draw()`
42 (menu.cpp) guarded by it.
43
44## Fonts (the long fight)
45
46Font alignment problem: `Tahoma` placeholder fontconfig-substituted to
47Noto Sans → per-glyph baseline jumps (fallback-substitution glyph metrics).
48Fixes tried in order: Tahoma → DejaVu Sans (+ANTIALIAS 0x10) → no AA →
49**winner: "Liberation Sans" 16px, weight 400, flags 0x400 (FONTFLAG_CUSTOM), glyph range pinned 0x20–0xFF**.
50`FONTFLAG_CUSTOM` disables the Asian fallback-compat glyph path; pinned
51range forces one font pass → uniform metrics, no jumping letters.
52
53fc-match findings (system fontconfig):
54- `Tahoma` → NotoSans-Regular.ttf (substituted — unusable)
55- `DejaVu Sans` → native, but jumping letters persisted
56- `Verdana` → Noto Sans (substituted)
57- `Liberation Sans` → LiberationSans-Regular.ttf (real, installed) ✓
58
59## Surface Internals Observed
60
61- Paint-stack count: `this+0x270` (nonzero while painting)
62- Stack array: `this+0x264`, 0x1c-byte entries
63- Popup array: `this+0x250/0x25c`
64- Current font: `this+0x290`; text pos: `this+0x2c/0x30`
65
66## Menu
67
68- Tree: HL2hake → Climb/Bunnyhop/ABH, children indented (`g_depth[]`, +12px/level).
69- Right-aligned state at x=200: `[on]`/`[off]` for Active rows,
70 `[keyname]` for Bind rows, `[press key]` while binding.
71- Feature gating: `feature_active(int)` (menu.cpp) — active AND
72 (bind==0 or key held). Called from `do_move` (hl2h.cpp) to gate
73 climb (middle-mouse) and bhop (jump) branches.
74- Config: INI at `~/.hl2hake.ini` (climb/bhop/abh active+bind).
75
76## Build / Run
77
78- `./build.sh`: g++ `-m32 -O2 -fPIC -shared`, links 5 TUs, `-ldl`,
79 `-Wall -Wextra -Werror` (all warnings fatal).
80- `./play.sh -windowed +map d1_trainstation_01` (Steam runtime wrapper,
81 `LD_PRELOAD=libhl2hake.so`).
82- Logs: `/tmp/hl2hake.log` (logf), `/tmp/hl2run.log` (stderr).
83- Probe logging (`hook fired:`, `probe:`) removed 2026-09-12.
84- `HL2H_DRAW_TEST=1` env: draws red rect + "HL2H DRAW TEST" without
85 opening the menu.
86
87## Lessons
88
891. **Never pipe-mask build failures**: `./build.sh | tail` hides g++ rc.
90 Use `./build.sh > /tmp/build.log 2>&1; echo rc=$?`. This masked a
91 dropped `static bool g_in_draw` declaration for a whole session.
922. PIC base recovery in vguimatsurface.so: string refs are
93 `lea -disp(%reg)` after `call thunk; add $const,%reg`; base 0x271000
94 for slot-87/114 region.
953. Check binary freshness: `strings libhl2hake.so | grep -c <newstr>`
96 — mtime can lie (build silently failed).
97
98## File Locations
99
100- Project: `/home/salmon/Documents/rust/hl2h/`
101- Hook + feature logic: `src/hl2h.cpp`
102- Menu + drawing + config + input: `src/menu.cpp`
103- Shared API: `src/hl2h.h`
104- SDK headers: `source-sdk-2013/src/public/vgui/`
diff --git a/src/hl2h.cpp b/src/hl2h.cpp
index 0261ff2..a4a1a88 100644
--- a/src/hl2h.cpp
+++ b/src/hl2h.cpp
@@ -117,9 +117,20 @@ static void *entitylist_get(int idx) {
117 117
118 118
119static void do_move(int seq) { 119static void do_move(int seq) {
120 static bool g_echo_sent = false;
120 if (!g_input || !g_net.ok()) 121 if (!g_input || !g_net.ok())
121 return; 122 return;
122 123
124 if (!g_echo_sent) {
125 g_echo_sent = true;
126 if (g_conmsg) {
127 g_conmsg("[hl2hake] succesfully loaded\n");
128 logf("console echo sent (first CreateMove)");
129 } else {
130 logf("g_conmsg is NULL — in-game console output unavailable");
131 }
132 }
133
123 int localIdx = engine_get_local_player(); 134 int localIdx = engine_get_local_player();
124 if (localIdx <= 0 || localIdx > 64) 135 if (localIdx <= 0 || localIdx > 64)
125 return; 136 return;
@@ -137,17 +148,16 @@ static void do_move(int seq) {
137 return; 148 return;
138 149
139 uintptr_t e = (uintptr_t)ent; 150 uintptr_t e = (uintptr_t)ent;
140 uint8_t movetype = *(uint8_t *)(e + g_net.movetype);
141 int flags = *(int *)(e + g_net.flags); 151 int flags = *(int *)(e + g_net.flags);
142 bool onground = (flags & FL_ONGROUND) != 0; 152 bool onground = (flags & FL_ONGROUND) != 0;
143 bool inwater = (flags & FL_INWATER) != 0; 153 bool inwater = (flags & FL_INWATER) != 0;
144 154
145 if (movetype == MOVETYPE_NOCLIP || movetype == MOVETYPE_LADDER) 155 bool climb_on = feature_active(FEAT_CLIMB); // bind held (or middle mouse when unbound)
146 return; 156 bool bhop_on = feature_active(FEAT_BHOP);
147 157
148 static bool lastground = false; 158 static bool lastground = false;
149 159
150 if (middle_mouse_down()) { 160 if (climb_on) {
151 if (cmd->command_number % 2) { 161 if (cmd->command_number % 2) {
152 cmd->buttons &= ~IN_JUMP; 162 cmd->buttons &= ~IN_JUMP;
153 cmd->buttons |= IN_USE; 163 cmd->buttons |= IN_USE;
@@ -156,7 +166,7 @@ static void do_move(int seq) {
156 cmd->buttons |= IN_JUMP; 166 cmd->buttons |= IN_JUMP;
157 } 167 }
158 note_modified(cmd, flags, (float *)(e + g_net.velocity)); 168 note_modified(cmd, flags, (float *)(e + g_net.velocity));
159 } else if (cmd->buttons & IN_JUMP) { 169 } else if (bhop_on && (cmd->buttons & IN_JUMP)) {
160 if (inwater && (cmd->buttons & IN_DUCK)) { 170 if (inwater && (cmd->buttons & IN_DUCK)) {
161 lastground = onground; 171 lastground = onground;
162 return; 172 return;
@@ -193,9 +203,63 @@ static void do_move(int seq) {
193using CreateMoveFn = bool (*)(void *, int, float, bool); 203using CreateMoveFn = bool (*)(void *, int, float, bool);
194static CreateMoveFn oCreateMove = nullptr; 204static CreateMoveFn oCreateMove = nullptr;
195 205
206using DrawPrintTextFn = void (*)(void *self, const wchar_t *text, int len, int drawType);
207static DrawPrintTextFn oDrawPrintText = nullptr;
208using PaintTraverseExFn = void (*)(void *, long, bool);
209static PaintTraverseExFn oPaintTraverseEx = nullptr;
210static bool g_in_draw = false;
211
212// Hooked ISurface slot 22. The game calls this from a valid paint context;
213// our overlay draws are submitted through outer_draw() (menu.cpp), which
214// wraps them in PushMakeCurrent/PopMakeCurrent with the embedded panel.
215static void hkdDrawPrintText(void *self, const wchar_t *text, int len, int drawType) {
216 if (g_in_draw) {
217 oDrawPrintText(self, text, len, drawType);
218 return;
219 }
220 g_in_draw = true;
221 oDrawPrintText(self, text, len, drawType);
222 // fallback trigger: fires when the game draws text (menus). Primary draw
223 // path is the PaintTraverseEx hook (slot 114), which fires every frame.
224 outer_draw();
225 g_in_draw = false;
226}
227
228// PaintTraverseEx (slot 114, verified: IsVisible check + popups loop + "Too many
229// popups" assert). Fires every frame for the root panel — unlike DrawPrintText
230// (slot 22) which only fires when text is drawn, so it never fires in-world.
231static void hkdPaintTraverseEx(void *self, long panel, bool paintPopups) {
232 using Fn = void (*)(void *, long, bool);
233 ((Fn)oPaintTraverseEx)(self, panel, paintPopups);
234 if (!g_in_draw) {
235 g_in_draw = true;
236 outer_draw();
237 g_in_draw = false;
238 }
239}
240
196static bool hkdCreateMove(void *self, int seq, float frametime, bool active) { 241static bool hkdCreateMove(void *self, int seq, float frametime, bool active) {
197 bool ret = oCreateMove(self, seq, frametime, active); 242 bool ret = oCreateMove(self, seq, frametime, active);
198 do_move(seq); 243 do_move(seq);
244
245 // Hook ISurface once: PaintTraverseEx (slot 114) fires every frame; keep
246 // DrawPrintText (slot 22) hooked as a fallback text-draw trigger.
247 static int isurface_hooked = 0;
248 if (!isurface_hooked && g_surface) {
249 void **svt = VTABLE(g_surface);
250 constexpr size_t VT_SIZE = 256;
251 void **newsvt = new void *[VT_SIZE];
252 memcpy(newsvt, svt, VT_SIZE * sizeof(void *));
253 oDrawPrintText = (DrawPrintTextFn)svt[22];
254 newsvt[22] = (void *)&hkdDrawPrintText;
255 oPaintTraverseEx = (PaintTraverseExFn)svt[114];
256 newsvt[114] = (void *)&hkdPaintTraverseEx;
257 VTABLE(g_surface) = newsvt;
258 isurface_hooked = 1;
259 logf("ISurface hooked: DrawPrintText slot 22 orig=%p, PaintTraverseEx slot 114 orig=%p",
260 (void *)oDrawPrintText, (void *)oPaintTraverseEx);
261 }
262
199 return ret; 263 return ret;
200} 264}
201 265
diff --git a/src/hl2h.h b/src/hl2h.h
index 6d3ebe7..68ddab6 100644
--- a/src/hl2h.h
+++ b/src/hl2h.h
@@ -75,6 +75,12 @@ extern NetVars g_net;
75extern uintptr_t g_input; 75extern uintptr_t g_input;
76 76
77// --------------------------------------------------------------------------- 77// ---------------------------------------------------------------------------
78// console output
79// ---------------------------------------------------------------------------
80
81extern void (*g_conmsg)(const char *, ...);
82
83// ---------------------------------------------------------------------------
78// CUserCmd 84// CUserCmd
79// --------------------------------------------------------------------------- 85// ---------------------------------------------------------------------------
80 86
@@ -118,6 +124,8 @@ float getfov(const QAngle &my_view, const QAngle &aim_ang);
118// cross-file declarations 124// cross-file declarations
119// --------------------------------------------------------------------------- 125// ---------------------------------------------------------------------------
120 126
127// feature ids shared by menu (feature_active) and hl2h.cpp (do_move)
128enum FeatureId { FEAT_CLIMB = 0, FEAT_BHOP = 1, FEAT_ABH = 2 };
121void note_modified(const CUserCmd *cmd, int flags, const float *vel); 129void note_modified(const CUserCmd *cmd, int flags, const float *vel);
122CUserCmd *get_usercmd(int seq); 130CUserCmd *get_usercmd(int seq);
123void fix_verified_crc(int seq, uint32_t crc); 131void fix_verified_crc(int seq, uint32_t crc);
@@ -129,5 +137,13 @@ void install_menu();
129void init_conmsg(); 137void init_conmsg();
130void init_x11(); 138void init_x11();
131bool middle_mouse_down(); 139bool middle_mouse_down();
132 140bool feature_active(int feature);
141void outer_draw();
142// raw ISurface slot calls used by the PaintTraverseEx hook (hl2h.cpp)
143using SurfFn = void (*)(void *, ...);
144long surf_call_slot(void *surf, int slot, long a1 = 0, long a2 = 0);
145
146// ISurface pointer (defined in menu.cpp, used for drawing tests)
147struct ISurface;
148extern ISurface *g_surface;
133#endif 149#endif
diff --git a/src/menu.cpp b/src/menu.cpp
index 91a0948..c62db47 100644
--- a/src/menu.cpp
+++ b/src/menu.cpp
@@ -1,7 +1,8 @@
1// menu.cpp — menu state, rendering, input, config, paint hook, X11 mouse 1// menu.cpp — menu state, rendering, input, config, paint hook, X11 mouse
2#include "hl2h.h" 2#include "hl2h.h"
3#include <cstdarg> 3#include <cmath>
4#include <cstdio> 4#include <cstdio>
5#include <cstdlib>
5#include <cstring> 6#include <cstring>
6 7
7// --------------------------------------------------------------------------- 8// ---------------------------------------------------------------------------
@@ -104,14 +105,14 @@ static FeatureState g_bhop{true, 0};
104static FeatureState g_abh{true, 0}; 105static FeatureState g_abh{true, 0};
105 106
106// tree 107// tree
107static MenuNode g_root{NODE_ROOT, "HL2hake", true, 0, {}}; 108static MenuNode g_root{NODE_ROOT, "HL2hake", true, 0, {}, nullptr, nullptr, nullptr};
108static MenuNode g_climb_feat{NODE_FEATURE, "Climb", true, 2, {}}; 109static MenuNode g_climb_feat{NODE_FEATURE, "Climb", true, 2, {}, nullptr, nullptr, nullptr};
109static MenuNode g_climb_active{NODE_TOGGLE, "Active", false, 0, {}, &g_climb.active}; 110static MenuNode g_climb_active{NODE_TOGGLE, "Active", false, 0, {}, &g_climb.active, nullptr, nullptr};
110static MenuNode g_climb_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_climb.bind, "None"}; 111static MenuNode g_climb_bind{NODE_BIND, "Bind", false, 0, {}, nullptr, &g_climb.bind, "None"};
111static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", true, 1, {}}; 112static MenuNode g_bhop_feat{NODE_FEATURE, "Bunnyhop", true, 1, {}, nullptr, nullptr, nullptr};
112static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active}; 113static MenuNode g_bhop_active{NODE_TOGGLE, "Active", false, 0, {}, &g_bhop.active, nullptr, nullptr};
113static MenuNode g_abh_feat{NODE_FEATURE, "ABH", true, 1, {}}; 114static MenuNode g_abh_feat{NODE_FEATURE, "ABH", true, 1, {}, nullptr, nullptr, nullptr};
114static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active}; 115static MenuNode g_abh_active{NODE_TOGGLE, "Active", false, 0, {}, &g_abh.active, nullptr, nullptr};
115 116
116static MenuNode *g_visible[16]; // flattened visible nodes for arrow navigation 117static MenuNode *g_visible[16]; // flattened visible nodes for arrow navigation
117static int g_visible_count = 0; 118static int g_visible_count = 0;
@@ -132,6 +133,8 @@ static void build_tree() {
132 g_root.child_count = 3; 133 g_root.child_count = 3;
133} 134}
134 135
136static int g_depth[16]; // depth of each visible row (for indentation)
137
135static void flatten(MenuNode *node, int depth) { 138static void flatten(MenuNode *node, int depth) {
136 if (g_visible_count >= 16) 139 if (g_visible_count >= 16)
137 return; 140 return;
@@ -139,6 +142,7 @@ static void flatten(MenuNode *node, int depth) {
139 : (node->child_count > 0) ? NODE_FEATURE 142 : (node->child_count > 0) ? NODE_FEATURE
140 : (node->active_ptr) ? NODE_TOGGLE 143 : (node->active_ptr) ? NODE_TOGGLE
141 : NODE_BIND; 144 : NODE_BIND;
145 g_depth[g_visible_count] = depth;
142 g_visible[g_visible_count++] = node; 146 g_visible[g_visible_count++] = node;
143 if (node->expanded) { 147 if (node->expanded) {
144 for (int i = 0; i < node->child_count; i++) 148 for (int i = 0; i < node->child_count; i++)
@@ -297,11 +301,28 @@ static bool key_pressed(int sc) {
297 return sc >= 0 && sc < 512 && g_prev_key_state[sc]; 301 return sc >= 0 && sc < 512 && g_prev_key_state[sc];
298} 302}
299 303
304// feature gate queried from hl2h.cpp do_move(): feature must be Active AND
305// the activation condition met. Climb activates on its bind key held (falls
306// back to the historic middle-mouse behavior when unbound); other features
307// activate on their bind key held, or are always-on when unbound.
308bool feature_active(int feature) {
309 static FeatureState *feats[3] = {&g_climb, &g_bhop, &g_abh};
310 if (feature < 0 || feature > 2)
311 return false;
312 FeatureState *f = feats[feature];
313 if (!f->active)
314 return false;
315 if (f->bind == 0)
316 return feature == FEAT_CLIMB ? middle_mouse_down() : true;
317 return key_held(f->bind);
318}
319
300static void menu_handle_input() { 320static void menu_handle_input() {
301 poll_keyboard(); 321 poll_keyboard();
302 322
303 if (!g_menu_open) { 323 if (!g_menu_open) {
304 if (key_pressed(SCAN_INSERT)) { 324 if (key_pressed(SCAN_INSERT)) {
325 std::logf("insert pressed");
305 g_menu_open = true; 326 g_menu_open = true;
306 g_bind_mode = false; 327 g_bind_mode = false;
307 rebuild_visible(); 328 rebuild_visible();
@@ -384,29 +405,53 @@ static void menu_handle_input() {
384// --------------------------------------------------------------------------- 405// ---------------------------------------------------------------------------
385 406
386// VGUI surface interface 407// VGUI surface interface
387using CreateInterfaceFn = void *(*)(const char *, int *);
388struct ISurface; 408struct ISurface;
389
390// VGUI font handle (opaque)
391using HFont = unsigned long; 409using HFont = unsigned long;
392 410
393// function pointers resolved at init 411ISurface *g_surface = nullptr;
394static ISurface *g_surface = nullptr;
395static HFont g_font = 0; 412static HFont g_font = 0;
396 413
397// surface vtable indices for 2007-era VGUI_SURFACE001 414// VGUI_Surface030 vtable slots (confirmed via binary disassembly of vguimatsurface.so):
398// verified: slot 16 = DrawSetTextColor, slot 17 = DrawSetTextFont, 415// 8 = PushMakeCurrent(VPANEL, bool useInsets) — reads args at 0xc/0x10(%ebp)
399// slot 19 = DrawSetTextPos, slot 22 = DrawPrintText, 416// 9 = PopMakeCurrent(VPANEL) — pops the make-current stack at this+0x264/0x270
400// slot 23 = DrawGetTextSize, slot 31 = CreateNewFont, slot 32 = SetFontGlyphSet 417// 10 = DrawSetColor(int r, int g, int b, int a) — stores rgba at this+0x24
401// These are from the standard VGUI surface vtable layout; will be verified 418// 12 = DrawFilledRect(int x0, int y0, int x1, int y1) — alpha check at this+0x27
402// against runtime if they fail. 419// 17 = DrawSetTextFont(HFont), 18 = DrawSetTextColor(int r,g,b,a)
403static constexpr int SURF_DrawSetTextColor = 16; 420// 20 = DrawSetTextPos(int x, int y), 22 = DrawPrintText(wchar_t*, len, drawType)
421// 66 = CreateFont(), 67 = AddGlyphSetToFont(font, name, tall, weight,
422// blur, scanlines, flags, rangeMin, rangeMax)
423static constexpr int SURF_GetEmbeddedPanel = 6;
424static constexpr int SURF_PushMakeCurrent = 8;
425static constexpr int SURF_PopMakeCurrent = 9;
426static constexpr int SURF_DrawSetColor = 10;
427static constexpr int SURF_DrawFilledRect = 12;
404static constexpr int SURF_DrawSetTextFont = 17; 428static constexpr int SURF_DrawSetTextFont = 17;
405static constexpr int SURF_DrawSetTextPos = 19; 429static constexpr int SURF_DrawSetTextColor = 18;
430static constexpr int SURF_DrawSetTextPos = 20;
406static constexpr int SURF_DrawPrintText = 22; 431static constexpr int SURF_DrawPrintText = 22;
407static constexpr int SURF_CreateNewFont = 31; 432static constexpr int SURF_CreateFont = 66;
408static constexpr int SURF_SetFontGlyphSet = 32; 433static constexpr int SURF_AddGlyphSetToFont = 67;
409 434
435static int surf_get_embedded_panel() {
436 using Fn = int (*)(void *);
437 return vfunc<Fn>(g_surface, SURF_GetEmbeddedPanel)(g_surface);
438}
439static void surf_push_make_current(int panel, bool use_insets) {
440 using Fn = void (*)(void *, int, bool);
441 vfunc<Fn>(g_surface, SURF_PushMakeCurrent)(g_surface, panel, use_insets);
442}
443static void surf_pop_make_current(int panel) {
444 using Fn = void (*)(void *, int);
445 vfunc<Fn>(g_surface, SURF_PopMakeCurrent)(g_surface, panel);
446}
447static void surf_draw_set_color(int r, int g, int b, int a) {
448 using Fn = void (*)(void *, int, int, int, int);
449 vfunc<Fn>(g_surface, SURF_DrawSetColor)(g_surface, r, g, b, a);
450}
451static void surf_draw_filled_rect(int x0, int y0, int x1, int y1) {
452 using Fn = void (*)(void *, int, int, int, int);
453 vfunc<Fn>(g_surface, SURF_DrawFilledRect)(g_surface, x0, y0, x1, y1);
454}
410static void surf_draw_set_text_color(int r, int g, int b, int a) { 455static void surf_draw_set_text_color(int r, int g, int b, int a) {
411 using Fn = void (*)(void *, int, int, int, int); 456 using Fn = void (*)(void *, int, int, int, int);
412 vfunc<Fn>(g_surface, SURF_DrawSetTextColor)(g_surface, r, g, b, a); 457 vfunc<Fn>(g_surface, SURF_DrawSetTextColor)(g_surface, r, g, b, a);
@@ -420,20 +465,23 @@ static void surf_draw_set_text_pos(int x, int y) {
420 vfunc<Fn>(g_surface, SURF_DrawSetTextPos)(g_surface, x, y); 465 vfunc<Fn>(g_surface, SURF_DrawSetTextPos)(g_surface, x, y);
421} 466}
422static void surf_draw_print_text(const wchar_t *text, int count) { 467static void surf_draw_print_text(const wchar_t *text, int count) {
423 using Fn = void (*)(void *, const wchar_t *, int); 468 using Fn = void (*)(void *, const wchar_t *, int, int);
424 vfunc<Fn>(g_surface, SURF_DrawPrintText)(g_surface, text, count); 469 vfunc<Fn>(g_surface, SURF_DrawPrintText)(g_surface, text, count, 0); // 0 = FONT_DRAW_DEFAULT
425} 470}
426static HFont surf_create_font() { 471static HFont surf_create_font() {
427 using Fn = HFont (*)(void *); 472 using Fn = HFont (*)(void *);
428 return vfunc<Fn>(g_surface, SURF_CreateNewFont)(g_surface); 473 return vfunc<Fn>(g_surface, SURF_CreateFont)(g_surface);
429} 474}
430static void surf_set_font_glyph_set(HFont f, const char *name, int tall, 475static bool surf_set_font_glyph_set(HFont f, const char *name, int tall,
431 int weight, int blur, int scanlines, 476 int weight, int blur, int scanlines,
432 int flags) { 477 int flags, int range_min = 0,
433 using Fn = void (*)(void *, HFont, const char *, int, int, int, int, int, int, 478 int range_max = 0) {
479 using Fn = bool (*)(void *, HFont, const char *, int, int, int, int, int, int,
434 int); 480 int);
435 vfunc<Fn>(g_surface, SURF_SetFontGlyphSet)(g_surface, f, name, tall, weight, 481 return vfunc<Fn>(g_surface, SURF_AddGlyphSetToFont)(g_surface, f, name, tall,
436 blur, scanlines, flags, 0, 0); 482 weight, blur, scanlines,
483 flags, range_min,
484 range_max);
437} 485}
438 486
439// wide string conversion for DrawPrintText 487// wide string conversion for DrawPrintText
@@ -442,114 +490,148 @@ static void draw_text(int x, int y, const char *text) {
442 mbstowcs(wtext, text, 255); 490 mbstowcs(wtext, text, 255);
443 wtext[255] = 0; 491 wtext[255] = 0;
444 int len = (int)wcslen(wtext); 492 int len = (int)wcslen(wtext);
493 surf_draw_set_text_font(g_font);
494 surf_draw_set_text_color(255, 255, 255, 255);
445 surf_draw_set_text_pos(x, y); 495 surf_draw_set_text_pos(x, y);
446 surf_draw_print_text(wtext, len); 496 surf_draw_print_text(wtext, len);
447} 497}
448 498
449// --------------------------------------------------------------------------- 499// Draw overlay. MUST be called from inside the ISurface paint path
450// paint hook — draws menu overlay every frame 500// (see hkdDrawPrintText hook in hl2h.cpp): we wrap our draws in
451// --------------------------------------------------------------------------- 501// PushMakeCurrent/PopMakeCurrent so they are submitted with a clean clip.
452 502void outer_draw() {
453using PaintFn = void (*)(void *, int); 503 menu_handle_input();
454static PaintFn oPaint = nullptr; 504 // HL2H_DRAW_TEST=1 draws a probe rect without needing the menu open
455 505 static bool draw_test = getenv("HL2H_DRAW_TEST") != nullptr;
456static void hkdPaint(void *self, int mode) { 506 if (!g_surface || (!g_menu_open && !draw_test))
457 oPaint(self, mode); 507 return;
458 508 if (!g_font) {
459 // init font on first paint
460 static bool font_inited = false;
461 if (!font_inited) {
462 g_font = surf_create_font(); 509 g_font = surf_create_font();
463 if (g_font) { 510 if (!g_font) {
464 surf_set_font_glyph_set(g_font, "Consolas", 14, 400, 0, 0, 0); 511 logf("outer_draw: CreateFont failed");
512 return;
513 }
514 // FONTFLAG_CUSTOM only; Liberation Sans is an installed real TTF (Verdana
515 // substitutes to Noto). Raw glyphs, ASCII range pinned.
516 if (!surf_set_font_glyph_set(g_font, "Liberation Sans", 16, 400, 0, 0,
517 0x400, 0x20, 0xFF)) {
518 logf("outer_draw: SetFontGlyphSet failed");
519 g_font = 0;
520 return;
465 } 521 }
466 font_inited = true;
467 } 522 }
468 if (!g_font) 523 int panel = surf_get_embedded_panel();
524 if (!panel)
469 return; 525 return;
470 526 surf_push_make_current(panel, false);
471 menu_handle_input(); 527 if (draw_test) {
472 528 surf_draw_set_color(255, 0, 0, 255);
473 if (!g_menu_open) 529 surf_draw_filled_rect(20, 20, 180, 60);
530 surf_draw_set_color(255, 255, 255, 255);
531 draw_text(28, 36, "HL2H DRAW TEST");
532 }
533 if (!g_menu_open) {
534 surf_pop_make_current(panel);
474 return; 535 return;
475 536 }
476 surf_draw_set_text_font(g_font);
477
478 int y = 10;
479 rebuild_visible();
480 for (int i = 0; i < g_visible_count; i++) { 537 for (int i = 0; i < g_visible_count; i++) {
481 MenuNode *n = g_visible[i]; 538 MenuNode *n = g_visible[i];
482 int indent = 0; 539 char line[96];
483 540 int x = 12 + g_depth[i] * 12;
484 // count depth by counting how many parents are visible before this node 541 snprintf(line, sizeof(line), "%s %s",
485 // (simple: first node = depth 0, others = depth 1 if label is Active/Bind) 542 (i == g_selected) ? ">" : (n->child_count ? "-" : " "), n->label);
486 if (n->type == NODE_TOGGLE || n->type == NODE_BIND) 543 draw_text(x, 10 + i * 20, line);
487 indent = 20; 544 // right-aligned state
488
489 char buf[256];
490 if (n->type == NODE_TOGGLE) { 545 if (n->type == NODE_TOGGLE) {
491 snprintf(buf, sizeof(buf), "%s: %s", n->label, 546 draw_text(200, 10 + i * 20, *n->active_ptr ? "[on]" : "[off]");
492 *n->active_ptr ? "ON" : "OFF");
493 } else if (n->type == NODE_BIND) { 547 } else if (n->type == NODE_BIND) {
494 snprintf(buf, sizeof(buf), "%s: %s", n->label, 548 if (g_bind_mode && i == g_selected)
495 scancode_name(*n->bind_ptr)); 549 draw_text(200, 10 + i * 20, "[press key]");
496 if (g_bind_mode && g_selected == i) 550 else {
497 snprintf(buf, sizeof(buf), "%s: ...", n->label); 551 /* stable name for current bind */
498 } else { 552 static char bn[3][32];
499 snprintf(buf, sizeof(buf), "%s", n->label); 553 static FeatureState *feats[] = {&g_climb, &g_bhop, &g_abh};
500 } 554 int fi = 0;
501 555 for (int k = 0; k < 3; k++)
502 if (i == g_selected) { 556 if (n->bind_ptr == &feats[k]->bind)
503 surf_draw_set_text_color(255, 255, 0, 255); // yellow highlight 557 fi = k;
504 } else if (n->type == NODE_TOGGLE) { 558 snprintf(bn[fi], 32, "[%s]", scancode_name(*n->bind_ptr));
505 surf_draw_set_text_color(*n->active_ptr ? 0 : 255, 255, 0, 255); 559 draw_text(200, 10 + i * 20, bn[fi]);
506 } else { 560 }
507 surf_draw_set_text_color(255, 255, 255, 255);
508 } 561 }
509
510 draw_text(10 + indent, y, buf);
511 y += 18;
512 } 562 }
563 surf_pop_make_current(panel);
564}
513 565
514 // reset text color 566// Call an arbitrary ISurface vtable slot with up to 2 long args (cdecl thiscall
515 surf_draw_set_text_color(255, 255, 255, 255); 567// on i386: this first, rest on stack).
568long surf_call_slot(void *surf, int slot, long a1, long a2) {
569 void **vt = *(void ***)surf;
570 using Fn = long (*)(void *, long, long);
571 return ((Fn)vt[slot])(surf, a1, a2);
516} 572}
517 573
518// --------------------------------------------------------------------------- 574// ---------------------------------------------------------------------------
519// paint hook installation (called from setup_thread) 575// ISurface discovery — try known modules and all loaded .so files
520// --------------------------------------------------------------------------- 576// ---------------------------------------------------------------------------
521 577
522// VEngineVGui001 vtable indices for this build 578static ISurface *find_surface_interface() {
523// (Paint is typically slot 6 — will be verified at runtime; if wrong, 579 static const char *known[] = {"engine.so", "client.so", nullptr};
524// we crash and can adjust. Common Source 2007 layout.) 580 static const char *iface_names[] = {
525static constexpr int VGUI_Paint_SLOT = 6; 581 "VGUI_Surface030", "VGUI_Surface001",
526 582 "MatSystemSurface008", "MatSystemSurface001",
527static void install_paint_hook() { 583 "VGUI_Surface002", "VGUI_SURFACE001",
528 // get IEngineVGui from engine 584 nullptr,
529 void *eh = dlopen(g_engine.path.c_str(), 585 };
530 RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL); 586 // try known modules
531 if (!eh) 587 for (const char **p = known; *p; p++) {
532 return; 588 void *h = dlopen(*p, RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
533 auto ef = (CreateInterfaceFn)dlsym(eh, "CreateInterface"); 589 if (!h) continue;
534 if (!ef) 590 auto fn = (CreateInterfaceFn)dlsym(h, "CreateInterface");
535 return; 591 if (!fn) continue;
536 void *evgui = ef("VEngineVGui001", nullptr); 592 for (const char **n = iface_names; *n; n++) {
537 if (!evgui) { 593 int ret = 0;
538 logf("VEngineVGui001 not found"); 594 ISurface *s = (ISurface *)fn(*n, &ret);
539 return; 595 if (s) { logf("ISurface found: %s via %s (ret=%d)", *n, *p, ret); return s; }
596 }
540 } 597 }
541 598 // 2) scan /proc/self/maps for every loaded .so, try CreateInterface on each
542 // hook Paint 599 FILE *maps = fopen("/proc/self/maps", "r");
543 void **vt = VTABLE(evgui); 600 if (!maps) return nullptr;
544 oPaint = (PaintFn)vt[VGUI_Paint_SLOT]; 601 char line[1024];
545 vt[VGUI_Paint_SLOT] = (void *)&hkdPaint; 602 while (fgets(line, sizeof(line), maps)) {
546 logf("paint hook installed: Paint %p -> %p", (void *)oPaint, 603 char *p = strchr(line, '/');
547 (void *)&hkdPaint); 604 if (!p) continue;
605 size_t pl = strlen(p);
606 while (pl && (p[pl-1] == '\n' || p[pl-1] == '\r')) p[--pl] = 0;
607 if (!strstr(p, ".so")) continue;
608 void *h = dlopen(p, RTLD_NOLOAD | RTLD_LAZY | RTLD_GLOBAL);
609 if (!h) continue;
610 auto fn = (CreateInterfaceFn)dlsym(h, "CreateInterface");
611 if (!fn) continue;
612 for (const char **n = iface_names; *n; n++) {
613 int ret = 0;
614 ISurface *s = (ISurface *)fn(*n, &ret);
615 if (s) {
616 logf("ISurface found: %s via %s", *n, p);
617 fclose(maps);
618 return s;
619 }
620 }
621 }
622 fclose(maps);
623 return nullptr;
548} 624}
549 625
550// public init function called from setup_thread
551void install_menu() { 626void install_menu() {
552 build_tree(); 627 build_tree();
553 config_load(); 628 config_load();
554 install_paint_hook(); 629 // resolve ISurface once (used by the DrawPrintText hook in hl2h.cpp and
630 // by outer_draw). DrawPrintText hook is installed from hkdCreateMove.
631 g_surface = find_surface_interface();
632 if (!g_surface) {
633 logf("ISurface not found; menu disabled");
634 return;
635 }
636 logf("ISurface = %p", g_surface);
555} 637}
diff --git a/vgui_surface030_vtable_identification.md b/vgui_surface030_vtable_identification.md
new file mode 100644
index 0000000..7adbda8
--- /dev/null
+++ b/vgui_surface030_vtable_identification.md
@@ -0,0 +1,227 @@
1# ISurface VGUI_Surface030 VTable Identification
2
3## Binary Info
4- **File**: `/home/salmon/.local/share/Steam/steamapps/common/Half-Life 2/bin/vguimatsurface.so`
5- **Format**: ELF 32-bit LSB shared object, Intel i386, **not stripped**
6- **Load bias**: `0xe2dee000`
7- **VTable location**: `.data.rel.ro` at file offset `0x246e60`, virtual address `0x247e60`
8- **Total vtable entries**: 179
9- **Typeinfo**: CMatSystemSurface typeinfo at vaddr `0x24a500`, referenced at vtable[-1]
10
11## Methodology
12
13The vtable was located by finding the CMatSystemSurface `__si_class_type_info` object at vaddr `0x24a500` (identified by its `__type_name` pointing to `"17CMatSystemSurface"` at `0x1ec4ec`), then finding where that address is stored in `.data.rel.ro` as a vtable typeinfo pointer. The vtable entries (consecutive `R_386_RELATIVE` relocations in `.data.rel.ro` pointing into `.text`) start immediately after the typeinfo pointer.
14
15Each function was identified by disassembling the function body and matching behavioral patterns against the ISurface.h header from the project.
16
17## VTable Layout (Itanium ABI)
18
19```
20vtable[-2] = offset-to-top = 0x00000000 (vaddr 0x247e58)
21vtable[-1] = typeinfo* = 0x0024a500 (vaddr 0x247e5c) → CMatSystemSurface
22vtable[ 0] = func ptr = 0x0006c010 (vaddr 0x247e60) → Connect (IAppSystem)
23...
24vtable[178]= 0x0006b250
25```
26
27---
28
29## Identified Function Slot Numbers
30
31### 1. DrawSetTextFont(HFont) → **Slot 17**
32- **File offset**: `0x00069360`
33- **Runtime**: `0xe2e57360` (load_bias + 0x69360)
34- **Member offset**: `this+0x290`
35
36```asm
3700069360: push %ebp
3800069361: mov %esp,%ebp
3900069363: mov 0x8(%ebp),%eax ; eax = this
4000069366: mov 0xc(%ebp),%edx ; edx = HFont
4100069369: mov %edx,0x290(%eax) ; this->m_hCurrentFont = font
420006936f: pop %ebp
4300069370: ret
44```
45
46**Evidence**: Trivially stores a single int (HFont) at `this+0x290`. Cross-validated: slot 22 (DrawPrintText) reads from `0x290(%esi)` to get the current font.
47
48---
49
50### 2. DrawSetTextColor(int r, int g, int b, int a) → **Slot 18**
51- **File offset**: `0x00069380`
52- **Runtime**: `0xe2e57380` (load_bias + 0x69380)
53- **Member offsets**: `this+0x28` (R), `this+0x29` (G), `this+0x2a` (B), `this+0x2b` (A)
54
55```asm
5600069380: push %ebp
5700069381: pxor %xmm0,%xmm0
5800069385: mov %esp,%ebp
590006938d: mov 0x8(%ebp),%edi ; edi = this
6000069390: cvtsi2ss 0x18(%ebp),%xmm0 ; xmm0 = (float)a
6100069395: mov 0xc(%ebp),%ebx ; ebx = r
6200069398: mulss 0x10(%edi),%xmm0 ; xmm0 *= this->m_flAlphaMultiplier
630006939d: movzbl 0x28(%edi),%ecx ; load current R
64000693a1: cvttss2si %xmm0,%esi ; esi = (int)(a * alphaMul)
65000693a5: cmp %ebx,%ecx ; if (currentR == newR)
66000693a7: je 693d8 ; skip vtable call
67000693a9: mov (%edi),%ecx ; vtable ptr
68000693af: call *0x60(%ecx) ; call vtable[24] (notification callback)
69000693b6: mov %bl,0x28(%edi) ; this->textColor.r = r
70000693bc: mov al,0x29(%edi) ; this->textColor.g = g (from 0x10(%ebp))
71000693c3: mov al,0x2a(%edi) ; this->textColor.b = b (from 0x14(%ebp))
72000693c8: mov al,0x2b(%edi) ; this->textColor.a = a (clamped/int)
73000693d2: ret
74```
75
76**Evidence**: Stores 4 consecutive bytes (RGBA) at `this+0x28..0x2b`. The alpha is multiplied by `m_flAlphaMultiplier` at `this+0x10` before clamping. If the red component changed, it calls a notification callback via vtable[0x60/4=24]. Cross-validated: DrawSetColor (slot 10) stores at `this+0x24..0x27` (shape color, different location).
77
78---
79
80### 3. DrawSetTextPos(int x, int y) → **Slot 20**
81- **File offset**: `0x00069440`
82- **Runtime**: `0xe2e57440` (load_bias + 0x69440)
83- **Member offsets**: `this+0x2c` (x), `this+0x30` (y)
84
85```asm
8600069440: push %ebp
8700069441: mov %esp,%ebp
8800069443: mov 0x8(%ebp),%eax ; eax = this
8900069446: mov 0xc(%ebp),%edx ; edx = x
9000069449: mov %edx,0x2c(%eax) ; this->m_nTextX = x
910006944c: mov 0x10(%ebp),%edx ; edx = y
920006944f: mov %edx,0x30(%eax) ; this->m_nTextY = y
9300069452: pop %ebp
9400069453: ret
95```
96
97**Evidence**: Stores 2 ints at `this+0x2c` and `this+0x30`. Cross-validated: DrawGetTextPos (slot 21) at `0x69460` reads from the same offsets (`0x2c(%eax)` and `0x30(%eax)`).
98
99---
100
101### 4. DrawPrintText(const wchar_t* text, int textLen, FontDrawType_t drawType) → **Slot 22**
102- **File offset**: `0x00072980`
103- **Runtime**: `0xe2e60980` (load_bias + 0x72980)
104
105```asm
10600072980: push %ebp
10700072981: mov %esp,%ebp
10800072983: push %edi
10900072984: push %esi
11000072985: push %ebx
11100072986: call 624a9 ; __x86.get_pc_thunk (PIC setup)
1120007298b: add $0x1fe675,%ebx
11300072991: sub $0x9c,%esp ; large stack frame
11400072997: mov 0xc(%ebp),%edi ; edi = text (wchar_t*)
115000729a5: mov 0x8(%ebp),%esi ; esi = this
116000729a8: test %edi,%edi ; if (!text) return
117000729b0: mov 0x290(%esi),%eax ; load current font (HFont)
118000729b6: test %eax,%eax ; if (!font) return
119000729be: mov 0x8(%esi),%edx ; base x
120000729c4: add 0x2c(%esi),%edx ; + text offset x
121000729db: push %eax ; font
122000729dc: push %esi ; this
123000729dd: call *0x114(%edx) ; vtable[0x114/4=69] → GetFontTall or similar
124```
125
126**Evidence**: Large function (~800+ bytes) that:
1271. Reads the current font from `this+0x290` (matching DrawSetTextFont's store)
1282. Computes render position from base coordinates + text offset
1293. Calls vtable[0x114] (a font metrics function)
1304. Contains complex text layout/rendering logic with stack frame of 0x9c bytes
1315. References the `FinishDrawing` string indirectly through the rendering pipeline
132
133---
134
135### 5. CreateFont() → **Slot 66**
136- **File offset**: `0x0006a2d0`
137- **Runtime**: `0xe2e582d0` (load_bias + 0x6a2d0)
138
139```asm
1400006a2d0: push %ebp
1410006a2d1: mov %esp,%ebp
1420006a2d3: push %ebx
1430006a2d4: call 624a9 ; __x86.get_pc_thunk
1440006a2d9: add $0x206d27,%ebx
1450006a2df: sub $0x4,%esp
1460006a2e2: call 1693b0 ; → get CFontManager* (PIC global lookup)
1470006a2e7: sub $0xc,%esp
1480006a2ea: push %eax ; pass font manager
1490006a2eb: call 16a200 ; → CFontManager::CreateFont() → returns HFont
1500006a2f0: mov -0x4(%ebp),%ebx
1510006a2f3: leave
1520006a2f4: ret ; return HFont in eax
153```
154
155**Evidence**: Calls `0x1693b0` which returns a pointer to the global CFontManager singleton (PIC-computed from GOT), then calls `0x16a200` which allocates a new font entry and returns its HFont handle. Simple wrapper — no arguments besides `this`.
156
157---
158
159### 6. AddGlyphSetToFont(HFont font, const char* windowsFontName, int tall, int weight, int blur, int scanlines, int flags, int nRangeMin, int nRangeMax) → **Slot 67**
160- **File offset**: `0x0006a300`
161- **Runtime**: `0xe2e58300` (load_bias + 0x6a300)
162
163```asm
1640006a300: push %ebp
1650006a301: mov %esp,%ebp
1660006a303: push %ebx
1670006a304: call 624a9 ; __x86.get_pc_thunk
1680006a309: add $0x206cf7,%ebx
1690006a30f: sub $0x4,%esp
1700006a312: call 1693b0 ; → get CFontManager*
1710006a317: sub $8,%esp
1720006a31a: push 0x2c(%ebp) ; arg 9: nRangeMax
1730006a31d: push 0x28(%ebp) ; arg 8: nRangeMin
1740006a320: push 0x24(%ebp) ; arg 7: flags
1750006a323: push 0x20(%ebp) ; arg 6: scanlines
1760006a326: push 0x1c(%ebp) ; arg 5: blur
1770006a329: push 0x18(%ebp) ; arg 4: weight
1780006a32c: push 0x14(%ebp) ; arg 3: tall
1790006a32f: push 0x10(%ebp) ; arg 2: windowsFontName (const char*)
1800006a332: push 0x0c(%ebp) ; arg 1: font (HFont)
1810006a335: push %eax ; font manager
1820006a336: call 16a550 ; → CFontManager::SetFontGlyphSet()
1830006a33b: mov -0x4(%ebp),%ebx
1840006a33e: leave
1850006a33f: ret
186```
187
188**Evidence**: Pushes all 9 user-visible arguments (HFont + const char* + 7 ints) plus the font manager pointer onto the stack and calls `0x16a550`. That inner function:
189- Multiplies HFont by `0x3c` (60 bytes per font entry) to index into the font array
190- Calls `0x169ec0` to resolve the font name string
191- Calls `0x16a220` to configure the font with FreeType parameters
192
193Note: The ISurface.h header in the project names this `SetFontGlyphSet`, but older Source SDK versions (VGUI_Surface030 from HL2) call it `AddGlyphSetToFont`. Same function, different name.
194
195---
196
197## Summary Table
198
199| Slot | Function | File Offset | Runtime Address | Member Offset |
200|------|----------|-------------|-----------------|---------------|
201| 17 | `DrawSetTextFont(HFont)` | `0x00069360` | `0xe2e57360` | `this+0x290` |
202| 18 | `DrawSetTextColor(int,int,int,int)` | `0x00069380` | `0xe2e57380` | `this+0x28..0x2b` |
203| 20 | `DrawSetTextPos(int,int)` | `0x00069440` | `0xe2e57440` | `this+0x2c, +0x30` |
204| 22 | `DrawPrintText(wchar_t*,int,FontDrawType_t)` | `0x00072980` | `0xe2e60980` | reads `this+0x290` |
205| 66 | `CreateFont()` | `0x0006a2d0` | `0xe2e582d0` | returns HFont |
206| 67 | `AddGlyphSetToFont(HFont, const char*, int×7)` | `0x0006a300` | `0xe2e58300` | calls `0x16a550` |
207
208## Related Slots
209
210| Slot | Function | Evidence |
211|------|----------|----------|
212| 10 | `DrawSetColor(int,int,int,int)` | stores at `this+0x24..0x27` (shape color) |
213| 11 | `DrawSetColor(Color_t)` | extracts packed int, stores at `this+0x24..0x27` |
214| 19 | `DrawSetTextColor(Color_t)` | stores at `this+0x28..0x2b` (text color, via packed Color_t) |
215| 21 | `DrawGetTextPos(int&,int&)` | reads from `this+0x2c, +0x30` (inverse of DrawSetTextPos) |
216
217## CMatSystemSurface Member Layout (inferred)
218
219| Offset | Type | Field |
220|--------|------|-------|
221| `+0x00` | `void**` | vtable pointer |
222| `+0x10` | `float` | `m_flAlphaMultiplier` |
223| `+0x24` | `uint8[4]` | `drawColor` (RGBA for shapes) |
224| `+0x28` | `uint8[4]` | `textColor` (RGBA for text) |
225| `+0x2c` | `int` | `m_nTextX` |
226| `+0x30` | `int` | `m_nTextY` |
227| `+0x290` | `HFont` | `m_hCurrentFont` |