fifa17-recon: store fix (v2/store gate + flags) + full FUT endpoint map

Store "not available" root cause reversed from CardsDLL:
- ut/v2/game/fifa17/store is an ELIGIBILITY gate (FutStorePackQuantities
  deser 0x1801758c0), not a quantity list. It reads one key "result"
  (atom 0x288); the store screen refuses to open unless SUCCESS. Was
  unhandled -> catch-all {} -> "not available". Now returns {"result":"SUCCESS"}.
- Store-screen entitlement checks (0x18001749d/0x1800175a2) read IS_*/
  *_PURCHASE_ENABLED Blaze flags, separate from storeEnabled. Added the full
  confirmed set (14 flags) to FUT_RS4_CONFIG.
- Catalog: assetId (0x23) is the real pack identity; extPrice inner keys are
  amount/currency (not mtx). (Also gated client-side by GetSystemMetrics>1024x768.)

Full FUT API reversed (clean-room, CardsDLL only) into docs/ENDPOINT_MAP.md:
~100 FutXServerResponse types across 7 feature groups (market, SBC, draft,
seasons/match, club, store, user/hub), each with deserializer VA, atom-mapped
field schema + types, freeze-risk flags, and minimal known-good JSON.
Tooling kept: tools/atomdump.py (dumps the 907-atom key table at 0x1802d2760)
-> docs/fut_atoms.tsv. Research prompt: docs/OPENCODE_ENDPOINT_PROMPT.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
This commit is contained in:
funman300
2026-08-02 17:13:11 -07:00
parent c7759a52c4
commit 4e89cce37d
6 changed files with 2498 additions and 1 deletions
+18 -1
View File
@@ -154,6 +154,11 @@ ROUTES = [
# ---- store / packs (match regardless of /ut/game vs /ut/v2/game prefix) ----
(re.compile(r"/store/purchasegroup"), lambda m, h: store_catalog(h)),
(re.compile(r"/store/transaction"), lambda m, h: store_buy(h)),
# ut/v2/game/fifa17/store = FutStorePackQuantities ELIGIBILITY GATE, not a
# quantity list. deser 0x1801758c0 reads one key "result" (atom 0x288); the
# store screen shows "not available" unless this is SUCCESS. (ENDPOINT_MAP
# store §2.) Bare /store only -- purchasegroup/transaction matched above.
(re.compile(r"/store(\?|$)"), lambda m, h: (200, {"result": "SUCCESS"})),
(re.compile(r"/purchased"), lambda m, h: purchased_items(h)),
(re.compile(r"^/ut/auth"), lambda m, h: (200, auth_body())),
(re.compile(r"^/ut/delete/auth"), lambda m, h: (200, {})),
@@ -214,10 +219,19 @@ def store_catalog(h):
# {name,funds,finalFunds}; display name is "description". (wf a245577b —
# {purchaseGroups:...} + packId/price were all unknown atoms => empty => "not
# available".) quantity:0 => unlimited.
# Parse format is verified correct ("purchase" array, per-pack 0x18013af30).
# The store still rejected minimal packs -> a pack must be COMPLETE to count as
# valid: content info + BOTH a coins price (currencies) and a FIFA-Points price
# (extPrice {finalPrice,originalPrice} -> {"mtx":N}).
packs = []
for p in PACK_CATALOG:
gold = p["gold"]
mtx = max(1, p["price"] // 100)
packs.append({
# assetId (atom 0x23) is the REAL pack identity the deser 0x18013af30
# reads (ENDPOINT_MAP store §). id/packType/quantity/saleType/isPremium
# are all unknown atoms -> SKIP (harmless no-ops, kept for readability).
"assetId": p["id"],
"id": p["id"],
"packType": "GOLD" if gold else "BRONZE",
"description": p["name"],
@@ -228,11 +242,14 @@ def store_catalog(h):
"saleType": "PERMANENT",
"sortPriority": p["id"] - 100,
"currencies": [{"name": "coins", "funds": p["price"], "finalFunds": p["price"]}],
# extPrice inner keys are amount(0x1b)/currency(0xc4), NOT mtx (skipped).
"extPrice": {"finalPrice": {"amount": mtx, "currency": "fifapoints"},
"originalPrice": {"amount": mtx, "currency": "fifapoints"}},
"packContentInfo": {
"bronzeQuantity": 0 if gold else p["count"],
"silverQuantity": 0,
"goldQuantity": p["count"] if gold else 0,
"rareQuantity": p["count"] if gold else 0,
"rareQuantity": 1 if gold else 0,
"itemQuantity": p["count"],
},
})