edab23f04a
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
54 lines
3.9 KiB
Markdown
54 lines
3.9 KiB
Markdown
# Agent brief — reverse the FIFA17 OSDK_UNDERAGE_ERROR (Origin error 0xa2000012) (clean-room)
|
||
|
||
## Clean-room rule (HARD)
|
||
Derive ONLY from decrypted dumps here + live /proc/PID/mem (`pgrep -x FIFA17.exe`, live 39211) +
|
||
our LSX/Blaze logs. NEVER leaked EA source.
|
||
|
||
## Where we are (this session's WIN)
|
||
The recipient-echo fix (lsx_responder_v2.py: response `sender` must byte-equal request `recipient`)
|
||
CASCADED the whole Origin login chain. Live-confirmed NATURAL (no forging): OriginSDK(*[0x144b7c7a0])
|
||
+0x3a0/+0x3a8 = 0x1f89493 (default user set), FIFA issued GetAuthCode ×3 on its own (FIFA17PC +
|
||
FIFA17PC-SERVER), SetPresence flowing ("In Menus"). The OSDK login state machine advanced
|
||
OSDK_INVALID_USER → **OSDK_UNDERAGE_ERROR**. On screen: "not eligible to use EA's online features due
|
||
to an age restriction". Blaze login (1/0x0A) still 0 — this age gate blocks before it.
|
||
|
||
## The exact gate
|
||
OSDK classifier **0x14717d5d0** (dump osdk_classifier_14717d5d0.bin.asm): reads the OSDK/Ebisu
|
||
manager's cached "last error" and maps it:
|
||
`cmp eax,0xa2000003 -> OSDK_INVALID_USER` (we cleared this — user now valid)
|
||
`cmp eax,0xa2000012 -> OSDK_UNDERAGE_ERROR` (the CURRENT error) <-- the target
|
||
The error code comes from the manager chain: manager `*[0x144b86bf8]` = live 0x43c46c70,
|
||
vptr **0x143959168**; classifier does `mov rcx,[0x144b86bf8]; mov rax,[rcx]; call [rax+0x60]` (=
|
||
**0x14719b1b0**, dump mgr_vt60_14719b1b0.bin.asm) -> rsi (a sub-object); then `rsi->vt[0x68]()` ->
|
||
the error code = 0xa2000012.
|
||
|
||
## What is ALREADY RULED OUT (do not re-chase)
|
||
- NOT from GetProfile: the IsUnderAge bool-parse (helper 0x14713ffa0: strconv result -> `setne al`,
|
||
so "false"->0 = not-underage) stores our IsUnderAge="false" correctly. GetProfile deserializer
|
||
0x147136140 reads only: UserId PersonaId Persona AvatarId Country IsUnderAge IsSubscriber
|
||
GeoCountry CommerceCountry CommerceCurrency — no DOB/age field.
|
||
- 0xa2000012 is NOT constructed by inline mov+lea/add arithmetic ANYWHERE in 0x146000000-0x1476f0000
|
||
(scanned, 0 sites). So it is a DATA-TABLE value (scan DATA regions for the raw dword `12 00 00 a2`
|
||
= an error-map table), or a value read from a Nucleus/HTTP/Blaze response, or a field.
|
||
- Nucleus :42131 was NOT hit this boot (no live conn) and Blaze login=0 — so the underage was cached
|
||
by an Origin/LSX operation during boot, before Blaze/Nucleus. (Confirm; don't assume.)
|
||
|
||
## The questions to answer
|
||
1. **Which sub-object does manager->vt[0x60] (0x14719b1b0) return, and where is its error field
|
||
(read by vt[0x68]) WRITTEN with 0xa2000012?** Find the writer = the operation that decided underage.
|
||
2. **What is 0xa2000012's source** — locate the raw dword in a data/error-map table (scan .rdata/.data
|
||
for `12 00 00 a2`), find the code that selects it, and what INPUT maps to it (an HTTP/Nucleus status?
|
||
a profile/account field? an entitlement? a hardcoded default when age is unverified?).
|
||
3. **What CONDITION makes FIFA underage** despite IsUnderAge="false"? Trace back from the writer to the
|
||
input we control (an LSX verb/field we answer wrong or omit, a Blaze reply, a missing DOB, an
|
||
entitlement/age-rating check). We own every server endpoint FIFA talks to.
|
||
4. **The fix**: concrete LSX/Blaze/Nucleus responder change (verb/field/value) that makes FIFA classify
|
||
the user as an adult, so OSDK advances past OSDK_UNDERAGE_ERROR toward Blaze login.
|
||
|
||
## Dumps / method
|
||
osdk_classifier_14717d5d0, mgr_vt60_14719b1b0, getprofile_deser_147136140, boolparse_ffa0,
|
||
authcode_impl_1470e67f0, sdk_connect_writer, + earlier auth/connect dumps. objdump Intel, VMA==runtime VA.
|
||
Dump more live: `f.seek(va); f.read(n)` + objdump --adjust-vma. Data-dword scan: read region, `d.find(b'\x12\x00\x00\xa2')`.
|
||
Every claim needs a VA/bytes/disasm/log line. We can WRITE /proc/mem too (A/B: poke a candidate field, watch
|
||
OSDK state 0x43d189d8+0x80 [re-find via vptr 0x14395c180] leave "OSDK_UNDERAGE_ERROR").
|