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.
This commit is contained in:
@@ -0,0 +1,82 @@
|
||||
#!/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")
|
||||
Reference in New Issue
Block a user