diff options
| author | salmonik <salmonik@tuta.io> | 2026-11-04 17:24:14 +0100 |
|---|---|---|
| committer | salmonik <salmonik@tuta.io> | 2026-11-04 17:24:14 +0100 |
| commit | e35f0dc0005139774533504965995da48a3dc90d (patch) | |
| tree | fe5dfb4d0ad56b49eb42cc98ed4ae5614cc4fea4 | |
| parent | Netvars: handle RecvPropArray3 array props (diff) | |
| download | hl2h-e35f0dc0005139774533504965995da48a3dc90d.tar.gz hl2h-e35f0dc0005139774533504965995da48a3dc90d.zip | |
Netvars: catch array element props and log the walk
- Match m_iAmmo/m_hMyWeapons by name prefix so element props such as
m_iAmmo[0] are accepted.
- Handle them before the nested-table recursion, since RecvPropArray3 may put
the element descriptor in m_pDataTable.
- Allow name strings outside client.so (engine/server too), and log the first
16 array props plus the first 8 unreadable prop names so the remaining
resolution failure is visible in the normal log.
| -rw-r--r-- | src/netvars.cpp | 86 |
1 files changed, 58 insertions, 28 deletions
diff --git a/src/netvars.cpp b/src/netvars.cpp index 9b7e3c9..6a34ee5 100644 --- a/src/netvars.cpp +++ b/src/netvars.cpp | |||
| @@ -6,7 +6,7 @@ | |||
| 6 | // ClientClass (2004-era): CreateFn@0, CreateEventFn@4, Name@8, RecvTable@0xC, Next@0x10 | 6 | // ClientClass (2004-era): CreateFn@0, CreateEventFn@4, Name@8, RecvTable@0xC, Next@0x10 |
| 7 | 7 | ||
| 8 | static bool module_string(uintptr_t p, char *out, size_t outn) { | 8 | static bool module_string(uintptr_t p, char *out, size_t outn) { |
| 9 | if (!g_client.contains(p)) | 9 | if (!g_client.contains(p) && !g_engine.contains(p) && !g_server.contains(p)) |
| 10 | return false; | 10 | return false; |
| 11 | const char *s = (const char *)p; | 11 | const char *s = (const char *)p; |
| 12 | size_t i = 0; | 12 | size_t i = 0; |
| @@ -61,12 +61,67 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 61 | uintptr_t ptable = *(uintptr_t *)(prop + 0x28); | 61 | uintptr_t ptable = *(uintptr_t *)(prop + 0x28); |
| 62 | 62 | ||
| 63 | char pname2[64]; | 63 | char pname2[64]; |
| 64 | if (!module_string(pname, pname2, sizeof(pname2))) | 64 | if (!module_string(pname, pname2, sizeof(pname2))) { |
| 65 | static int bad_names = 0; | ||
| 66 | if (bad_names < 8) { | ||
| 67 | bad_names++; | ||
| 68 | logf("netvar: unreadable prop name %#lx in %s", (unsigned long)pname, | ||
| 69 | tname); | ||
| 70 | } | ||
| 65 | continue; | 71 | continue; |
| 72 | } | ||
| 66 | 73 | ||
| 67 | if (dump) | 74 | if (dump) |
| 68 | logd(" [%s] %s type=%d off=%d(0x%x) table=%s array=%s", tname, pname2, | 75 | logd(" [%s] %s type=%d off=%d(0x%x) table=%s array=%s", tname, pname2, |
| 69 | ptype, poff, poff, ptable ? "yes" : "no", parray ? "yes" : "no"); | 76 | ptype, poff, poff, ptable ? "yes" : "no", parray ? "yes" : "no"); |
| 77 | // RecvPropArray3 props can store their element descriptor in either | ||
| 78 | // m_pArrayProp or (misleadingly) m_pDataTable. Handle the two arrays we | ||
| 79 | // need before the generic nested-table recursion drops them. | ||
| 80 | if (g_net.my_weapons < 0 && | ||
| 81 | strncmp(pname2, "m_hMyWeapons", 12) == 0) { | ||
| 82 | int off = (int)(base_off + (uint32_t)poff); | ||
| 83 | uintptr_t elem = parray ? parray : ptable; | ||
| 84 | if (elem) { | ||
| 85 | int32_t eoff = *(int32_t *)(elem + 0x2C); | ||
| 86 | if (eoff) | ||
| 87 | off = (int)(base_off + (uint32_t)eoff); | ||
| 88 | } | ||
| 89 | if (off >= 0x20 && off <= 0x2000) { | ||
| 90 | g_net.my_weapons = off; | ||
| 91 | logf("netvar: m_hMyWeapons prop=%s poff=%d elem=%p -> %d", pname2, | ||
| 92 | poff, (void *)elem, off); | ||
| 93 | } else { | ||
| 94 | static int bad = 0; | ||
| 95 | if (bad < 8) { | ||
| 96 | bad++; | ||
| 97 | logf("netvar: m_hMyWeapons prop=%s rejected poff=%d elem=%p off=%d", | ||
| 98 | pname2, poff, (void *)elem, off); | ||
| 99 | } | ||
| 100 | } | ||
| 101 | continue; | ||
| 102 | } | ||
| 103 | if (g_net.ammo < 0 && strncmp(pname2, "m_iAmmo", 7) == 0) { | ||
| 104 | int off = (int)(base_off + (uint32_t)poff); | ||
| 105 | uintptr_t elem = parray ? parray : ptable; | ||
| 106 | if (elem) { | ||
| 107 | int32_t eoff = *(int32_t *)(elem + 0x2C); | ||
| 108 | if (eoff) | ||
| 109 | off = (int)(base_off + (uint32_t)eoff); | ||
| 110 | } | ||
| 111 | if (off >= 0x20 && off <= 0x2000) { | ||
| 112 | g_net.ammo = off; | ||
| 113 | logf("netvar: m_iAmmo prop=%s poff=%d elem=%p -> %d", pname2, poff, | ||
| 114 | (void *)elem, off); | ||
| 115 | } else { | ||
| 116 | static int bad = 0; | ||
| 117 | if (bad < 8) { | ||
| 118 | bad++; | ||
| 119 | logf("netvar: m_iAmmo prop=%s rejected poff=%d elem=%p off=%d", | ||
| 120 | pname2, poff, (void *)elem, off); | ||
| 121 | } | ||
| 122 | } | ||
| 123 | continue; | ||
| 124 | } | ||
| 70 | if (ptable && !parray) { | 125 | if (ptable && !parray) { |
| 71 | collect_netvars(ptable, base_off + (uint32_t)poff, depth + 1); | 126 | collect_netvars(ptable, base_off + (uint32_t)poff, depth + 1); |
| 72 | continue; | 127 | continue; |
| @@ -75,7 +130,7 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 75 | static int alogs = 0; | 130 | static int alogs = 0; |
| 76 | if (alogs < 16) { | 131 | if (alogs < 16) { |
| 77 | alogs++; | 132 | alogs++; |
| 78 | logd("netvar array %s poff=%d eoff=%d base=%d", pname2, poff, | 133 | logf("netvar array %s poff=%d eoff=%d base=%d", pname2, poff, |
| 79 | *(int32_t *)(parray + 0x2C), (int)base_off); | 134 | *(int32_t *)(parray + 0x2C), (int)base_off); |
| 80 | } | 135 | } |
| 81 | } | 136 | } |
| @@ -120,31 +175,6 @@ static void collect_netvars(uintptr_t table, uintptr_t base_off, int depth) { | |||
| 120 | int off = (int)(base_off + (uint32_t)poff); | 175 | int off = (int)(base_off + (uint32_t)poff); |
| 121 | if (off >= 0x20 && off <= 0x2000) | 176 | if (off >= 0x20 && off <= 0x2000) |
| 122 | g_net.dmg_radius = off; | 177 | g_net.dmg_radius = off; |
| 123 | } else if (strcmp(pname2, "m_hMyWeapons") == 0 && | ||
| 124 | g_net.my_weapons < 0) { | ||
| 125 | int off = (int)(base_off + (uint32_t)poff); | ||
| 126 | if (parray) { | ||
| 127 | int32_t eoff = *(int32_t *)(parray + 0x2C); | ||
| 128 | if (eoff) | ||
| 129 | off = (int)(base_off + (uint32_t)eoff); | ||
| 130 | } | ||
| 131 | if (off >= 0x20 && off <= 0x2000) { | ||
| 132 | g_net.my_weapons = off; | ||
| 133 | logd("netvar: m_hMyWeapons base_off=%d poff=%d parray=%p -> %d", | ||
| 134 | (int)base_off, poff, (void *)parray, off); | ||
| 135 | } | ||
| 136 | } else if (strcmp(pname2, "m_iAmmo") == 0 && g_net.ammo < 0) { | ||
| 137 | int off = (int)(base_off + (uint32_t)poff); | ||
| 138 | if (parray) { | ||
| 139 | int32_t eoff = *(int32_t *)(parray + 0x2C); | ||
| 140 | if (eoff) | ||
| 141 | off = (int)(base_off + (uint32_t)eoff); | ||
| 142 | } | ||
| 143 | if (off >= 0x20 && off <= 0x2000) { | ||
| 144 | g_net.ammo = off; | ||
| 145 | logd("netvar: m_iAmmo base_off=%d poff=%d parray=%p -> %d", | ||
| 146 | (int)base_off, poff, (void *)parray, off); | ||
| 147 | } | ||
| 148 | } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { | 178 | } else if (strcmp(pname2, "m_iTeamNum") == 0 && g_net.teamnum < 0) { |
| 149 | g_net.teamnum = (int)(base_off + (uint32_t)poff); | 179 | g_net.teamnum = (int)(base_off + (uint32_t)poff); |
| 150 | } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { | 180 | } else if (strcmp(pname2, "m_vecOrigin") == 0 && g_net.origin < 0) { |
