# FIFA 17 SBC response reconciliation **Verdict:** the live client receives HTTP 200 for `GET /ut/game/fifa17/sbs/sets`, but the typed `FutSBCLoadCategoryDetailsServerResponse` deserializer is not invoked. The evidence does **not** identify a server-controlled header, envelope field, or correlation value that can fix this. The previous `0x180154860` “SBC continuation” diagnosis was based on the wrong request class and is retracted. ## Scope and authority This pass used only: - the shipped `CardsDLL_Win64_retail.dll` copied to `/tmp/fut/cardsdll.dll`; - read-only `/proc//mem` access to the running game; - the local OpenFUT request log; and - existing clean-room notes and scripts in this repository. No game memory was written, no breakpoint was inserted, and no service or game process was restarted during the measurement. ## Fresh live observation The control run used fresh FIFA process **PID 59054**. The CardsDLL mapping resolved to `0x6ffffc140000`, giving slide `0x6ffe7c140000`. Bytes at static control function `0x180180d00` matched the on-disk DLL, proving the mapping/slide before data reads. At the FUT hub, before opening SBC: - `A = *[0x1802e6398] = 0xb78f7c50`; - `M = *(A+0x20a68) = 0`; - hub cache byte `*(A+0x1fd70+0x28) = 1` (fresh hub response ready); and - SBC cache byte `*(A+0x1f9d8+0x28) = 0`. The user then opened the SBC tile. The real client exchange was: ```text [10:20:20] GET /ut/game/fifa17/sbs/sets User-Agent: ProtoHttp 1.3/DS 15.1.2.1.0 (Windows) Accept: application/json Content-Type: application/json X-UT-SID: OPENFUT-SID-0000000000000001 Accept-Encoding: gzip -> 200 {"categories":[...]} ``` The game displayed “There was a problem communicating with the FIFA Ultimate Team servers.” With that modal still open, the same slide was re-proved and `M` was still exactly zero. ### What `M == 0` proves The typed `/sets` deserializer is `0x18017b2b0`. At `0x18017b309`–`0x18017b327` it obtains the FUT root and calls vtable slot `+0x9b0`, the lazy getter `0x18011b7d0`. That getter allocates and stores `A+0x20a68` before the deserializer examines the root object or the `categories` key. Consequently: - valid JSON would leave `M` non-null; - malformed or empty JSON reaching this function would also leave `M` non-null; and - `M == 0` after the completed HTTP transaction means `0x18017b2b0` was not invoked. The normal reset of `M` is `0x180114ee0`; its observed use belongs to broad FUT-root initialization/reset work, not the `/sets` completion path. There is no evidence that the deserializer ran and then immediately cleared `M` during this transaction. ## Correct class map Three classes were conflated in earlier notes: | Function/class | Proven URI | Role | |---|---|---| | `FutSBCLoadCategoryDetailsServerResponse`, request URI builder `0x18017a980`, factory `0x18017aa10`, response deser `0x18017b2b0` | `/sets` under the `ut/%s/sbs` base | Initial category/set list; this is the live failing request | | `FutSBCSetDataServerResponse`, factory `0x18016fca0`, deser `0x18016fe90` | `/squadBuildingSets` (`0x18022bd88`) | Parses `reset`; not the observed `/sbs/sets` request | | `FutLoadSetTypesServerResponse`, deser `0x180154990` | `/challenge/%d/squad` (`0x1802270e0`) | Parses `challengeId`, `playerRequirements`, and `squad`; later challenge flow | This corrects two prior claims: 1. `FutSBCSetDataServerResponse` does **not** share the literal `/sets` URI in this binary; its URI string is `/squadBuildingSets`. 2. `0x180154860` is not a dedicated completion continuation for the initial category-list request. `0x180154830` is a generic callback thunk used by multiple request classes, while the nearby `0x180154990` parser and `/challenge/%d/squad` URI belong to `FutLoadSetTypesServerResponse`. Therefore the earlier hub-versus-`0x180154860` comparison contrasted the hub with a later challenge-squad operation, not with `GET /sbs/sets`. Its proposed “copy the hub re-arm path” fix is unsupported for the category-list failure. ## What the generic completion code actually checks The shared request completion routine `0x18016cca0`: 1. calls request vtable slot `+0x80` at `0x18016cd32` to create the class-selected typed response object; 2. stores the received status at request offset `+0x48` (`0x18016cd3d`); and 3. compares it with decimal 200 at `0x18016cdd0`. Exactly 200 takes the success branch to `0x18016d0b9`. Non-200 status invokes the error translation path through request slot `+0x60` first. Response construction is selected by the request vtable; it is not selected by an HTTP response header or a JSON envelope field. No pre-deserialization branch found in this path reads `Content-Type`, a request/correlation ID, the `X-UT-SID` response header, or a top-level JSON key. The live server already supplies the one proven transport-level success input: status 200. ## Hub comparison The fresh hub response was consumed successfully and set the hub cache byte to one. After the subsequent navigation its resting value returned to zero. The SBC cache byte remained zero. This confirms that cache `+0x28` is transient async-result/TTL state; a later resting zero does not establish which completion branch ran. The previous report's live snapshot—where both values were zero long after the requests—was therefore insufficient to infer the hub/SBC divergence. The fresh before/after measurement supersedes it. ## Server-fixability verdict **Not demonstrated.** In particular: - changing the category JSON cannot make the typed parser start, because the lazy store is allocated before any JSON key is inspected; - the server already returns the proven success status, 200; - request-class/response-class selection is client-owned; and - no header, envelope, or correlation field was found feeding a pre-parser decision. This does not mathematically prove that no transport variation could ever affect the client. It does prove that the specific server-fix candidates proposed by the killed workflow were speculative and had no reading instruction behind them. ## Exact remaining unknown and next measurement The unresolved boundary is between: ```text ProtoHttp completion with status 200 -> class-selected response object creation -> delivery of response bytes/SAX cursor -> response vtable +0x08 (`0x18017b2b0`) ``` The next useful experiment is transient tracing of calls—not another resting-state scan. Instrument, in a disposable/local diagnostic build or a non-mutating tracing facility: - request factory `0x18017aa10`; - typed deserializer `0x18017b2b0`; - generic completion entry `0x18016cca0` and its status at `0x18016cdd0`; and - the generic response-body/SAX dispatch site that calls response vtable slot `+0x08`. Record whether the factory is called, whether it returns an object with vtable `0x18022e5b0`, and whether a body/SAX object is delivered. That separates three remaining client-side possibilities: wrong request instance despite the URI, typed object created but body not attached, or body attached but virtual deserialization dispatch skipped. Until that transient trace exists, the defensible implementation direction remains the client-side hook described in `docs/sbc-hook-dll-spec.md`, but its rationale must be stated as “native category deserializer is not reached,” not the retracted `0x180154860` hub-rearm theory. ## 2026-08-07 passive-trace result: deserialization is proven The first gated passive client trace supersedes the final inference above. During exactly one SBC navigation, with every mutation feature disabled, the hook recorded: ```text SBC_TRACE: factory entry=1 exit=1 tid=652 this=0xb80cd910 result=0x7a99178; deser entry=1 exit=1 tid=652 this=0x7a99178 reader=0x7fcff7f8 result=true ``` The matching UTAS request occurred at `11:09:01`: `GET /ut/game/fifa17/sbs/sets` returned HTTP 200 with one category and two sets. No degraded hook state was reported, and FIFA remained alive until the operator closed it after the single permitted attempt. This proves all of the following for the observed request: - the category response factory is called exactly once and returns a non-null object; - the native category deserializer is called exactly once on that same object; - the body reader is non-null; - deserialization returns success (`true`); and - both calls return normally on the same native thread. Therefore the earlier `M == 0` resting snapshot did not prove that `0x18017b2b0` was skipped. The failure boundary is now strictly **after successful native deserialization**. The next measurement must trace the response object's post-deserializer completion, ownership handoff, and publication into the SBC UI/cache collection. Repeating the factory or deserializer trace will not add useful information. ## 2026-08-07 post-deserializer handoff trace A second one-shot run combined the factory/deserializer probes with atomic replacements of the category request vtable slots `+0x90` (completion callback dispatch) and `+0x88` (response ownership transfer). All four calls completed on native thread 656: ```text request = 0xb80cdfe0 factory response = 0x7c94808 deserializer this = 0x7c94808, result=true +0x90 callback argument = 0x7c94808 +0x88 owner-slot address = 0xbc51f7e8 ``` The matching `GET /ut/game/fifa17/sbs/sets` at `11:24:00` returned HTTP 200, and the same communication modal appeared. Both callback probes reported `entry=1 exit=1`; no degraded hook state or process failure occurred. This proves that the parsed response reaches the category request's completion dispatcher and that its ownership-transfer routine also returns normally. The remaining failure boundary begins at the receiving owner object's vtable `+0x18` consumer invoked from `0x1801631e0`, or later collection/cache/UI validation. Network transport, response construction, native parsing, callback dispatch, and request-side ownership handoff are no longer candidate root causes.