"""D5 Q1/Q2/Q3/Q5: the store ServerCall classes, the currency element deser,
the transaction state enum table, and the client-side purchase validators.

HYPOTHESES
 H1 (fork): PurchasePack and PurchaseItems are two separate ServerCall classes,
    both POSTing to a "/transaction"-suffixed URL; the fork is decided client-side
    at construction time and the server has no say in it.
 H2 (price): the pack currency record is
    {std::string name; u32 funds; u32 finalFunds; u32 origExtPriceId; u32 finalExtPriceId}
    and FUN_180138bd0 is the element parser that fills name/funds/finalFunds.
 H3 (state enum): the 9-entry table at 0x1802d02c0 (u32 value, char* name) is the
    complete `state` vocabulary of FutPurchaseItemsServerResponse.

CONTROL: dec(0x180171a60) must be the FutSquadSave deserializer (a big SAX loop
calling FUN_1801c7f10); dec(0x180120380) the FutCreateMatch one. Both printed.
"""
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)

    P("=== CONTROL: sizes of two known deserializers ===")
    for c in (0x180171a60, 0x180120380):
        s = dec(c)
        P("  %#x %-24s len=%d  has_sax_loop=%s" % (c, fname(c), len(s), "FUN_1801c7f10" in s))

    # ---- H3: transaction state enum table ----
    P("")
    P("=== state enum table @0x1802d02c0 (u32 value, char* name) x12 ===")
    for i in range(12):
        base = 0x1802d02c0 + i * 16
        try:
            val = dword(base)
            ptr = qword(base + 8)
            nm = rd_str(ptr) if 0x180000000 <= ptr < 0x181000000 else "?"
        except Exception as e:
            P("  [%d] ERR %s" % (i, e)); continue
        P("  [%2d] %#010x  ptr=%#x  %r" % (i, val, ptr, nm))

    # ---- the atom name table, to prove the request key names live there ----
    P("")
    P("=== atom-name pointer table around 0x1802d42a8 (useCredits) ===")
    for off in range(-6, 7):
        a = 0x1802d42a8 + off * 8
        try:
            ptr = qword(a)
            nm = rd_str(ptr) if 0x180000000 <= ptr < 0x181000000 else "?"
        except Exception as e:
            nm = "ERR %s" % e; ptr = 0
        P("  %#x -> %#x %r" % (a, ptr, nm))

    # ---- who references FUN_180126720 (the /transaction URL builder) ----
    P("")
    for tgt, tag in ((0x180126720, "url_builder_/transaction"),
                     (0x1801269f0, "purchaseitems_deser"),
                     (0x180162880, "createpack_deser"),
                     (0x1801267b0, "http409_handler"),
                     (0x1801669b0, "state_str_to_enum"),
                     (0x180138bd0, "currency_elem_deser"),
                     (0x1801234e0, "packtypes_deser")):
        try:
            xs = xrefs_to(tgt)
        except Exception as e:
            P("XREFS %s %#x ERR %s" % (tag, tgt, e)); continue
        P("XREFS to %s %#x : %d" % (tag, tgt, len(xs)))
        for frm, typ, fn, ent in xs:
            P("    from %#x %-12s in %s @ %#x" % (frm, typ, fn, ent))

    # ---- vtables around the store server-call classes ----
    P("")
    P("=== scan .rdata for qword == 0x180126720 / 0x1801269f0 / 0x180162880 (vtable slots) ===")
    import struct
    for tgt in (0x180126720, 0x1801269f0, 0x180162880, 0x1801267b0, 0x1801234e0, 0x1801758c0):
        pat = struct.pack("<Q", tgt)
        for hit in find_all(pat, (".rdata", ".data")):
            P("  %#x in vtable? -> target %#x" % (hit, tgt))
            for j in range(-4, 10):
                a = hit + j * 8
                try:
                    q = qword(a)
                except Exception:
                    continue
                f = fm.getFunctionAt(addr(q)) if 0x180000000 <= q < 0x181000000 else None
                s = ""
                if 0x180000000 <= q < 0x181000000 and f is None:
                    try:
                        t = rd_str(q, 60)
                        if t and all(32 <= ord(c) < 127 for c in t):
                            s = repr(t)
                    except Exception:
                        pass
                P("     %+3d %#x -> %#x %s %s" % (j, a, q, f.getName() if f else "", s))

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

    # ---- decompiles ----
    TARGETS = {
        "currency_elem_deser_180138bd0": 0x180138bd0,
        "script_PurchasePack_18003f010": 0x18003f010,
        "script_EnterStore_18003efd0": 0x18003efd0,
        "script_ExitStore_18003eff0": 0x18003eff0,
        "script_ValidateCoinPurchase_18003f060": 0x18003f060,
        "script_ValidatePointsPurchase_18003f090": 0x18003f090,
        "http409_1801267b0": 0x1801267b0,
        "urlbuild_180126720": 0x180126720,
        "state_enum_1801669b0": 0x1801669b0,
    }
    for tag, va in sorted(TARGETS.items()):
        try:
            f = func(va); src = dec(va)
        except Exception as e:
            f = None; src = "// ERR %s" % e
        w("d5_q2_dec_%s.txt" % tag,
          "// %s va=%#x entry=%s len=%d\n" % (tag, va, fname(va), len(src)) + src)

    # ---- dump every function in the store-service .text cluster ----
    P("")
    P("=== functions in 0x180126400..0x180127400 ===")
    cluster = []
    it = fm.getFunctions(addr(0x180126400), True)
    while it.hasNext():
        f = it.next()
        e = int(f.getEntryPoint().getOffset())
        if e > 0x180127400:
            break
        cluster.append(e)
    P("  %d funcs: %s" % (len(cluster), ", ".join("%#x" % c for c in cluster)))
    txt = []
    for e in cluster:
        s = dec(e)
        txt.append("// ===== %#x %s len=%d\n%s" % (e, fname(e), len(s), s))
    w("d5_q2_cluster_126400.txt", "\n".join(txt))

    # createpack cluster
    cluster2 = []
    it = fm.getFunctions(addr(0x180162400), True)
    while it.hasNext():
        f = it.next()
        e = int(f.getEntryPoint().getOffset())
        if e > 0x180162e00:
            break
        cluster2.append(e)
    txt = []
    for e in cluster2:
        s = dec(e)
        txt.append("// ===== %#x %s len=%d\n%s" % (e, fname(e), len(s), s))
    w("d5_q2_cluster_162400.txt", "\n".join(txt))

    w("d5_q2_notes.txt", "\n".join(buf) + "\n")
    print("DONE")
except Exception:
    traceback.print_exc()
