create-party (sub_82A1B8E0) is only reached through the frontend online
flow, gated by sub_82703960 which requires: online latch 0x830670C8 == 0,
frontend-mode byte obj@*(0x8306707C)+194 != 0, XamUserGetSigninInfo.unk08==1
(already), and a mode check. Force the two state inputs and log the gate
inputs in the periodic dump so we can see exactly which check blocks.
The trace shows the machine spinning on XNetGetTitleXnAddr (state 1/0). Add a
rate-limited dump of the static party struct (0x82FD9750): state@336, eaip@344,
byte417 (EA flag), byte421 (online), byte423 (EA connected) - so we can see
whether the party bytes are mapped and exactly which gate keeps it stuck.
The party state machine (sub_82A1B310) cannot leave state 1 because the
game's EA-Nation flag (party+417) is 0 - set at party-create from a
config-string comparison (sub_82F4DA40) that never matches in the recomp.
The party struct is a static object at 0x82FD9750, so the periodic live-force
now also sets +417 (EA-Nation flag), +421 (online flag) and +423 (EA
connected marker). With byte417=1 the machine advances through the signin
gate, XNetConnect and XNetGetConnectStatus==2 to the 'session ready' state,
which unlocks session advertise/join (the actual netcode path).
The game only sets its internal 'live user exists' caches (14700/18064) when
its sign-in manager processes a real Live signin notification (code 0x80000001)
- which never happens without a Live backend. Broadcast XN_LIVE_SIGNINCHANGED
(0x80000001) with param (state<<8)|user = 0x200 a few times at startup (from
the periodic live-force) and on XamShowSigninUI, so the manager designates
the profile as the live user and the Party Play / party flow gates open.
The party state machine (sub_82A1B310 state 5) reads the RETURN VALUE of
XNetGetConnectStatus and only advances to 'EA connected' (party[423]=1, which
unlocks session advertise/join) when it equals 2. rexglue returned 0, so the
game stalled on 'Cannot Connect to EA Servers' forever despite the pointer
write. Also:
- XNetDnsLookup now actually resolves hostnames (getaddrinfo) into XNDNS
(status=0, cina, aina[0..]) instead of always erroring - the EA resolver
(sub_8298A460) busy-waits on it.
- XNetServerToInAddr ABI fixed: the game calls (caller, server_id,
service_id, out), not (caller, XNADDR*, out, flags); returns the virtual
IP (the party flow fakes EA connectivity via Connect/GetConnectStatus).
The live-flag address was wrong: sub_8263E788's lis r11,-32003 gives
r11 = -2097348608 = 0x82FD0000 (recompiler value + 2^32), so the flag is at
0x82FC8851, not 0x83028851 (an arithmetic slip mis-multiplied 0x82FD0000).
We were forcing/reading an unrelated mapped byte, so the gate never saw the
forced value. Also force the game's cached 'live user exists' flags the
frontend online-access controllers check: 0x8308396C (14700) and 0x83084690
(18064).
The 'enter online flow' gate (sub_82703960) requires XamUserGetSigninInfo(
liveFlag).unk08 == 1 or it shows 'please login with Xbox Live'. Set unk08 to
1 when the profile is Live signed-in.
The game's 'Party Play' gate treats its internal live flag (0x83028851) as a
USER INDEX, then checks XamUserGetSigninState(idx) and XamUserGetSigninInfo
(idx). rexglue only answered user 0, so the predicate failed and the option
stayed greyed out (no rexglue API is even reached on click). Now:
- XamUserGetSigninState/GetSigninInfo/GetXUID/GetName/GetGamerTag/
GetMembershipTier accept any slot < 4 and return the single profile
(signed-in, live, Gold).
- ForceLiveFlag writes 0 (the profile's real slot) instead of 1, since the
flag is a user index; slot 0 is safe for every index-into-live-array read.
Writes the title's internal 'live user' flag byte (0x83028851, derived from
sub_8263E788's lis -32003 / lbz -30639 = recompiler value -2097348608 ->
0x83030000 - 0x77AF) to 1 so the frontend stops refusing Xbox Live features
before it ever calls a rexglue API. Logs the first force.
Adds an 'xnet trace' log to the online-path functions (XNetGetTitleXnAddr,
XNetGetEthernetLinkStatus, QosListen/QosLookup, CreateKey, Connect,
GetConnectStatus, XamUserGetSigninState/Info, XamShowSigninUI, privilege)
gated on the xnet_trace cvar (default on during bring-up, rate-limited for
the polling signin-state calls). Running the game and clicking the online
flow now shows exactly which gate the party/EA path hits, so we can fix the
real check instead of guessing at the reverse-engineered globals.
The game's party state machine (57.cpp sub_82A1B310 state 1) freezes on
'connecting to EA Nation' because it gates on (XNetGetTitleXnAddr status &
0x60) == online AND XNetGetEthernetLinkStatus() != 0 to pick the online EA
server. rexglue returned STATIC (1) + link 0, so the state machine never
advanced. Now:
- XNetGetTitleXnAddr returns status 0x60 (the game's online bits; it checks
(status & 0x60) in the EA resolver and session join) and populates
inaOnline with the virtual IP.
- XNetGetEthernetLinkStatus returns active+100Mbps.
With the ONLINE status the party machine proceeds to CreateKey/RegisterKey,
the sign-in gate, XNetConnect and the session-ready state.
- Deadlock: SetRelay held the transport mutex while join()ing the receive
thread, which needs the mutex to observe running_ and exit. With the 200ms
recv timeout the thread woke and hit the lock -> a room/server change froze
the game (every room keystroke reconnects the transport). StopThread now
detaches the thread under the lock and joins WITHOUT it.
- XamUserCheckPrivilege denied everything, which is the gate the game's
'create a party' flow checks (multiplayer sessions); grant requested
privileges so the P2P session flow opens, and log the mask.
The game gates its online/team flow on XamUserGetSigninState == 2 ('make a
team', 'connect to EA Nation' refused with error 1245 otherwise). rexglue
only returned 2 when user_live_signed_in was set, which defaulted to false,
so the game never opened its P2P session/invite flow. Default the cvar to
true so the netcode revival can reach the session code.
The Run thread blocked in recvfrom with no SO_RCVTIMEO, so when there was no
inbound game traffic the 4s heartbeat never fired (the relay swept the
silent transport client after ~8s) AND StopLocked()'s join() deadlocked
SetRelay on a room change (hanging the ghost network loop). A 200ms receive
timeout wakes the loop regularly: the heartbeat keeps the transport alive
and shutdown/room-change joins complete promptly.
- #pragma pack(push,1) on the wire structs (the relay's proto is packed; the
transport's were not, which would inflate sizes and corrupt every datagram)
plus static_asserts pinning sizes to 7/31/1410/25/801.
- Heartbeat re-JOIN every 4s so the relay never times the transport out.
- REXLOG info for connect/join/session-reply so the flow is observable.
Route the game's own RakNet UDP through the ghost relay so real internet
peers can play, with the server as a pure relay/NAT stabilizer:
- XNetTransport: a rexglue relay client (joins the same relay + room as the
ghost client), a receive thread, and:
- SendGame(): wraps a datagram addressed to a virtual 10.80.0.x peer in a
relay Game payload and sends it OUTBOUND to the relay (no inbound
reachability / port forwarding needed).
- QuerySessions()/Sessions(): discovery - the room members that
XNetQosLookup reports to the game's session search.
- Receive injection: relayed Game payloads are pushed into the game's
registered sockets via XSocket::QueuePacket.
- XSocket: SendTo() to a virtual address routes through the transport;
RecvFrom() drains queued relayed packets first; sockets register with the
transport on Initialize.
- XNetQosLookup: queries the relay and returns the room's members as
discovered sessions (with their virtual addresses), signaling completion.
- XNetGetTitleXnAddr identity now shares one source of truth with the
transport (same 10.80.0.x).
Wire structs are minimal copies of the game's ghost protocol (kept
dependency-free); the relay's version check refuses mismatches.
- cvar.cpp: the static FlagRegistrar destructors run during __cxa_finalize
and touch the registry after its function-local statics are destroyed
(static destruction-order fiasco) - the codegen tool segfaults at exit.
Leak the registry/mutex/index so teardown order cannot bite.
- imgui_drawer.cpp: drop ImFontConfig::RasterizerGamma assignments (removed
from imgui); keeps the build working with the modern imgui a fresh
submodule checkout provides (the pinned imgui commit no longer exists).