"""Extract the whole /settings flag switch, and name every case.

MECHANISM (recovered 2026-08-05, q_settings.py). The body really is just
`configs` (0xa2) -> array of {type(0x354), value(0x377)}, so ENDPOINT_MAP's key
ladder was right. What it missed is what happens AFTER an element closes:

    iVar2 = FUN_180180d00(type_string)      <- the atom hasher, applied to the
                                               STRING VALUE of `type`
    switch (iVar2) { case 0x98: ... }       <- ~40 arms, one per feature flag

So a flag is not a JSON key. It is a row: {"type":"coinEnabled","value":"1"},
and the client hashes "coinEnabled" itself. That is why the array being empty
leaves every flag at its C++ constructor default.

This prints every case with its atom name, its getter, and its destination field
so the response can be built without guessing.
"""
import re, os

# NOTE: queries are exec'd inside ghidra_env.py, so __file__ is the HARNESS, not
# this file. Relative-to-__file__ paths silently resolve one directory too high,
# and the resulting exception skips the harness's os._exit(0), which then hangs
# the JVM until the timeout kills it. Use an absolute path.
REPO = "/home/alex/Documents/OpenFUT/fifa17-recon"

ATOMS = {}
tsv = os.path.join(REPO, "docs", "fut_atoms.tsv")
for line in open(tsv):
    p = line.rstrip("\n").split("\t")
    if len(p) >= 3:
        try:
            ATOMS[int(p[1], 16)] = p[2]
        except ValueError:
            pass

GETTER = {
    "FUN_1800d7af0": "int/bool",
    "FUN_1800d7ad0": "short",
    "FUN_1800d7b10": "?",
}

src = dec(0x18013C6D0)
f = func(0x18013C6D0)
print("decompile %d chars / body %d bytes -- full text searched" %
      (len(src), f.getBody().getNumAddresses()))

# The switch arms live after the element loop closes. Walk line by line and keep
# the most recent case label so each arm's body can be attributed.
cur = None
arms = {}
for line in src.splitlines():
    m = re.search(r"\bcase (0x[0-9a-f]+):", line)
    if m:
        cur = int(m.group(1), 16)
        arms.setdefault(cur, [])
        continue
    m = re.search(r"if \(iVar2 == (0x[0-9a-f]+)\)", line)
    if m:
        cur = int(m.group(1), 16)
        arms.setdefault(cur, [])
        continue
    if cur is not None and line.strip():
        arms[cur].append(line.strip())

print("\n%-8s %-42s %-10s %s" % ("ATOM", "NAME", "GETTER", "DESTINATION"))
print("-" * 100)
unknown = []
for a in sorted(arms):
    body = " ".join(arms[a])[:200]
    g = next((GETTER.get(x, x) for x in GETTER if x in body), "")
    dst = ""
    m = re.search(r"param_2\[(0x[0-9a-f]+|\d+)\]", body)
    if m:
        dst = "param_2[%s]" % m.group(1)
    m2 = re.search(r"\*\(undefined1 \*\)\(param_2 \+ (0x[0-9a-f]+|\d+)\)", body)
    if m2:
        dst = "byte param_2+%s" % m2.group(1)
    if "(**(code **)" in body:
        dst += " (vcall)"
    name = ATOMS.get(a, "?? UNKNOWN ATOM")
    if a not in ATOMS:
        unknown.append(a)
    print("%-8s %-42s %-10s %s" % (hex(a), name, g, dst))

print("\n%d switch arms; %d unnamed" % (len(arms), len(unknown)))
