Commit Graph

27 Commits

Author SHA1 Message Date
funman300 cef1e8d1f4 fifa17-recon: consumable artwork CONFIRMED FIXED, and the two wrong guesses recorded
Real card art, tier colours and the GK glove icon all draw once cardassetid carries
the art id. Records both refuted hypotheses with the evidence that killed them, since
each was plausible and someone will reach for them again.

The generalisable trap: an fcc_ row has BOTH carddbid and cardassetid, they are not
interchangeable, and _item copies resourceId into cardassetid -- right for players,
wrong for every other family. Club items will hit it next: balls 37, kits 35,
stadium 36, badges 39, league logos 40.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-05 11:47:05 -07:00
funman300 550313362c fifa17-recon: consumables CONFIRMED LIVE -- artwork, stacks and correct amounts
Rendering with real artwork, quantity badges and +5/+10/+15 amounts, so atom 0x1b
reaches record+0xbf. The client's own dialog names the class we resolved: 'Search
Type: Consumables Search'.

Records the three things that each had to be right and each failed silently with a
200: the count gates the fetch, the route is club/consumables/<category> (a /club
PREFIX, so it was being answered with the player list), and the element is a five-atom
stack wrapper whose 0x16a member is the only thing that carries the item.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-05 11:36:47 -07:00
funman300 0f83d73364 fifa17-recon: the consumables panel asks 41 times a session and we answer with players
Round 3, 10 agents. The headline is measured, not inferred: GET club/stats/consumables
is requested 41 times per session by the real client (ProtoHttp), and _club_stat_set()
answers it with the PLAYER stat set. The panel reads 14 consumables* names that we
have never sent, so it is told '205 players' when it asked how many contracts the club
owns, and it has nothing to show.

That also explains why last round's 126-item consumable shelf was never requested. It
serves type=contract|training|healing|development and an UNTYPED /club with no
team=/league=, and all 9 of the client's untyped requests this session carry team=. The
only +126 item(s) line in the whole log came from one of our own probes.

Vocabulary recovered: the 14 consumables* rows plus badgeDBid 0x2e, kitsHome 0x29,
kitsAway 0x2a, leagueLogos 0x2f, trophiesSeasonOnline 0x38.

Other measured surfaces the client asks for and we fob off: GET /settings 11x answered
with an empty config array (a 40-flag feature gate, the biggest untouched lever in the
project), leaderboards/options 5x with {}, user/accountinfo 4x with {}.
club/stats/staff is a DIFFERENT class (FutStaffBonus); the staff counts come from the
Stats2 store, which is why the staff screen worked while we answered {}.

Refuted: ENDPOINT_MAP's claim that objectives have no route. FUN_180151610 builds
<base>/objective/%d/reward and FUN_180147780 builds .../complete.

New modules only. utas_server.py is deliberately untouched: whether to wire the counts
depends on a free observation the human can make on the client that is already running,
and spending a restart before that is what this round exists to avoid.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-05 11:13:54 -07:00
funman300 456ec24360 fifa17-recon: managers and coaches CONFIRMED LIVE, and the manager template paints our fields
34 staff cards, zero DB Error. Every coach id hit its table first time, which is the
payoff from enumerating the game's own database instead of sweeping for ids: the loud
miss-fill exists and never fired.

10 of 10 managers resolved with historically correct nations and leagues.

RESOLVED, previously TODO/CONFIRM: the manager card template paints record+0xde
(nation) and record+0xe0 (league). Luis Enrique draws the Spain flag and 'LaLiga
Santander'; the Premier League managers draw their flag and 'ENG 1'. The merge never
writes either field, so nothing but our own JSON could have supplied them.

Corrected: negotiation at +0xe3 is NOT on the card front, which shows CONTRACT 7
there. I had told the user to look for it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-05 10:22:34 -07:00
funman300 22ba361578 fifa17-recon: repair_club keeps dead cards unless asked, plus the 2026-08-05 plan
Deleting cards from someone's club is their call, not the tool's. The nine
unrepairable blanks are now KEPT unless --delete-dead is passed. A blank card is ugly,
not harmful, and the 175 stale cards were never the deletion candidates anyway: they
are real players wearing old invented numbers and they get repaired in place.

Also records the build round's synthesis as docs/plan-2026-08-05-families.md.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-05 10:16:13 -07:00
funman300 49733f79d1 fifa17-recon: card-family enumeration round -- managers cracked, consumables need no ids
11-agent round, every investigation adversarially reviewed. The headline is that this
was never five id hunts: the client's database is resident in ordinary heap as a
self-describing catalog of bit-packed fixed-stride row arrays, walkable READ-ONLY, so
the id sets fall out at zero cost in human club visits.

