netcode-revival: answer Xam user shims for every slot + live flag = user 0
Some checks are pending
Lint & Format Check / Format Check (push) Waiting to run

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.
This commit is contained in:
MiTHRAL 2026-08-28 14:52:17 -04:00
parent 73d8f36af2
commit e09f224a6b
2 changed files with 38 additions and 32 deletions

View file

@ -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<X_USER_SIGNI
}
std::memset(info, 0, sizeof(X_USER_SIGNIN_INFO));
if (user_index) {
// netcode-revival: answer every slot with the one profile (the game's
// "live user index" is not necessarily 0).
if (user_index >= 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;
}

View file

@ -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<uint8_t*>(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;
}
}