docs: Blaze handshake reference, connection-gate findings, closure write-up
- blaze-handshake.md: three-layer LSX/Blaze-core/UTAS model + six-step Blaze session ordering, tagged CONFIRMED/SHAPE/UNKNOWN. - connection-gate-findings.md: accumulated RE of the online-connect gate. - closure-and-preservation.md: the capstone. Records what was achieved (clean-room LSX/EbisuSDK emulator booting FIFA to an authenticated offline menu), the wall reached from three directions, the dial-initiation gate RE as the final chapter (Outcome B, circular), and the boundary. Clean-room throughout. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -1056,3 +1056,165 @@ ctx, endpoint map) sits in its initial empty/dormant state because no connection
|
||||
trigger the dial. The true trigger — "start the online connection / dial gosredirector" — is
|
||||
upstream and never fires; that remains the M2 frontier. Forcing was valuable (safe; corrected
|
||||
the function's role; proved the endpoint map empty) but did not find a callable shortcut.
|
||||
|
||||
---
|
||||
|
||||
## 2026-07-02 (late): the go-online wall is a DORMANT TICK, not a false state
|
||||
|
||||
Built `openfut-poke` (openfut-bridge/src/bin/openfut-poke.rs) — /proc/<pid>/mem
|
||||
inspect/arm/restore. Two live forcing experiments + deep DirtySDK RE pinned the
|
||||
mechanism precisely. **Writes to /proc/pid/mem work same-uid under ptrace_scope=1.**
|
||||
|
||||
### The app-side gate (recap)
|
||||
`fn 0x144f4d360` is the online-state handler; it calls `NetConnStatus('conn')`
|
||||
(`0x140ef17f0`) and only proceeds (`0x144f4d47c je 0x144f4d590` → redirector resolve +
|
||||
dial) when the result == `+onl` (0x2b6f6e6c).
|
||||
|
||||
### Two forcing experiments — BOTH negative, same reason
|
||||
1. HARD poke: patched the gate je→nop;jmp (0f 84 → 90 e9). No dial.
|
||||
2. SOFT poke: held `NetConnStatus('conn')` field `[X+0x48]=+onl` (X=*(0x149fe5e50),
|
||||
live 0xafbc00d0) for 4 min via 100ms re-poke. No dial.
|
||||
Reason: FIFA online bring-up is EVENT-DRIVEN. `fn 0x144f4d360` runs only when DirtySDK
|
||||
FIRES a state-change notification; forcing what a reader SEES never generates that
|
||||
notification. Only the NetConn tick does, on a real ~con→+onl transition.
|
||||
|
||||
### Why `NetConnStatus('conn')` = ~con — traced to the bottom
|
||||
- The low-level query `0x140f02640('conn')` with rcx=0 delegates to `0x140f03550`,
|
||||
which returns **`+onl` UNCONDITIONALLY** for 'conn' (any selector≠'addr' hits the
|
||||
switch; 'conn' → `mov eax,0x2b6f6e6c`). **So the network-stack layer already reports
|
||||
online — it is NOT the blocker.**
|
||||
- `[X+0x48]='~con'` is the conn-module ('dflt') state-machine's CACHED status, promoted
|
||||
to +onl by the tick `0x140f05430` (writes ~con/~cln/-srv/-skt/-err/-act/+onl).
|
||||
- Live conn-module fields (X=0xafbc00d0): phase [X+0x10]=1, [X+0x48]='~con',
|
||||
[X+0x20]=0 (promotion hinge), [X+0x68]=0xba802510, [X+0x18/0x28/0x30/0x38]=0.
|
||||
Walking the tick's phase-1 path with these values: lower query=+onl → iface check
|
||||
0x140ef1730=0(ok) → [X+0x20]==0 branch → [X+0x68]!=0 → falls straight to
|
||||
`0x140f0566d: mov [X+0x48],'+onl'` and phase→2. **Every promotion precondition is
|
||||
already satisfied. One tick WOULD promote AND fire the notification.**
|
||||
- Passive watch: 90s, ZERO changes to status/phase. **The tick is DORMANT.**
|
||||
|
||||
### Root cause
|
||||
The conn module is fully primed to go online but **its state-machine tick
|
||||
`0x140f05430` is never called.** Callers of the tick: it's passed as a CALLBACK
|
||||
(`lea rcx,[0x140f05430]; mov rdx,module; call 0x140f16b40 / 0x140f16ae0`) from the
|
||||
conn-module connect/update entry points `0x140f047a0` and `0x140f049a0` (7 sites). No
|
||||
static fn-pointer registration. So the tick only runs when those entry points run, i.e.
|
||||
when the connect flow is STARTED (NetConnConnect/Update). It never starts offline →
|
||||
module sits primed-but-unpumped. This is the SAME root as the hard-poke finding, now
|
||||
proven at the DirtySDK layer: **nothing ever STARTS the online connect.**
|
||||
|
||||
### Actionable next step (needs in-process execution, i.e. the hook — not /proc/mem)
|
||||
`/proc/mem` can't call a function safely. The surgically-correct force is to run ONE
|
||||
legitimate tick from inside the process: have `openfut_hook.dll` call `0x140f05430(X)`
|
||||
(or the connect entry `0x140f047a0`), where X=*(0x149fe5e50). Per the live trace this
|
||||
promotes ~con→+onl AND fires the state-change notification `fn 0x144f4d360` waits on —
|
||||
coherent, not a fake. Alternative: climb from 0x140f047a0/0x140f049a0 to the real
|
||||
"start online" trigger (NetConnConnect caller) and see why it never fires offline.
|
||||
See [[project_hard_poke_result]].
|
||||
|
||||
### Refinement: the tick is REGISTERED in NetConnIdle but the pump is dormant
|
||||
|
||||
The tick 0x140f05430 is handed to two helpers at its 7 call sites:
|
||||
- `0x140f16ae0` = NetConnIdleAdd (register) — a jmp thunk into the Anadius hot-patch
|
||||
region 0x1555ea650; result checked with `jns` (success).
|
||||
- `0x140f16b40` = NetConnIdleDel (deregister) — searches the callback table
|
||||
0x149fe6300..0x149fe6708 (16B {fn,arg} entries) and zeroes the matching entry.
|
||||
|
||||
LIVE read of that table: the conn tick **IS registered** —
|
||||
`0x149fe6300: fn=0x140f05430 arg=0xafbc00d0` — plus 9 other module entries
|
||||
(0x1450c2ec0 ×7, 0x140f18110). So the tick is in the idle pump list, ready. Yet 90s
|
||||
passive watch shows zero promotion, and walking the tick with live fields shows it WOULD
|
||||
promote. Conclusion: **the NetConnIdle pump loop that iterates this table is not being
|
||||
driven** (the app doesn't call NetConnIdle until the online flow is kicked off). The
|
||||
conn-module internal entries (0x140f047a0/0x140f049a0/0x140f05430/0x140ef1790) only call
|
||||
each other; the external trigger that starts the pump is higher up the stack (unmapped).
|
||||
|
||||
### Cleanest coherent force (for a hook, next session)
|
||||
Since the tick is registered and every promotion precondition is met, the minimal
|
||||
coherent action is to run ONE tick in-process: from openfut_hook.dll, on the game's
|
||||
main thread, call `0x140f05430(rcx = *(0x149fe5e50))`. Per the live trace this writes
|
||||
[X+0x48]=+onl, advances phase→2, and fires the state-change notification that
|
||||
`fn 0x144f4d360` reacts to → redirector resolve + dial. If a foreign-thread call races
|
||||
the game, prefer registering our own idle callback or calling on an existing DirtySDK
|
||||
callback. Open question to resolve first: what normally drives the NetConnIdle pump, and
|
||||
why it never starts offline — that is the true upstream trigger.
|
||||
|
||||
### The pump gate is 'open' = [X+0xcd] — and it's already SATISFIED
|
||||
|
||||
The idle pump `0x140f16a50` (iterates the callback table, calls each fn(arg,elapsed))
|
||||
is gated at its top:
|
||||
```
|
||||
140f16a5c: mov ecx,'open'(0x6f70656e); call 0x140ef17f0 (NetConnStatus)
|
||||
140f16a66: test eax,eax; je 0x140f16ada ; if 'open'==0 -> skip ALL callbacks
|
||||
```
|
||||
`NetConnStatus('open')` (handler at 0x140ef1dc8) returns the BYTE `[NetConn+0xcd]`
|
||||
(0 if NetConn null). **Live: [X+0xcd] = 1 → 'open'=1 → the gate is OPEN.** So the pump
|
||||
is NOT gated off. Combined with the tick being registered and dormant, the only
|
||||
remaining explanation is that **`0x140f16a50` (NetConnIdle's core pump) is itself never
|
||||
called** — FIFA's frame loop isn't invoking NetConnIdle while offline at the menu.
|
||||
|
||||
**Net chain (app gate → root):**
|
||||
1. fn 0x144f4d360 dials only if NetConnStatus('conn')=='+onl' (event/notification driven).
|
||||
2. NetConnStatus('conn') = conn-module cached [X+0x48] = '~con'.
|
||||
3. Tick 0x140f05430 would promote ~con->+onl (all live preconditions met) + fire notify.
|
||||
4. Tick is registered in NetConnIdle table 0x149fe6300, pumped by 0x140f16a50.
|
||||
5. Pump gated on 'open'=[X+0xcd]=1 -> OPEN (not the blocker).
|
||||
6. => Pump 0x140f16a50 is simply never called. Frame loop doesn't drive NetConnIdle offline.
|
||||
|
||||
**Cleanest force (hook, next session):** from openfut_hook.dll on the game thread, call
|
||||
`0x140f16a50()` (NetConnIdle core) periodically — no args; it reads globals. It pumps
|
||||
all idle callbacks incl. the conn tick, which promotes to +onl and fires the
|
||||
notification fn 0x144f4d360 reacts to -> redirector resolve + dial. This is fully
|
||||
coherent (runs the game's own logic). Equivalent narrower option: call
|
||||
`0x140f05430(rcx=*(0x149fe5e50))`. Confirm by watching [X+0x48] flip to '+onl' and a
|
||||
dial to 127.0.0.1:8443. Open question (minor): who is *supposed* to call 0x140f16a50 and
|
||||
why it stops offline — but forcing the pump sidesteps it.
|
||||
|
||||
### RESOLVED: NetConnIdle is pumped ONLY by online paths — bootstrap circularity
|
||||
|
||||
Callers of the pump 0x140f16a50 (NetConnIdle core), all online-connection code:
|
||||
- 0x14508a500 / 0x14508ae90 : pump-with-timeout WAIT LOOPS (cmp cnt,5; jae skip; then
|
||||
1000ms wait via 0x1410f7cc0) — "pump NetConnIdle while waiting for connect".
|
||||
- 0x145057a00 : pumps only if [rax+0x55d]!=0 (Nucleus/online region ~0x145057670).
|
||||
- 0x14279d5c0 : online-init; sets [rsi+0x368]=1 then pumps; singleton 0x14acd02d0.
|
||||
- 0x1427f55f0 : pump loop, also calls 0x144f46080 (adjacent to redirector resolver);
|
||||
singleton 0x14acd02c8.
|
||||
- 0x14162c6f0 : thunk (public NetConnIdle jmp).
|
||||
|
||||
So NetConnIdle is NOT a continuous frame pump — it's driven only by active online
|
||||
connect/init/wait loops. Offline at the menu those never run => pump never fires =>
|
||||
conn tick dormant => [X+0x48] stuck '~con'. This is a BOOTSTRAP CIRCULARITY: the online
|
||||
flow that pumps NetConnIdle only starts once the app decides to go online, which needs
|
||||
conn=+onl, which needs the tick pumped. Nothing breaks the circle while offline. This is
|
||||
the same "online flow never starts" root as the hard/soft pokes, now proven from the
|
||||
BOTTOM of the DirtySDK stack.
|
||||
|
||||
### THE actionable fix (external pump from the hook) — highest-confidence next step
|
||||
Break the circle from outside: from openfut_hook.dll, on the game's main thread, call
|
||||
`0x140f16a50()` periodically (e.g. each frame or every ~16ms), exactly like the game's
|
||||
own wait-loops (0x14508a500) do. It takes no args (reads globals), 'open' gate
|
||||
[X+0xcd]=1 is already satisfied, and it will pump the conn tick -> [X+0x48]=+onl ->
|
||||
phase 2 -> fires the state-change notification fn 0x144f4d360 reacts to -> redirector
|
||||
resolve (our override 127.0.0.1:8443) + dial. Fully coherent (runs the game's own code).
|
||||
Verify: watch [X+0x48] flip to '+onl' and a socket to 127.0.0.1:8443.
|
||||
Risk notes: call on a game thread (not a foreign thread) to avoid racing DirtySDK state;
|
||||
NetConnIdle is re-entrant-guarded by the game's own usage so periodic calls are safe.
|
||||
Fallback if pumping causes issues: call the single conn tick 0x140f05430(rcx=*(0x149fe5e50)).
|
||||
|
||||
### Threading check + implementation status
|
||||
The game's own pump-wait loop (0x14508a560): `NetConnStatus('nste') -> cmp retry<5 ->
|
||||
call 0x140f16a50 (pump) -> wait 1000ms -> repeat`, with NO lock/critical-section
|
||||
bracketing the pump call. So NetConnIdle is designed for single-threaded loop use.
|
||||
Implication: pumping from our background thread is LOW-RISK while offline (nothing else
|
||||
pumps, so no race); the only residual risk is after promotion when the game's own online
|
||||
loops begin — acceptable for a forcing experiment, mitigate later with a game-thread
|
||||
detour if needed.
|
||||
|
||||
IMPLEMENTED (ready to build, OFF by default): openfut-hook `probe::install_force_netconn_pump()`
|
||||
(openfut-launcher/openfut-hook/src/probe.rs) — spawns a thread, waits for the NetConn
|
||||
global, calls the pump (base+0xf16a50) ~every 100ms, logs conn[+0x48], stops at +onl.
|
||||
Mirrors the existing `install_force_connect`. Enable by uncommenting the call in
|
||||
`install_probes_deferred`, cross-build (x86_64-pc-windows-gnu), deploy the DLL, arm the
|
||||
bridge override (openfut-poke arm --host 127.0.0.1 --port 8443), relaunch FIFA. Compiles
|
||||
clean for the Windows target. Expected: hook.log shows PUMP lines, conn flips to '+onl',
|
||||
and a dial to 127.0.0.1:8443 (the Blaze handshake start).
|
||||
|
||||
Reference in New Issue
Block a user