#!/usr/bin/env python3
"""Watch FIFA 17's FUT online-mode state machine live (READ-ONLY, no patching).

Polls FUT::CompetitionManager (singleton global 0x1802e6328) and prints when its
phase / ready-token / state-stack changes. Use to discover WHICH in-game context
starts the online-mode handshake (the store gate is downstream of it).

  phase    mgr+0x218   -1=idle, then 0->1->2->3
  ready    mgr+0x6d4   becomes 0x1fbd0 when "service ready"
  stackIdx mgr+0x214   -1=empty state stack

Run:  python3 tools/watch_online_mode.py     (Ctrl-C to stop)
Then navigate FIFA: FUT hub, Store, Online Seasons, FUT Champions, Draft, etc.
Needs FIFA running + read access to /proc/PID/mem (ptrace_scope=0).
"""
import glob, struct, time, sys

IMG_BASE = 0x180000000
SINGLETON_VA = 0x1802e6328
OFF_PHASE, OFF_READY, OFF_STKIDX = 0x218, 0x6d4, 0x214
DLL = "CardsDLL"


def find_pid():
    for d in glob.glob('/proc/[0-9]*'):
        try:
            if open(d + '/comm').read().strip() == 'FIFA17.exe':
                return int(d.split('/')[-1])
        except Exception:
            pass
    raise SystemExit("FIFA17.exe not running")


def cardsdll_base(pid):
    for line in open(f'/proc/{pid}/maps'):
        if DLL in line:
            return int(line.split('-')[0], 16)
    raise SystemExit("CardsDLL not mapped")


def rd(mem, va, n):
    try:
        mem.seek(va); return mem.read(n)
    except Exception:
        return None


def i32(b):
    return struct.unpack('<i', b)[0] if b and len(b) == 4 else None


def main():
    pid = find_pid()
    base = cardsdll_base(pid)
    gva = base + (SINGLETON_VA - IMG_BASE)
    print(f"FIFA pid={pid} CardsDLL base={base:#x} singleton@{gva:#x}")
    print("watching phase/ready/stackIdx — navigate FIFA now (Ctrl-C to stop)")
    mem = open(f'/proc/{pid}/mem', 'rb')
    last = None
    while True:
        p = rd(mem, gva, 8)
        mgr = struct.unpack('<Q', p)[0] if p else 0
        if mgr:
            phase = i32(rd(mem, mgr + OFF_PHASE, 4))
            ready = i32(rd(mem, mgr + OFF_READY, 4))
            stk = i32(rd(mem, mgr + OFF_STKIDX, 4))
            cur = (mgr, phase, ready, stk)
        else:
            cur = (0, None, None, None)
        if cur != last:
            rt = f"{ready:#x}" if ready is not None else "?"
            print(f"[{time.strftime('%H:%M:%S')}] mgr={mgr:#x} phase={phase} "
                  f"ready={rt}{'  <== READY!' if ready == 0x1fbd0 else ''} stackIdx={stk}")
            last = cur
        time.sleep(0.5)


if __name__ == "__main__":
    try:
        main()
    except KeyboardInterrupt:
        print("\nstopped")
