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.
80 lines
3.1 KiB
Python
80 lines
3.1 KiB
Python
#!/usr/bin/env python3
|
|
"""Did clubPlayers actually land in the client, or was it eaten as the envelope key?
|
|
|
|
Read-only. The argument cannot settle this; the client's own memory can.
|
|
|
|
Chain, from the comment at utas_server.py:1076 (established earlier by two independent
|
|
agents and two reviewers, so this probe TESTS that chain rather than assuming it):
|
|
R = <model> + 0x1fd70 (FUN_18011a810 is `lea rax,[rcx+0x1fd70]; ret`)
|
|
clubPlayers -> R + 0x3c
|
|
auctionCount -> R + 0x38
|
|
The server logged "HUB: clubPlayers=205 auctionCount=0" for this session.
|
|
|
|
PREDICTIONS, stated before reading so this cannot be rationalised after the fact:
|
|
* if R+0x3c reads 205, clubPlayers reached its arm. The flat two-key hub body is
|
|
fine and the envelope worry does not apply to this root.
|
|
* if R+0x3c reads 0 while R+0x38 reads 0 too, the result is ambiguous, because
|
|
auctionCount is legitimately 0 this session. Say so rather than claiming a result.
|
|
* if R+0x3c reads 0 and some other plausible field is populated, clubPlayers was
|
|
eaten as the first key/value pair and the MY CLUB tile is showing a wrong number.
|
|
"""
|
|
import os
|
|
import struct
|
|
|
|
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
|
|
if not pid:
|
|
raise SystemExit("FIFA17.exe not running")
|
|
|
|
base = None
|
|
for ln in open('/proc/%d/maps' % pid):
|
|
if 'CardsDLL' in ln:
|
|
base = int(ln.split('-')[0], 16)
|
|
if not base:
|
|
raise SystemExit("CardsDLL not mapped: the client has not reached Ultimate Team")
|
|
slide = base - 0x180000000
|
|
print("pid %d cardsdll %#x slide %#x" % (pid, base, slide))
|
|
|
|
fd = os.open('/proc/%d/mem' % pid, os.O_RDONLY)
|
|
|
|
|
|
def rd(va, n):
|
|
return os.pread(fd, n, va)
|
|
|
|
|
|
# Prove the slide before trusting any address derived from it.
|
|
pe = open('/mnt/games/FIFA 17/CardsDLL_Win64_retail.dll', 'rb').read()
|
|
off = 0x180180D00 - 0x180000000 - 0x1000 + 0x400
|
|
ok = pe[off:off + 32] == rd(0x180180D00 + slide, 32)
|
|
print("slide control (FNV prologue): %s" % ("MATCH" if ok else "MISMATCH -- STOP"))
|
|
if not ok:
|
|
raise SystemExit(1)
|
|
|
|
model = struct.unpack('<Q', rd(0x1802E6398 + slide, 8))[0]
|
|
print("model singleton %#x" % model)
|
|
R = model + 0x1FD70
|
|
club, auction = struct.unpack('<i', rd(R + 0x3C, 4))[0], struct.unpack('<i', rd(R + 0x38, 4))[0]
|
|
print("\n R = model+0x1fd70 = %#x" % R)
|
|
print(" R+0x3c clubPlayers = %d (server sent 205)" % club)
|
|
print(" R+0x38 auctionCount = %d (server sent 0)" % auction)
|
|
|
|
print("\nVERDICT:")
|
|
if club == 205:
|
|
print(" clubPlayers REACHED its arm. The flat hub body parses correctly and the")
|
|
print(" envelope concern does not apply to FutGetHubData.")
|
|
elif club == 0:
|
|
print(" clubPlayers is 0. Either it was eaten as the first key/value pair, or the")
|
|
print(" hub has not been loaded this session. Check the tile in game before")
|
|
print(" concluding: auctionCount is legitimately 0, so it cannot break the tie.")
|
|
else:
|
|
print(" clubPlayers = %d, which is neither 205 nor 0. The chain above is wrong"
|
|
" somewhere." % club)
|
|
os.close(fd)
|