diff --git a/fifa17-recon/tools/utas_server.py b/fifa17-recon/tools/utas_server.py index 0699469..358115b 100755 --- a/fifa17-recon/tools/utas_server.py +++ b/fifa17-recon/tools/utas_server.py @@ -1250,12 +1250,81 @@ def club_stats_route(h): CLUB_PAGE = os.environ.get("FUT_CLUB_PAGE") == "1" +# ---- FUT_ID_SWEEP: use the game itself as the player-database oracle -------- +# +# Card identity is never taken from the wire. Every item we serve is registered +# into the client's CardsDb map, and just before that the client merges in its OWN +# local `players` table keyed on `resourceId & 0xffffff`. So serving a RANGE of +# candidate playerids and then reading the map back (tools/card_identity_probe.py) +# classifies the whole range in one pass. That is the entire database-extraction +# problem solved from the outside, without unpacking anything: dbdata.dll turned +# out to be an anti-tamper decoy, and the real table only exists inside the running +# game. +# +# Three outcomes per id, all distinguishable in the record (proven live 2026-08-04): +# HIT real name at +0xb8/+0xc8; teamid/nation/leagueId filled from the +# DB because we send them as ZERO (the merge only fills zeros). +# NAMELESS ROW our sentinel rating survives but the name is the DB's default +# row, "Jamal Blackman". The row exists and is empty. +# TRUE MISS the merge's miss fill: rating 0x32, teamid 0x78d, nation 0xe. +# SENTINEL_RATING is deliberately NOT 50, so a miss can never be mistaken for it. +# +# The sweep is SYNTHETIC: nothing is written to the save, so clearing the flag +# restores the real club exactly. Format: FUT_ID_SWEEP="-". +_ID_SWEEP = os.environ.get("FUT_ID_SWEEP", "") +SWEEP_FILE = os.environ.get("FUT_ID_SWEEP_FILE", "/tmp/fut_id_sweep") +SWEEP_ID_BASE = 900000000 # item ids, distinct from the save's 100000000+ +SWEEP_SENTINEL_RATING = 7 + + +def sweep_window(): + """The active sweep window: the control FILE if it has content, else the env. + + Read per REQUEST, on purpose. A full database sweep is many windows, and + restarting this server to re-aim it is exactly the action that once produced + "error connecting to FIFA 17 Ultimate Team" mid-session -- a plain connection + refusal, misread for weeks as a protocol bug. Writing a range into the file + re-aims the sweep with the client still live; emptying the file restores the + real club on the very next fetch. + """ + try: + with open(SWEEP_FILE) as f: + s = f.read().strip() + if s: + return s + except (IOError, OSError): + pass + return _ID_SWEEP + + +def sweep_items(): + """Synthetic club contents for one id-sweep window.""" + win = sweep_window() + try: + lo, hi = (int(x, 0) for x in win.split("-", 1)) + except Exception: + log(" SWEEP: bad window %r, want '-'" % win) + return [] + if hi < lo: + lo, hi = hi, lo + out = [] + for i, pid in enumerate(range(lo, hi + 1)): + it = _item(SWEEP_ID_BASE + i, pid, SWEEP_SENTINEL_RATING, + "ST", 0, 0, 0, [1, 1, 1, 1, 1, 1]) + out.append(it) + log(" SWEEP: serving %d candidate playerid(s) %d..%d as the club " + "[synthetic, nothing saved]" % (len(out), lo, hi)) + return out + + def club_route(h): # PUT only -- ENDPOINT_MAP row 3 gives ChangeClubName as PUT. Every other # method keeps the exact body this route served before, so the rename support # cannot change the behaviour of anything that already worked. if h.command == "PUT": return club_rename_route(h) + if sweep_window(): + return 200, {"itemData": sweep_items()} items = STORE.items() if CLUB_PAGE: log(" CLUB: returning %d item(s) [FUT_CLUB_PAGE experiment -- compare this "