// 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++; logf("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) logf(" [%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_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); } } if (g_net.ok()) return; } } // 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) { logf("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) { logf("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; } 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++; char name[64]; if (!module_string(*(uintptr_t *)(cc + 0x08), name, sizeof(name))) break; collect_netvars(*(uintptr_t *)(cc + 0x0C), 0, 0); if (g_net.flags >= 0 && g_net.velocity >= 0) break; } logf("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.ok()) { logf("netvars: m_fFlags=+0x%X m_vecVelocity[0]=+0x%X m_MoveType=+0x%X", g_net.flags, g_net.velocity, g_net.movetype); } else { logf("netvar resolution incomplete: flags=%d velocity=%d movetype=%d", g_net.flags, g_net.velocity, g_net.movetype); } }