netcode-revival: trace the online-path XNet/Xam calls
Some checks are pending
Lint & Format Check / Format Check (push) Waiting to run

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.
This commit is contained in:
MiTHRAL 2026-08-28 14:20:58 -04:00
parent 9e300e14bc
commit 7b6322b317
2 changed files with 59 additions and 0 deletions

View file

@ -15,6 +15,7 @@
#include <cstring> #include <cstring>
#include <rex/chrono/clock.h> #include <rex/chrono/clock.h>
#include <rex/cvar.h>
#include <rex/kernel/xam/module.h> #include <rex/kernel/xam/module.h>
#include <rex/kernel/xam/private.h> #include <rex/kernel/xam/private.h>
#include <rex/kernel/xboxkrnl/error.h> #include <rex/kernel/xboxkrnl/error.h>
@ -48,6 +49,15 @@ namespace xam {
using namespace rex::system; using namespace rex::system;
using namespace rex::system::xam; using namespace rex::system::xam;
// netcode-revival: trace the online-path calls so a run shows exactly which
// XNet/Xam gates the game hits (party/Ea Nation debugging). Default on during
// the revival bring-up; disable with xnet_trace=false before any release.
REXCVAR_DEFINE_BOOL(xnet_trace, true, "Kernel",
"Log online-path XNet/Xam calls (netcode debugging)");
bool XnetTrace() {
return REXCVAR_GET(xnet_trace);
}
// https://github.com/G91/TitanOffLine/blob/1e692d9bb9dfac386d08045ccdadf4ae3227bb5e/xkelib/xam/xamNet.h // https://github.com/G91/TitanOffLine/blob/1e692d9bb9dfac386d08045ccdadf4ae3227bb5e/xkelib/xam/xamNet.h
enum { enum {
XNCALLER_INVALID = 0x0, XNCALLER_INVALID = 0x0,
@ -498,6 +508,10 @@ u32 NetDll_XNetGetTitleXnAddr_entry(u32 caller, ppc_ptr_t<XNADDR> addr_ptr) {
std::memset(addr_ptr->abOnline, 0, 20); std::memset(addr_ptr->abOnline, 0, 20);
// 0x60 = the game's "online" status bits (not the standard 0-3 enum; the // 0x60 = the game's "online" status bits (not the standard 0-3 enum; the
// game checks (status & 0x60) everywhere it means "connected to LIVE"). // game checks (status & 0x60) everywhere it means "connected to LIVE").
if (XnetTrace()) {
REXLOG_INFO("xnet trace: XNetGetTitleXnAddr(caller={}) -> status 0x60 (online)",
caller);
}
return 0x60; return 0x60;
} }
@ -576,6 +590,10 @@ u32 NetDll_XNetGetEthernetLinkStatus_entry(u32 caller) {
// machine only reaches the EA-Nation/online path when the link is up // machine only reaches the EA-Nation/online path when the link is up
// (sub_82A1AAE8 returns the online server IP only if this is non-zero), // (sub_82A1AAE8 returns the online server IP only if this is non-zero),
// so returning 0 left it frozen on "connecting to EA Nation" forever. // so returning 0 left it frozen on "connecting to EA Nation" forever.
if (XnetTrace()) {
REXLOG_INFO("xnet trace: XNetGetEthernetLinkStatus(caller={}) -> active 100Mbps",
caller);
}
return XEthernetStatus::XNET_ETHERNET_LINK_ACTIVE | return XEthernetStatus::XNET_ETHERNET_LINK_ACTIVE |
XEthernetStatus::XNET_ETHERNET_LINK_100MBPS; XEthernetStatus::XNET_ETHERNET_LINK_100MBPS;
} }
@ -639,6 +657,10 @@ u32 NetDll_XNetQosListen_entry(u32 caller, mapped_void id, mapped_void data, u32
(void)data_size; (void)data_size;
(void)r7; (void)r7;
(void)flags; (void)flags;
if (XnetTrace()) {
REXLOG_INFO("xnet trace: XNetQosListen(caller={}, flags={}) -> ok (advertise)",
caller, flags);
}
return 0; return 0;
} }
@ -696,6 +718,11 @@ u32 NetDll_XNetQosLookup_entry(u32 caller, u32 cxna, mapped_void apxna, mapped_v
} }
qos->count = qos->count_pending = n; qos->count = qos->count_pending = n;
if (XnetTrace()) {
REXLOG_INFO("xnet trace: XNetQosLookup(caller={}) -> {} session(s) found",
caller, n);
}
if (apxnqosResults) { if (apxnqosResults) {
*apxnqosResults = qos_guest; *apxnqosResults = qos_guest;
} }
@ -713,6 +740,9 @@ u32 NetDll_XNetCreateKey_entry(u32 caller, mapped_void key) {
if (!key) { if (!key) {
return X_ERROR_INVALID_PARAMETER; return X_ERROR_INVALID_PARAMETER;
} }
if (XnetTrace()) {
REXLOG_INFO("xnet trace: XNetCreateKey(caller={})", caller);
}
uint8_t* out = static_cast<uint8_t*>(key); uint8_t* out = static_cast<uint8_t*>(key);
uint64_t r = XnetNextRand(); uint64_t r = XnetNextRand();
for (int i = 0; i < 16; ++i) { for (int i = 0; i < 16; ++i) {
@ -726,6 +756,9 @@ u32 NetDll_XNetCreateKey_entry(u32 caller, mapped_void key) {
// netcode-revival: accept + (Phase 1) register the session key. // netcode-revival: accept + (Phase 1) register the session key.
u32 NetDll_XNetRegisterKey_entry(u32 caller, mapped_void key) { u32 NetDll_XNetRegisterKey_entry(u32 caller, mapped_void key) {
if (XnetTrace()) {
REXLOG_INFO("xnet trace: XNetRegisterKey(caller={})", caller);
}
(void)key; (void)key;
return 0; return 0;
} }
@ -737,6 +770,10 @@ u32 NetDll_XNetUnregisterKey_entry(u32 caller, mapped_void key) {
// netcode-revival: connecting to a peer is reported as established. // netcode-revival: connecting to a peer is reported as established.
u32 NetDll_XNetConnect_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr) { u32 NetDll_XNetConnect_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr) {
if (XnetTrace()) {
REXLOG_INFO("xnet trace: XNetConnect(caller={}, peer=10.80.0.{})",
caller, xn_addr ? (ntohl(xn_addr->ina.s_addr) & 0xFFu) : 0);
}
(void)xn_addr; (void)xn_addr;
return 0; return 0;
} }
@ -744,6 +781,9 @@ u32 NetDll_XNetConnect_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr) {
// https://learn.microsoft.com/en-us/previous-versions/windows/desktop/xna/xnetgetconnectstatus // https://learn.microsoft.com/en-us/previous-versions/windows/desktop/xna/xnetgetconnectstatus
// INT XNetGetConnectStatus(const XNADDR* pxna, INT* pxncs) // INT XNetGetConnectStatus(const XNADDR* pxna, INT* pxncs)
u32 NetDll_XNetGetConnectStatus_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr, mapped_void status_ptr) { u32 NetDll_XNetGetConnectStatus_entry(u32 caller, ppc_ptr_t<XNADDR> xn_addr, mapped_void status_ptr) {
if (XnetTrace()) {
REXLOG_INFO("xnet trace: XNetGetConnectStatus(caller={}) -> CONNECTED", caller);
}
(void)xn_addr; (void)xn_addr;
if (status_ptr) { if (status_ptr) {
const uint32_t connected = 2; // XNET_CONNECT_STATUS_CONNECTED const uint32_t connected = 2; // XNET_CONNECT_STATUS_CONNECTED

View file

@ -43,6 +43,8 @@ using namespace rex::system::xam;
extern std::atomic<int> xam_dialogs_shown_; extern std::atomic<int> xam_dialogs_shown_;
REXCVAR_DECLARE(bool, xnet_trace);
i32 XamUserGetXUID_entry(u32 user_index, u32 type_mask, mapped_u64 xuid_ptr) { i32 XamUserGetXUID_entry(u32 user_index, u32 type_mask, mapped_u64 xuid_ptr) {
assert_true(type_mask == 1 || type_mask == 2 || type_mask == 3 || type_mask == 4 || assert_true(type_mask == 1 || type_mask == 2 || type_mask == 3 || type_mask == 4 ||
type_mask == 7); type_mask == 7);
@ -82,6 +84,17 @@ u32 XamUserGetSigninState_entry(u32 user_index) {
signin_state = user_profile->signin_state(); signin_state = user_profile->signin_state();
} }
} }
if (REXCVAR_GET(xnet_trace)) {
// Rate-limited: the frontend polls this frequently.
static double last_log = 0.0;
const double now =
double(std::chrono::steady_clock::now().time_since_epoch().count()) / 1e9;
if (now - last_log >= 0.5) {
last_log = now;
REXLOG_INFO("xam trace: XamUserGetSigninState(user={}) -> {}", user_index,
signin_state);
}
}
return signin_state; return signin_state;
} }
@ -113,6 +126,10 @@ i32 XamUserGetSigninInfo_entry(u32 user_index, u32 flags, ppc_ptr_t<X_USER_SIGNI
info->xuid = user_profile->xuid(); info->xuid = user_profile->xuid();
info->signin_state = user_profile->signin_state(); info->signin_state = user_profile->signin_state();
rex::string::util_copy_truncating(info->name, user_profile->name(), rex::countof(info->name)); rex::string::util_copy_truncating(info->name, user_profile->name(), rex::countof(info->name));
if (REXCVAR_GET(xnet_trace)) {
REXLOG_INFO("xam trace: XamUserGetSigninInfo(user={}, flags={}) -> signin_state={}",
user_index, flags, user_profile->signin_state());
}
return X_E_SUCCESS; return X_E_SUCCESS;
} }
@ -538,6 +555,8 @@ u32 XamUserAreUsersFriends_entry(u32 user_index, u32 unk1, u32 unk2, mapped_u32
} }
u32 XamShowSigninUI_entry(u32 unk, u32 unk_mask) { u32 XamShowSigninUI_entry(u32 unk, u32 unk_mask) {
REXLOG_INFO("xam trace: XamShowSigninUI(unk={}, mask={}) - game asked for sign-in",
unk, unk_mask);
// Mask values vary. Probably matching user types? Local/remote? // Mask values vary. Probably matching user types? Local/remote?
KernelState* kernel_state = REX_KERNEL_STATE(); KernelState* kernel_state = REX_KERNEL_STATE();