Files
OpenFUT/fifa17-recon/tools/autopatch.py
T
funman300 b0bbc2a07f fifa17-recon: sweep auto-advance + the three-state oracle, live-proven
The oracle is three-valued, and all three fingerprints are now confirmed live
against a running client rather than read out of Ghidra:

  NAMED        our sentinel rating 7 survives and a real name appears. The id is
               real, and teamid/nation/leagueId come back FILLED by the game
               because we send them as zero.
  placeholder  rating 7 survives but the name is 'Jamal Blackman', team 0. The
               players-table row exists and is an empty slot. This is the trap:
               169193 does this and it was in VERIFIED_ASSET_IDS.
  MISS         rating 0x32, teamid 0x78d, nation 0xe, position 2, name ' '. That
               is the binary's miss-fill, byte for byte, and it is exactly the
               blank card photographed in a pack today.

Scale: 5000 candidates per response ingests cleanly; 20000 was served and then
silently not ingested (the map did not change at all), so the ceiling is between
them and auto chunks default to 5000.

Auto-advance: the client PAGES the club, so one visit yields several fetches.
'auto:lo-hi:step' hands out the next chunk per fetch. Item ids derive from the
candidate's offset in the WHOLE range, not its index in the chunk, so chunks never
collide and results accumulate across fetches for a single probe at the end.

sweep_collect.py accumulates into data/players.json and rejects the placeholder
name as a matter of course. Yield in 20000-24999 was 19 real ids per 5000, which
is why auto-advance matters: the real roster clusters in 150000-240000.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 20:44:02 -07:00

92 lines
3.0 KiB
Python

#!/usr/bin/env python3
"""Watch for a (re)launched FIFA17.exe and auto-apply both ProtoSSL cert patches
the moment its unpacked code is mapped. Idempotent; keeps watching across relaunches."""
import glob, time, struct
# Watch for a (re)launched FIFA17.exe and auto-apply ProtoSSL cert + FUT store patches
import glob, time, os
GATE2=0x1461361b0; GATE2_ORIG=bytes.fromhex("48895c"); GATE2_PATCH=bytes.fromhex("31c0c3")
GATE1=0x146132548; GATE1_ORIG=bytes.fromhex("0f8576010000"); GATE1_PATCH=bytes.fromhex("90"*6)
RET_TRUE = bytes.fromhex("b801000000c3")
NOP2 = bytes.fromhex("9090")
IMG_BASE = 0x180000000
STORE_PATCHES = {
0x1800f7fb0: RET_TRUE,
0x1800fb850: RET_TRUE,
0x180100500: RET_TRUE,
0x180013cf0: RET_TRUE,
0x180017543: bytes.fromhex("eb3f"),
0x180017487: NOP2,
0x180017490: NOP2,
0x1800175aa: NOP2,
}
LOG="/tmp/autopatch.log"
def log(m):
line=f"[{time.strftime('%H:%M:%S')}] {m}"
print(line,flush=True); open(LOG,"a").write(line+"\n")
def find_pids():
out=[]
for d in glob.glob('/proc/[0-9]*'):
try:
if open(d+'/comm').read().strip()=='FIFA17.exe': out.append(int(d.split('/')[-1]))
except: pass
return out
def cardsdll_base(pid):
try:
for line in open(f'/proc/{pid}/maps'):
if 'CardsDLL' in line: return int(line.split('-')[0], 16)
except: pass
return None
def rd(pid,va,n):
with open(f'/proc/{pid}/mem','rb') as f:
f.seek(va); return f.read(n)
def wr(pid,va,b):
with open(f'/proc/{pid}/mem','r+b') as f:
f.seek(va); f.write(b)
patched=set()
store_patched=set()
log("=== AUTOPATCH watching for FIFA17.exe ===")
while True:
for pid in find_pids():
if pid not in patched:
try:
g2=rd(pid,GATE2,3); g1=rd(pid,GATE1,6)
except Exception:
continue # code not mapped yet
if g2==GATE2_PATCH and g1==GATE1_PATCH:
log(f"pid {pid}: cert gates already patched"); patched.add(pid)
elif g2==GATE2_ORIG and g1==GATE1_ORIG:
try:
wr(pid,GATE2,GATE2_PATCH); wr(pid,GATE1,GATE1_PATCH)
log(f"pid {pid}: PATCHED cert gates")
patched.add(pid)
except Exception as e:
log(f"pid {pid}: cert patch write failed: {e}")
# Continuously enforce store patches every tick
cbase = cardsdll_base(pid)
if cbase is not None:
try:
for va, data in STORE_PATCHES.items():
live = cbase + (va - IMG_BASE)
if rd(pid, live, len(data)) != data:
wr(pid, live, data)
log(f"pid {pid}: ENFORCED store patch @ {live:#x}")
if pid not in store_patched:
log(f"pid {pid}: PATCHED store gates in CardsDLL @ {cbase:#x}")
store_patched.add(pid)
except Exception as e:
log(f"pid {pid}: store patch write failed: {e}")
time.sleep(1)