MEASURED:
  managercards carddbid 1000001..1001455, assetid == carddbid; two agents using two
    different block locators agreed 417/417 (docs/managercards_ids.txt). This is why
    the earlier sweeps of 1..5000 and 6000..8000 were silent.
  staff bands: headcoach 2000004+, fitnesscoach 3000019+, physio 4000002+,
    gkcoach 9000001+, corroborated by the four miss-fallback assetids hard-coded in
    FUN_180141660 each landing inside its own table's decoded range.
  all four coach branches write a LOUD miss-fill: firstname/lastname "DB Error",
    rating 0x32, rare 1, attrs[0] 0xf, plus a table-unique assetid. Managers write
    none, so a wrong manager id is silent and a wrong coach id labels itself.
  consumables have NO table and NO id space: cardtype 6 has no arm in the merge,
    FUN_18013f4d0's only callees are a range clamp and an enum map, and every string
    is a hardcoded FUT_CONSUMABLE_* literal. A contract is three JSON keys.
  the ?type= taxonomy is 29 explicit arms plus a default: badge 11, kit 12, stadium
    13, ball 14, equippables 15, leaguelogos 16, misc 26. club/stats kits and
    badgeDBid are PLAIN COUNTS, not ids.
  fancards is a boolean column of the fixtures table; newcards is FUT atom 0x1d7.
    NEITHER is a card family, so two of the five hunts never existed.

REFUTED, and worth keeping: the live table-directory walk was off by one entry
(descriptor for table T is at entry-0x20, not entry+0x08), which had mislabelled
managercards as factory_teams and shifted every column count. managercards names are
32-bit string-pool offsets and the pool was never located -- we get ids, not names,
and we do not need names because the client supplies them.

TRAPS RECORDED: rareflag=1 silently converts a Player Fitness card (219) into Squad
Fitness, and fut_store._item() hardcodes rareflag 1 on every item.

Four proposed club-item sweep windows were killed by review as invariant by
construction: with no merge arm there is no miss-fill, so every id yields a
byte-identical record and the probe cannot discriminate. That is a wasted human action
correctly caught before it cost one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 22:40:24 -07:00
funman300 c76cf706ef fifa17-recon: CARD_SYSTEM -- record the solved identity mechanism and the oracle
Supersedes the parts of this document that were wrong: the CardsDb map is not empty
offline, and dbdata.dll is not the player database.

Records the merge dispatch table, the field-fill asymmetry that the pool design
depends on (send zero for what the client knows, send our own only where it knows
nothing), the three-state oracle with all three fingerprints, the 5000-item ingest
ceiling, the fact that the map is WIPED on every club fetch, and where the 17,547
player roster came from plus its independent cross-validation.

Also records the state of the other card families so the next session starts from
the manager branch writing no miss-fill, rather than rediscovering it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 22:00:18 -07:00
funman300 e9e6f203c2 fifa17-recon: the card pool is now the REAL FIFA 17 roster, 17547 players
The old pool was 79 hand-written rows whose asset ids were mostly invented, on
the premise that the client's card map is empty offline so no id could render.
That premise was refuted by a live screenshot, and this replaces its consequence.

Source: tools/dbdata_extract.py reads FIFA's own rating-sorted index out of a
running process (0x40 stride, self-validating {begin,end,end+1} name-pointer
triple, anchored on 20801 = Ronaldo 94) -> data/roster.json. dbdata.dll was a
dead end and is documented as such: its single export getTableData is an
anti-tamper attestation routine, not a data accessor.

Cross-validated against a completely independent method. tools/sweep_collect.py
serves candidate ids as a synthetic club and reads back the identity the CLIENT
resolved through its own merge. 573 of 573 overlapping names agreed exactly, and
the single id present in one and not the other is 26501, the target of the
documented 22800..22879 Legends remap -- which is also what produced 'Alex Hunter
x80' in a sweep and had looked like a bug.

Field honesty, because half of these are real and half are not:
  playerid/rating/name  REAL   the roster
  club/nation/league    REAL   we send zeros and the CLIENT fills them (the merge
                               only fills those fields when they arrive as zero)
  position              PARTLY 59 from the game's own per-card cache, 17 curated
                               by hand, the rest synthetic but deterministic
  attributes            SYNTH  derived from rating and position

169193 is dropped from the curated set: it was in VERIFIED_ASSET_IDS and is not a
real player. The client resolves it to the database's empty placeholder row, which
renders as 'Jamal Blackman'. Two independent methods agreed.

NOTE BEFORE PUSHING ANYWHERE PUBLIC: data/roster.json is EA's player data,
extracted from your own installation. Fine locally; think twice about publishing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 21:08:52 -07:00
funman300 a3fd9e870f fifa17-recon: REFUTED -- the CardsDb map is not empty offline
CARD_SYSTEM.md has claimed since it was written that offline the CardsDb map is EMPTY,
every lookup misses, and the view-model reads every rendered field from the resolved
record and NEVER from our item JSON. A live pack open falsifies both halves.

One bronze pack, five cards. Two rendered as real players with names, club badges and
national flags. Three rendered blank: rating 50, position RWB, every attribute 1. Our
pool contains no rating 50, no RWB and no all-ones attributes, so the blank is the
client default.

