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).