Files
OpenFUT/fifa17-recon/docker/fifa17-python/tools/watch_online_mode.py
T
root 70a64e3709 fifa17-python: commit working FUT backend deployment (client/server split)
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.
2026-08-10 23:54:04 +00:00

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")