"""D5 Q1/Q4 recon: the store/transaction purchase fork.

HYPOTHESIS: there are TWO distinct client server-calls that both POST to
"/transaction" -- PurchasePack (expects FutCreatePackServerResponse) and
PurchaseItems (expects FutPurchaseItemsServerResponse) -- and the fork is decided
CLIENT-SIDE before the request is sent, by which ServerCall object is constructed,
not by anything the server does.

CONTROL: class_deser("FutSquadSaveServerResponse") must resolve to 0x180171a60 and
class_deser("FutCreateMatchServerResponse") to 0x180120380. If the controls come back
empty the whole run is untrustworthy.

Outputs everything to /tmp/.../packres/d5_q1_*.txt with len() printed for every
decompile, so no absence is ever concluded from a truncation.
"""
import traceback, os

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

def w(name, text):
    p = os.path.join(OUT, name)
    with open(p, "w") as f:
        f.write(text)
    print("WROTE %s (%d bytes)" % (p, len(text)))

try:
    buf = []
    def P(*a):
        s = " ".join(str(x) for x in a)
        print(s)
        buf.append(s)

    # ---------- CONTROLS ----------
    P("=== CONTROLS ===")
    for c, expect in (("FutSquadSaveServerResponse", 0x180171a60),
                      ("FutCreateMatchServerResponse", 0x180120380),
                      ("FutSquadListServerResponse", 0x180172140)):
        try:
            r = class_deser(c)
        except Exception as e:
            r = "ERR %s" % e
        P("  %-34s -> %s   (expect %#x)" % (c, r, expect))

    # ---------- string xrefs ----------
    STRS = {
        0x1802203e8: "/transaction",
        0x18022fe80: "useCredits",
        0x18022fea0: "usePreOrder",
        0x18022fb78: "transaction",
        0x18022fb88: "transactionId",
        0x1802203c8: "User already has a transaction",
        0x180220400: "PURCHASEERROR",
        0x18021f2d8: "PURCHASEPACK",
        0x18021f2e8: "PurchaseItems",
        0x18021f2f8: "PURCHASEITEMS",
        0x1801f4e48: "PurchasePack",
        0x1801f4e78: "ValidateCoinPurchase",
        0x1801f4e90: "ValidatePointsPurchase",
        0x1801ff7c8: "PURCHASE_INSUFFICIENT_FUNDS",
        0x1802293a8: "NOTRANSACTION",
        0x1802293b8: "TRANSACTIONCREATED",
        0x1802293d0: "PURCHASESTARTED",
        0x1802293e0: "PURCHASECOMPLETE",
        0x180229420: "TRANSACTIONCOMPLETE",
        0x180229438: "TRANSACTIONCANCEL",
        0x180231010: "extPrice",
        0x180230b48: "currencies",
        0x1802310f0: "finalPrice",
        0x180231c78: "originalPrice",
        0x1802310e0: "finalFunds",
        0x180231110: "firstPartyStoreId",
        0x1802321a0: "purchaseLimit",
        0x180232160: "purchaseCount",
        0x1801ec120: "OnTransactionFailure",
        0x1801f0668: "OnServerTransactionIdResponse",
        0x18022ed10: "FUT_STORE_POINTS_",
        0x1801ff788: "PURCHASE_METHOD",
    }
    P("")
    P("=== STRING XREFS ===")
    funcs_of_interest = {}
    for va, nm in sorted(STRS.items()):
        try:
            xs = xrefs_to(va)
        except Exception as e:
            P("  %-32s %#x  ERR %s" % (nm, va, e)); continue
        P("  %-32s %#x  %d xrefs" % (nm, va, len(xs)))
        for frm, typ, fn, ent in xs:
            P("      from %#x  %-14s  in %s @ %#x" % (frm, typ, fn, ent))
            if ent:
                funcs_of_interest.setdefault(ent, set()).add(nm)

    P("")
    P("=== FUNCS OF INTEREST (%d) ===" % len(funcs_of_interest))
    for ent, names in sorted(funcs_of_interest.items()):
        P("  %#x  %-40s  <- %s" % (ent, fname(ent), ",".join(sorted(names))))

    w("d5_q1_xrefs.txt", "\n".join(buf) + "\n")

    # ---------- decompiles ----------
    TARGETS = {
        "purchaseitems_deser": 0x180126a04,
        "createpack_deser": 0x180162880,
        "packtypes_deser": 0x1801234e0,
        "pack_elem_deser": 0x18013af30,
        "extprice_finalprice": 0x180139070,
        "extprice_originalprice": 0x18013aae0,
        "currencies_deser": 0x180122c50,
        "packquantities_deser": 0x1801758c0,
        "updatecredits_deser": 0x1801738b2,
    }
    for tag, va in sorted(TARGETS.items()):
        try:
            f = func(va)
            src = dec(va)
        except Exception as e:
            src = "// ERR %s" % e
            f = None
        hdr = "// target %s va=%#x entry=%s name=%s len=%d\n" % (
            tag, va, ("%#x" % int(f.getEntryPoint().getOffset())) if f else "None",
            f.getName() if f else "None", len(src))
        w("d5_q1_dec_%s.txt" % tag, hdr + src)

    # decompile every func-of-interest, full text
    for ent, names in sorted(funcs_of_interest.items()):
        try:
            src = dec(ent)
        except Exception as e:
            src = "// ERR %s" % e
        hdr = "// entry %#x name=%s strings=%s len=%d\n" % (
            ent, fname(ent), ",".join(sorted(names)), len(src))
        w("d5_q1_fn_%x.txt" % ent, hdr + src)

    print("DONE")
except Exception:
    traceback.print_exc()
