# Context: HL2 Linux Internal Cheat (`hl2hake`) ## What this is A 32-bit Linux internal for Half-Life 2 (native `hl2_linux`), loaded with `LD_PRELOAD` through Steam's bundled 32-bit runtime. It started as a port of the Windows `hake` reference (auto-bhop / auto-ABH / auto-climb) and now also covers ESP, aimbot, triggerbot and silent aim. ## Build / run - `./build.sh` -> `libhl2hake.so` (`g++ -m32 -O2 -std=c++17 -Wall -Wextra -Werror -fPIC -shared`, sources: `hl2h modules cinput netvars menu esp server bones vischeck`, `-ldl`). - `./play.sh` -> Steam runtime + `hl2.sh -steam -novid -console +map d1_town_01`. - Logs: `/tmp/hl2hake.log` (`logf`) and stderr. `./play.sh > /tmp/hl2run.log 2>&1` captures everything. - Debug env: - `HL2H_DRAW_TEST=1` — red probe rect + text, no menu needed. - `HL2H_BONE_DEBUG=1` — white marker at each NPC head hitbox. - `HL2H_NO_SERVER=1` — disable server-side health reads. - Config: `$XDG_CONFIG_HOME/hl2hake/config`, falling back to `~/.config/hl2hake/config`. Plain INI, saved on every change. ## Runtime architecture - Modules from `/proc/self/maps`: `client.so`, `engine.so`, `server.so`. PROT_NONE ranges excluded; file-backed bss tails merged. - Calling convention: cdecl (i386); `this` is the first stack argument. - Hooks (copy-and-swap vtable, 256 slots): - `CHLClient` slot 21 `CreateMove`; slot 35 `FrameStageNotify`. - `ISurface` slot 114 `PaintTraverseEx` (primary draw); slot 22 `DrawPrintText` fallback. Draws wrapped in `PushMakeCurrent`/`PopMakeCurrent`. - `CInput*` discovered by byte-scanning the `CHLClient::CreateMove` prologue. Usercmd CRC-32 is repaired after any cmd edit (`note_modified`). - Console output via `ConMsg` from `libtier0.so` (`_Z6ConMsgPKcz`), not `IVEngineClient::ClientCmd`. ## Interfaces / indices | Interface | Version | Notes | |---|---|---| | Client | `VClient017` | | | Entity list | `VClientEntityList003` | | | Engine | `VEngineClient014` | | | Model info | `VModelInfoClient006` | | | Engine server | `VEngineServer021` | edicts / listen-server entities | | Debug overlay | `VDebugOverlay003` | accurate world->screen | | Engine trace | `EngineTraceClient003` | vischeck / trigger ray | | Surface | `VGUI_Surface030` | | Relevant vtable slots (this build): - Engine: `GetLocalPlayer` 12, `Time` 14, `GetViewAngles` 19, `SetViewAngles` 20. - Entity list: `GetClientNetworkable` 0, `GetClientEntity` 3, `GetHighestEntityIndex` 6. - `IClientNetworkable`: `GetClientClass` 2. `ClientClass::m_pNetworkName` at +8. - `IClientUnknown`: `GetClientRenderable` 6. - `IClientRenderable`: `GetModel` 9, `SetupBones` 16. - `IVModelInfo`: `GetModel` 2, `GetModelName` 4, `GetStudiomodel` 29. - `IVEngineServer`: `PEntityOfEntIndex` 19. Edict `m_pUnk` at +0xC; `IServerUnknown::GetBaseEntity` 6. - `IVDebugOverlay`: `ScreenPosition` 9. - `IEngineTrace`: `TraceRay` 4. `Ray_t` uses 16-byte `VectorAligned`; `trace_t`: `fraction` 44, `startsolid` 55, `m_pEnt` 76. ## Netvars / entity data (this build) | Field | Client offset | |---|---| | `m_fFlags` | `0x33C` | | `m_vecVelocity[0]` | `0xE0` | | `m_MoveType` (prediction datamap) | `0x164` | | `m_iHealth` | `0x80` | | `m_iMaxHealth` | not networked | | `m_iTeamNum` | `0x88` | | `m_vecOrigin` | `0x324` | | `m_vecViewOffset[0]` | `0xD4` | | `m_vecMins` / `m_vecMaxs` | `0x1A8` / `0x1B4` | | `m_lifeState` | `0x7F` | | `m_nModelIndex` | `0x7C` | - Eye position = `m_vecOrigin + m_vecViewOffset` on **all three axes**. (The old code added the X component to Z, which left the eye ~64 units low.) - NPC health is not networked. Alive state comes from the networked client `m_lifeState`; real HP is read from the listen-server `CBaseEntity` found via `PEntityOfEntIndex -> edict->m_pUnk -> GetBaseEntity`, using offsets scanned from `server.so`'s datamap: - `m_iHealth` `0xF4`, `m_iMaxHealth` `0xF0`, `m_lifeState` `0xF8`, `m_nModelIndex` `0x7A` (short). ## Menu Starts fully collapsed on first open. `Insert` toggles; `Up`/`Down` navigate; `Enter` expands/toggles; `Left`/`Right` edit values; `Escape` closes. Bind rows enter bind-select mode: press a key/button to assign, `Escape` sets None (always-on), `Enter` cancels. ``` HL2hake Bunnyhop Active Uni-directional auto-strafe ABH Active ESP Active Show neutral / healthbar / healthnum / name / skeleton / box Aimbot Active Bind Vischeck Bones -> Head / Torso / Left arm / Right arm / Left leg / Right leg Smoothness (1..45 deg/tick, Left/Right) Triggerbot Active Bind Silent aim Active Bind ``` Binds use SDL scancodes; mouse buttons are pseudo-scancodes 600..604. ## Bones / ESP - `SetupBones(BONE_USED_BY_ANYTHING)` with engine `Time()`; studio header via `GetStudiomodel`; hitboxes and bone names/parents from that header. - HL2 strips many ValveBiped bone names (the zombie's spine/legs/left arm are unnamed), so the humanoid skeleton is built from the **union of hitbox bone parent paths** (`HITGROUP_*`). Head group 1 includes the zombie headcrab body and its `HC_Head` child; fingers, toes and other `HC_*` limbs are excluded. - Non-humanoid NPCs (headcrabs, crows, antlions, ...) draw the full bone tree. - `aim_points` = head hitbox/bone + hitbox centers for the selected groups; the aim features pick the nearest visible point. ## Aim features - Shared target selection (`select_aim_target`): live hostile NPCs, skip friendly/neutral, rank by angle to the crosshair, then pick the nearest selected hitgroup point. Target candidates are tried nearest-first. - Vischeck: `EngineTraceClient003::TraceRay`, `MASK_VISIBLE`, `TRACE_WORLD_ONLY` (world only; static props are not tested — deliberate). - Aimbot: bounded smoothing — 0.05 deg deadzone, max `Smoothness` deg/tick, shortest-path yaw, pitch clamped to ±89; pushes the new view with `SetViewAngles`. - Triggerbot: crosshair ray with `MASK_SHOT` + `TRACE_EVERYTHING` (filter ignores the local player). First hit must be a live hostile NPC; sets `IN_ATTACK`. - Silent aim: exact target angle written to `cmd->viewangles`, `IN_ATTACK`, then the rendered view is restored immediately **and** at `FRAME_RENDER_START`; `forwardmove`/`sidemove` are rewritten to preserve the world-space movement wish vector. - ABH: on the landing/jump tick, face opposite the horizontal velocity with zero forward/side movement; HL2's jump speed-bound subtraction then accelerates along the velocity. Camera restored at `FRAME_RENDER_START`. ## Gotchas / lessons - Never pipe-mask build failures: `./build.sh > /tmp/build.log 2>&1; echo rc=$?`. - `CHLClient` slot numbers: `CreateMove` 21, `FrameStageNotify` 35. - `ISurface` binary slot = SDK header slot + 4; see `src/ISURFACE_STATUS.md`. - Silent aim and ABH temporarily replace the usercmd view; silent aim preserves movement, ABH intentionally zeroes it for the jump tick. - Aimbot vischeck (`MASK_VISIBLE`/world-only) differs from triggerbot (`MASK_SHOT`/all entities): glass and static props can behave differently.