// netvars.cpp — ClientClass walk, RecvTable resolution, movetype from pred-TD #include "hl2h.h" // RecvTable layout: pProps@0, nProps@4, pDecoder@8, pNetTableName@0xC // RecvProp layout: pVarName@0, pArrayProp@0x18, pDataTable@0x28, offset@0x2C // ClientClass (2004-era): CreateFn@0, CreateEventFn@4, Name@8, RecvTable@0xC, Next@0x10 static bool module_string(uintptr_t p, char *out, size_t outn) { if (!g_client.contains(p)) return false; const char *s = (const char *)p; size_t i = 0; while (i < outn - 1) { unsigned char ch = (unsigned char)s[i]; if (ch == 0) break; if (ch < 0x20 || ch > 0x7E) return false; out[i] = (char)ch; i++; } if (i == 0 || i >= outn - 1) return false; out[i] = 0; return true; } static int g_dumped_tables = 0; static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { if (depth > 8) return; uintptr_t props = *(uintptr_t *)(table + 0x00); int32_t nprops = *(int32_t *)(table + 0x04); if (!props || !g_client.contains(props) || nprops <= 0 || nprops > 512) return; char tname[64]; if (!module_string(*(uintptr_t *)(table + 0x0C), tname, sizeof(tname))) return; if (strncmp(tname, "DT_", 3) != 0) return; bool dump = g_dumped_tables < 2 && (strcmp(tname, "DT_BaseEntity") == 0 || strcmp(tname, "DT_BasePlayer") == 0); if (dump) { g_dumped_tables++; logd("dump %s: %d props @ table %#lx (base_off %#lx)", tname, nprops, (unsigned long)table, (unsigned long)base_off); } for (int32_t i = 0; i < nprops; i++) { uintptr_t prop = props + (size_t)i * 0x3C; if (!g_client.contains(prop)) return; uintptr_t pname = *(uintptr_t *)(prop + 0x00); int32_t ptype = *(int32_t *)(prop + 0x04); int32_t poff = *(int32_t *)(prop + 0x2C); uintptr_t parray = *(uintptr_t *)(prop + 0x18); uintptr_t ptable = *(uintptr_t *)(prop + 0x28); char pname2[64]; if (!module_string(pname, pname2, sizeof(pname2))) continue; if (dump) logd(" [%s] %s type=%d off=%d(0x%x) table=%s array=%s", tname, pname2, ptype, poff, poff, ptable ? "yes" : "no", parray ? "yes" : "no"); if (ptable) { collect_netvars(ptable, base_off + (uint32_t)poff, depth + 1); continue; } if (strcmp(pname2, "m_lifeState") == 0 && g_net.lifestate < 0) { g_net.lifestate = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_nModelIndex") == 0 && g_net.modelindex < 0) { g_net.modelindex = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_fFlags") == 0 && g_net.flags < 0) { g_net.flags = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_vecVelocity") == 0 && g_net.velocity < 0) { if (parray && g_client.contains(parray)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.velocity = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { g_net.velocity = (int)(base_off + (uint32_t)poff); } } else if (strcmp(pname2, "m_iHealth") == 0 && g_net.health < 0) { g_net.health = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_iMaxHealth") == 0 && g_net.maxhealth < 0) { g_net.maxhealth = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_hActiveWeapon") == 0 && g_net.activeweapon < 0) { g_net.activeweapon = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_clrRender") == 0 && g_net.clr_render < 0) { int off = (int)(base_off + (uint32_t)poff); // Reject the 0 offset some embedded tables report; the real render // colour lives well inside C_BaseEntity. if (off >= 0x20 && off <= 0x2000) g_net.clr_render = off; } else if (strcmp(pname2, "m_vecPunchAngle") == 0 && g_net.punch < 0) { int off = (int)(base_off + (uint32_t)poff); if (off >= 0x20 && off <= 0x2000) g_net.punch = off; } else if (strcmp(pname2, "m_iDefaultFOV") == 0 && g_net.default_fov < 0) { int off = (int)(base_off + (uint32_t)poff); if (off >= 0x20 && off <= 0x2000) g_net.default_fov = off; } else if (strcmp(pname2, "m_DmgRadius") == 0 && g_net.dmg_radius < 0) { int off = (int)(base_off + (uint32_t)poff); if (off >= 0x20 && off <= 0x2000) g_net.dmg_radius = off; } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { g_net.teamnum = (int)(base_off + (uint32_t)poff); } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { if (parray && g_client.contains(parray)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.origin = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { g_net.origin = (int)(base_off + (uint32_t)poff); } } else if (strncmp(pname2, "m_vecViewOffset", 15) == 0 && g_net.viewoffset < 0) { if (parray && g_client.contains(parray)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.viewoffset = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { g_net.viewoffset = (int)(base_off + (uint32_t)poff); } } else if (strcmp(pname2, "m_vecMins") == 0 && g_net.mins < 0) { if (parray && g_client.contains(parray)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.mins = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { g_net.mins = (int)(base_off + (uint32_t)poff); } } else if (strcmp(pname2, "m_vecMaxs") == 0 && g_net.maxs < 0) { if (parray && g_client.contains(parray)) { int32_t eoff = *(int32_t *)(parray + 0x2C); g_net.maxs = (int)(base_off + (uint32_t)(eoff ? eoff : poff)); } else { g_net.maxs = (int)(base_off + (uint32_t)poff); } } } } // prediction datamap scan for m_MoveType (FIELD_CHARACTER, fieldOffset in range) static bool resolve_movetype() { static const char needle[] = "m_MoveType"; const size_t nlen = sizeof(needle); uintptr_t str_va = 0; for (auto &r : g_client.ranges) { const uint8_t *base = (const uint8_t *)r.lo; size_t span = r.hi - r.lo; for (size_t i = 0; i + nlen <= span; i++) { if (memcmp(base + i, needle, nlen) == 0) { str_va = r.lo + i; break; } } if (str_va) break; } if (!str_va) { logd("m_MoveType string not found"); return false; } uint32_t offs[16]; int counts[16] = {}; int ncand = 0; const uint32_t target = (uint32_t)str_va; for (auto &r : g_client.ranges) { const uint8_t *base = (const uint8_t *)r.lo; size_t span = r.hi - r.lo; for (size_t off = 4; off + 8 <= span; off += 4) { if (*(const uint32_t *)(base + off) != target) continue; uint32_t ftype = *(const uint32_t *)(base + off - 4); int32_t foff = *(const int32_t *)(base + off + 4); if (ftype != 8 || foff < 0x40 || foff > 0x400) continue; int k; for (k = 0; k < ncand; k++) if (offs[k] == (uint32_t)foff) break; if (k == ncand && ncand < 16) { offs[ncand] = (uint32_t)foff; ncand++; } if (k < ncand) counts[k]++; } } if (ncand == 0) { logd("m_MoveType typedescription not found"); return false; } int best = 0; for (int k = 1; k < ncand; k++) if (counts[k] > counts[best]) best = k; g_net.movetype = (int)offs[best]; return true; } static bool resolve_absvelocity() { static const char needle[] = "m_vecAbsVelocity"; const size_t nlen = sizeof(needle); uintptr_t str_va = 0; for (auto &r : g_client.ranges) { const uint8_t *base = (const uint8_t *)r.lo; size_t span = r.hi - r.lo; for (size_t i = 0; i + nlen <= span; i++) { if (memcmp(base + i, needle, nlen) == 0) { str_va = r.lo + i; break; } } if (str_va) break; } if (!str_va) { logd("m_vecAbsVelocity string not found"); return false; } uint32_t offs[16]; int counts[16] = {}; int ncand = 0; const uint32_t target = (uint32_t)str_va; for (auto &r : g_client.ranges) { const uint8_t *base = (const uint8_t *)r.lo; size_t span = r.hi - r.lo; for (size_t off = 4; off + 8 <= span; off += 4) { if (*(const uint32_t *)(base + off) != target) continue; uint32_t ftype = *(const uint32_t *)(base + off - 4); int32_t foff = *(const int32_t *)(base + off + 4); if (ftype != 3 || foff < 0x40 || foff > 0x400) continue; int k; for (k = 0; k < ncand; k++) if (offs[k] == (uint32_t)foff) break; if (k == ncand && ncand < 16) { offs[ncand] = (uint32_t)foff; ncand++; } if (k < ncand) counts[k]++; } } if (ncand == 0) { logd("m_vecAbsVelocity typedescription not found"); return false; } int best = 0; for (int k = 1; k < ncand; k++) if (counts[k] > counts[best]) best = k; g_net.absvelocity = (int)offs[best]; return true; } void resolve_netvars() { using GetAllClassesFn = void *(*)(void *); uintptr_t head = (uintptr_t)vfunc(g_iface.client, 8)(g_iface.client); if (!g_client.contains(head)) { logf("GetAllClasses returned %p, outside client module", (void *)head); return; } int classes = 0; for (uintptr_t cc = head; cc && g_client.contains(cc) && classes < 1024; cc = *(uintptr_t *)(cc + 0x10)) { classes++; collect_netvars(*(uintptr_t *)(cc + 0x0C), 0, 0); } logd("client classes walked: %d", classes); if (g_net.movetype < 0 && !resolve_movetype()) { // movetype unresolved — bhop still works, just no noclip/ladder guard logf("movetype guard disabled"); } if (g_net.absvelocity < 0 && !resolve_absvelocity()) logf("m_vecAbsVelocity unresolved; jumpbug assist disabled"); logf("netvars: flags=%d velocity=%d movetype=%d health=%d maxhealth=%d " "teamnum=%d origin=%d viewoffset=%d mins=%d maxs=%d lifeste=%d " "modelindex=%d absvelocity=%d activeweapon=%d owner=%d " "weapon_owner=%d clr_render=%d punch=%d default_fov=%d " "dmg_radius=%d", g_net.flags, g_net.velocity, g_net.movetype, g_net.health, g_net.maxhealth, g_net.teamnum, g_net.origin, g_net.viewoffset, g_net.mins, g_net.maxs, g_net.lifestate, g_net.modelindex, g_net.absvelocity, g_net.activeweapon, g_net.owner, g_net.weapon_owner, g_net.clr_render, g_net.punch, g_net.default_fov, g_net.dmg_radius); }