Files
OpenFUT/fifa17-recon/docker/fifa17-python/tools/gate_byte_probe.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

67 lines
3.0 KiB
Python
Executable File

#!/usr/bin/env python3
"""Read the FutDataManagerImpl UI gate bytes out of the LIVE FIFA 17 client.
Why this exists: on 2026-08-05 the /settings gate plan concluded that
IS_FRIENDLY_SEASON_ENABLED and IS_DRAFT_MODE_ENABLED had never been set true by
anything. Measured against the running client, both are 1, and have been all along.
The applier FUN_18011dc50 runs whether or not the configs array has content, and the
settings struct it is handed defaults these fields to 1. "Nothing populates the array"
is not "nothing writes the byte".
Read-only. Opens /proc/<pid>/mem O_RDONLY and preads. Nothing here can write.
Nothing is assumed:
* the pid is resolved by exact /proc/*/comm match, never hardcoded
* the CardsDLL base is read from /proc/<pid>/maps, never cached across launches
(Wine copies the sections into anonymous memory, so only the 4 KiB PE header is
file-backed and `grep CardsDLL maps` returns exactly ONE line, which is easy to
misread as "barely mapped")
* the slide is PROVEN against the FNV atom-hash prologue at 0x180180d00, read from
the on-disk PE, before any other address is trusted
* each gate byte displacement is DECODED from its accessor stub (0f b6 81 <disp32>,
movzx eax, byte [rcx+disp32]) rather than taken from a table
Requires the client to have reached Ultimate Team, since CardsDLL loads only then.
Usage: python3 gate_byte_probe.py
"""
import os, struct, sys
pid=None
for d in os.listdir('/proc'):
if d.isdigit():
try:
if open('/proc/%s/comm'%d).read().strip()=='FIFA17.exe': pid=int(d); break
except Exception: pass
assert pid, "not running"
print("pid", pid)
base=None
for ln in open('/proc/%d/maps'%pid):
if 'CardsDLL' in ln:
base=int(ln.split('-')[0],16); print("cardsdll map line:", ln.strip())
assert base
slide = base - 0x180000000
print("base %#x slide %#x" % (base, slide))
fd=os.open('/proc/%d/mem'%pid, os.O_RDONLY)
def rd(va,n): return os.pread(fd, n, va)
# control: FNV prologue, bytes taken from the on-disk PE
pe=open('/mnt/games/FIFA 17/CardsDLL_Win64_retail.dll','rb').read()
# .text rva 0x1000 rawptr 0x400
def f(va): return va-0x180000000-0x1000+0x400
ctl_disk=pe[f(0x180180d00):f(0x180180d00)+32]
ctl_live=rd(0x180180d00+slide,32)
print("CONTROL FNV", "MATCH" if ctl_disk==ctl_live else "MISMATCH", ctl_live.hex())
# model singleton
dat=0x1802e6398+slide
obj=struct.unpack('<Q', rd(dat,8))[0]
print("DAT_1802e6398 ->", hex(obj))
vt=struct.unpack('<Q', rd(obj,8))[0]
print("vtable live %#x static %#x" % (vt, vt-slide))
for off,name in [(0x2b0,'friendlySeasons'),(0x2c8,'draftMode'),(0x2e0,'packOpeningAnimation')]:
slot=struct.unpack('<Q', rd(vt+off,8))[0]
stub=rd(slot,8)
disp=struct.unpack('<I', stub[3:7])[0] if stub[:3]==b'\x0f\xb6\x81' else None
val=rd(obj+disp,1)[0] if disp is not None else None
print(" slot +%#x -> %#x stub=%s disp=%s value=%s" % (off, slot-slide, stub.hex(), hex(disp) if disp else None, val))
# unopenedPacks total
print("model+0x20950 =", struct.unpack('<I', rd(obj+0x20950,4))[0])
os.close(fd)