diff --git a/fifa17-recon/docs/ENDPOINT_MAP.md b/fifa17-recon/docs/ENDPOINT_MAP.md index 739b9cd..13bdfe2 100644 --- a/fifa17-recon/docs/ENDPOINT_MAP.md +++ b/fifa17-recon/docs/ENDPOINT_MAP.md @@ -944,11 +944,55 @@ freezes any of these — GAPs are "feature missing", not "crash". | 9 | FutDiscardCardByRes | `0x1801279c0` | DELETE `ut/delete/%s/item` (by res) | `totalCredits`(0x326) → int | GAP | HIGH | | 10 | FutMoveCard | `0x180128600` | PUT `ut/%s/item` (move) | `itemData`(0x16b) → **array** [FREEZE-RISK]; `chemistry`(0x81) → bool | GAP | HIGH | | 11 | FutMoveCardByRes | `0x180128e30` | PUT `ut/%s/item` (move by res) | `itemData`(0x16b) → **array** [FREEZE-RISK]; `chemistry`(0x81) → bool (+ 2 str/1 int minor) | GAP | HIGH / extra-fields MED | -| 12 | FutConsumablesSearch | `0x180130d10` | GET `ut/%s/item?type=…` (GetFilteredConsumableSearchResults) | `itemData`(0x16b) → **array[consumable-item]** via `0x18013fe00` [FREEZE-RISK]; `displayGroupUseDefaultImage`(0xdb) → int + count scalars | GAP | deser HIGH / scalars MED | -| 13 | FutStaffBonus | `0x18012b730` | GET `ut/%s/…` (CardsGetStaffBonuses) | `bonus`(0x5c) → **nested** (branch sets bool @rbp+0x51) [FREEZE-RISK]; `assetId`(0x23) → int | GAP | MED | +| 12 | FutConsumablesSearch | `0x180130d10` | GET `ut/%s/club/consumables/` (ConsumablesSearch) **[CORRECTED 2026-08-21]** | `itemData`(0x16b) → **array[consumable-stack]** via `0x18013fe00` [FREEZE-RISK]; `displayGroupUseDefaultImage`(0xdb) → int + count scalars | SERVED (Rust host) | deser HIGH / scalars MED | +| 13 | FutStaffBonus | `0x18012b730` | GET `ut/%s/club/stats/staff` (StaffStats, thunk `0x18012b080`) **[CORRECTED 2026-08-21]** | `bonus`(0x5c) → **nested** (branch sets bool @rbp+0x51) [FREEZE-RISK]; `assetId`(0x23) → int | SERVED (`{}`, the oracle body) | MED | | 14 | FutGetAvailableLoanPlayers | `0x18014e030` → sub `0x18013a1c0` | GET `ut/%s/item` (FUT_AVAILABLE_LOAN_PLAYERS_DP) | `loans`(0x19b) → **array** [FREEZE-RISK]; `itemData`(0x16b) → **array[card-item]** [FREEZE-RISK]; `default`(0xcd) → int | GAP | deser HIGH / fields MED | | 15 | FutSignLoanPlayer | `0x1801642c0` | PUT `ut/%s/item` (sign loan) | **none** (immediate `ret`) | ack — `{}` fine | HIGH | -| 16 | FutStickerBookSearch | `0x18012eff0` | GET `ut/%s/…` (stickerbook search) | `itemData`(0x16b) → **array[card-item]** via `0x18013fe00` [FREEZE-RISK] | GAP | HIGH | +| 16 | FutStickerBookSearch | `0x18012eff0` | GET `ut/%s/club?` (ClubSearch, `FUN_18012ddf0`) **[CORRECTED 2026-08-21]** | `itemData`(0x16b) → **array[card-item]** via `0x18013fe00` [FREEZE-RISK] | SERVED (Rust host) | HIGH | + + +### The four `ut/%s/club` routes are a TABLE, not an inference (2026-08-21) + +The URLs for rows 12, 13 and 16 above were previously guessed as `ut/%s/item?…` +or left as `ut/%s/…`. The binding is exact: the 125-row action table at +`0x1802caa20` indexes the 48-entry URL-base table at `0x18021df80` through column +1, and **base index 3 = `ut/%s/club` is carried by exactly four rows** — so the +client can emit exactly four request families on that base and no others. + +``` +| ClubSearch | FUN_18012ddf0 | GET ut/%s/club? | FutStickerBookSearchServerResponse | +| ClubStats | FUN_18012f4f0 | GET ut/%s/club/stats/[/] | FutStickerBookStats2ServerResponse | +| StaffStats | thunk 0x18012b080 | GET ut/%s/club/stats/staff | FutStaffBonusServerResponse | +| ConsumablesSearch | FUN_1801308c0 | GET ut/%s/club/consumables/ | FutConsumablesSearchServerResponse | +``` + +**Club query grammar**, complete and ordered: `?year=2017` (always, hardcoded), +then `type`, `start` (omitted at 0), `count` (omitted at 100), `filter`, then +EITHER the filter block (`position, formation, state, level, rare, nation, +country, league, playStyle, team, sort`) OR a comma-joined `defId=` list, never +both. Live control from the log: +`GET /ut/game/fifa17/club?year=2017&type=equippables&count=11&level=any&sort=desc` +matches the predicted order and every suppression rule. + +Sub-vocabularies: `filter` = available/base/exact/any; `level` = +bronze/silver/gold/any; `sort` = asc/desc; `rare` = the literal string `SP`, not +a boolean; `state` = the itemState names plus `any` — and note the REQUEST spells +it `onSale` where the RESPONSE value is `forSale`. + +`?type=` has 30 values (`FUN_18012ec50`: 29 cases plus a default of `any`). + +**`/club/stats` has exactly seven forms**: `club`, `year`, `country/`, +`league/`, `newcards`, `consumables`, and the separately-dispatched `staff`. +**There is no `/club/stats/team/`** — verified twice (the switch has six cases +with no such arm, and an exhaustive PE string scan finds no literal containing +`stats/team`). Any handling of a `team` stats mode is dead code. + +**Two holes in the base table**, recorded so nobody re-derives them as findings: +base index 43 = `ut/v2/%s/store` is carried by no action row and has zero +references in `.text`, yet `ut/v2/store` is live-proven; base index 9 = +`ut/%s/activeMessage` is a second hole of the same kind. So at least one route is +composed OUTSIDE CardsDLL, most likely in the packed exe — every "the table bounds +it" statement here is bounded to CardsDLL only. Notes: - **`0x1801642c0`** is a shared no-op deserializer (function body = `ret`). Three responses diff --git a/fifa17-recon/docs/plan-2026-08-06-card-subsystem.md b/fifa17-recon/docs/plan-2026-08-06-card-subsystem.md index f6a5399..94ddf21 100644 --- a/fifa17-recon/docs/plan-2026-08-06-card-subsystem.md +++ b/fifa17-recon/docs/plan-2026-08-06-card-subsystem.md @@ -1054,6 +1054,10 @@ change a working wire. Bundle it with the next change that needs a launch. ## 7. Proposed corrections to existing documents +> **APPLIED 2026-08-21.** Every correction below has been made in the named file +> and marked there with a dated note. This section is kept as the rationale and +> the audit trail, not as an outstanding to-do. + ### `docs/CARD_SYSTEM.md` **Replace the "STILL UNKNOWN, AND NOT GUESSED" section entirely.** It is answered.