"""D2 QUICK SELL, batch 2. Nail down the remaining unknowns from batch 1.

BATCH 1 ESTABLISHED (raw asm, 0x180141025..0x180141140 inside FUN_18013fe00):
    if ([RBP+0x198] == 0)                       // server discardValue, JA = unsigned
        price = SELECT [0x1802231e4] FROM "fcc_discardcoins"
                  WHERE "cardtype" == [RBP+0x1ac]
                    AND [0x180207848] == [RBP+0x1b4]
                    AND "rare"     == [RBP+0x1b8]
        v = (byte[RBP+0x214] * price) / 100, +1 if remainder >= 0x32
        [RBP+0x19c] = v

REMAINING QUESTIONS
  1. what are the strings at 0x1802231e4 and 0x180207848?
  2. EVERY instruction in FUN_18013fe00 that WRITES [RBP+0x1b4] (the second key,
     the decompiler shows no atom arm writing it, which would mean it is always
     the initialiser value; that must be checked on instructions, not on the
     decompiler's merged locals). Same for [RBP+0x198], [RBP+0x19c], [RBP+0x1ac],
     [RBP+0x214], [RBP+0x1b8]. CONTROL: [RBP+0x214] must show exactly one write
     from the atom-0x274 (rating) arm, which we already know exists.
  3. where do [RBP+0x198] and [RBP+0x19c] end up in the heap item object? print
     the tail copy-construct.
  4. Q2 ASSIGN-vs-ADD. resolve the three vtables that hold the discard triple
     (0x1802fb340.., 0x180270580.., 0x180220470..) and 0x1801f3100.., print every
     slot, then decompile the response-apply methods so the consumer of resp+0x28
     is read directly.
  5. the FUT manager singleton DAT_1802e6398: print vtable slots 0x9c0..0xa80 so
     the credit setter next to the item-remove slot 0xa30 / id-touch slot 0xa48 is
     visible, and decompile 0xa30 and any credit-looking neighbour.
  6. every function in the DLL that contains the immediate 0x326 (totalCredits)
     in any of the four dispatch forms, so all credit consumers are enumerated.
     CONTROL: the list must contain FUN_180127300 and FUN_1801279c0, both of which
     are already known to carry a `== 0x326` arm.

Nothing is truncated; len() printed for every decompile.
"""
import traceback, os

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/store/qs/"
os.makedirs(OUT, exist_ok=True)


def dump(tag, va, path=None):
    try:
        src = dec(va)
    except Exception as e:
        src = "// decompile threw %r" % (e,)
    print("=" * 78)
    print("%s  %#x  fname=%s  len(src)=%d  (FULL)" % (tag, va, fname(va), len(src)))
    print("=" * 78)
    print(src)
    if path:
        open(OUT + path, "w").write(src)
    return src


def insns(va):
    f = func(va)
    out = []
    if f is None:
        return out
    it = listing.getInstructions(f.getBody(), True)
    while it.hasNext():
        i = it.next()
        out.append((int(i.getAddress().getOffset()), str(i)))
    return out


try:
    print("##### 1: the two unknown literals #####")
    for a in (0x1802231e4, 0x180207848, 0x180223208, 0x18022315c):
        print("  %#x -> %r" % (a, rd_str(a, 64)))

    print("\n##### 2: every reference to the six stack slots in the ITEM deser #####")
    ins = insns(0x18013fe00)
    print("  instruction count in FUN_18013fe00 = %d" % len(ins))
    slots = ("0x198", "0x19c", "0x1ac", "0x1b0", "0x1b4", "0x1b8", "0x214", "0x1a0",
             "0x1a4", "0x1a8")
    for s in slots:
        pat = "RBP + " + s + "]"
        hits = [(a, t) for a, t in ins if pat in t]
        print("  --- [RBP + %s] : %d refs ---" % (s, len(hits)))
        for a, t in hits:
            print("      %#x  %s" % (a, t))

    print("\n##### 3: the tail of the ITEM deser (copy-construct into the heap item) #####")
    f = func(0x18013fe00)
    lo = int(f.getEntryPoint().getOffset())
    hi = int(f.getBody().getMaxAddress().getOffset())
    print("  function body %#x..%#x" % (lo, hi))
    for a, t in ins:
        if a >= 0x180141160:
            print("      %#x  %s" % (a, t))

    print("\n##### 4: the discard action vtables #####")
    for base in (0x1802fb300, 0x180270560, 0x180220450, 0x1801f3100):
        print("  --- vtable-ish dump at %#x ---" % base)
        for off, tgt, nm in vtable(base, 48):
            extra = ""
            if 0x180000000 <= tgt < 0x181000000:
                try:
                    extra = repr(rd_str(tgt, 40))
                except Exception:
                    extra = ""
            print("     +%#05x %#018x %-20s %s" % (off, tgt, nm, extra if not nm else ""))

    print("\n##### 4b: response-apply candidates #####")
    # everything referenced next to the deser in those tables
    seen = set()
    for base in (0x1802fb300, 0x180270560, 0x180220450, 0x1801f3100):
        for off, tgt, nm in vtable(base, 48):
            if nm and tgt not in seen and fm.getFunctionAt(addr(tgt)):
                seen.add(tgt)
    print("  %d distinct functions in those tables" % len(seen))
    for t in sorted(seen):
        try:
            src = dec(t)
        except Exception as e:
            src = "// threw %r" % e
        if "0x28" in src or "0x326" in src or "credit" in src.lower():
            print("=" * 78)
            print("CANDIDATE %#x %s len=%d" % (t, fname(t), len(src)))
            print("=" * 78)
            print(src)

    print("\n##### 5: FUT manager singleton vtable #####")
    try:
        mgr_vt_holder = 0x1802e6398
        print("  DAT_1802e6398 is a runtime pointer; using static xrefs instead")
    except Exception:
        pass
    # find the vtable by looking at who writes DAT_1802e6398
    for frm, typ, fn, ent in xrefs_to(0x1802e6398):
        print("   xref to DAT_1802e6398: %#x %s %s %#x" % (frm, typ, fn, ent))

    print("\n##### 6: every function containing the 0x326 immediate #####")
    ATOM = 0x326
    found = {}
    it = fm.getFunctions(True)
    n = 0
    while it.hasNext():
        fn = it.next()
        n += 1
        try:
            body = listing.getInstructions(fn.getBody(), True)
        except Exception:
            continue
        hit = []
        prev = None
        while body.hasNext():
            i = body.next()
            t = str(i)
            if "0x326" in t:
                hit.append((int(i.getAddress().getOffset()), t))
        if hit:
            found[int(fn.getEntryPoint().getOffset())] = (fn.getName(), hit)
    print("  scanned %d functions; %d contain 0x326" % (n, len(found)))
    for e in sorted(found):
        nm, hit = found[e]
        print("   %#x %s" % (e, nm))
        for a, t in hit:
            print("        %#x %s" % (a, t))

except Exception:
    traceback.print_exc()
