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

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)