70a64e3709
Freeze the running offline FUT backend into version control as fifa17-recon/docker/fifa17-python/ - declarative and rebuildable from a fresh checkout: * OPENFUT_BIND / OPENFUT_ADVERTISE client/server split in the responders (lsx, blaze, roster, utas, pow) + entrypoint.sh; OPENFUT_ADVERTISE is required for remote mode (compose and entrypoint fail without it) * docker-compose.yml reproducing the frozen baseline container exactly (env, ports incl. the 8085->8080 POW-content remap, /state bind, restart) * .env.example / .env for site config - the LAN IP is never hardcoded in source * tools/ + data/ staged from openfut-fut-backend:python-baseline-2026-08-10, verified byte-identical to the running container at freeze time * client_arm.sh (the 105 client-side arming counterpart) * Dockerfile bakes /app/SHA256SUMS.txt so any image is self-identifying * docs/BASELINE-python-2026-08-10.md: frozen image/container/hash record, restore instructions and rebuild-equivalence procedure Secrets (redir key/cert, .env) and runtime state (docker/state) stay gitignored. The live container is untouched pending the .105 launcher audit.
83 lines
2.5 KiB
Python
83 lines
2.5 KiB
Python
#!/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")
|