Files
Xenia-Canary/src/xenia/kernel/xam/apps/xgi_app.cc
Adrian f6323beb20 [XGI] Restrict XSessionCreate to offline sessions
Fixed Peggle from crashing after completing a level.
2025-06-22 20:04:09 +02:00

339 lines
12 KiB
C++

/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2021 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#include "xenia/kernel/xam/apps/xgi_app.h"
#include "xenia/base/logging.h"
namespace xe {
namespace kernel {
namespace xam {
namespace apps {
/*
* Most of the structs below were found in the Source SDK, provided as stubs.
* Specifically, they can be found in the Source 2007 SDK and the Alien Swarm
* Source SDK. Both are available on Steam for free. A GitHub mirror of the
* Alien Swarm SDK can be found here:
* https://github.com/NicolasDe/AlienSwarm/blob/master/src/common/xbox/xboxstubs.h
*/
struct XGI_XUSER_ACHIEVEMENT {
xe::be<uint32_t> user_index;
xe::be<uint32_t> achievement_id;
};
static_assert_size(XGI_XUSER_ACHIEVEMENT, 0x8);
struct XGI_XUSER_GET_PROPERTY {
xe::be<uint32_t> user_index;
xe::be<uint32_t> unused;
xe::be<uint64_t> xuid; // If xuid is 0 then user_index is used.
xe::be<uint32_t>
property_size_ptr; // Normally filled with sizeof(XUSER_PROPERTY), with
// exception of binary and wstring type.
xe::be<uint32_t> context_address;
xe::be<uint32_t> property_address;
};
static_assert_size(XGI_XUSER_GET_PROPERTY, 0x20);
struct XGI_XUSER_SET_CONTEXT {
xe::be<uint32_t> user_index;
xe::be<uint32_t> unused;
xe::be<uint64_t> xuid;
XUSER_CONTEXT context;
};
static_assert_size(XGI_XUSER_SET_CONTEXT, 0x18);
struct XGI_XUSER_SET_PROPERTY {
xe::be<uint32_t> user_index;
xe::be<uint32_t> unused;
xe::be<uint64_t> xuid;
xe::be<uint32_t> property_id;
xe::be<uint32_t> data_size;
xe::be<uint32_t> data_address;
};
static_assert_size(XGI_XUSER_SET_PROPERTY, 0x20);
struct XUSER_STATS_VIEW {
xe::be<uint32_t> ViewId;
xe::be<uint32_t> TotalViewRows;
xe::be<uint32_t> NumRows;
xe::be<uint32_t> pRows;
};
struct XUSER_STATS_COLUMN {
xe::be<uint16_t> ColumnId;
X_USER_DATA Value;
};
struct XUSER_STATS_RESET {
xe::be<uint32_t> user_index;
xe::be<uint32_t> view_id;
};
struct XUSER_ANID {
xe::be<uint32_t> user_index;
xe::be<uint32_t> cchAnIdBuffer;
xe::be<uint32_t> pszAnIdBuffer;
xe::be<uint32_t> value_const; // 1
};
XgiApp::XgiApp(KernelState* kernel_state) : App(kernel_state, 0xFB) {}
// http://mb.mirage.org/bugzilla/xliveless/main.c
X_HRESULT XgiApp::DispatchMessageSync(uint32_t message, uint32_t buffer_ptr,
uint32_t buffer_length) {
// NOTE: buffer_length may be zero or valid.
auto buffer = memory_->TranslateVirtual(buffer_ptr);
switch (message) {
case 0x000B0006: {
assert_true(!buffer_length ||
buffer_length == sizeof(XGI_XUSER_SET_CONTEXT));
const XGI_XUSER_SET_CONTEXT* xgi_context =
reinterpret_cast<const XGI_XUSER_SET_CONTEXT*>(buffer);
XELOGD("XGIUserSetContext({:08X}, ID: {:08X}, Value: {:08X})",
xgi_context->user_index.get(),
xgi_context->context.context_id.get(),
xgi_context->context.value.get());
UserProfile* user = nullptr;
if (xgi_context->xuid != 0) {
user = kernel_state_->xam_state()->GetUserProfile(xgi_context->xuid);
} else {
user =
kernel_state_->xam_state()->GetUserProfile(xgi_context->user_index);
}
if (user) {
kernel_state_->xam_state()->user_tracker()->UpdateContext(
user->xuid(), xgi_context->context.context_id,
xgi_context->context.value);
}
return X_E_SUCCESS;
}
case 0x000B0007: {
assert_true(!buffer_length ||
buffer_length == sizeof(XGI_XUSER_SET_PROPERTY));
const XGI_XUSER_SET_PROPERTY* xgi_property =
reinterpret_cast<const XGI_XUSER_SET_PROPERTY*>(buffer);
XELOGD("XGIUserSetPropertyEx({:08X}, {:08X}, {}, {:08X})",
xgi_property->user_index.get(), xgi_property->property_id.get(),
xgi_property->data_size.get(), xgi_property->data_address.get());
UserProfile* user = nullptr;
if (xgi_property->xuid != 0) {
user = kernel_state_->xam_state()->GetUserProfile(xgi_property->xuid);
} else {
user = kernel_state_->xam_state()->GetUserProfile(
xgi_property->user_index);
}
if (user) {
Property property(
xgi_property->property_id,
Property::get_valid_data_size(xgi_property->property_id,
xgi_property->data_size),
memory_->TranslateVirtual<uint8_t*>(xgi_property->data_address));
kernel_state_->xam_state()->user_tracker()->AddProperty(user->xuid(),
&property);
}
return X_E_SUCCESS;
}
case 0x000B0008: {
assert_true(!buffer_length ||
buffer_length == sizeof(XGI_XUSER_ACHIEVEMENT));
uint32_t achievement_count = xe::load_and_swap<uint32_t>(buffer + 0);
uint32_t achievements_ptr = xe::load_and_swap<uint32_t>(buffer + 4);
XELOGD("XGIUserWriteAchievements({:08X}, {:08X})", achievement_count,
achievements_ptr);
auto* achievement =
memory_->TranslateVirtual<XGI_XUSER_ACHIEVEMENT*>(achievements_ptr);
for (uint32_t i = 0; i < achievement_count; i++, achievement++) {
kernel_state_->achievement_manager()->EarnAchievement(
achievement->user_index, kernel_state_->title_id(),
achievement->achievement_id);
}
return X_E_SUCCESS;
}
case 0x000B0010: {
XELOGD("XSessionCreate({:08X}, {:08X}), implemented in netplay",
buffer_ptr, buffer_length);
assert_true(!buffer_length || buffer_length == 28);
// Sequence:
// - XamSessionCreateHandle
// - XamSessionRefObjByHandle
// - [this]
// - CloseHandle
uint32_t session_ptr = xe::load_and_swap<uint32_t>(buffer + 0x0);
uint32_t flags = xe::load_and_swap<uint32_t>(buffer + 0x4);
uint32_t num_slots_public = xe::load_and_swap<uint32_t>(buffer + 0x8);
uint32_t num_slots_private = xe::load_and_swap<uint32_t>(buffer + 0xC);
uint32_t user_xuid = xe::load_and_swap<uint32_t>(buffer + 0x10);
uint32_t session_info_ptr = xe::load_and_swap<uint32_t>(buffer + 0x14);
uint32_t nonce_ptr = xe::load_and_swap<uint32_t>(buffer + 0x18);
// 584107FB expects offline session creation using flags 0 to succeed
// while offline.
// 58410889 expects stats session creation failure while offline.
//
// Allow offline session creation, but do not allow Xbox Live featured
// session creation.
if (flags) {
return 0x80155209; // X_ONLINE_E_SESSION_NOT_LOGGED_ON
}
XELOGD(
"XGISessionCreateImpl({:08X}, {:08X}, {}, {}, {:08X}, {:08X}, "
"{:08X})",
session_ptr, flags, num_slots_public, num_slots_private, user_xuid,
session_info_ptr, nonce_ptr);
return X_E_SUCCESS;
}
case 0x000B0011: {
XELOGD("XGISessionDelete({:08X}, {:08X}), implemented in netplay",
buffer_ptr, buffer_length);
return X_STATUS_SUCCESS;
}
case 0x000B0012: {
assert_true(buffer_length == 0x14);
uint32_t session_ptr = xe::load_and_swap<uint32_t>(buffer + 0x0);
uint32_t user_count = xe::load_and_swap<uint32_t>(buffer + 0x4);
uint32_t unk_0 = xe::load_and_swap<uint32_t>(buffer + 0x8);
uint32_t user_index_array = xe::load_and_swap<uint32_t>(buffer + 0xC);
uint32_t private_slots_array = xe::load_and_swap<uint32_t>(buffer + 0x10);
assert_zero(unk_0);
XELOGD("XGISessionJoinLocal({:08X}, {}, {}, {:08X}, {:08X})", session_ptr,
user_count, unk_0, user_index_array, private_slots_array);
return X_E_SUCCESS;
}
case 0x000B0014: {
// Gets 584107FB in game.
// get high score table?
XELOGD("XSessionStart({:08X}), implemented in netplay", buffer_ptr);
return X_STATUS_SUCCESS;
}
case 0x000B0015: {
// send high scores?
XELOGD("XSessionEnd({:08X}, {:08X}), implemented in netplay", buffer_ptr,
buffer_length);
return X_STATUS_SUCCESS;
}
case 0x000B0021: {
XELOGD("XUserReadStats");
struct XUserReadStats {
xe::be<uint32_t> titleId;
xe::be<uint32_t> xuids_count;
xe::be<uint32_t> xuids_guest_address;
xe::be<uint32_t> specs_count;
xe::be<uint32_t> specs_guest_address;
xe::be<uint32_t> results_size;
xe::be<uint32_t> results_guest_address;
}* data = reinterpret_cast<XUserReadStats*>(buffer);
if (!data->results_guest_address) {
return 1;
}
}
case 0x000B0036: {
// Called after opening xbox live arcade and clicking on xbox live v5759
// to 5787 and called after clicking xbox live in the game library from
// v6683 to v6717
XELOGD("XGIUnkB0036({:08X}, {:08X}), unimplemented", buffer_ptr,
buffer_length);
return X_E_FAIL;
}
case 0x000B003D: {
// Used in 5451082A, 5553081E
// XUserGetCachedANID
XELOGI("XUserGetANID({:08X}, {:08X}), implemented in netplay", buffer_ptr,
buffer_length);
return X_E_FAIL;
}
case 0x000B0041: {
assert_true(!buffer_length ||
buffer_length == sizeof(XGI_XUSER_GET_PROPERTY));
const XGI_XUSER_GET_PROPERTY* xgi_property =
reinterpret_cast<const XGI_XUSER_GET_PROPERTY*>(buffer);
UserProfile* user = nullptr;
if (xgi_property->xuid != 0) {
user = kernel_state_->xam_state()->GetUserProfile(xgi_property->xuid);
} else {
user = kernel_state_->xam_state()->GetUserProfile(
xgi_property->user_index);
}
if (!user) {
XELOGD(
"XGIUserGetProperty - Invalid user provided: Index: {:08X} XUID: "
"{:16X}",
xgi_property->user_index.get(), xgi_property->xuid.get());
return X_E_NOTFOUND;
}
// Process context
if (xgi_property->context_address) {
XUSER_CONTEXT* context = memory_->TranslateVirtual<XUSER_CONTEXT*>(
xgi_property->context_address);
XELOGD("XGIUserGetProperty - Context requested: {:08X} XUID: {:16X}",
context->context_id.get(), user->xuid());
auto context_value =
kernel_state_->xam_state()->user_tracker()->GetUserContext(
user->xuid(), context->context_id);
if (!context_value) {
return X_E_INVALIDARG;
}
context->value = context_value.value();
return X_E_SUCCESS;
}
if (!xgi_property->property_size_ptr || !xgi_property->property_address) {
return X_E_INVALIDARG;
}
// Process property
XUSER_PROPERTY* property = memory_->TranslateVirtual<XUSER_PROPERTY*>(
xgi_property->property_address);
XELOGD("XGIUserGetProperty - Property requested: {:08X} XUID: {:16X}",
property->property_id.get(), user->xuid());
return kernel_state_->xam_state()->user_tracker()->GetProperty(
user->xuid(),
memory_->TranslateVirtual<uint32_t*>(xgi_property->property_size_ptr),
property);
}
case 0x000B0071: {
XELOGD("XGIUnkB0071({:08X}, {:08X}), unimplemented", buffer_ptr,
buffer_length);
return X_E_SUCCESS;
}
}
XELOGE(
"Unimplemented XGI message app={:08X}, msg={:08X}, arg1={:08X}, "
"arg2={:08X}",
app_id(), message, buffer_ptr, buffer_length);
return X_E_FAIL;
}
} // namespace apps
} // namespace xam
} // namespace kernel
} // namespace xe