"""Q3/Q4. Two jobs.

(1) callers() returned NOTHING for the market request builder FUN_180162c90 and the
seasons builder FUN_180175bd0. That is the absence trap again: these are almost
certainly virtual. So search for ABSOLUTE 8-byte pointers to them anywhere in the image
(vtable membership) as well as rel32 direct calls.
CONTROL: FUN_18011dc50 is KNOWN (prior run) to have zero rel32 callers and exactly one
absolute-pointer site at 0x18021cc28 = model vtable +0x988. If my scanner does not
reproduce that exact result, the scanner is wrong and every other row is void.

(2) Enumerate the FUT UI surface names: every "*viewmodel" literal and every
"data/ui/layout/fut/*" literal, so the transfer-market screen's provider can be named.
"""
import struct, re, traceback

try:
    blocks = {}
    for b in mem.getBlocks():
        blocks[str(b.getName())] = (int(b.getStart().getOffset()), int(b.getEnd().getOffset()))

    def blk(name):
        s, e = blocks[name]
        out = b""
        a = s
        while a <= e:
            n = min(1 << 20, e - a + 1)
            out += read_bytes(a, n)
            a += n
        return s, out

    ts, text = blk(".text")
    rs, rdata = blk(".rdata")
    ds, data = blk(".data")
    print("len text=%d rdata=%d data=%d" % (len(text), len(rdata), len(data)))

    def abs_ptr_sites(va):
        pat = struct.pack("<Q", va)
        out = []
        for base, buf, nm in ((rs, rdata, ".rdata"), (ds, data, ".data"), (ts, text, ".text")):
            i = buf.find(pat)
            while i != -1:
                out.append((base + i, nm))
                i = buf.find(pat, i + 1)
        return out

    def rel32_calls(va):
        out = []
        for i in range(0, len(text) - 5):
            if text[i] == 0xE8:
                d = struct.unpack_from("<i", text, i + 1)[0]
                if ts + i + 5 + d == va:
                    out.append(ts + i)
        return out

    TARGETS = [
        ("CONTROL applier FUN_18011dc50", 0x18011dc50),
        ("market builder FUN_180162c90", 0x180162c90),
        ("seasons hist builder FUN_180175bd0", 0x180175bd0),
    ]
    MODEL_VT = 0x18021c2a0
    for lbl, va in TARGETS:
        aps = abs_ptr_sites(va)
        rcs = rel32_calls(va)
        print("\n### %s %#x" % (lbl, va))
        print("  rel32 direct callers: %d %s" % (len(rcs), [hex(x) for x in rcs[:10]]))
        print("  absolute-pointer sites: %d" % len(aps))
        for a, nm in aps:
            note = ""
            if MODEL_VT <= a < MODEL_VT + 0xb20:
                note = "  == MODEL vtable slot +%#x" % (a - MODEL_VT)
            print("     %#x %s%s" % (a, nm, note))

    print("\n\n=== UI SURFACE LITERALS ===")
    strre = re.compile(rb"[\x20-\x7e]{5,120}")
    for base, buf, nm in ((rs, rdata, ".rdata"), (ds, data, ".data")):
        for m in strre.finditer(buf):
            s = m.group()
            low = s.lower()
            if (b"viewmodel" in low or b"layout/fut" in low or b"layout\\fut" in low
                    or b"transfer" in low or b"market" in low or b"tradepile" in low
                    or b"watchlist" in low or b"auction" in low):
                end = m.end()
                if end < len(buf) and buf[end] != 0:
                    continue
                va = base + m.start()
                x = xrefs_to(va)
                print("%#x %s xr=%d %s | %s" % (va, nm, len(x),
                      ",".join(sorted({n for _, _, n, _ in x})[:4]), s.decode("latin1")))
except Exception:
    traceback.print_exc()
