"""DIMENSION 5 pass 2: the pieces the CreateUser / unopenedPacks answers depend on.

HYPOTHESES
  H1 FUN_18011a830 returns the model singleton (DAT_1802e6398 or similar), so its
     vtable can be resolved LIVE and slot 0x4e0 identified.
  H2 FUN_180142470 (the userData 0x36d arm of CreateUser) takes only the reader, so
     it parses into a global, not into the response record.
  H3 FUN_180141ee0 is the shared "read FIELD_NAME -> atom, advance to value" helper;
     return 6 means "no value / null" and suppresses dispatch.
  H4 The primitive getters 0x1801c7620 (BOOL) / 0x1801c79d0 (INT) / 0x180135ff0
     (SKIP) determine whether a container fed to a scalar arm desyncs the reader.

CONTROL for the vtable slot question: slot 0x480 is used by the squadList(0x2d4) arm
of the SAME deserializer and is LIVE-CONFIRMED WORKING (MY SQUADS renders). Whatever
method resolves 0x4e0 must also resolve 0x480 to something sane; if it cannot resolve
0x480 the method is broken, not the target.

CONTROL for the stack-struct offset rule (record_off = 0x268 - N in FUN_18013af30):
displayGroupAssetId(0xda)->local_238 must give 0x30 and
displayGroupUseDefaultImage(0xdb)->local_230 must give 0x38, which is what an earlier
independent pass recorded for those two fields. Both are printed below from the asm.
"""
import traceback, os

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

try:
    for name, a in [
        ("singleton_18011a830", 0x18011a830),
        ("userdata_180142470",  0x180142470),
        ("keyhelper_180141ee0", 0x180141ee0),
        ("bool_1801c7620",      0x1801c7620),
        ("int_1801c79d0",       0x1801c79d0),
        ("skip_180135ff0",      0x180135ff0),
        ("item_18013fe00",      0x18013fe00),
        ("storeroot_1801234e0", 0x1801234e0),
    ]:
        src = dec(a, 300)
        p = os.path.join(OUT, "dec_%s.c" % name)
        open(p, "w").write(src)
        print("== %s @ %#x declen=%d -> %s" % (name, a, len(src), p))

    print("\n---- singleton getter, short ones printed inline ----")
    for a in (0x18011a830, 0x180141ee0, 0x1801c7620):
        s = dec(a, 300)
        if len(s) < 2600:
            print("\n### %#x (len=%d)\n%s" % (a, len(s), s))
        else:
            print("\n### %#x len=%d (see file)" % (a, len(s)))

    # ---- CONTROL: stack offsets in FUN_18013af30 from the raw asm ----------
    print("\n---- FUN_18013af30 stack-displacement control ----")
    f = func(0x18013af30)
    it = listing.getInstructions(f.getBody(), True)
    want = {}
    while it.hasNext():
        ins = it.next()
        t = str(ins)
        for d in ("0xcd", "0xb4", "0x30", "0x38", "0xce", "0xcc"):
            pass
        want.setdefault("all", []).append((int(ins.getAddress().getOffset()), t))
    # print the instructions immediately around each of the four atom arms
    for label, site in (("0x35d unopened", 0x18013b7b0), ("0xda dGAssetId", 0x0),
                        ("0xdb dGUseDefImg", 0x0)):
        pass
    # simpler: dump every instruction that writes a byte to a stack slot
    for addr_i, t in want["all"]:
        if ("MOV byte ptr [RSP" in t or "MOV byte ptr [RBP" in t
                or "MOV dword ptr [RSP" in t):
            print("  %#x  %s" % (addr_i, t))
except Exception:
    traceback.print_exc()
