diff --git a/fifa17-recon/tools/utas_server.py b/fifa17-recon/tools/utas_server.py index 5f3b1cc..c165ec0 100755 --- a/fifa17-recon/tools/utas_server.py +++ b/fifa17-recon/tools/utas_server.py @@ -863,6 +863,10 @@ ROUTES = [ # full active-squad object; the list parser 0x180142260 recognises ONLY # squad(0x2cd) and skipped every one of those keys -> "MY SQUADS: 0". (re.compile(G + r"/squad/list"), lambda m, h: (200, squad_list_body())), + # Same bug as /squad/list, second instance: `ut/%s/squad/mode` + `/draft/state` + # is composed by appending a suffix, so it is invisible to the request-template + # table, and the generic /squad route below was swallowing it. MUST precede it. + (re.compile(G + r"/squad/mode/draft/state"), lambda m, h: draft_state_route(h)), (re.compile(G + r"/squad"), lambda m, h: squad_route(h)), (re.compile(G + r"/match/keepalive"), lambda m, h: (204, None)), # PUT ut/%s/match/reset = FutResetMatch. Seen live at boot (2026-08-03) as an @@ -1039,6 +1043,54 @@ def leaderboard_route(h): return 200, {"entries": []} +# ---- Draft current state ----------------------------------------------------- +# GET ut/%s/squad/mode/draft/state?mode=ONLINE|SINGLE_PLAYER +# -> FutGetDraftCurrentStateServerResponse, deser 0x180147070. +# +# THE ROOT CONTAINER IS A JSON ARRAY, not an object. That single fact is the whole +# finding. The deserializer's prologue discards tokens until it sees START_ARRAY; +# handed a top-level object it never reaches its exit condition and spins in the +# inner `while (tok != END_OBJECT)` loop with the tokenizer returning EOF forever. +# Process alive, no crash dump, no error dialog: exactly the signature observed live +# on 2026-08-03 when our generic /squad route answered this with a full active-squad +# object (23 slots, nested itemData, the 33-int `custom` string). +# +# Verified, not assumed: +# * 7 top-level atoms, counted at instruction level (4 int-getter calls, 3 +# string-getter calls, 0 bool, 2 skip) over the whole body [0x180147070, +# 0x1801475a3]. FUN_180135ff0 IS present (2 sites), so unknown keys are inert. +# * NO ATOM COLLIDES with the squad object we were serving. The freeze was purely +# the container level, not a per-field type desync. +# * roundsInfo[] element parser FUN_180146eb0 (2477 chars, read in full): 7 scalar +# atoms, all safe. Empty array is safest and is what we send. +# * entranceCriteria(0x108) is an object of three int keys COINS/DRAFT_TOKEN/ +# POINTS. OMITTED here: knowing a shape is not a reason to send it. +# * squadState(0x2d5) is a string enum. "DRAFTSQUAD_ON" from the old notes is NOT +# an accepted spelling. +# +# A reviewer independently simulated this exact body through the deserializer line by +# line: 16 token reads, clean exit, nothing left over. +# +# DEFAULT ON, which is a deliberate exception to "default to the live-proven value". +# The live-proven value here HANGS THE GAME. There is no working screen to protect: +# Draft cannot be entered at all today. FUT_DRAFT_STATE=0 restores the old routing if +# this turns out to be wrong. +DRAFT_STATE = os.environ.get("FUT_DRAFT_STATE", "1") == "1" + + +def draft_state_route(h): + if not DRAFT_STATE: + return squad_route(h) + return 200, [{ + "squadState": "INVALID", # 0x2d5 STRING enum + "stateParam1": "INVALID", # STRING + "stateParam2": "0", # STRING (the int getter also accepts it) + "gamesWonCurrentMatch": 0, # INT + "roundsInfo": [], # array of the 7-scalar element; empty is safe + # entranceCriteria: OMITTED. Shape known, not needed, skip-safe. + }] + + def champion_route(h): """ut/%s/champion -- registration 0x18014980d (ack, no atoms), topX 0x18014a09d ({"entries":[]}), friends 0x18014b7ad ({} safe)."""