"""D2 QUICK SELL, batch 7: Q2 (assign vs add) and Q3 (bulk discard shape).

Q2 CONTEXT. FutDiscardCardServerResponse is a 0x38-byte object freshly allocated
per request by FUN_180127160 ("RS4:FutDiscardCardServerResponse", size 0x38,
vtable 0x180220488). Its deserialiser stores totalCredits with a plain
MOV dword [obj+0x28] and the item id with MOV qword [obj+0x30]; there is no
read-modify-write anywhere in the deser, and only two functions in the whole DLL
carry the 0x326 immediate. What remains is: who READS obj+0x28, and does that
consumer assign or accumulate into the wallet? Route to it: FUN_180127290 (vtable
slot +0xa0) dispatches to a delegate stored at servercall+0x50 / +0x60.

Q3 CONTEXT. FUN_180126f40 emits {"itemId":[<int64>, ...]} using atom 0x16d. Find
which of the three discard actions owns it (DiscardCard 0x1802cb230 factory
0x180123cd0, DiscardCardByRes 0x1802cb260 factory 0x180123ce0, DiscardACard
0x1802cb290 factory 0x180123cc0) and what url/method that action uses.

CONTROL for Q3: factory 0x180123cd0 must produce an object whose vtable slot
+0x08 is the request-body/url set that includes 0x180127570 (the "/%llu" single-id
url builder we have already seen on the wire as DELETE /ut/game/fifa17/item/<id>).
"""
import traceback, os, struct

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


def dump(tag, va, path=None):
    try:
        src = dec(va)
    except Exception as e:
        src = "// 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


try:
    print("##### Q3: the three discard action factories #####")
    for nm, a in (("DiscardACard", 0x180123cc0), ("DiscardCard", 0x180123cd0),
                  ("DiscardCardByRes", 0x180123ce0)):
        dump("factory " + nm, a, "qs_fac_%x.txt" % a)

    print("\n##### Q3: the pointer table around 0x1801f3118 #####")
    for off in range(-0x40, 0x100, 8):
        a = 0x1801f3118 + off
        try:
            v = qword(a)
        except Exception:
            continue
        f = fm.getFunctionAt(addr(v)) if 0x180000000 <= v < 0x181000000 else None
        print("   %#x -> %#x %s" % (a, v, f.getName() if f else ""))

    print("\n##### Q3: url/method providers #####")
    for a in (0x180126f00, 0x1801277c0, 0x180127800, 0x180068320, 0x180127890,
              0x180122420, 0x18011f940):
        dump("provider", a, "qs_prov_%x.txt" % a)

    print("\n##### Q3: the url-suffix table entry 0x0d / 0x0e / 0x0f #####")
    # the action rows point at a url index; print the table of url format strings
    for i in range(0x28):
        try:
            p = qword(0x1801f2f00 + i * 8)
            print("   idx %#04x -> %#x %r" % (i, p, rd_str(p, 60) if p else ""))
        except Exception as e:
            print("   idx %#04x ERR %r" % (i, e))

    print("\n##### Q2: every RS4 class with 'Credit' or 'User' in the name #####")
    for h in find_all(b"RS4:Fut"):
        s = rd_str(h, 90)
        if "Credit" in s or "UserData" in s or "UserInfo" in s:
            print("   %#x %r" % (h, s))
            for frm, typ, fn, ent in xrefs_to(h - 4):
                print("       xref %#x %s %s %#x" % (frm, typ, fn, ent))

    print("\n##### Q2: functions containing the credits atom 0xc0 as a compare #####")
    it = fm.getFunctions(True)
    n = 0
    found = []
    while it.hasNext():
        f = it.next()
        n += 1
        ii = listing.getInstructions(f.getBody(), True)
        got = []
        while ii.hasNext():
            i = ii.next()
            t = str(i)
            if ("CMP" in t or "SUB" in t) and (",0xc0" in t):
                got.append((int(i.getAddress().getOffset()), t))
        if got:
            found.append((int(f.getEntryPoint().getOffset()), f.getName(), got))
    print("  scanned %d functions, %d contain a CMP/SUB with 0xc0" % (n, len(found)))
    for e, nm, got in found:
        print("   %#x %s" % (e, nm))
        for a, t in got:
            print("       %#x %s" % (a, t))

except Exception:
    traceback.print_exc()
