From c28cad281bf09ab84e091e0e0dd7b86d502ea6ec Mon Sep 17 00:00:00 2001 From: funman300 Date: Tue, 4 Aug 2026 21:18:01 -0700 Subject: [PATCH] fifa17-recon: sweep can target the non-player card tables The merge FUN_180141660 dispatches on record+0x4c, which FUN_1800d8330 derives from cardsubtypeid alone, and each branch queries a different table by carddbid = record+0x18 -- the same field players use for playerid: 0..3 -> 1 players (live-proven) 5 -> 3 headcoachcards 4 -> 2 manager 8 -> 4 fitnesscoachcards 6 -> 10 gkcoachcards 7 -> 5 physiocards 9..b -> 7 unidentified absent -> 0x156 -> 0, no merge at all So a 't@' prefix on the sweep window probes any of them the same way players were probed: 't5@auto:1-20000:5000'. Only cardsubtypeid changes. itemType stays 'player' because the merge dispatches on the subtype alone and the wire shape of a real staff item has NEVER been observed -- across every logged session the client has only ever asked for type=player and type=custom. Inventing a shape for an unobserved request is the change class behind every freeze this project has had. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW --- fifa17-recon/tools/utas_server.py | 44 ++++++++++++++++++++++++++----- 1 file changed, 38 insertions(+), 6 deletions(-) diff --git a/fifa17-recon/tools/utas_server.py b/fifa17-recon/tools/utas_server.py index 3bda61c..33c97b2 100755 --- a/fifa17-recon/tools/utas_server.py +++ b/fifa17-recon/tools/utas_server.py @@ -1305,10 +1305,34 @@ def sweep_window(): # fetches; one probe at the end reads them all. _SWEEP_SPEC = None _SWEEP_POS = 0 +_SWEEP_SUBTYPE = 0 # 0..3 = player; see _parse_window for the other tables def _parse_window(win): - """'lo-hi' or 'auto:lo-hi:step' -> (lo, hi, step or None). None on garbage.""" + """'lo-hi' or 'auto:lo-hi:step' -> (lo, hi, step or None). None on garbage. + + An optional 't@' prefix sweeps a NON-PLAYER card table. The merge + FUN_180141660 dispatches on record+0x4c, which FUN_1800d8330 derives from + cardsubtypeid alone, and each branch queries a different table by + carddbid = record+0x18 (the same field players use for playerid): + + 0..3 -> 1 players 5 -> 3 headcoachcards + 4 -> 2 manager 8 -> 4 fitnesscoachcards + 6 -> 10 gkcoachcards 7 -> 5 physiocards + 9..b -> 7 (unidentified) absent -> 0x156 -> 0, NO merge at all + + So 't5@auto:1-20000:5000' sweeps head coaches exactly the way the default + sweeps players. Nothing about the non-player branches is live-proven yet; + they are read out of the binary. + """ + global _SWEEP_SUBTYPE + _SWEEP_SUBTYPE = 0 + if win.startswith("t") and "@" in win: + head, win = win.split("@", 1) + try: + _SWEEP_SUBTYPE = int(head[1:], 0) + except ValueError: + return None auto = win.startswith("auto:") step = None if auto: @@ -1358,13 +1382,21 @@ def sweep_items(): else: start, end = lo, hi - out = [_item(SWEEP_ID_BASE + (pid - lo), pid, SWEEP_SENTINEL_RATING, - "ST", 0, 0, 0, [1, 1, 1, 1, 1, 1]) - for pid in range(start, end + 1)] - log(" SWEEP: serving %d candidate playerid(s) %d..%d%s " + out = [] + for pid in range(start, end + 1): + it = _item(SWEEP_ID_BASE + (pid - lo), pid, SWEEP_SENTINEL_RATING, + "ST", 0, 0, 0, [1, 1, 1, 1, 1, 1]) + if _SWEEP_SUBTYPE: + # Only the subtype is changed. itemType stays "player" because the + # merge dispatches on cardsubtypeid alone and the wire shape for a + # staff item has never been observed -- inventing one is the change + # class that freezes the client. + it["cardsubtypeid"] = _SWEEP_SUBTYPE + out.append(it) + log(" SWEEP: serving %d candidate id(s) %d..%d%s subtype=%d " "[synthetic, nothing saved]" % (len(out), start, end, - (" (auto, %d..%d done)" % (lo, end)) if step else "")) + (" (auto, %d..%d done)" % (lo, end)) if step else "", _SWEEP_SUBTYPE)) return out