"""DIMENSION 4 (duplicates), pass 3.

Q2 is "what REQUEST does the duplicate flow emit". Request serializers in this
client do NOT use string literals for keys: they call FUN_180180cd0(<atom>) to
turn an atom id into its wire name (proved by the CreatePack request serializer
0x180162530, which serialises atoms 0x20b, 0x369, 0x36b, 0xc4 that way).

HYPOTHESIS: no serializer ever emits duplicateItemId (0xeb), duplicateItemIdList
(0xec), duplicateItemLoans (0xed) or itemLoans (0x16f); they are response-only.

METHOD / CONTROL: enumerate EVERY call site of FUN_180180cd0 from the reference
manager, disassemble backwards up to 12 instructions in the same function, and
record every immediate moved into ECX/RCX. Then assert the four control atoms
0x20b/0x369/0x36b/0xc4 ARE found (if the method cannot see known-present atoms it
cannot be trusted to prove absence). Immediate forms handled: MOV ECX,imm and
XOR ECX,ECX (zero) and LEA ECX,[imm]; anything unresolved is reported as UNKNOWN
so absence is never inferred from a silent miss.

Also: GetCardDuplicate script handler, and the owner of the item container.
"""
import traceback

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/store/dup3_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)

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

    # ---------------- A: atom serialisation census ----------------
    w(fh, "=" * 70)
    w(fh, "A. every call site of the atom->wire-name helper FUN_180180cd0")
    w(fh, "=" * 70)
    sites = [(frm, fn, ent) for frm, typ, fn, ent in xrefs_to(0x180180CD0)
             if "CALL" in typ]
    w(fh, "call sites: %d" % len(sites))
    found = {}
    unknown = []
    for frm, fn, ent in sites:
        ins = listing.getInstructionAt(addr(frm))
        val = None
        cur = ins
        for _ in range(12):
            if cur is None:
                break
            cur = cur.getPrevious()
            if cur is None:
                break
            m = str(cur.getMnemonicString()).upper()
            ops = str(cur)
            if m == "MOV" and ops.upper().startswith("MOV ECX,"):
                t = ops.split(",")[1].strip()
                try:
                    val = int(t, 16) if t.startswith("0x") else int(t)
                except ValueError:
                    val = ("RAW", t)
                break
            if m == "XOR" and "ECX,ECX" in ops.upper().replace(" ", ""):
                val = 0
                break
            if m == "CALL":
                break
        if isinstance(val, int):
            found.setdefault(val, []).append((frm, fn, ent))
        else:
            unknown.append((frm, fn, ent, val))
    w(fh, "resolved distinct atoms: %d ; unresolved sites: %d" % (len(found), len(unknown)))
    w(fh, "")
    w(fh, "CONTROLS (must be present): 0x20b=%s 0x369=%s 0x36b=%s 0xc4=%s" % (
        0x20B in found, 0x369 in found, 0x36B in found, 0xC4 in found))
    w(fh, "TARGETS: 0xeb(duplicateItemId)=%s 0xec(duplicateItemIdList)=%s "
          "0xed(duplicateItemLoans)=%s 0x16f(itemLoans)=%s 0x16d(itemId)=%s" % (
        0xEB in found, 0xEC in found, 0xED in found, 0x16F in found, 0x16D in found))
    for t in (0xEB, 0xEC, 0xED, 0x16F, 0x16D):
        if t in found:
            for frm, fn, ent in found[t]:
                w(fh, "   atom %#x serialised at %#x in %s @ %#x" % (t, frm, fn, ent))
    w(fh, "")
    w(fh, "-- all resolved atoms, sorted:")
    w(fh, " ".join("%#x" % k for k in sorted(found)))
    w(fh, "")
    w(fh, "-- UNRESOLVED call sites (absence claims must exclude these):")
    for frm, fn, ent, val in unknown:
        w(fh, "   %#x in %s @ %#x  last=%r" % (frm, fn, ent, val))

    # ---------------- B: script handler ----------------
    dump(fh, 0x180039FB0, "GetCardDuplicate script handler")
    dump(fh, 0x180039E10, "CONTROL: GetCardCategory script handler")

    # ---------------- C: who owns the item container ----------------
    w(fh, "")
    w(fh, "=" * 70)
    w(fh, "C. writers/readers of the singleton pointer DAT_1802e6398")
    w(fh, "=" * 70)
    for frm, typ, fn, ent in xrefs_to(0x1802E6398):
        w(fh, "   %#x %s in %s @ %#x" % (frm, typ, fn, ent))

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