# Connection-gate findings (clean-room) **Status:** observed behaviour only — derived from running the client and reading the repack's own files. No EA source used. Unconfirmed values are marked `TODO/CONFIRM` rather than guessed. ## Components (observed) | Component | Role | |---|---| | `FIFA23.exe` | Game. Statically links EA's **Ebisu SDK** (online) and **DirtySDK/ProtoSSL** (transport). Loads at fixed base `0x140000000` — no ASLR seen across launches. | | `_ProtoSSLSendPacket` @ `FIFA23.exe+0xEFA530` | DirtySDK TLS record assembler — plaintext in, encrypts in place. Already located + hooked. | | `anadius64.dll` | anadius EA-app/Origin **LSX server** emulator (ASLR'd). Provides offline DRM / entitlements / persona so the game *launches*; deliberately keeps it **offline**. | | `FakeEAACLauncher` | Anti-cheat bypass only. Irrelevant to the online gate. | ## Observed online-startup flow 1. Ebisu SDK opens LSX socket(s) to anadius; a **challenge/response handshake** completes — captured XML: `` / `` / ``, `sender="EALS"`, `ContentId 16115019`. 2. River/PIN telemetry `` is emitted. 3. **No further socket traffic.** Clicking Ultimate Team → "connecting to the EA Servers…" → **zero** network attempts (no Blaze DNS, no `connect`, nothing on `send`/`recv`/`WSASend`/`WSARecv`) → falls back to offline. ## Conclusion: the decision is upstream and in-process - The online/offline verdict is delivered through anadius's **in-process detoured Ebisu calls**, not socket messages. (No `GetAuthCode`/`GoOnline`/ entitlement query appears on any socket, sync or async.) - The game therefore **never reaches DirtySDK/ProtoSSL for Blaze** — it aborts before any `ProtoSSLConnect(gosredirector…)`. The gate is an **Ebisu-SDK connection-state / online-session check upstream of the transport.** - `ONLINE_ACCESS` is a `Blaze::Nucleus::EntitlementType` (enum value `1`). anadius's `GoOnline` LSX handler (`anadius64.dll+0x2BB90`) is a success stub; `anadius64.dll+0xB6B1` is a large entitlement/profile builder that *does* reference `ONLINE_ACCESS`. Reverse-engineering that entitlement-status logic is the **wrong fight** (see strategy). ## Strategy (decided) Do **not** make anadius report "online." anadius exists to keep the game offline, and it can't be removed without breaking launch/DRM. Instead: > Let the game run its **real** online flow and answer that flow ourselves via > the hook + brain. Target the Ebisu connection-state check the FUT-entry path > polls; make the game *pass* it so it issues the real `ProtoSSLConnect` to the > Blaze redirector → our redirect + ProtoSSL hook capture the real frames. This deletes the "reverse-engineer anadius's entitlement logic" problem. ## Next RE step (converges with the ProtoSSL hook) 1. Locate and **read-only hook `ProtoSSLConnect`** (same DirtySDK module and technique that found `_ProtoSSLSendPacket`). Confirms the game never initiates the Blaze connection (pins the gate strictly upstream) and gives us the exact symbol that will flip from "never called" to "called" on success. 2. Locate the Ebisu **connection-state getter** the FUT-entry / "connecting" UI polls. Candidate string anchors (observed): `ONLINE_STATUS_EVENT`, `GoOnline` result handling, the "connecting to the EA Servers" UI trigger. 3. Determine the minimal intervention to make that getter report **connected** (out-hook it in our `version.dll`, winning over anadius's detour) so the game proceeds to `ProtoSSLConnect`. - `TODO/CONFIRM`: whether out-hooking the getter is sufficient, or secondary checks (auth-token presence) also gate the attempt. --- ## M1 results (read-only investigation) Method: `protossl-scan` (xref / disasm / callers, app-module-restricted) against the live client, plus the existing `connect`/DNS/LSX hooks. Observed behaviour only — no EA source. ### Point 1 — is `ProtoSSLConnect` reached on the "connecting" abort? **NO — gate is upstream. CONFIRMED.** - The existing `connect` / `GetAddrInfoW` / `getaddrinfo` hooks show the client makes **zero** network attempts during the "connecting to the EA Servers" abort: no DNS for any `gosredirector.*` host, no `connect` to any external address. - The DirtySDK/ProtoSSL transport is therefore never reached — the abort is entirely upstream and in-process. - `ProtoSSLConnect` has no debug string and sits behind the DirtySDK API, so an explicit hook on it isn't needed to prove this; the behavioural evidence is conclusive. `TODO/CONFIRM`: locate `ProtoSSLConnect` by signature later, as a positive M2 trip-wire (it should fire once we flip the gate). ### Surface mapped (clean-room) - **Redirector host table** (`FIFA23.exe` .rdata, pointer table @ `+0x83FC858`): `spring18.gosredirector.{sdev,stest,scert}.ea.com` + production `spring18.gosredirector.ea.com`. - **Redirector request/response config** (same region): `X-BLAZE-ERRORCODE`, **`Authorization:`**, `` — the redirector request carries an **Authorization header (a Nucleus token)**. - **Enum-name tables** (serialization only, NOT decision code): `ONLINE_ACCESS` (`Blaze::Nucleus::EntitlementType` = 1), `ONLINE_STATUS_EVENT`, … These are reflection tables keyed by enum *value*; string-xref of them is a dead end for the decision (the decision compares values, not strings). ### Point 2 — name the connection-state function: **PARTIAL.** - The exact connection-state decision is behind heavy C++ vtable indirection (the redirector config's two code pointers resolved to a virtual-dispatch thunk @ `FIFA23.exe+0x27BD4C0` and a `ret 0` stub @ `+0x4F3CBC0`). The memory-scan toolkit (string / pattern / xref / callers) cannot efficiently navigate this. - **Pinning the exact function needs a static disassembler with decompilation (Ghidra / IDA) on `FIFA23.exe`.** `protossl-scan` remains the bridge to the live process (mapping static addresses to the ASLR'd runtime, confirming hits, installing hooks). - `TODO/CONFIRM`: name the connection-state getter by module+offset via Ghidra. ### Point 3 — gate count: **TWO gates (state + token). Evidence-backed.** - The online-connect path **reads/requires an auth (Nucleus) token**: the redirector request carries an `Authorization:` header, and the SDK surface has `GetAuthCode` / `FakeAuth` / ``. So it is **not** a single connection-state boolean flip — even with the state forced "online", the client needs a valid token to build the redirector request. - **Conclusion for M2: plan for two gates** — (a) the connection-state decision, and (b) supplying an auth token the client accepts. - `TODO/CONFIRM` the exact abort point (no-token vs state-says-offline vs both) — needs Ghidra-level control-flow tracing. ### Dynamic probe result (read-only) — `GoOnline` is NOT the gate A read-only detour on anadius's `GoOnline` handler (`anadius64.dll+0x2BB90`) shows the game **does** call `GoOnline` during the "connecting" attempt (incl. on the Ultimate Team click) and anadius returns success — **yet no Blaze connection follows** (still zero external `CONNECT`/DNS). Therefore the gate is **downstream of `GoOnline`**: the game decides to go online and the request is accepted, then it aborts at the **auth-token step (`GetAuthCode`) and/or while waiting for the "online established" status callback** (`ONLINE_STATUS_EVENT`), and times out into offline. This sharpens the two-gate picture: `GoOnline` passes; the real blocker is the **token / online-status step**. `TODO/CONFIRM` which (token-missing vs status-never-fires) — via a `GetAuthCode` probe and/or Ghidra. ### Recommendation / next Name the downstream gate. Two complementary routes: - **Dynamic:** probe anadius's `GetAuthCode` handler (does it return a token or fail?) — distinguishes token-gate from status-callback. - **Static (Ghidra):** xref the `GoOnline` / `GetAuthCode` / `ONLINE_STATUS_EVENT` strings in `FIFA23.exe` to find the FUT online-flow code that issues `GoOnline` then waits for the token/status, and decompile it. FIFA isn't ASLR'd, so Ghidra addresses (image base `0x140000000`) map 1:1 to our recorded offsets. --- ## M1 COMPLETE — the gate is `GetInternetConnectedState` Found without Ghidra, by reading anadius's LSX **command-registration** function (`anadius64.dll+0x14C0`), which lists every command-name → handler inline. NB the `lea rax,[handler]` is **offset by one** from the `lea rdx,[name]` in that listing (verified empirically: `+0x27060` decompiles to `GetProfile` — it builds a `GetProfileResponse` for persona "fun"). Corrected handler map: | anadius LSX command | handler (anadius64.dll + …) | |---|---| | GetProfile | 0x27060 | | **GetInternetConnectedState** | **0x27790** | | GoOnline | 0x2BB90 | | GetAuthCode | 0x2BBC0 | ### The gate, named: `GetInternetConnectedState` @ `anadius64.dll+0x27790` It serializes an LSX `InternetConnectedState` response with a `connected` attribute whose value is: ``` connected = (byte[+0xCAB1B] != 0 || byte[+0xCAB1A] != 0) ? str(+0xAF530) : str(+0xADE64) ``` Both flag bytes **default to 0**, so it reports the offline value (`+0xADE64`). That is precisely why the client sits at "connecting to the EA Servers" and falls back offline. ### Answers to the three M1 points 1. **ProtoSSLConnect reached on the abort? NO — gate upstream.** Confirmed. 2. **The connection-state function:** `GetInternetConnectedState` @ `anadius64.dll+0x27790`. Decision = the two-flag branch above. 3. **Gate count: ONE actionable gate.** The auth token is already satisfied — `GoOnline` (`+0x2BB90`) is called during the attempt and returns success, and anadius provides a fake auth code; the blocker is purely the connection-state. So M2 = make `GetInternetConnectedState` report **connected** (then the game proceeds with its existing token). ### M2 attempt results — the gate is an async EVENT, not a poll Tried (read/write, EAAC neutralized): - Forced `GetInternetConnectedState` → connected (set flags +0xCAB1A/+0xCAB1B → value `"1"`; strings confirmed: +0xADE64 = `"0"` offline, +0xAF530 = `"1"` connected). - Flipped `GoOnline` to report `"1"` (replicated its builder `+0x25BE0` with the connected string instead of `"0"`). **Neither made the game proceed.** Both handlers fire, no crash — but the game **keeps retrying `GoOnline` every ~7s** and never attempts the Blaze connect. That retry-on-timeout pattern means the FUT-online flow is **event-driven**: the game submits `GoOnline`, gets success, then **waits for an async "online established" event** (ONLINE_STATUS_EVENT-class) that anadius — being offline-only — never pushes (the only `` it ever sends is the Challenge handshake). So flipping poll/return values can't unblock it. **Implication:** getting past "connecting" requires **emulating the EA-app online event sequence** the game waits for (inject the online-status event over LSX, in the format/order EbisuSDK expects), not a single function flip. This is a substantially deeper task (and precedes the Blaze backend emulation). Next: trace the FIFA-side FUT online-flow (what the game does after `GoOnline` and exactly which event/condition it waits on) — Ghidra on FIFA23.exe (import saved at `C:\openfut\gh-proj`), or RE anadius's LSX event-send path. `TODO/CONFIRM`. ### M2 deeper finding — worker-thread + event architecture (confirmed) A live call-stack capture from inside the GoOnline detour (manual stack scan, bounded by `GetCurrentThreadStackLimits`) found **zero FIFA23.exe frames** and showed `sp` sitting ~2.4 KB below the thread's stack top. So the handler runs at the top of a short stack — i.e. on an **anadius worker thread** (IOCP/threadpool), not FIFA's calling thread. anadius **queues** the GoOnline command and a worker services it. Combined with the retry behaviour, the architecture is now clear and three-way corroborated: **FIFA calls `EbisuSDK::GoOnline` → anadius queues it → returns → FIFA waits for an async "online established" event → anadius (offline-only, no online-event code) never pushes it → timeout/retry.** No handler-response flip can unblock this; the game waits on a *push* anadius never produces. **Conclusion:** crossing this gate requires emulating the EA-app online-event sequence (synthesize + inject the online-status event on the worker→game callback / LSX path, in EbisuSDK's expected format) — a research-grade emulation effort, preceding the Blaze backend. The cheap in-process flips are exhausted. Reaching the FIFA-side flow would need either: (a) locate `EbisuSDK::GoOnline` in FIFA23.exe via anadius's detour table, then `callers` to the online-flow; or (b) find anadius's LSX event-send path and reverse the online-event format. Both are deep. `TODO/CONFIRM`. ### Path A attempt: reach the FIFA-side online-flow (blocked with live toolkit) Goal: find `EbisuSDK::GoOnline` in FIFA23.exe → `callers` → the game's online-flow → read what event it waits on. Every angle our live-memory toolkit offers is blocked: - **String xref:** FIFA23.exe contains no `"GoOnline"` string (typed SDK call, not a string-built command). - **Call-stack from the handler:** GoOnline runs on an anadius worker thread; a bounded stack scan finds zero FIFA frames. - **Detour scan (`jmpscan`):** scanning FIFA23.exe for function-entry `E9` jumps leaving the module yields ~3875 hits — overwhelmingly false positives, because the 505 MB image is mostly embedded *data* (not code), and the real detours don't cleanly cluster. (A .text-section-only scan would help but the chain after — isolate GoOnline → callers → event format → emulate — remains long and each link is gated by SDK abstraction / anadius indirection / worker threads.) **Verdict:** crossing this gate to *playable* FUT is research-grade. It needs an interactive disassembler (IDA/Ghidra GUI, human-driven) to trace the EbisuSDK online-flow, and then a full EA-online + Blaze emulator. The live-memory toolkit (string/xref/disasm/callers/read/jmpscan) has been exhausted for the FIFA side. The clean-room spec (this document) is the finished, valuable artifact. - Check whether the flags at `+0xCAB1A` / `+0xCAB1B` are settable via anadius config / a hidden option (cheapest flip). - Else out-detour `GetInternetConnectedState` in our `version.dll` to force the `+0xAF530` ("connected") path. - Then watch for the client to attempt the real Blaze connect (our `connect` redirect + ProtoSSL hook capture the first plaintext — milestone M3). - `TODO/CONFIRM`: exact text of the offline/connected value strings (`+0xADE64` / `+0xAF530`); whether any secondary check gates the attempt after the state flips.