From e09f224a6b0b3f21cacad525df187cffce85eed9 Mon Sep 17 00:00:00 2001 From: MiTHRAL Date: Fri, 28 Aug 2026 14:52:17 -0400 Subject: [PATCH] netcode-revival: answer Xam user shims for every slot + live flag = user 0 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. --- src/kernel/xam/xam_user.cpp | 48 ++++++++++++++--------------------- src/system/xnet_transport.cpp | 22 +++++++++++++--- 2 files changed, 38 insertions(+), 32 deletions(-) diff --git a/src/kernel/xam/xam_user.cpp b/src/kernel/xam/xam_user.cpp index e3b9c71..1b17644 100644 --- a/src/kernel/xam/xam_user.cpp +++ b/src/kernel/xam/xam_user.cpp @@ -54,19 +54,17 @@ i32 XamUserGetXUID_entry(u32 user_index, u32 type_mask, mapped_u64 xuid_ptr) { uint32_t result = X_E_NO_SUCH_USER; uint64_t xuid = 0; if (user_index < 4) { - if (user_index == 0) { - const auto& user_profile = REX_KERNEL_STATE()->user_profile(); - if (user_profile->is_signed_in()) { - auto type = user_profile->type() & type_mask; - if (type & (2 | 4)) { - // maybe online profile? - xuid = user_profile->xuid(); - result = X_E_SUCCESS; - } else if (type & 1) { - // maybe offline profile? - xuid = user_profile->xuid(); - result = X_E_SUCCESS; - } + const auto& user_profile = REX_KERNEL_STATE()->user_profile(); + if (user_profile->is_signed_in()) { + auto type = user_profile->type() & type_mask; + if (type & (2 | 4)) { + // maybe online profile? + xuid = user_profile->xuid(); + result = X_E_SUCCESS; + } else if (type & 1) { + // maybe offline profile? + xuid = user_profile->xuid(); + result = X_E_SUCCESS; } } } else { @@ -78,11 +76,12 @@ i32 XamUserGetXUID_entry(u32 user_index, u32 type_mask, mapped_u64 xuid_ptr) { u32 XamUserGetSigninState_entry(u32 user_index) { uint32_t signin_state = 0; + // netcode-revival: the party flow reads signin state for the "live user + // index" (the game's live flag is a user index, not a boolean). We model + // one profile, so answer every slot with the same signed-in state. if (user_index < 4) { - if (user_index == 0) { - const auto& user_profile = REX_KERNEL_STATE()->user_profile(); - signin_state = user_profile->signin_state(); - } + const auto& user_profile = REX_KERNEL_STATE()->user_profile(); + signin_state = user_profile->signin_state(); } if (REXCVAR_GET(xnet_trace)) { // Rate-limited: the frontend polls this frequently. @@ -114,7 +113,9 @@ i32 XamUserGetSigninInfo_entry(u32 user_index, u32 flags, ppc_ptr_t= 4) { return X_E_NO_SUCH_USER; } @@ -138,10 +139,6 @@ u32 XamUserGetName_entry(u32 user_index, mapped_string buffer, u32 buffer_len) { return X_E_INVALIDARG; } - if (user_index) { - return X_E_NO_SUCH_USER; - } - const auto& user_profile = REX_KERNEL_STATE()->user_profile(); if (!user_profile->is_signed_in()) { if (buffer && buffer_len) { @@ -160,10 +157,6 @@ u32 XamUserGetGamerTag_entry(u32 user_index, mapped_wstring buffer, u32 buffer_l return X_E_INVALIDARG; } - if (user_index) { - return X_E_NO_SUCH_USER; - } - const auto& user_profile = REX_KERNEL_STATE()->user_profile(); if (!user_profile->is_signed_in()) { return X_ERROR_NO_SUCH_USER; @@ -506,9 +499,6 @@ u32 XamUserGetMembershipTier_entry(u32 user_index) { if (user_index >= 4) { return X_ERROR_INVALID_PARAMETER; } - if (user_index) { - return X_ERROR_NO_SUCH_USER; - } if (!REX_KERNEL_STATE()->user_profile()->is_live_signed_in()) { return X_ERROR_NOT_LOGGED_ON; } diff --git a/src/system/xnet_transport.cpp b/src/system/xnet_transport.cpp index bb59cd9..42f01c8 100644 --- a/src/system/xnet_transport.cpp +++ b/src/system/xnet_transport.cpp @@ -162,9 +162,25 @@ void XNetTransport::ForceLiveFlag() { // 0x83030000 (recompiler's -2097348608) then lbz at -30639 => // 0x83028851. (Fragile: tied to this title's build; revival only.) uint8_t* flag = REX_KERNEL_MEMORY()->TranslateVirtual(0x83028851); - if (flag != nullptr && *flag == 0) { - REXLOG_INFO("xnet trace: forcing game live flag 0x83028851 = 1"); - *flag = 1; + // Rate-limited status so we can SEE whether the address maps and its value. + static double last_log = 0.0; + const double now = + double(std::chrono::steady_clock::now().time_since_epoch().count()) / 1e9; + const bool rate = now - last_log >= 2.0; + if (rate) { + last_log = now; + REXLOG_INFO("xnet trace: live flag 0x83028851 {} value={}", + flag != nullptr ? "mapped" : "NOT MAPPED", + flag != nullptr ? int(*flag) : -1); + } + // The flag is the LIVE USER INDEX (not a boolean). The profile lives in + // slot 0, so force 0 (any slot answers after the shim fix, but 0 is the + // safest for index-into-live-array readers). + if (flag != nullptr && *flag != 0) { + if (!rate) { + REXLOG_INFO("xnet trace: forcing game live flag 0x83028851 = 0"); + } + *flag = 0; } }