Full gate map: LSX bootstrap DONE; EbisuSDK online report DONE at SDK level
(events parse, listener no-op); EASFC subsystem DORMANT (built at boot, never
activated); GetAuthCode/Blaze never reached. The missing link is runtime
'go online' activation between the SDK online-report and the EASFC subsystem.
No single activation flag isolable via byte-scan — the subsystem is pervasive
(GetConnState has 61 callers), consistent with online-mode being a whole
subsystem activation, not a switch. Next tool: Ghidra/IDA or continued dynamic
probing. Confirms the M2 frontier with pre-Blaze infra present.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Probe shows connect states are constructed at boot (ctor x4) but never touched
(GetConnState=0, no vtable method fires). The online-connect subsystem is dormant;
nothing activates it. FIFA parks in the earlier EbisuSDK-login layer and never hands
off to EASFC. Confirms we're at the M2/Blaze boundary with pre-Blaze infra present.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Live probe result: the Nucleus session context [M+0x778] is non-null (0xba8315a0),
but nucleusConnectREST/Trusted and the connect-state tick are NEVER called. FIFA
completes LSX bootstrap, parks at 'connecting', and never starts the EASFC online
connect. So the gate is an upstream trigger, not the null-check the static analysis
suggested. Also documents the FLE hook-injection path (deploys must target the FLE dir).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Dig 2: traced the GetAuthCode send path up to FIFA's game-side
nucleusConnectREST (+0x2861910) / nucleusConnectTrusted (+0x5078370).
Both null-check the same Nucleus session context [NucleusManager+0x778]
and short-circuit when null (REST returns 0/sends nothing; Trusted returns
HRESULT 0x80060000 not-ready). So the online->auth gate is that +0x778 is
never instantiated -- consistent with the no-op-listener finding (event
updates stored state but doesn't drive the login state machine to create
the context). Next: a targeted dynamic probe on REST + the +0x778 read.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Traced the far end of the online gate: FIFA's GetAuthCodeT::Serialize
(+0x278d2c0) yields the exact LSX request it would send to start the
Nucleus->Blaze chain -- <GetAuthCode UserId ClientId Scope AppendAuthSource/>.
It is wrapped by a GetAuthCodeJob in the EASFC online-job module. The wall
is not the LSX wire or event delivery (events parse fine) but that FIFA's
EASFC controller never enqueues the job -- it polls cached online state and
our single isOnline flag doesn't satisfy the combined condition. Closes the
is-it-the-events question (no); next artifact is the controller poll guard.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Add an unsolicited event-push path to the LSX server: after the session
establishes, re-push (every 2s) a Login then OnlineStatusEvent, encrypted with
the live session seed and null-terminated like any session frame. Formats RE'd
from FIFA23.exe's OriginSDK deserializers (authoritative):
<Login UserIndex="0" IsLoggedIn="true" LoginReasonCode="0"/> (LoginT, +0x287a30)
<OnlineStatusEvent isOnline="true"/> (OnlineStatusEventT, +0x28a4d0)
Value encoding is the true/false string literal (FIFA's bool parser at
FIFA23.exe+0x28fb50 compares to "false"); isOnline="1" is parsed as not-online.
Result (verified with in-process probes): FIFA parses both events every cycle and
returns success, and stops its impatient GetProfile polling / sets INGAME presence
— the first time FIFA has been driven "online" at the EbisuSDK level (anadius, being
offline DRM, never does this). But FIFA does not proceed to the GetAuthCode→Nucleus
→Blaze chain; the gate is its game-side event consumer (virtual dispatch at
FIFA23.exe+0x274d4e2), not delivery. Full trail in docs/connection-gate-findings.md.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Implement the LSX (EA App bootstrap, TCP :3216→:3217) server in the bridge
and clear the loading-screen crash gate. FIFA 23 now completes the full LSX
handshake + session against OpenFUT and advances to the online/Blaze phase
("<persona> is connecting to the EA Servers…") — verified live, not a cached
session (the displayed persona "fun" comes from our GetProfile).
Root cause of the invariant loading-screen crash: get_config() omitted the
PROGRESSIVE_INSTALLATION and PROGRESSIVE_INSTALLATION_EVENT services (Name="PI").
FIFA builds its service registry from GetConfig and resolves that handle during
online-init; the missing service left a null handle it dereferenced at
FIFA23.exe+0x133dfc5 (mov rax,[r13+0x18], r13=NULL) ~2s after LSX — identical
across every prior run. Found via FIFA's own CrashReport_*.json (stable RIP) +
diffing anadius64.dll's service-registration table against ours.
Also in this change:
- process_frame(): handle FIFA's pipelined (batched) LSX messages per read,
eliminating the ~15s handshake hang.
- Real anadius values (RE'd from anadius64.dll + anadius.cfg): PersonaId/UserId/
persona, locale list, GetGameInfo fixed `GameInfo="<value>"` attribute,
GetAllGameInfo 14-attr schema, GetSetting `Setting=` attribute.
- IsProgressiveInstallationAvailable served from sender="PI".
- docs/connection-gate-findings.md: full RE trail; relocate stale openfut-hook
copy under docs/ (canonical hook lives in openfut-launcher).
Next gate: Blaze/online (M2), ridden over ProtoSSL in-process.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
protossl-scan: add `jmpscan` (find function-entry E9 detours leaving a module).
Used to try to locate EbisuSDK::GoOnline in FIFA23.exe, but the 505MB image is
mostly embedded data => ~3875 false positives, no clean detour cluster. Combined
with the no-string and worker-thread blocks, the FIFA-side online-flow is not
cleanly reachable with the live-memory toolkit. Documented the verdict in
connection-gate-findings.md: playable FUT is research-grade (needs interactive
IDA/Ghidra GUI + full EA-online/Blaze emulator); the clean-room spec is the
finished deliverable.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a bounded manual stack-scan in the GoOnline detour (FIFA-frame finder).
Result: zero FIFA23.exe frames, sp ~2.4KB below stack top => GoOnline runs on an
anadius worker thread (queued), not FIFA's thread. Three-way corroboration that
the gate is an async "online established" event anadius (offline-only) never
pushes; FIFA waits/retries. No handler-response flip can cross it. Documented in
connection-gate-findings.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Hook: force GetInternetConnectedState->connected (flags +0xCAB1A/+0xCAB1B) and
flip GoOnline to report "1" (+0xAF530) instead of "0" (+0xADE64).
- protossl-scan: add `read` mode (hex/ascii dump at addr|module+off).
- Finding: neither flip unblocks the game; it keeps retrying GoOnline every ~7s.
The FUT-online flow is event-driven -- the game waits for an async "online
established" event anadius (offline-only) never pushes. Documented in
connection-gate-findings.md. Next: trace FIFA-side flow (Ghidra) / anadius
event-send to inject the online event.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Found without Ghidra, via anadius's LSX command-registration table (handler list
is offset-by-one from the names; verified by +0x27060 = GetProfile). The gate is
GetInternetConnectedState @ anadius64.dll+0x27790: its LSX "connected" attribute
= (byte[+0xCAB1B]||byte[+0xCAB1A]) ? str(+0xAF530) : str(+0xADE64); both flags
default 0 -> reports offline. That is why the client aborts at "connecting".
M1 answers: (1) ProtoSSLConnect not reached (gate upstream); (2) connection-state
function named; (3) single actionable gate (token already provided by anadius
FakeAuth / GoOnline passes). M2 (next session) = make GetInternetConnectedState
report connected, then watch for the real Blaze connect.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add a read-only detour on anadius's GoOnline handler (anadius64.dll+0x2BB90).
Result: the game calls GoOnline during the "connecting" attempt and anadius
returns success, yet no Blaze connection follows. The gate is therefore
downstream of GoOnline -- the auth-token (GetAuthCode) and/or the "online
established" status callback. Recorded in connection-gate-findings.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Read-only M1 investigation results appended to connection-gate-findings.md:
- Confirmed ProtoSSLConnect is never reached on the "connecting" abort (gate is
upstream/in-process) via the existing connect/DNS hooks.
- Mapped the surface: redirector host table (gosredirector.* per env), the
redirector request config (X-BLAZE-ERRORCODE, Authorization:, <errorCode>),
and the enum-name serialization tables (ONLINE_ACCESS, ONLINE_STATUS_EVENT).
- Key answer: the online-connect path requires a Nucleus auth token (the
redirector request carries an Authorization header) => TWO gates (state +
token), not a single boolean flip.
- The exact connection-state function is behind C++ vtable indirection; pinning
it needs Ghidra. protossl-scan stays the live-process bridge.
protossl-scan: add `callers` mode (find call/jmp sites to a function) and
restrict xref/callers scans to the app modules (FIFA23.exe/anadius64.dll) for
speed.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Stop fighting anadius's offline verdict. Document the clean-room findings
(the gate is an upstream in-process Ebisu connection-state check, not socket
traffic) and a milestone roadmap with two first-class outcomes: full playable
AI FUT (A) and a clean-room spec deliverable (B). Name M3 (first ProtoSSL
plaintext on the real Blaze connection) as the smallest end-to-end proof.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add `module+0xoffset` addressing (e.g. `anadius64.dll+0x2BB90`) to the
disasm and xref commands. Offsets are stable across launches while the
base is ASLR'd, so this resolves the module's current base in the running
process instead of requiring a stale absolute address each session.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Hook send/recv and WSASend/WSARecv, filtered to LSX/XML content, to read
the Ebisu-SDK <-> anadius conversation (challenge handshake, etc.).
- Hook getaddrinfo/GetAddrInfoW to log DNS lookups.
- Log connect() return value + WSA error; add accept/close diagnostics on
the local listeners.
- Serialize log writes behind a mutex so concurrent threads don't corrupt
each other's lines.
Used to establish that the online/offline decision is made via in-process
detoured calls, not socket traffic (no GetAuthCode/GoOnline on the wire).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Add the reverse-engineering work for FIFA 23 online integration:
- tools/protossl-scan: read-only memory scanner that confirms the
DirtySDK/ProtoSSL transport, cross-references strings to code
(xref/xref-str), and disassembles the enclosing function (disasm).
Located _ProtoSSLSendPacket at FIFA23.exe+0xEFA530 (plaintext TLS
record assembler) via the "_ProtoSSLSendPacket" debug string xref.
- openfut-hook: version.dll proxy (forwards all 17 real exports) that
injects into FIFA 23 and installs an inline detour on
_ProtoSSLSendPacket, plus connect/DNS (getaddrinfo) capture and local
LSX/redirect listeners. Logs to C:\openfut\hook.log.
- CLAUDE.md: bridge project context + the gate-sequence task roadmap.
Findings so far: with the anadius offline emulator the game never
attempts the online Blaze connection (local auth gate), so capture of
live Blaze frames is blocked pending an online-session fake.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- mapper.rs: add GET /division/history route; bump exact routes assertion to ≥56
- dashboard: formation pitch view in Squad tab with CSS pitch markings, player
chips positioned by line, bench row, Grid/Pitch toggle button; season zone
bar in Division tab (relegation/safe/promotion colour bands + current points
marker); season history list from GET /division/history; division response
now includes promotion_pts, relegation_pts, season_length for richer display
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Onboarding overlay: on DOMContentLoaded the dashboard calls GET
/auth/status. If no profile exists, a full-screen setup card replaces
the normal UI (header, nav, and main are hidden). The user enters a
username and clicks Start Playing — POST /auth/local is called, then
the overlay hides and the Club tab loads normally. Pressing Enter in
the username field also submits. Validation: minimum 2 characters,
error message inline below the button.
Danger Zone in Settings tab: a red-bordered section with a Reset All
Progress button. Clicking opens a confirmation modal that requires the
user to type the word RESET before the action is enabled. On confirm,
POST /auth/reset is called, a toast is shown, and the page reloads
after 1.5 s — which triggers the onboarding flow again since the
profile was deleted.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
New Achievements tab (tab-achievements) shows all 18 achievement cards in
a responsive grid. Each card displays: icon, title, rarity badge (color-
coded: common=grey, rare=blue, epic=purple), description, reward coins, and
either a green "Unlocked <date>" label or a dimmed "Locked" state. Unlocked
achievements sort first (newest-first); locked sort alphabetically after.
Header nav button shows earned count badge next to "Achievements".
Match result handler reads achievements_unlocked from the Core response and
fires one toast per newly unlocked achievement immediately after match
submission. Level-up toasts continue to work independently.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Notifications tab now fully functional. Each notification shows a type
icon, colored left border (gold=level_up, green=objective, amber=warning,
red=expired, blue=season), bold title when unread, body text, timestamp,
and a per-item Mark read button for persistent notifications.
Header badge and the unread count label now use unread_count from the
Core response rather than total notification count.
A Mark all read button calls POST /notifications/read-all and refreshes
the list. Per-item PATCH /notifications/:id/read hides the button and
dims the card on success.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Each card in the Collection tab now shows a Sell button displaying the
quick-sell coin value (computed from overall rating using the same tiers
as the backend: 85+→1500c, 80-84→900c, 75-79→600c, 65-74→300c, <65→150c).
Clicking calls DELETE /collection/:id with a confirmation prompt, shows a
toast with the actual coins received, and refreshes both the collection
and the header coin counter. Loan cards show a disabled button instead
to prevent accidental early sale.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Match result display now shows a level-up row per level gained (new
level, coins granted, milestone pack) and fires a toast notification.
Settings/Profile card now uses xp_to_next_level and xp_for_next_level
from the enhanced GET /profile response so the XP bar accurately shows
progress within the current level rather than total XP %.
Squad View mode gains an All Squads panel below Chemistry listing every
saved squad (name + formation badge). Each row has a Delete button that
calls DELETE /squads/:id and refreshes the view. Squads are loaded via
GET /squads alongside the active squad on every Squad tab open.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Card Catalog — full card database browser using GET /cards with client-
side filters (name, position, rarity, nation, league, club, OVR range),
pagination in 80-card chunks, "Owned" green overlay/badge for cards
already in the collection (cross-referenced via GET /collection).
Settings/Profile — profile card with username, level, XP progress bar;
editable game settings (default match difficulty, preferred formation)
via GET/PUT /settings; confirmation flash on save.
Squad Builder — Squad tab gains a Build/View toggle. Build mode shows
11 starter slots (labelled by position for the selected formation) plus
7 bench slots; clicking any slot opens an inline search-picker filtered
to unused collection cards; GK slot highlighted; filled slots show
player name, OVR, and position with a one-click clear button; Save
Squad POSTs the full squad to Core (validates 11 starters required) then
switches back to View mode and refreshes the chemistry display.
Formations supported: 4-4-2, 4-3-3, 4-2-3-1, 4-1-2-1-2, 3-5-2, 5-3-2.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Matches — difficulty selector, generate opponent (shows AI squad rating
and formation), quick-score buttons (3-0 / 1-1 / 0-2), submit custom
score via POST /matches/result, inline reward display (coins, XP,
objective triggers, season points), live match history list.
Pack Store — lists all purchasable definitions from GET /packs/store
(name, description, cost, card count) with one-click Buy that calls
POST /packs/buy; coin balance refreshes after purchase.
SBC — renders all challenges with requirements as pills (min OVR,
required nations/leagues/clubs, same-club counts), card picker
(select from owned collection, click pill to remove), Submit fires
POST /sbc/submit and shows reward or validation failures; cards
re-loaded from Core after a successful submission.
Statistics — career stat grid (matches, W/D/L, win rate, goals, coin
totals, streaks, packs, SBCs), goals-by-position bar chart, paginated
match log with outcome badges and date, sourced from
GET /statistics and GET /statistics/history.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Four new tabs complete the dashboard UI for all existing Core endpoints:
Squad — view active squad with per-player chemistry dots and team
chemistry bar (colour-coded green/amber/red).
Draft — start a session with difficulty selector, pick from 5 candidates
per slot through all 11 positions, see picks accumulate in real time,
reward shown on completion.
Market — browse NPC listings with live search filter, one-click buy,
sell own cards with price input, cancel own listings; Refresh button
repopulates NPC inventory via POST /market/refresh.
Events — list all data-driven events with active/inactive state, bonus
effects summary (score/coin multipliers, extra market cards, pack
discounts), Activate/Deactivate buttons.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Single-page club management UI served by the bridge. The Core URL is
injected server-side so client JS calls Core directly without routing
through the proxy. Vanilla HTML/CSS/JS, dark theme matching admin.html.
Sections: Club (name/manager/coins/stats edit), Collection (filterable
card grid with stat bars + chemistry/training pills), Packs (open with
modal, history), Objectives (progress bars + one-click claim), Division
(W/D/L record, season progress, rivals weekly claim), FUT Champions
(current session with in-browser match simulation, claim, history),
Notifications (badge count in nav).
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- GET /_bridge/cert.pem — serves the TLS CA cert as a downloadable PEM file
(only when TLS_ENABLED=true; 404 otherwise)
- GET /_bridge/guide — HTML setup page with hosts-file instructions, cert install
steps per OS, and troubleshooting tips
- ProxyState gains cert_pem field (Arc<Vec<u8>>); set via with_cert() builder
- main.rs generates cert before state construction so /_bridge/cert.pem can serve it;
both cert_pem and key_pem passed to serve_tls() for acceptor + state separately
- All 13 bridge tests pass, clippy clean
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- proxy.rs: forward_to_core() now accepts the incoming headers vec
and passes X-UT-SID, X-UT-PHISHING-TOKEN, and X-Request-ID through
to Core on every mapped request (#19, #20)
- TODO.md: mark #15-#20 as complete with implementation notes;
update test counts; clean up duplicate #15 entry
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- shaper.rs: shape_response() dispatches on core_path to wrap Core
JSON in FUT envelope format; shapes auth (/ut/auth → sid/pid/
phishingToken/persona envelope), profile, club, squad, market,
packs; unknown paths pass through unchanged
- proxy.rs: calls shape_response() on every successful Core response
before returning to the FIFA 23 client
- mapper.rs: expanded from 6 to 18 speculative FUT endpoint mappings
covering auth, profile/settings, club/usermassinfo, item/collection,
squad (GET+PUT), packs+purchase, transfer market+watchlist+bid,
objectives, events, squad battles, and match result submission
- admin.rs: GET /_bridge/captures/diff?a=<id>&b=<id> compares two
captures; reports method/path/status changes, header diffs, and
structured JSON body diffs (key-level for objects, length for others)
- main.rs: registers /_bridge/captures/diff route
- Unit tests: 4 shaper tests, 6 mapper tests (10 total, all passing)
- All 13 integration tests still passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
FIFA 23 reverse-engineering proxy and integration scaffold.
- Catch-all HTTP proxy that captures all incoming FIFA 23 traffic
- Known-route mapper (speculative FUT paths → Core API calls)
- Placeholder JSON responses for unmapped endpoints
- Admin endpoints: GET /_bridge/captures, GET /_bridge/unknown
- Capture persistence to captures/*.json for RE analysis
- 4 unit tests passing
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>