"""DIMENSION 4, pass 12: name the two endpoints in the loan-signing chain, and
name the response class whose deserializer is 0x1801293d0 (one of the four
duplicateItemIdList consumers).

Chain: <sign loan player> -> FUN_18009c360 (HTTP 403 -> "GotoAlreadySignedPopup")
       -> manager vt+0x158 request, vtable 0x1801ed690, continuation FUN_18009bc40
       -> FUN_18009bc40 branches on item->duplicateItemId
          not dup -> vtable 0x1801f3158 request = PUT item {"itemData":[{"id":..,
                     "pile":"club","swap":0,"tradeId":0}]}   (atoms 0x16b/0x15c/
                     0x226/0x87/0x2fe/0x331 -- decoded from FUN_180127cc0)
          dup     -> UI command "GotoNewItems", no request at all

So: dump vtable 0x1801ed690 (its +0x38 URL builder and +0x10 body serializer),
and resolve the class name of the response whose deser is 0x1801293d0 by taking
the vtable that holds it at slot +0x08 (data ref 0x180220ba8 => vtable base
0x180220ba0) and looking for the RS4: literal referenced by its factory.

CONTROL: for the RS4 resolution, also run the same procedure on the known
CreatePack deser 0x180162880 (whose class RS4:FutCreatePackServerResponse is
already established) and check it comes out right.
"""
import traceback

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/store/dup12_out.txt"

def w(fh, s=""):
    fh.write(str(s) + "\n")

def dump(fh, a, label):
    s = dec(a)
    w(fh, "")
    w(fh, "#" * 70)
    w(fh, "# %s  %#x  len(src)=%d  FULL" % (label, a, len(s)))
    w(fh, "#" * 70)
    w(fh, s)

def rs4_for_deser(fh, deser, label):
    """find vtables holding `deser` at slot +8, then the RS4: name near a factory"""
    w(fh, "")
    w(fh, "=== RS4 resolution for deser %#x (%s) ===" % (deser, label))
    for frm, typ, fn, ent in xrefs_to(deser):
        if typ != "DATA":
            continue
        vt_base = frm - 8
        w(fh, "  data ref at %#x -> candidate vtable %#x" % (frm, vt_base))
        try:
            q0 = qword(vt_base)
        except Exception:
            continue
        f0 = fm.getFunctionAt(addr(q0)) if 0x180000000 <= q0 < 0x181000000 else None
        w(fh, "     slot0 = %#x %s" % (q0, f0.getName() if f0 else "(not a function)"))
        for frm2, typ2, fn2, ent2 in xrefs_to(vt_base):
            w(fh, "     vtable referenced from %#x in %s @ %#x" % (frm2, fn2, ent2))
            if not ent2:
                continue
            f = func(ent2)
            if f is None:
                continue
            it = listing.getInstructions(f.getBody(), True)
            while it.hasNext():
                ins = it.next()
                for r in ins.getReferencesFrom():
                    t = int(r.getToAddress().getOffset())
                    try:
                        s = rd_str(t, 70)
                    except Exception:
                        continue
                    if s.startswith("RS4:"):
                        w(fh, "        -> %s   (at %#x)" % (s, t))

try:
    fh = open(OUT, "w")

    w(fh, "=== vtable 0x1801ed690 (the request made after signing) ===")
    for off, tgt, nm in vtable(0x1801ED690, 12):
        w(fh, "   +%#05x -> %#x  %s" % (off, tgt, nm))
    for off in (0x10, 0x38):
        t = qword(0x1801ED690 + off)
        if fm.getFunctionAt(addr(t)) is not None:
            dump(fh, t, "vtable 0x1801ed690 slot +%#04x" % off)

    w(fh, "")
    w(fh, "=== builders that use vtable 0x1801ed690 ===")
    for frm, typ, fn, ent in xrefs_to(0x1801ED690):
        w(fh, "   %#x %s in %s @ %#x" % (frm, typ, fn, ent))

    rs4_for_deser(fh, 0x1801293D0, "one of the four dup consumers")
    rs4_for_deser(fh, 0x18013BD40, "another dup consumer")
    rs4_for_deser(fh, 0x180162880, "CONTROL: CreatePack deser")

    fh.close()
    print("WROTE", OUT)
except Exception:
    traceback.print_exc()