The two that resolved match our item JSON field for field:
  (232517, 62, RB, nation 36, league 19, team 175, [72,44,58,60,62,61])
    -> SILVA, 62 RB, Wolfsburg badge, Norway flag, 72 PAC 44 SHO 58 PAS 60 DRI 62 DEF
  (235066, 60, GK, nation 34, league 31, team 48, [62,63,33,61,17,62])
    -> NOWAK, 60 GK, 62/61 63/17 33/62

Those attribute numbers were invented by hand this afternoon. They cannot have come
from a database.

So: stats come from our JSON, name/badge/flag come from the client keyed by assetId,
and an assetId the client does not know collapses the WHOLE card to the blank, which
is why a bad id looks like a rendering failure rather than a lookup failure.

THE CONSEQUENCE IS THAT THE PLANNED WORK IS UNNECESSARY. This document recommended
populating the map by driving the insert, hand-building a red-black tree node, or
patching the resolve miss path, all of which write to a live process. None of it is
needed. The rule is: use asset ids that exist in the client database. fut_cards.py has
18 verified ids and 61 structural placeholders, and the placeholders are the blanks.
The remaining work is a DATA problem, not a code-injection problem.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 15:38:41 -07:00
funman300 a7ac5a09fd fifa17-recon: club stats LIVE-PROVEN, default ON; S19's verdict was over-scoped
MY CLUB -> ENGLAND -> Premier League now reads 17. First non-zero number ever rendered
on that screen. Nothing froze, no other screen changed, 392 + 61 checks green with the
flag defaulted on and no env override.

S19 concluded "the MY CLUB counter is not server-fixable". That was too broadly scoped.
The nation and league drill-downs ARE server-fixable and are now fixed. S20 records the
corrected scope.

What made it work, from FUN_180043b90 case 3:

  uVar7  = (**(param_2 + 0x18))(param_2, row, "LEAGUE_ID")   THE UI ROW'S OWN ID
  bronze/silver/gold = (+0x7f8)(store, uVar7, 2 / 3 / 4)
  publish("PLAYERS_EMPLOYED", gold + silver + bronze)         COMPUTED, never read
  rare/kits/badges   = (+0x7f8)(store, uVar7, 5 / 0x28 / 0x2d)

Still open and now correctly scoped: the hub tile's "0 TOTAL PLAYERS" and the MY CLUB
summary rows read the GLOBAL bucket via +0x800 in cases 1 and 5. We serve those rows.
The unchanged question is what SELECTS those cases, since the mode tag is copied from
the completed request and the client requests year, consumables, staff, country/<id>
and league/<id> but never club.

The method note, which is the durable part: two rounds of reasoning about this endpoint
produced two wrong bodies; twenty lines of the consumer produced the right one. Reading
the PARSER tells you what is accepted. Only reading the CONSUMER tells you what is used.
That question was answerable from the start and went unasked until live screenshots
forced it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 14:50:06 -07:00
funman300 d2bbb4d378 fifa17-recon: S19 -- the MY CLUB counter is NOT server-fixable, with the mechanism
Two experiments, both negative, and the negative has a mechanism behind it rather than
being another failed guess.

  FUT_CLUB_PAGE   served 114 items to /club?...count=11   tile still 0 TOTAL PLAYERS
  FUT_CLUBSTATS   full stat set, players=114, all modes   panel still Players 0

The bodies went out: four "CLUBSTATS: 11 stat rows (players=114)" responses in the log,
panel re-entered afterwards.

  FUN_18012fbe0   store+0x78 = request+0xc4      the mode tag is copied from the
                                                 REQUEST that just completed
  FUN_180043b90   switch (store+0x78)
    case 1  +0x800(1)->PLAYERS_EMPLOYED, (0x1e)->BALLS_EARNED, (0x28)->KITS_AVAILABLE,
            (0x14)->STADIA_OWNED, (10)->STAFF_EMPLOYED, (0x32)->TROPHIES_WON
    case 6  CARDS_NO_TRAINING_*, CARDS_NO_CONTRACT_*, CARDS_NO_FITNESS_*

The MY CLUB summary is case 1, which needs mode 1 (club). The client requests staff,
year and consumables and NEVER club, so the tag settles at 6 and case 1 is never
selected. Our values are stored correctly (contextId 1 forces contextValue 0, the
global bucket the +0x800 getter reads) and case 1 reads exactly the six ids we set.
Nothing ever asks for them.

THE MODE IS CHOSEN CLIENT-SIDE FROM THE REQUEST URL. No response body can change it,
so there is no body that fixes this and generating more of them is wasted work.

Two independent corroborations rather than one story that merely fits:
- case 6 reads 0x3d CONTRACTS, 0x3e TRAINING, 0x40 FITNESS, exactly the three ids
  FUN_18012fd40 cannot produce from any type string. The consumables view is
  unsettable from this endpoint by construction.
