#!/usr/bin/env python3 """Verify the craft -> roster link at roster_base + 0x08. The delta histogram showed exactly 300 pointers at +0x08 with 300 craft present, every other delta appearing at most twice. That is a candidate 1:1 link. Before believing it, this checks the thing a coincidence cannot survive: the craft's own unit type (from its definition pointer) must equal the unit type of the roster record it points at (resolved independently via that record's +0x04 name chain). """ import os, sys, struct, collections, bisect sys.path.insert(0, __file__.rsplit('/', 1)[0]) import gmem, gworld, entities2 import importlib.util _w3 = importlib.util.spec_from_file_location('w3', __file__.rsplit('/', 1)[0] + '/wave3_probe.py') wave3 = importlib.util.module_from_spec(_w3); _w3.loader.exec_module(wave3) ROSTER_VT = struct.pack('>I', 0x820AF030) DELTA, WIN, LINK = 0x130, 0x400, 0x08 def scan_vt(fd, size, vt): out = [] for a, b in gmem.extents(fd, size): pos = a while pos < b: n = min(1 << 24, b - pos) blob = os.pread(fd, n, pos) i = blob.find(vt) while i != -1: if (pos + i) % 4 == 0: out.append(pos + i) i = blob.find(vt, i + 1) pos += n return sorted(out) def main(): w = gworld.World(); fd = w.fd defs = entities2.definitions(w) if not defs: print('NOT IN A MISSION'); return 2 roster = scan_vt(fd, w.size, ROSTER_VT) f = os.fdopen(os.dup(fd), 'rb') rec = {} # roster VA -> (offset, unit id) for o in roster: va = gmem.primary_va(o) if va is None: continue rec[va] = (o, wave3.resolve_id(f, w.size, o)[0] or '?') print('roster records: %d (labelled %d)' % (len(rec), sum(1 for _, n in rec.values() if n != '?'))) lo, hi = gmem.va_to_off(entities2.ENT_VA_LO), gmem.va_to_off(entities2.ENT_VA_HI) craft, pos = [], lo while pos < hi: n = min(1 << 24, hi - pos) blob = os.pread(fd, n, pos) for k in range(0, len(blob) - 3, 4): nm = defs.get(blob[k:k+4]) if nm: craft.append((pos + k - DELTA, nm)) pos += n print('live craft: %d' % len(craft)) want = {va + LINK: va for va in rec} fan = collections.Counter(); agree = disagree = nolink = multi = 0 per_record = collections.Counter(); mismatches = [] for off, nm in craft: blob = os.pread(fd, WIN, off) tgt = [] for k in range(0, len(blob) - 3, 4): (p,) = struct.unpack_from('>I', blob, k) if p in want: tgt.append((k, want[p])) fan[len(tgt)] += 1 if not tgt: nolink += 1; continue if len(tgt) > 1: multi += 1 rva = tgt[0][1]; per_record[rva] += 1 rn = rec[rva][1] if rn == nm: agree += 1 else: disagree += 1 if len(mismatches) < 6: mismatches.append((nm, rn)) print('\n--- link at roster_base + %#x ---' % LINK) print(' craft fan-out histogram:', sorted(fan.items())) print(' craft with no link: %d with >1: %d' % (nolink, multi)) print(' UNIT TYPE AGREES: %d disagrees: %d' % (agree, disagree)) if mismatches: print(' sample mismatches (craft vs record):', mismatches) print(' pointer offsets used:', collections.Counter(k for off, nm in craft[:0] for k in []).most_common()) print('\n--- craft per roster record ---') print(' records referenced: %d / %d' % (len(per_record), len(rec))) print(' fan-in histogram:', sorted(collections.Counter(per_record.values()).items())) top = sorted(per_record.items(), key=lambda kv: -kv[1])[:6] for va, c in top: print(' %#010x %-32s x%d' % (va, rec[va][1], c)) return 0 if __name__ == '__main__': sys.exit(main())