# -*- coding: utf-8 -*-
"""
q_pack_inv_8 -- DIMENSION 1 pass 8: the model vtable slot 0x4e0, and the STORE
request builder that would have to return a My Packs group.

HYPOTHESES
  (a) Pass 7's vtable scan was too strict (it demanded every qword be inside .text,
      so any vtable containing a NULL or a non-.text thunk was rejected; it found
      only 2 candidates, neither plausible). Relaxing to "pointer into .text OR
      zero" should surface the FUT model vtable, whose slot +0x4e0 is the
      unopenedPacks-total setter reached from FUN_18013ec10 @0x18013f223.
  (b) FUN_180123430 appends "/purchasegroup" + "/all" + "?ppInfo=true"; its caller
      is the STORE request builder and shows the exact URL and HTTP verb.

CONTROLS
  * The relaxed vtable scan is controlled by requiring that the reported vtable's
    slot +0x160 and +0x940 also resolve to real functions (both are used on the
    same singleton by FUN_18013bd40 and FUN_18017fc20 respectively). A table that
    satisfies all three slots is the right object.
  * class_deser("FutSquadSaveServerResponse") must still be 0x180171a60.
"""
import struct
import traceback

OUT = "/tmp/claude-1000/-home-alex-Documents-OpenFUT/8e521ca1-ca3e-4138-bb96-df1744dd1d30/scratchpad/packres"
LO, HI = 0x180001000, 0x1801E4F62

try:
    got = sorted(set(x[0] for x in class_deser("FutSquadSaveServerResponse")))
    print("CONTROL FutSquadSaveServerResponse -> %s %s"
          % ([hex(g) for g in got], "PASS" if 0x180171A60 in got else "FAIL"))

    blk = [b for b in mem.getBlocks() if b.getName() == ".rdata"][0]
    start = int(blk.getStart().getOffset())
    end = int(blk.getEnd().getOffset())
    data = read_bytes(start, end - start + 1)
    n = len(data) // 8
    qs = struct.unpack_from("<%dQ" % n, data, 0)

    def okslot(v):
        return v == 0 or (LO <= v <= HI)

    cands = []
    i = 0
    while i < n:
        if LO <= qs[i] <= HI:
            j = i
            while j < n and okslot(qs[j]):
                j += 1
            if (j - i) * 8 >= 0x950:
                cands.append((start + i * 8, (j - i) * 8))
            i = j
        else:
            i += 1
    print("relaxed vtable candidates >= 0x950 bytes: %d" % len(cands))
    for va, sz in cands:
        slots = {}
        good = True
        for off in (0x8, 0x160, 0x1F8, 0x480, 0x4E0, 0x940):
            if off >= sz:
                good = False
                break
            t = qword(va + off)
            f = fm.getFunctionAt(addr(t))
            slots[off] = (t, f.getName() if f else None)
            if f is None:
                good = False
        print("\n  vtable %#x size %#x  allslots=%s" % (va, sz, good))
        for off in sorted(slots):
            print("      +%#05x -> %#x %s" % (off, slots[off][0], slots[off][1]))
        if good:
            for off in (0x4E0, 0x940, 0x160):
                t = slots[off][0]
                s = dec(t)
                print("\n   ==== slot +%#x %#x len=%d\n%s" % (off, t, len(s), s))
                open(OUT + "/d1_vtslot_%x_%x.txt" % (off, t), "w").write(s)

    print("\n########## STORE REQUEST BUILDER ##########")
    for (fr, ty, fn, en) in xrefs_to(0x180123430):
        print("  ref to FUN_180123430: %#x %s @%#x" % (fr, fn, en))
        if en:
            s = dec(en)
            print("  --- caller %#x len=%d\n%s" % (en, len(s), s))
            open(OUT + "/d1_storebuilder_%x.txt" % en, "w").write(s)

    print("\n########## starting_pack_opened ##########")
    for va, tag in ((0x180083990, "startingpack_990"), (0x180083B70, "startingpack_b70")):
        s = dec(va)
        print("\n--- %s %#x len=%d" % (tag, va, len(s)))
        print(s)
        open(OUT + "/d1_%s.txt" % tag, "w").write(s)

    print("\nDONE q_pack_inv_8")
except Exception:
    traceback.print_exc()
