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
This commit is contained in:
+56
-37
@@ -74,9 +74,12 @@ at `0x1801c7f1a`. This is the single most common way to break the game, and it h
|
||||
this project repeatedly. Arrays must be arrays; nested objects must be objects.
|
||||
|
||||
**Unknown keys are usually skipped safely** — most deserializers route an unrecognised
|
||||
atom to a value-skip handler (`FUN_180135ff0`), so extra fields are inert. **But not
|
||||
always**: at least one deserializer (`FutMoveCard`, `0x180128600`) has *no* skip handler
|
||||
at all, so any unexpected key leaves its value unconsumed and desyncs the reader.
|
||||
atom to a value-skip handler (`FUN_180135ff0`), so extra fields are inert. This document
|
||||
previously named `FutMoveCard` (`0x180128600`) as an exception with no skip handler at
|
||||
all. **That was wrong**, and the retraction is in §5a: it has two skip-handler call sites
|
||||
and parses seven atoms. No deserializer in this project is currently known to lack one.
|
||||
Treat any future "this class has no skip handler" claim as unproven until the search is
|
||||
shown to have covered the whole function.
|
||||
|
||||
### Working method
|
||||
|
||||
@@ -97,7 +100,7 @@ omission is skip-safe, a wrong shape freezes the game.
|
||||
| Squad building — `PUT /squad/<id>` fires, persists across relaunch | ✅ |
|
||||
| Squad roster ("MY SQUADS") | ✅ |
|
||||
| Transfer market — browse, bid, buy-now, sell, watchlist | ✅ |
|
||||
| Packs — buy, cards land in club | ✅ (via a workaround, see §5) |
|
||||
| Packs — buy, reveal, Send to Club | ✅ (the workaround is retired, see §5a) |
|
||||
| Quick sell — destroys cards, credits coins | ✅ |
|
||||
| Match loop — create/ready/play/destroy + coin rewards | ✅ implemented, **never played in-game** |
|
||||
| Online/EASFC status bar (no "servers unreachable") | ✅ when enabled |
|
||||
@@ -114,37 +117,34 @@ screens were broken by shipping "corrections" on by default.
|
||||
|
||||
## 5. Open problems
|
||||
|
||||
### 5a. "Send to Club" kills the FUT session — UNSOLVED
|
||||
### 5a. "Send to Club" kills the FUT session — SOLVED 2026-08-04
|
||||
|
||||
Opening a pack shows the cards correctly. Choosing **Send to Club** results in:
|
||||
Opening a pack shows the cards correctly; choosing **Send to Club** used to produce
|
||||
*"there has been an error connecting to FIFA 17 Ultimate Team"* and a logout, seven
|
||||
attempts running. The cards always moved server-side; only the acknowledgement was
|
||||
rejected.
|
||||
|
||||
> *"We are sorry but there has been an error connecting to FIFA 17 Ultimate Team. You
|
||||
> will be returned to the FIFA 17 Main Menu."*
|
||||
The cause was the response body. `PUT ut/%s/item` returns per-item **verdict** records,
|
||||
not an acknowledgement, and the completion handler raises
|
||||
`EVENT_CARDS_MOVE_CARD_FAILURE` when the record vector is empty or when `success != 1`.
|
||||
Every body the project returned, `{}` included, reported the move as failed. The fix is
|
||||
the real shape:
|
||||
|
||||
The client then POSTs `ut/delete/auth` (logout) about a second later. The cards **do**
|
||||
move correctly server-side every time — only the acknowledgement is rejected.
|
||||
```json
|
||||
{"itemData":[{"id":100000125,"pile":"club","success":true}, ...]}
|
||||
```
|
||||
|
||||
**Eliminated by live test:**
|
||||
Live: five cards to the club, session survived, cards persisted, no `ut/delete/auth`.
|
||||
The autoclub workaround is retired.
|
||||
|
||||
| Hypothesis | Result |
|
||||
|---|---|
|
||||
| Missing `chemistry` field | Failed without it too |
|
||||
| Extra keys + no skip handler in the deserializer | Real finding; sending *only* the parsed key still failed |
|
||||
| The response body at all | **A bare `{}` also fails** |
|
||||
| A network failure ("error connecting") | Zero non-loopback connections during a failure — that string is FIFA's *generic* session-error text |
|
||||
| Missing per-call service URLs | 146 were genuinely missing and are now served; unchanged |
|
||||
| The POW layer saturating HTTP | Same failure with POW fully disabled |
|
||||
| The reveal screen's exit path | **"Quick Sell All" from the same screen works** |
|
||||
| The club not being loaded | Loading MY CLUB first, then moving, still failed |
|
||||
Two corrections this closed, both worth carrying forward:
|
||||
|
||||
**The key asymmetry:** quick sell (`POST ut/delete/<sku>/item`) and move
|
||||
(`PUT ut/<sku>/item`) both receive an identical bare `{}` from the same server, with the
|
||||
same headers and status. Quick sell succeeds; move kills the session. Whatever decides
|
||||
this is **client-side state, not the wire**.
|
||||
|
||||
**Workaround in place:** pack contents are deposited straight into the club at open time
|
||||
and the pending pile is kept empty, so the client is never offered a move. Packs are
|
||||
fully usable; the cost is that the reveal screen shows nothing to assign.
|
||||
- The repo's claim that this deserializer had **no skip handler** and parsed only two
|
||||
keys was false. It came from searching a truncated decompile. It implied the body
|
||||
could not be at fault, which is what sent seven attempts after client-side state.
|
||||
Never conclude an absence from a truncated or unverified-length extraction.
|
||||
- The quick-sell asymmetry was not evidence of client state. Quick sell's callbacks
|
||||
read only the transport status code and never touch the body.
|
||||
|
||||
### 5b. "MY CLUB" counter always reads 0 — UNSOLVED
|
||||
|
||||
@@ -171,6 +171,24 @@ element parser**. Sending it populated **froze the store** (the type-desync busy
|
||||
it is behind a flag, default off. Doing it properly needs the group's own field set worked
|
||||
out rather than a self-referential copy of the pack.
|
||||
|
||||
### 5d. Seasons and Draft refuse — UNSOLVED, and not obviously server-side
|
||||
|
||||
Selecting **single-player Seasons** raises *"There was a problem communicating with the
|
||||
FIFA Ultimate Team servers"* while making **zero requests to any layer**. UTAS, Blaze and
|
||||
POW logs show only pings and one census subscription across the whole failure window. No
|
||||
response can be wrong because no request was made. POW is eliminated (same failure with it
|
||||
enabled and disabled).
|
||||
|
||||
**Online Draft** hangs the client rather than crashing it (process alive, no dump). The
|
||||
one suspicious thing on the wire is `GET ut/%s/squad/mode/draft/state`, which our generic
|
||||
`/squad` route answers with a full active-squad object: 23 slots, nested `itemData`, a
|
||||
33-integer formation string. The real class wants `roundsInfo` plus a state enum, so this
|
||||
is a textbook type-desync candidate and the timing matches. **Nothing has isolated it**;
|
||||
it is a suspect, not a cause.
|
||||
|
||||
Both matter beyond themselves, because they are the only two routes into a match, and the
|
||||
`/match` request shape has therefore never been captured.
|
||||
|
||||
---
|
||||
|
||||
## 6. Notable reverse-engineering findings
|
||||
@@ -230,16 +248,17 @@ out rather than a self-referential copy of the pack.
|
||||
|
||||
## 9. Where help would be most valuable
|
||||
|
||||
1. **The move-vs-quick-sell asymmetry (§5a).** Two sibling endpoints, identical responses,
|
||||
one works. What client-side precondition could a "move item between piles" operation
|
||||
have that a "discard item" operation does not — a loaded destination collection, a
|
||||
known pile capacity, a valid target index?
|
||||
2. **The MY CLUB counter (§5b).** Given the client demonstrably *has* the items and
|
||||
1. **The MY CLUB counter (§5b).** Given the client demonstrably *has* the items and
|
||||
*renders* them, what else could a tab counter read from? Note that a sibling counter in
|
||||
the same bar works correctly.
|
||||
3. **Whether either is fixable server-side at all**, or whether the honest answer is that
|
||||
the deciding logic lives in the packed executable and only live instrumentation can
|
||||
settle it.
|
||||
2. **Seasons refusing with zero requests to any server (§5d).** The client raises a
|
||||
"problem communicating with the FIFA Ultimate Team servers" without contacting
|
||||
anything. Nothing on the wire can be wrong because nothing went on the wire.
|
||||
3. **Whether the remaining problems are fixable server-side at all**, or whether the
|
||||
deciding logic lives in the Denuvo-packed executable and only live instrumentation can
|
||||
settle it. Note that this question was asked about `Send to Club` too, and there the
|
||||
answer turned out to be a plain wire fix, so treat "it must be client-side" as a
|
||||
hypothesis needing evidence rather than a fallback explanation.
|
||||
|
||||
Useful framing: this project's failures have almost always come from proposing a fix
|
||||
before testing the assumption under it. Hypotheses that come with a cheap way to
|
||||
|
||||
Reference in New Issue
Block a user