diff options
| author | salmonik <salmonik@tuta.io> | 2026-10-26 15:33:02 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-10-26 15:33:02 +0100 |
| commit | d7edcc9d9e3d90e1c53885870461e046389dee96 (patch) | |
| tree | d4e80c026f65cbff9804c80184293d0c1b63afa8 /src/netvars.cpp | |
| parent | Default map d1_town_01 (intro-less, enemies spawned) (diff) | |
| download | hl2h-d7edcc9d9e3d90e1c53885870461e046389dee96.tar.gz hl2h-d7edcc9d9e3d90e1c53885870461e046389dee96.zip | |
ESP working: NPC detection + authoritative server-side health
- entitylist slot 6 (GetHighestEntityIndex); class name via
GetClientNetworkable -> GetClientClass (replaces the unsafe slot-2 call)
- HL2 SP NPCs: class CAI_BaseNPC + m_lifeState, since NPC m_iHealth is not
networked; model names from VModelInfoClient006 for labels/friend-foe
- real HP/HP-max from the listen-server entity (IVEngineServer021 ->
PEntityOfEntIndex -> edict->m_pUnk -> GetBaseEntity) using offsets scanned
from server.so's datamap; validated against the client model index
- world_to_screen via IVDebugOverlay003::ScreenPosition (fixes box drift)
- maxs sanity clamp, NPC-only debug logging, neutral colour for wildlife
- build.sh: restore -o libhl2hake.so and add server.cpp
Diffstat (limited to 'src/netvars.cpp')
| -rw-r--r-- | src/netvars.cpp | 67 |
1 files changed, 51 insertions, 16 deletions
diff --git a/src/netvars.cpp b/src/netvars.cpp index 5c8c296..f702c41 100644 --- a/src/netvars.cpp +++ b/src/netvars.cpp | |||
| @@ -27,7 +27,24 @@ static bool module_string(uintptr_t p, char *out, size_t outn) { | |||
| 27 | 27 | ||
| 28 | static int g_dumped_tables = 0; | 28 | static int g_dumped_tables = 0; |
| 29 | 29 | ||
| 30 | static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | 30 | // per-client-class health offset (m_iHealth lives at different offsets per |
| 31 | // class chain: CBasePlayer via DT_BasePlayer=128, NPCs via DT_BaseCombatCharacter) | ||
| 32 | static struct { | ||
| 33 | char name[32]; | ||
| 34 | int off; | ||
| 35 | } g_class_health[256]; | ||
| 36 | static int g_class_health_n = 0; | ||
| 37 | |||
| 38 | int class_health_off(const char *name) { | ||
| 39 | for (int i = 0; i < g_class_health_n; i++) | ||
| 40 | if (strcmp(name, g_class_health[i].name) == 0) | ||
| 41 | return g_class_health[i].off; | ||
| 42 | return -1; | ||
| 43 | } | ||
| 44 | |||
| 45 | static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth, | ||
| 46 | int *out_health) | ||
| 47 | { | ||
| 31 | if (depth > 8) | 48 | if (depth > 8) |
| 32 | return; | 49 | return; |
| 33 | uintptr_t props = *(uintptr_t *)(table + 0x00); | 50 | uintptr_t props = *(uintptr_t *)(table + 0x00); |
| @@ -67,13 +84,19 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 67 | if (dump) | 84 | if (dump) |
| 68 | logf(" [%s] %s type=%d off=%d(0x%x) table=%s array=%s", tname, pname2, | 85 | logf(" [%s] %s type=%d off=%d(0x%x) table=%s array=%s", tname, pname2, |
| 69 | ptype, poff, poff, ptable ? "yes" : "no", parray ? "yes" : "no"); | 86 | ptype, poff, poff, ptable ? "yes" : "no", parray ? "yes" : "no"); |
| 70 | |||
| 71 | if (ptable) { | 87 | if (ptable) { |
| 72 | collect_netvars(ptable, base_off + (uint32_t)poff, depth + 1); | 88 | collect_netvars(ptable, base_off + (uint32_t)poff, depth + 1, |
| 89 | out_health); | ||
| 73 | continue; | 90 | continue; |
| 74 | } | 91 | } |
| 75 | 92 | ||
| 76 | if (strcmp(pname2, "m_fFlags") == 0 && g_net.flags < 0) { | 93 | if (out_health && *out_health < 0 && strcmp(pname2, "m_iHealth") == 0) |
| 94 | *out_health = (int)(base_off + (uint32_t)poff); | ||
| 95 | if (strcmp(pname2, "m_lifeState") == 0 && g_net.lifestate < 0) { | ||
| 96 | g_net.lifestate = (int)(base_off + (uint32_t)poff); | ||
| 97 | } else if (strcmp(pname2, "m_nModelIndex") == 0 && g_net.modelindex < 0) { | ||
| 98 | g_net.modelindex = (int)(base_off + (uint32_t)poff); | ||
| 99 | } else if (strcmp(pname2, "m_fFlags") == 0 && g_net.flags < 0) { | ||
| 77 | g_net.flags = (int)(base_off + (uint32_t)poff); | 100 | g_net.flags = (int)(base_off + (uint32_t)poff); |
| 78 | } else if (strcmp(pname2, "m_vecVelocity") == 0 && g_net.velocity < 0) { | 101 | } else if (strcmp(pname2, "m_vecVelocity") == 0 && g_net.velocity < 0) { |
| 79 | if (parray && g_client.contains(parray)) { | 102 | if (parray && g_client.contains(parray)) { |
| @@ -95,7 +118,8 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 95 | } else { | 118 | } else { |
| 96 | g_net.origin = (int)(base_off + (uint32_t)poff); | 119 | g_net.origin = (int)(base_off + (uint32_t)poff); |
| 97 | } | 120 | } |
| 98 | } else if (strcmp(pname2, "m_vecViewOffset") == 0 && g_net.viewoffset < 0) { | 121 | } else if (strncmp(pname2, "m_vecViewOffset", 15) == 0 && |
| 122 | g_net.viewoffset < 0) { | ||
| 99 | if (parray && g_client.contains(parray)) { | 123 | if (parray && g_client.contains(parray)) { |
| 100 | int32_t eoff = *(int32_t *)(parray + 0x2C); | 124 | int32_t eoff = *(int32_t *)(parray + 0x2C); |
| 101 | g_net.viewoffset = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); | 125 | g_net.viewoffset = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); |
| @@ -117,9 +141,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 117 | g_net.maxs = (int)(base_off + (uint32_t)poff); | 141 | g_net.maxs = (int)(base_off + (uint32_t)poff); |
| 118 | } | 142 | } |
| 119 | } | 143 | } |
| 120 | if (g_net.esp_ok()) | 144 | } |
| 121 | return; | ||
| 122 | } | ||
| 123 | } | 145 | } |
| 124 | 146 | ||
| 125 | // prediction datamap scan for m_MoveType (FIELD_CHARACTER, fieldOffset in range) | 147 | // prediction datamap scan for m_MoveType (FIELD_CHARACTER, fieldOffset in range) |
| @@ -195,21 +217,34 @@ void resolve_netvars() { | |||
| 195 | for (uintptr_t cc = head; cc && g_client.contains(cc) && classes < 1024; | 217 | for (uintptr_t cc = head; cc && g_client.contains(cc) && classes < 1024; |
| 196 | cc = *(uintptr_t *)(cc + 0x10)) { | 218 | cc = *(uintptr_t *)(cc + 0x10)) { |
| 197 | classes++; | 219 | classes++; |
| 198 | collect_netvars(*(uintptr_t *)(cc + 0x0C), 0, 0); | 220 | char cname[32]; |
| 199 | if (g_net.esp_ok() || (g_net.flags >= 0 && g_net.velocity >= 0 && | 221 | int hoff = -1; |
| 200 | g_net.health < 0)) | 222 | // per-class health offset — NPCs send m_iHealth via their own table chain, |
| 201 | break; | 223 | // not DT_BasePlayer's; g_net.health keeps the player's for aimbot use |
| 224 | if (module_string(*(uintptr_t *)(cc + 0x08), cname, sizeof(cname))) { | ||
| 225 | collect_netvars(*(uintptr_t *)(cc + 0x0C), 0, 0, &hoff); | ||
| 226 | if (hoff >= 0 && g_class_health_n < 256) { | ||
| 227 | char *dst = g_class_health[g_class_health_n].name; | ||
| 228 | const char *src = cname; | ||
| 229 | size_t n = sizeof(g_class_health[g_class_health_n].name) - 1; | ||
| 230 | for (size_t i = 0; i < n && src[i]; i++) dst[i] = src[i]; | ||
| 231 | dst[n] = 0; | ||
| 232 | g_class_health[g_class_health_n].off = hoff; | ||
| 233 | g_class_health_n++; | ||
| 234 | } | ||
| 235 | } | ||
| 202 | } | 236 | } |
| 203 | logf("client classes walked: %d", classes); | 237 | logf("client classes walked: %d, health-offset classes: %d", classes, |
| 238 | g_class_health_n); | ||
| 204 | 239 | ||
| 205 | if (g_net.movetype < 0 && !resolve_movetype()) { | 240 | if (g_net.movetype < 0 && !resolve_movetype()) { |
| 206 | // movetype unresolved — bhop still works, just no noclip/ladder guard | 241 | // movetype unresolved — bhop still works, just no noclip/ladder guard |
| 207 | logf("movetype guard disabled"); | 242 | logf("movetype guard disabled"); |
| 208 | } | 243 | } |
| 209 | |||
| 210 | logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " | 244 | logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " |
| 211 | "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d", | 245 | "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " |
| 246 | "modelindex=%d", | ||
| 212 | g_net.flags, g_net.velocity, g_net.movetype, g_net.health, | 247 | g_net.flags, g_net.velocity, g_net.movetype, g_net.health, |
| 213 | g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, | 248 | g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, |
| 214 | g_net.mins, g_net.maxs); | 249 | g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex); |
| 215 | } | 250 | } |