- FUT_CLUB_PAGE eliminated the only other candidate: the tile is not a count of the
  list we return.

Both flags stay implemented and default OFF. FUT_CLUBSTATS is correct against the
verified schema and would populate the moment a club-mode request occurred; deleting it
would throw away the schema work for no gain.

Recorded against myself: I argued from the matching labels (tile "TOTAL PLAYERS", panel
"Players", both zero while we served {}) that the two read the same store and one body
would fix both. The store IS shared. The SELECTION is not, and that is what decides it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 14:25:56 -07:00
funman300 285d4f6cb7 fifa17-recon: the blockers plan from the multi-agent pass
Five parallel Ghidra investigations, one adversarial reviewer each, one synthesis.
Kept in the repo because the reviewer corrections are load-bearing: they refuted claims
in four of the five reports, two of which would have shipped wrong behaviour (a
speculative /season body justified by our own curl traffic in the log, and a store
field block that was a freeze rather than a regression).

Carries the next live session (one launch, three flags, four menu actions, one
read-only memory probe), the implementation queue, what is genuinely blocked and why,
and a what-could-make-this-plan-wrong section that names the store change as the
concrete regression risk.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 14:04:00 -07:00
funman300 1e9cfb6da9 fifa17-recon: ENDPOINT_MAP -- remove two documented hang recipes, fix five entries
This file has been handing out bodies that freeze the client, under the heading
"MINIMAL known-good".

1. FutGetDraftCurrentState. The root container is a JSON ARRAY. The documented body was
   object-root, used the spelling DRAFTSQUAD_ON which is NOT an accepted squadState
   value, and embedded a full squad object. Anyone serving it would have reproduced the
   exact hang the entry existed to prevent, which is what happened live on 2026-08-03
   when our generic /squad route answered this endpoint with a squad object.
   Path corrected too: it is ut/%s/squad/mode + /draft/state, not ut/%s/draft/state.
   The `squad/mode` segment was missing, which is why the URL is invisible to the
   request-template table. Established by live capture, not statically: FUN_180146ac0
   appends the suffix to a caller-supplied buffer and has no resolvable callers.

2. FutGetDraftAward (0x1801510c0) has the same array-root prologue, and its documented
   object-root body would hang identically. Corrected, and marked TODO/CONFIRM on the
   member list, which was not re-verified this pass.

   Both of these survived because a census claimed only three array-root readers
   existed in the DLL. It missed one. The census run to check it was wrong in the other
   direction. ~23 of 86 top-level readers are still unclassified, so the file now says:
   do not serve any endpoint here until its root container is classified by reading the
   actual prologue, not by regex.

3. roundsInfo element: `score` and `penaltyScore` offsets were swapped (+0x10 / +0x18).

4. FutSeasonList: deserializer is 0x1801683f0, not 0x180167740 (that is the ELEMENT
   parser), and the root is an OBJECT with one key `seasons`(0x2ad), not an array.
   Someone documented the element parser's key set at the document level, and
   utas_server.py served that shape for months on the strength of this row. Three of
   the listed element keys are inner members of elgReq and inert at element level.
   Added: element ordering (type before divisionId), stride 0x318, the (0xb-divisionId)
   short, and the three array-loop members that must stay omitted.

5. Recorded on the season entry that the client has NEVER requested /season across 486
   real requests, so no body there is observable yet.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 14:03:33 -07:00
funman300 224874d1d3 fifa17-recon: close both standing items in the priority doc
User-Agent filter: implemented as the default in futlog.py.

The two wrong .rdata addresses: verified they never reached any document. They existed
only in an agent recon report, so there was nothing to correct. Kept the reviewer's
corrected values because they are verified and useful:
  RS4:FutGetClubInfoServerResponse         0x180221a38  (not 0x180220e38)
  RS4:FutStickerBookSearchServerResponse   0x180221e48  (not 0x180221248)

Closing an item by checking it was never a problem counts as closing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 11:35:23 -07:00
funman300 38b10e5ec5 fifa17-recon: S18d -- a testable hypothesis for the Seasons blocker
/leaderboards/options is the ONLY mode-related endpoint the real client has ever
requested, and we answer {} because FUT_MODES is off. Three of its seven occurrences
are followed within ~2 minutes by /user/accountinfo and a fresh /ut/auth, which is the
signature of hitting an error, returning to the main menu, and re-entering FUT.

Hypothesis: the client fetches mode options on entering the play area, caches them, and
later refuses Seasons from that cached EMPTY body without issuing another request. That
would explain the zero-requests-at-failure observation, which no response-shape theory
has been able to account for: the deciding fetch happened minutes earlier.

Stated as a hypothesis, not a finding. The correlation is real; the causation is not
established. Cheap to test: leaderboard_route already implements an options body behind
FUT_MODES=1.

