From ccf912c157c8676a63840abd76974a189aabe3fa Mon Sep 17 00:00:00 2001 From: funman300 Date: Wed, 5 Aug 2026 11:59:38 -0700 Subject: [PATCH] fifa17-recon: club items crashed the client -- unestablished fields, and too wide a blast radius The game hung and then crashed on the first equippables fetch. My fault twice over. CAUSE, primary. I copied teamid, leagueid and value straight out of the fcc row as extras. `value` appears elsewhere as an OBJECT member (displayGroup {"value": ...}), and a scalar where an object is expected is the type-desync busy loop at 0x1801c7f1a -- which presents exactly as "the game is taking its time" and then dies. Omission is safe; an unestablished field is not. That is this project's own rule and I broke it for three fields that were not needed to draw a card. All three are gone. CAUSE, contributing. The last request before the crash was type=equippables&count=11 and we answered with 30 items spanning FIVE unverified cardsubtypeids at once: the widest possible blast radius for a wrong shape, and it tells you nothing about which subtype was wrong. equippables now answers [] until the subtypes are confirmed one family at a time, and shelf() takes a families= filter so a test can serve exactly one. Adds FUT_CLUBITEMS=probe:, which serves one item per candidate subtype for a single family, so the screen names the correct subtype instead of me guessing a third time. Eight items, one family, one question. The flag already defaulted off, so a plain restart cannot serve any of this. 439 + 414 checks green. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW --- fifa17-recon/tools/fut_clubitems.py | 27 ++++++++++++++++++++------- fifa17-recon/tools/utas_server.py | 25 +++++++++++++++++++------ 2 files changed, 39 insertions(+), 13 deletions(-) diff --git a/fifa17-recon/tools/fut_clubitems.py b/fifa17-recon/tools/fut_clubitems.py index 91f9233..1d03124 100644 --- a/fifa17-recon/tools/fut_clubitems.py +++ b/fifa17-recon/tools/fut_clubitems.py @@ -92,21 +92,34 @@ def _item(item_id, carddbid, cardassetid, subtype, extra=None): return it -def shelf(next_id=CLUBITEM_ID_BASE): - """The starter club-item shelf, {family: [item]}.""" +def shelf(next_id=CLUBITEM_ID_BASE, families=None): + """The starter club-item shelf, {family: [item]}. + + `families` limits which are built. The combined `equippables` view is what + crashed the client: 30 items across FIVE unverified subtypes in one response is + the widest possible blast radius for a wrong shape. One family at a time is the + only way to learn which subtype is wrong. + """ out, nid = {}, next_id for name, table, art, _sid, _sname, subtype in FAMILIES: + if families is not None and name not in families: + out[name] = [] + continue rows = _rows(table) picked = [] for r in rows[:STARTER_N.get(name, 4)]: cid = r.get("carddbid") if not cid: continue - extra = {} - for k in ("teamid", "leagueid", "value"): - if r.get(k): - extra[k] = r[k] - picked.append(_item(nid, cid, r.get("cardassetid", art), subtype, extra)) + # NO EXTRAS. An earlier version copied teamid/leagueid/value straight + # out of the fcc row and the game hung and then CRASHED on the first + # equippables fetch (2026-08-05). `value` is the prime suspect: it + # appears elsewhere as an OBJECT member (displayGroup {"value": ...}), + # and a scalar where an object is expected is the type-desync busy loop + # at 0x1801c7f1a, which reads exactly like "the game is taking its time" + # and then dies. Omission is safe; an unestablished field is not. None of + # the three was needed to draw a card. + picked.append(_item(nid, cid, r.get("cardassetid", art), subtype)) nid += 1 out[name] = picked return out diff --git a/fifa17-recon/tools/utas_server.py b/fifa17-recon/tools/utas_server.py index 80bd2aa..13e156d 100755 --- a/fifa17-recon/tools/utas_server.py +++ b/fifa17-recon/tools/utas_server.py @@ -1204,7 +1204,10 @@ CONSUM_UNTRADEABLE = os.environ.get("FUT_CONSUM_UNTRADEABLE", "0") == "1" # # Default OFF: nothing here has ever been requested by the client, so unlike the # consumables stat fix this is not correcting a demonstrably wrong answer. -CLUBITEMS = os.environ.get("FUT_CLUBITEMS", "0") == "1" +# "1" serves each family on its own type= arm and serves NOTHING for equippables. +# "probe:" serves one item per candidate cardsubtypeid for that family only. +_CLUBITEMS_MODE = os.environ.get("FUT_CLUBITEMS", "") +CLUBITEMS = bool(_CLUBITEMS_MODE) and _CLUBITEMS_MODE != "0" def _consumable_stat_rows(): @@ -1809,13 +1812,23 @@ def club_route(h): if CLUBITEMS and kind in ("stadium", "ball", "badge", "kit", "leaguelogos", "equippables"): import fut_clubitems - sh = fut_clubitems.shelf() fam = {"stadium": "stadia", "ball": "balls", "badge": "badges", "kit": "kits", "leaguelogos": "leaguelogos"}.get(kind) - if fam: - got = sh.get(fam, []) - else: - got = [i for fam_items in sh.values() for i in fam_items] + if _CLUBITEMS_MODE.startswith("probe:"): + want = _CLUBITEMS_MODE.split(":", 1)[1] + got = fut_clubitems.probe_shelf(want) if fam == want else [] + log(" CLUBITEMS: PROBE %s on type=%s -> %d item(s), one per candidate " + "subtype" % (want, kind, len(got))) + return 200, {"itemData": got} + if fam is None: + # equippables is the COMBINED view and it is what crashed the client on + # 2026-08-05: 30 items across five unverified subtypes in one response. + # Until the subtypes are confirmed one family at a time, answer it empty. + # An empty itemData is a shape the client already accepts everywhere. + log(" CLUBITEMS: type=equippables -> [] (combined view withheld until " + "the subtypes are verified; it crashed the client once)") + return 200, {"itemData": []} + got = fut_clubitems.shelf(families={fam}).get(fam, []) log(" CLUBITEMS: type=%s -> %d item(s)" % (kind, len(got))) return 200, {"itemData": got}