"""DIMENSION 5 / unopenedPacks -- pass 1: dump the four deserializers end to end.

HYPOTHESIS: (a) FutCreateUser deser 0x18014cc60 has arms for starterPack(0x2e5) and
bonusPacks(0x5d) that call dedicated sub-deserializers; (b) userInfo 0x18013ec10 has an
arm for unopenedPacks(0x35e) that calls a sub-deser and then a singleton vtbl slot;
(c) pack element 0x18013af30 has an arm for packContentInfo(0x20c) calling a sub-deser
that holds unopened(0x35d).

CONTROL: 0x18013c6d0 (settings deser) is a deserializer of the SAME family with a
KNOWN answer -- exactly one key `configs`(0xa2). If the dump/parse pipeline is sound,
the settings dump must show 0xa2 and nothing else in its key ladder. Same syntactic
form family (ladder), so it controls the extraction, not just the decompile.

NO ABSENCE CLAIMS FROM THIS PASS: it only dumps. Full text goes to disk, lengths are
printed so truncation is visible.
"""
import traceback, os

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

try:
    os.makedirs(OUT, exist_ok=True)
    targets = {
        "createuser_18014cc60": 0x18014cc60,
        "userinfo_18013ec10":   0x18013ec10,
        "packelem_18013af30":   0x18013af30,
        "settings_18013c6d0":   0x18013c6d0,   # CONTROL
    }
    for name, a in targets.items():
        src = dec(a, 300)
        p = os.path.join(OUT, "dec_%s.c" % name)
        with open(p, "w") as f:
            f.write(src)
        f2 = func(a)
        print("== %s @ %#x  fn=%s  body=%#x-%#x  declen=%d" % (
            name, a, f2.getName() if f2 else "?",
            int(f2.getEntryPoint().getOffset()) if f2 else 0,
            int(f2.getBody().getMaxAddress().getOffset()) if f2 else 0,
            len(src)))
        print("   written %s" % p)

    # Raw instruction-level immediate enumeration for each target, so the ladder /
    # switch / sub-dec forms are all visible regardless of how Ghidra renders them.
    for name, a in targets.items():
        f2 = func(a)
        if f2 is None:
            print("!! no function at %#x" % a)
            continue
        lines = []
        it = listing.getInstructions(f2.getBody(), True)
        while it.hasNext():
            ins = it.next()
            lines.append("%#x  %s" % (int(ins.getAddress().getOffset()), str(ins)))
        p = os.path.join(OUT, "asm_%s.txt" % name)
        with open(p, "w") as fh:
            fh.write("\n".join(lines))
        print("== asm %s: %d instructions -> %s" % (name, len(lines), p))
except Exception:
    traceback.print_exc()