Risk noted in advance: FUT_MODES=1 also enables /season, whose array-root shape is a
flagged freeze candidate. That risk cannot fire while the client never asks. If this
hypothesis is right, a populated options body is precisely what would make it ask for
the first time, so succeeding at step one arms step two.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 11:33:56 -07:00
funman300 dd8dddd7af fifa17-recon: log archaeology -- /club is only ever the SEARCH form
REBUILD_RESEARCH S18, from running futlog.py over the full 3044-request history.

The client has requested /club six times and EVERY ONE carried a query string
(year/type/count/position/level/nation/league/team/sort). The bare path has never
been requested. ENDPOINT_MAP says GET ut/%s/club is FutGetClubInfo, whose only
recognised member is user(0x36c), and concludes our itemData body is skipped and the
club list must therefore be empty. The club list is NOT empty; every card renders. So
either the query form dispatches elsewhere or the row is wrong. TODO/CONFIRM.

Relevant to the MY CLUB counter: we ignore the query string completely and return all
109 items to a request asking for count=11 with position and sort filters, and our
response carries no result total. A paged search response is exactly where a tab
counter would read its number from.

Also recorded: the unmapped view is a standing detector for suffix endpoints the URL
template table cannot show. It has caught four so far.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 11:30:33 -07:00
funman300 e5bfe58fc8 fifa17-recon: futlog.py -- client-filtered log reader, and a correction it caught
Implements the standing requirement recorded in priority-2026-08 S5: the User-Agent
filter is now the DEFAULT in the log tooling, not an option. The real client sends
ProtoHttp; our own probes send curl/* or Python-urllib/*. Reading the log unfiltered
gave this project a materially wrong picture of itself (/clubUser and /user/list had 93
and 180 hits, none from the game).

The old futlog.py was a one-off with a hardcoded path and no notion of who made the
request. Replaced with a real tool: timeline or summary, body/response display, path
regex, time window, status filter, and an --unmapped view that lists the endpoints the
client wants and we catch-all. --all and --probes exist for when you deliberately want
our own traffic.

Over the full 3044-request history: 486 requests came from the game.

IT IMMEDIATELY CAUGHT ME OVERSTATING SOMETHING. Yesterday's commit called the PUT /item
request shape "captured for the first time (the client had never successfully reached
this path)". False. There are NINE client PUT /item requests in the log, eight of them
during the failed attempts, every one carrying swap and tradeId:

  08:45:11 08:51:13 08:55:21 08:58:19 09:10:52 09:15:26 09:22:31 09:37:12 | 11:15:48

The request was on the wire and in the log the whole time. What was new was reading it.
Same class of error as the truncated decompile in S16: evidence already collected and
not looked at. REBUILD_RESEARCH S17 corrected.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 11:28:42 -07:00
funman300 5b139864ee fifa17-recon: SOLVED "Send to Club" -- it was the response body all along
Live 2026-08-04 with FUT_MOVE_BODY=ack and FUT_PACK_AUTOCLUB=0. Bought a bronze pack,
opened it, chose Send to Club. The session SURVIVED, the five cards persisted into the
club pile, and there was no ut/delete/auth logout -- the logout that accompanied all
seven previous attempts.

  11:15:48 PUT /item
    req {"itemData":[{"id":100000125,"pile":"club","swap":0,"tradeId":0}, ... x5]}
    res {"itemData":[{"id":100000125,"pile":"club","success":true}, ... x5]}
  11:15:49 GET /user/credits      session alive
  11:15:51 GET /hub               no error dialog
  11:16:06 GET /club?year=2017... MY CLUB opened

PUT ut/%s/item never was an ack endpoint. It builds per-item VERDICT records, and the
completion handler raises EVENT_CARDS_MOVE_CARD_FAILURE when the vector is empty or
success != 1. Every body this project ever returned, {} included, told the client the
move had FAILED, and the client ended the FUT session because that is what that event
does. We were failing our own move.

Defaults flipped: FUT_MOVE_BODY empty -> ack, FUT_PACK_AUTOCLUB 1 -> 0. The autoclub
workaround is retired.

New intel, captured for the first time because the client had never got this far: the
request carries `swap` and `tradeId` beside id/pile. We ignore both and the move
succeeded, so neither is load-bearing for a pending-to-club move.

PROCESS, and this is the part worth keeping. The FIRST attempt at this test produced
no PUT /item at all: FUT_PACK_AUTOCLUB=1 had already emptied the pending pile at
purchase time, so the reveal screen had nothing to assign and the client never issued
the request. The workaround for the bug was hiding the bug. Before testing a fix,
check the configuration still lets the client make the call the fix is for.

Two self-inflicted incidents, both recorded in REBUILD_RESEARCH S17:
- Restarting the server to inject a flag WHILE FIFA was running produced the exact
  "error connecting to FIFA 17 Ultimate Team" dialog this project spent weeks chasing,
  from a plain connection refusal during the ~30s window. Restart only at the main
  menu, and check the log for ProtoHttp requests before blaming a response.
- pgrep -f matched the invoking shell twice, killing it before the restart, because
  the same command contained the literal script name in a later clause.

Docs updated: REBUILD_RESEARCH S17 (the solve), priority-2026-08 S2/S3.1/S6 (next task
is now the MY CLUB counter), PROJECT_REPORT 6a, HANDOFF 5a plus the stale "FutMoveCard
has no skip handler" claim in S3 and a new 5d for Seasons/Draft.

380 + 51 checks green.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 11:20:03 -07:00
funman300 18d864908e fifa17-recon: priority doc for August 2026
The deliverable owed from the assessment brief. Ordered by cost of the measurement
that would settle each item, not by how important the outcome feels.

Headline reordering: the observation session ran and did NOT produce the match
shape. Seasons refuses with zero requests to any layer and Draft hangs, so both
routes into a match are blocked and /match is now behind a fix rather than in front
of one. The next task is instead one launch with FUT_MOVE_BODY=ack, which is the
only open problem where the decompiler has produced a verified necessary condition
that has never been satisfied.

Also recorded:
- The User-Agent split. Real client (ProtoHttp) vs our own probes. /clubUser and
  /user/list have 93 and 180 recorded hits and not one came from the game. Every
  "the client asks for X" claim predating the split is unsupported until rechecked.
- The narrowed-core measurement. The proposed boundary ("ownership and economy keyed
  by opaque integer item ids") is correct and every per-item prediction held, but
  narrowing is not what guts core: FIFA 17 relevance is. Six services totalling 869
  lines model features with no FIFA 17 endpoint at all, survive narrowing perfectly,
  and are worth nothing here. Suite: 61 of 101, not the 81 previously claimed.
  Recommendation is to reuse the scaffolding and the 61 tests, not the service layer.
- Port timing stays "after", led by the match-result-shape argument: three of the
  four queued items will change a response schema, and porting a placeholder schema
  means porting the correction too.
- test_fut_contract.py is now implementation-independent (380 checks over HTTP), so
  it can certify a Rust port. That is the port's main de-risking asset and it exists.
- A "What could make this plan wrong" section, including that a negative ack result
  is not a refutation, and that every negative claim in ENDPOINT_MAP.md is weaker
  than the corresponding positive one after the FutMoveCard retraction.

Standing requirements recorded, including the User-Agent filter default (required,
not yet implemented) and two wrong .rdata addresses still to correct.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 11:09:14 -07:00
funman300 f488793b34 fifa17-recon: RETRACT the FutMoveCard "no skip handler" claim; stage the ack shape
RETRACTION. This repo claimed, in REBUILD_RESEARCH S14c and in utas_server's
item_route comment, that FutMoveCard 0x180128600 "HAS NO SKIP HANDLER
(FUN_180135ff0 appears zero times, unique among FUT deserializers)" and "parses
only itemData -> dreamSquads". Every part of that is false. Full decompile:

  FUN_180135ff0 call sites : 2   (offsets 5006, 6080)
  atoms parsed             : 7   active dreamSquads id itemData pile reason success

Cause: the decompile was written out as src[:4000] and then searched. The function
is 6193 chars, so BOTH skip-handler call sites and four of the seven atoms lay past
the cut. An absence was reported from a truncated listing -- the same failure mode
as the Memory.getBytes bytearray scan that silently returned zero hits. Never
conclude an absence without asserting the searched region covers the function.

Cost: the false claim implied "any extra key desyncs this parser", which sent the
investigation after client-side state for seven attempts, and the derived premise
"the deciding factor is client-side state, not the wire" was wrong too.

VERIFIED SHAPE. PUT ut/%s/item is not an ack endpoint; it returns per-item VERDICT
records:
  id(0x15c)      INT     0x1801c79d0  -> record+0x00
  pile(0x226)    STRING  0x1801c7aa0  -> enum 0x180142650 (club=7 purchased=6 trade=5)
  success(0x2fa) BOOL    0x1801c7620  -> record+0x0c
  reason(0x279)  STRING               -> "Destination Full" = 0xf
  dreamSquads(0xe9) INT array;  else  -> FUN_180135ff0 (skip)

The completion handler raises EVENT_CARDS_MOVE_CARD_FAILURE when the record vector
is EMPTY or record+0x0c != 1, and success is initialised to '\0' per element. So
every body ever returned reported the move as FAILED, {} included. Quick sell
survives an identical {} because its callbacks read only the transport code and
ignore the body -- that is the whole asymmetry, and it was on the wire after all.

STAGED, NOT DEFAULTED. FUT_MOVE_BODY=ack emits the correct shape; the default stays
`empty` because sufficiency is untested. One launch settles it.

The ack is answered BEFORE the `if moved:` gate: under FUT_PACK_AUTOCLUB=1 the
cards are already in the club when the reveal asks to move them, so move_items()
returns nothing and a moved-derived body would be zero-record -- failing in exactly
the configuration ack exists to fix. Caught in review before it ran. success is
asserted only for ids that were moved now or are already in the club; anything else
gets an honest success:false rather than an invented verdict.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 11:05:38 -07:00
funman300 5d5198f5d1 fifa17-recon: match rewards, POW online layer, account backend, quick sell
Second session. FUT core loop, the EASFC/POW online layer, a central account
backend, and a lot of corrections. Everything risky is behind an env flag with
the default set to whatever was live-proven.

WORKING END TO END (live-verified this session):
  * match loop -- POST/PUT/POST/DELETE ut/%s/match, rewards via FutDestroyMatch
    (0x180121b60). Play a match, get coins, W/D/L updates.
  * packs -- buy, cards land in the club, session survives (FUT_PACK_AUTOCLUB=1)
  * quick sell -- POST ut/delete/%s/item was UNMAPPED and paid NOTHING; six cards
    were destroyed for 0 coins. Now credits discardValue.
  * POW/EASFC online -- the "EA FC servers unreachable" banner is powdll's layer,
    a THIRD http api on :8094 nobody had served. Redirect needs no root: powdll
    FUN_18005a460 reads FIFA_POW_URL from the same client-config store as
    ROSTERUPDATE_URL. FUT_POW=1.
  * account backend -- fut_account.py replaces 7 hardcoded copies of the persona
    across 5 files; club/persona/online-profile editable via CLI.

CORRECTIONS TO ENDPOINT_MAP (all re-extracted from the deserializers):
  * FutStoreGetPackTypes: id/packType/isPremium/quantity/saleType/purchaseLimit/
    purchaseCount are NOT skipped no-ops -- all are parsed. extPrice inner objects
    take externalPriceId(0x11a), not amount/currency.
  * FutMoveCard 0x180128600 has NO skip handler (FUN_180135ff0 appears zero times,
    unique among FUT deserializers) and parses only itemData -> dreamSquads.
  * class -> deserializer resolution: the name literal is preceded by a 4-BYTE
    HEADER and the factory LEA points at the header, so look up name_addr - 4.
    Six attempts failed on this; now ghidra_env.class_deser(). Unlocked 11 SBC/
    Draft schemas.
  * live-only endpoints the request table never lists: ut/%s/squad/list,
    ut/%s/user/club, ut/%s/club/stats/*, ut/%s/clientdata/<key>. The template
    table is a floor, not a ceiling -- the log is the only ground truth.
  * 163 RS4 call names exist; we served 17. All now served.

FIXED: club/stats/* was answering with the entire 28-item club inventory on every
poll (it fell through to the generic /club route).

UNSOLVED: the pack reveal's "Send to Club" (PUT ut/%s/item) kills the FUT session
whatever we answer -- {} included -- while its sibling quick-sell endpoint accepts
a bare {}. Seven hypotheses eliminated by live test, documented in
REBUILD_RESEARCH.md S14c so none get re-walked. FUT_PACK_AUTOCLUB routes around it.
Also unfixed: store tiles render "unknown" (displayGroup is parsed RECURSIVELY by
the same element parser; sending it FROZE the store, so FUT_STORE_GROUPS=1 is
default off).

Tests: test_fut_contract.py 380 (live, read-only) + test_match_rewards.py 51 (pure).

Note: fut_store.py carries some pre-existing uncommitted changes from before this
session (pack catalogue ids, pending-pile behaviour) that could not be separated
from this session's additions in the same file.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-04 09:42:59 -07:00
funman300 59934b4ef0 fifa17-recon: FUT squad blocker solved + userInfo delivered
The client now issues PUT /squad and the hub renders coins, record and the
squad roster. Three separate root causes, all verified live.

Squad blocker (the long-standing "client never sends PUT /squad"):
  AddPlayerToSquad, GetSquads and SelectSquadById issue ZERO network requests
  (pure local model reads/mutations, FutSquadServiceImpl vtable 0x180233ff0);
  only SaveCurrentSquad writes, and it is unguarded. The client simply needed a
  populated ACTIVE squad model, which arrives via the massinfo `squad` member.
  No response of ours was ever being rejected.

userMassInfo is NOT required to be {}:
  0x180174630 is a FLAT {userInfo, squad, settings, userData} body -- the old
  "wrapper key is user" note was wrong, and the historical freeze was the
  malformed squad member, not the envelope.

clubNameChangeAllowed must be false:
  sending true advertises a club-rename flow whose UI model is never populated;
  the client shows a naming prompt and dies confirming it (ACCESS_VIOLATION
  reading 0x0 at FIFA17.exe+0x71b8651, 4/4 runs, no CardsDLL frame and no request
  in flight). Isolated by a single-variable run; guarded by a contract check.

Endpoint/schema corrections found in live traffic, invisible to static analysis:
  * GET ut/%s/squad/list is a real endpoint and must return {"squad":[...]},
    not the active-squad object (the /list suffix is appended by the caller, so
    it never appeared in the request table)
  * PUT lands on ut/%s/squad/<id>, not a bare ut/%s/squad
  * userInfo currencies are read as name/funds/finalFunds/active -- there is no
    "value" key, so coins always rendered 0
  * squad-list elements take STRING formation/squadType, not ints
  * the CardsDLL script-API thunk<->name table was off by one (AddPlayerToSquad
    is 0x18004aa70; 0x18004aff0 is GetPotentialChemistry_Club)

FUT_MASSINFO / FUT_USERINFO ladders keep every step of the bisect reproducible.
Contract suite 311 -> 358 checks. Tooling added: PyGhidra harness (Ghidra's
Java/OSGi script path is broken on this box), minidump reader, live code grabber.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VUT92pz6RWKih9dSr8ZpxW
2026-08-03 20:47:16 -07:00
funman300 b05ccd7ce5 fifa17-recon: record market-implemented status + SBC config-flag leads
enableSquadBuildingSetsFeature / FUT/SBC_USE_STUBS (client-side stub SBCs) /
SBC_ELG_KEY_ found in CardsDLL -- the next lead for SBCs, to validate live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
2026-08-02 20:03:46 -07:00
funman300 4e89cce37d 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
2026-08-02 17:13:11 -07:00
funman300 2083a8821e fifa17-recon: SOLVED — real player cards render offline
A full 88-rated real FUT squad (Ronaldo/Messi/Suárez/Ramos/Kroos/Alba/
Hazard/Oblak/Alaba/Boateng) renders 100% offline, no EA servers.

Mechanism: the definition fetch was unnecessary — FIFA has all player
identity locally in dbdata.dll. The CLUB-SEARCH / Add-Player flow
(GET /club?type=player&count=N, served by our /club route) makes FIFA
resolve each item's assetId against its own local DB (real name/photo/
club/nation) and merge the rating/attributes our /club item carries,
caching a real record in the CardsDb store. No idList fetch, no leaked
data.

Recipe: utas FUT_SQUAD_STEP=s3v0 -> /club serves the full XI; in FUT,
Squads -> Add Player/search -> results render real -> add to slots.

docs/CARD_SYSTEM.md updated with the SOLVED mechanism + recipe.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
2026-08-01 20:48:03 -07:00
funman300 6ddd5e9d47 fifa17-recon: offline FUT squad-shell working + full card-system RE
Milestone: FIFA 17 Ultimate Team boots end-to-end on our offline backend
past every EA gate into the hub and a live Squads editor (correct 4-4-2,
5-star squad, no freezes).

Key findings this session:
- userMassInfo MUST stay {} (any content desyncs the massinfo parser
  0x180174630 -> tokenizer busy-loop freeze). Deliver the squad via
  GET /squad/0 (fetched on Squads-tab entry) instead.
- Player cards render generic because the card view-model (0x1800d7920)
  reads identity/rating/face from a resolved record at item+0x10, filled
  by a lookup (0x18011cca0) in the FUT item-definition std::map at
  CardsDb+0x160c0 -- which is EMPTY offline -> default blank record.
- Version advertising (itemDbVersion/checkServerDbVersion) is proven inert
  (JSON fields routed to the skip handler). Owned items don't auto-trigger
  a definition fetch. In-place map overwrite is dead (map stays empty).
- Definition-serving endpoints (item/resource, defid, item?idList) built +
  ready; the fetch trigger lives in the packed FIFA17.exe.

New: docs/CARD_SYSTEM.md (findings + ordered next-steps plan for real
player cards: patch-POC, dbdata extractor, drive FIFA17.exe fetch, or
live-memory store injection). Plus tools: fut_seed.py (squad ladder +
definition serving), fifadrive.sh, vgamepad.py, and the login-RE toolset.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
2026-08-01 20:24:30 -07:00
funman300 edab23f04a fifa17-recon: package the working offline FUT backend
Emulates FIFA 17's full online + Ultimate Team stack against an offline,
clean-room backend (no EA servers). Proven end-to-end 2026-08-01:
Origin login -> Blaze login -> device-trust -> the FUT hub.

Package:
- tools/openfut-fut.sh   one-command orchestrator (start/stop/status/restart)
- tools/root_arm.sh      idempotent host arm (sysctls, DNAT, /etc/hosts easw)
- tools/{lsx_responder_v2,blaze_responder_v3b,roster_server,utas_server,autopatch}.py
  the 5 servers (Origin LSX :4216, Blaze :42127/42130/42131, roster :8081,
  FUT/UTAS :8099) + heat2.py (Fire2/Heat2 TDF codec)
- FUT-RUNBOOK.md         runbook + gate-ladder troubleshooting
- docs/, tools/login_dump/*.md   the reverse-engineering write-ups

All findings are clean-room, from binaries we own; nothing from any leak.
The wire protocol maps 1:1 to FIFA 23.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PN5bmpDVQR1aXgefyWAt7o
2026-08-01 09:12:17 -07:00