[XAM] Allow custom profiles - This breaks current content visibility

This includes creating many profiles
This commit is contained in:
Gliniak 2023-01-23 22:29:59 +01:00
parent 1ae524a8d5
commit bb5db2e3da
12 changed files with 296 additions and 112 deletions

View File

@ -936,13 +936,22 @@ void EmulatorWindow::ShowContentDirectory() {
std::filesystem::path target_path; std::filesystem::path target_path;
auto content_root = emulator_->content_root(); auto content_root = emulator_->content_root();
if (!emulator_->is_title_open() || !emulator_->kernel_state()) { if (!emulator_->kernel_state()) {
target_path = content_root; target_path = content_root;
}
if (!emulator_->is_title_open()) {
auto xuid_str = fmt::format(
"{:016X}", emulator_->kernel_state()->user_profile((uint32_t)0)->xuid());
auto package_root = content_root / xuid_str;
target_path = package_root;
} else { } else {
// TODO(gibbed): expose this via ContentManager? // TODO(gibbed): expose this via ContentManager?
auto xuid_str = fmt::format(
"{:016X}", emulator_->kernel_state()->user_profile((uint32_t)0)->xuid());
auto title_id = auto title_id =
fmt::format("{:08X}", emulator_->kernel_state()->title_id()); fmt::format("{:08X}", emulator_->kernel_state()->title_id());
auto package_root = content_root / title_id; auto package_root = content_root / xuid_str / title_id;
target_path = package_root; target_path = package_root;
} }

View File

@ -368,13 +368,20 @@ X_STATUS Emulator::InstallContentPackage(const std::filesystem::path& path) {
return X_STATUS_INVALID_PARAMETER; return X_STATUS_INVALID_PARAMETER;
} }
auto xuid_str = fmt::format("{:016X}", 0);
if (device->content_type() == XContentType::kSavedGame) {
xuid_str = fmt::format("{:016X}",
kernel_state()->user_profile((uint32_t)0)->xuid());
}
std::filesystem::path installation_path = std::filesystem::path installation_path =
content_root() / fmt::format("{:08X}", device->title_id()) / content_root() / xuid_str / fmt::format("{:08X}", device->title_id()) /
fmt::format("{:08X}", device->content_type()) / path.filename(); fmt::format("{:08X}", device->content_type()) / path.filename();
std::filesystem::path header_path = std::filesystem::path header_path =
content_root() / fmt::format("{:08X}", device->title_id()) / "Headers" / content_root() / xuid_str / fmt::format("{:08X}", device->title_id()) /
fmt::format("{:08X}", device->content_type()) / path.filename(); "Headers" / fmt::format("{:08X}", device->content_type()) /
path.filename();
if (std::filesystem::exists(installation_path)) { if (std::filesystem::exists(installation_path)) {
// TODO(Gliniak): Popup // TODO(Gliniak): Popup
@ -631,34 +638,40 @@ bool Emulator::ExceptionCallback(Exception* ex) {
std::string crash_msg; std::string crash_msg;
crash_msg.append("==== CRASH DUMP ====\n"); crash_msg.append("==== CRASH DUMP ====\n");
crash_msg.append(fmt::format("Thread ID (Host: 0x{:08X} / Guest: 0x{:08X})\n", crash_msg.append(fmt::format("Thread ID (Host: 0x{:08X} / Guest: 0x{:08X})\n",
current_thread->thread()->system_id(), current_thread->thread_id())); current_thread->thread()->system_id(),
crash_msg.append(fmt::format("Thread Handle: 0x{:08X}\n", current_thread->handle())); current_thread->thread_id()));
crash_msg.append(fmt::format("PC: 0x{:08X}\n", crash_msg.append(
guest_function->MapMachineCodeToGuestAddress(ex->pc()))); fmt::format("Thread Handle: 0x{:08X}\n", current_thread->handle()));
crash_msg.append(
fmt::format("PC: 0x{:08X}\n",
guest_function->MapMachineCodeToGuestAddress(ex->pc())));
crash_msg.append("Registers:\n"); crash_msg.append("Registers:\n");
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
crash_msg.append(fmt::format(" r{:<3} = {:016X}\n", i, context->r[i])); crash_msg.append(fmt::format(" r{:<3} = {:016X}\n", i, context->r[i]));
} }
for (int i = 0; i < 32; i++) { for (int i = 0; i < 32; i++) {
crash_msg.append(fmt::format(" f{:<3} = {:016X} = (double){} = (float){}\n", i, crash_msg.append(fmt::format(" f{:<3} = {:016X} = (double){} = (float){}\n",
*reinterpret_cast<uint64_t*>(&context->f[i]), context->f[i], i,
*(float*)&context->f[i])); *reinterpret_cast<uint64_t*>(&context->f[i]),
context->f[i], *(float*)&context->f[i]));
} }
for (int i = 0; i < 128; i++) { for (int i = 0; i < 128; i++) {
crash_msg.append(fmt::format(" v{:<3} = [0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X}]\n", i, crash_msg.append(
context->v[i].u32[0], context->v[i].u32[1], context->v[i].u32[2], fmt::format(" v{:<3} = [0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X}]\n", i,
context->v[i].u32[3])); context->v[i].u32[0], context->v[i].u32[1],
context->v[i].u32[2], context->v[i].u32[3]));
} }
XELOGE("{}", crash_msg); XELOGE("{}", crash_msg);
std::string crash_dlg = fmt::format( std::string crash_dlg = fmt::format(
"The guest has crashed.\n\n" "The guest has crashed.\n\n"
"Xenia has now paused itself.\n\n" "Xenia has now paused itself.\n\n"
"{}", crash_msg); "{}",
crash_msg);
// Display a dialog telling the user the guest has crashed. // Display a dialog telling the user the guest has crashed.
if (display_window_ && imgui_drawer_) { if (display_window_ && imgui_drawer_) {
display_window_->app_context().CallInUIThreadSynchronous([this, &crash_dlg]() { display_window_->app_context().CallInUIThreadSynchronous([this,
xe::ui::ImGuiDialog::ShowMessageBox( &crash_dlg]() {
imgui_drawer_, "Uh-oh!", crash_dlg); xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_, "Uh-oh!", crash_dlg);
}); });
} }

View File

@ -486,7 +486,7 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref<UserModule> module) {
} }
std::vector<xam::XCONTENT_AGGREGATE_DATA> tu_list = std::vector<xam::XCONTENT_AGGREGATE_DATA> tu_list =
content_manager()->ListContent(1, xe::XContentType::kInstaller, content_manager()->ListContent(1, 0, xe::XContentType::kInstaller,
module->title_id()); module->title_id());
if (tu_list.empty()) { if (tu_list.empty()) {
@ -500,7 +500,7 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref<UserModule> module) {
// TODO(Gliniak): Support for selecting from multiple TUs // TODO(Gliniak): Support for selecting from multiple TUs
const xam::XCONTENT_AGGREGATE_DATA& title_update = tu_list.front(); const xam::XCONTENT_AGGREGATE_DATA& title_update = tu_list.front();
X_RESULT open_status = X_RESULT open_status =
content_manager()->OpenContent("UPDATE", title_update, disc_number); content_manager()->OpenContent("UPDATE", 0, title_update, disc_number);
// Use the corresponding patch for the launch module // Use the corresponding patch for the launch module
std::filesystem::path patch_xexp = fmt::format("{0}.xexp", module->name()); std::filesystem::path patch_xexp = fmt::format("{0}.xexp", module->name());

View File

@ -128,6 +128,13 @@ class KernelState {
} }
xam::UserProfile* user_profile(uint32_t index) const { xam::UserProfile* user_profile(uint32_t index) const {
if (index == 0xFF) {
index = 0;
}
if (index == 0xFE) {
index = recently_used_profile_;
}
if (!IsUserSignedIn(index)) { if (!IsUserSignedIn(index)) {
return nullptr; return nullptr;
} }
@ -282,6 +289,8 @@ class KernelState {
std::condition_variable_any dispatch_cond_; std::condition_variable_any dispatch_cond_;
std::list<std::function<void()>> dispatch_queue_; std::list<std::function<void()>> dispatch_queue_;
// This should be in ProfileManager, but it is what it is
uint8_t recently_used_profile_ = 0;
BitMap tls_bitmap_; BitMap tls_bitmap_;
uint32_t ke_timestamp_bundle_ptr_ = 0; uint32_t ke_timestamp_bundle_ptr_ = 0;
std::unique_ptr<xe::threading::HighResolutionTimer> timestamp_timer_; std::unique_ptr<xe::threading::HighResolutionTimer> timestamp_timer_;

View File

@ -0,0 +1 @@
#include "profile_manager.h"

View File

@ -0,0 +1,69 @@
/**
******************************************************************************
* Xenia : Xbox 360 Emulator Research Project *
******************************************************************************
* Copyright 2023 Ben Vanik. All rights reserved. *
* Released under the BSD license - see LICENSE in the root for more details. *
******************************************************************************
*/
#ifndef XENIA_KERNEL_PROFILE_MANAGER_H_
#define XENIA_KERNEL_PROFILE_MANAGER_H_
#include <random>
#include <map>
#include <vector>
#include "xenia/kernel/xam/user_profile.h"
namespace xe {
namespace kernel {
class ProfileIOHandler {
public:
virtual bool LoadProfile(uint64_t xuid);
virtual bool SaveProfile(uint64_t xuid);
};
class ProfileIOHandlerToml : public ProfileIOHandler {
public:
bool LoadProfile(uint64_t xuid);
bool SaveProfile(uint64_t xuid);
};
class ProfileManager {
public:
ProfileManager(){};
~ProfileManager(){};
void CreateProfile();
void DeleteProfile(uint64_t xuid);
void Login(uint64_t xuid, uint32_t user_index);
void Logout(uint64_t xuid);
xam::UserProfile* GetProfile(uint32_t user_index);
xam::UserProfile* GetProfile(uint64_t xuid);
private:
static uint64_t GenerateXuid() {
std::random_device rd;
std::mt19937 gen(rd());
return ((uint64_t)0xE03 << 52) + (gen() % (1 << 31));
}
void LoadProfile(ProfileIOHandler* io, uint64_t xuid);
void SaveProfile(ProfileIOHandler* io, uint64_t xuid);
std::vector<uint32_t> profiles_;
std::map<uint8_t, xam::UserProfile*> logged_profile_;
//KernelState* kernel_state_;
};
} // namespace kernel
} // namespace xe
#endif // XENIA_KERNEL_PROFILE_MANAGER_H_

View File

@ -61,23 +61,27 @@ ContentManager::ContentManager(KernelState* kernel_state,
ContentManager::~ContentManager() = default; ContentManager::~ContentManager() = default;
std::filesystem::path ContentManager::ResolvePackageRoot( std::filesystem::path ContentManager::ResolvePackageRoot(
XContentType content_type, uint32_t title_id) { const uint64_t xuid, XContentType content_type, uint32_t title_id) {
if (title_id == kCurrentlyRunningTitleId) { if (title_id == kCurrentlyRunningTitleId) {
title_id = kernel_state_->title_id(); title_id = kernel_state_->title_id();
} }
auto xuid_str = fmt::format("{:016X}", xuid);
auto title_id_str = fmt::format("{:08X}", title_id); auto title_id_str = fmt::format("{:08X}", title_id);
auto content_type_str = fmt::format("{:08X}", uint32_t(content_type)); auto content_type_str = fmt::format("{:08X}", uint32_t(content_type));
// Package root path: // Package root path:
// content_root/title_id/content_type/ // content_root/title_id/content_type/
return root_path_ / title_id_str / content_type_str; return root_path_ / xuid_str / title_id_str / content_type_str;
} }
std::filesystem::path ContentManager::ResolvePackagePath( std::filesystem::path ContentManager::ResolvePackagePath(
const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) { const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number) {
// Content path: // Content path:
// content_root/title_id/content_type/data_file_name/ // content_root/title_id/content_type/data_file_name/
auto package_root = ResolvePackageRoot(data.content_type, data.title_id); auto package_root =
ResolvePackageRoot(xuid, data.content_type, data.title_id);
std::string disc_directory = ""; std::string disc_directory = "";
std::filesystem::path package_path = std::filesystem::path package_path =
package_root / xe::to_path(data.file_name()); package_root / xe::to_path(data.file_name());
@ -89,7 +93,8 @@ std::filesystem::path ContentManager::ResolvePackagePath(
} }
std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent( std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
uint32_t device_id, XContentType content_type, uint32_t title_id) { uint32_t device_id, const uint64_t xuid, XContentType content_type,
uint32_t title_id) {
std::vector<XCONTENT_AGGREGATE_DATA> result; std::vector<XCONTENT_AGGREGATE_DATA> result;
if (title_id == kCurrentlyRunningTitleId) { if (title_id == kCurrentlyRunningTitleId) {
@ -97,8 +102,8 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
} }
// Search path: // Search path:
// content_root/title_id/type_name/* // content_root/username/title_id/type_name/*
auto package_root = ResolvePackageRoot(content_type, title_id); auto package_root = ResolvePackageRoot(xuid, content_type, title_id);
auto file_infos = xe::filesystem::ListFiles(package_root); auto file_infos = xe::filesystem::ListFiles(package_root);
for (const auto& file_info : file_infos) { for (const auto& file_info : file_infos) {
if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) { if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) {
@ -107,9 +112,9 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
} }
XCONTENT_AGGREGATE_DATA content_data; XCONTENT_AGGREGATE_DATA content_data;
if (XSUCCEEDED( if (XSUCCEEDED(ReadContentHeaderFile(
ReadContentHeaderFile(xe::path_to_utf8(file_info.name) + ".header", xe::path_to_utf8(file_info.name) + ".header", xuid, content_type,
content_type, content_data, title_id))) { content_data, title_id))) {
result.emplace_back(std::move(content_data)); result.emplace_back(std::move(content_data));
} else { } else {
content_data.device_id = device_id; content_data.device_id = device_id;
@ -124,9 +129,9 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
} }
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage( std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data, const std::string_view root_name, const uint64_t xuid,
const uint32_t disc_number) { const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) {
auto package_path = ResolvePackagePath(data, disc_number); auto package_path = ResolvePackagePath(xuid, data, disc_number);
if (!std::filesystem::exists(package_path)) { if (!std::filesystem::exists(package_path)) {
return nullptr; return nullptr;
} }
@ -138,18 +143,24 @@ std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
return package; return package;
} }
bool ContentManager::ContentExists(const XCONTENT_AGGREGATE_DATA& data) { bool ContentManager::ContentExists(const uint64_t xuid,
auto path = ResolvePackagePath(data); const XCONTENT_AGGREGATE_DATA& data) {
auto path = ResolvePackagePath(xuid, data);
return std::filesystem::exists(path); return std::filesystem::exists(path);
} }
X_RESULT ContentManager::WriteContentHeaderFile( X_RESULT ContentManager::WriteContentHeaderFile(
const XCONTENT_AGGREGATE_DATA* data) { const uint64_t xuid, const XCONTENT_AGGREGATE_DATA* data) {
if (!kernel_state_->IsUserSignedIn(xuid)) {
return X_STATUS_INVALID_PARAMETER_1;
}
auto xuid_str = fmt::format("{:016X}", xuid);
auto title_id = fmt::format("{:08X}", kernel_state_->title_id()); auto title_id = fmt::format("{:08X}", kernel_state_->title_id());
auto content_type = auto content_type =
fmt::format("{:08X}", load_and_swap<uint32_t>(&data->content_type)); fmt::format("{:08X}", load_and_swap<uint32_t>(&data->content_type));
auto header_path = auto header_path = root_path_ / xuid_str / title_id /
root_path_ / title_id / kGameContentHeaderDirName / content_type; kGameContentHeaderDirName / content_type;
if (!std::filesystem::exists(header_path)) { if (!std::filesystem::exists(header_path)) {
if (!std::filesystem::create_directories(header_path)) { if (!std::filesystem::create_directories(header_path)) {
@ -170,6 +181,7 @@ X_RESULT ContentManager::WriteContentHeaderFile(
} }
X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name, X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name,
const uint64_t xuid,
XContentType content_type, XContentType content_type,
XCONTENT_AGGREGATE_DATA& data, XCONTENT_AGGREGATE_DATA& data,
const uint32_t title_id) { const uint32_t title_id) {
@ -178,8 +190,13 @@ X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name,
title_id_str = fmt::format("{:08X}", kernel_state_->title_id()); title_id_str = fmt::format("{:08X}", kernel_state_->title_id());
} }
if (!kernel_state_->IsUserSignedIn(xuid)) {
return X_STATUS_NO_SUCH_FILE;
}
auto xuid_str = fmt::format("{:016X}", xuid);
auto content_type_directory = fmt::format("{:08X}", content_type); auto content_type_directory = fmt::format("{:08X}", content_type);
auto header_file_path = root_path_ / title_id_str / auto header_file_path = root_path_ / xuid_str / title_id_str /
kGameContentHeaderDirName / content_type_directory / kGameContentHeaderDirName / content_type_directory /
file_name; file_name;
constexpr uint32_t header_size = sizeof(XCONTENT_AGGREGATE_DATA); constexpr uint32_t header_size = sizeof(XCONTENT_AGGREGATE_DATA);
@ -206,13 +223,14 @@ X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name,
// usually requires title_id to be provided // usually requires title_id to be provided
// Kinda simple workaround for that, but still assumption // Kinda simple workaround for that, but still assumption
data.title_id = title_id; data.title_id = title_id;
data.unk134 = kernel_state_->user_profile(uint32_t(0))->xuid(); data.xuid = xuid;
return X_STATUS_SUCCESS; return X_STATUS_SUCCESS;
} }
return X_STATUS_NO_SUCH_FILE; return X_STATUS_NO_SUCH_FILE;
} }
X_RESULT ContentManager::CreateContent(const std::string_view root_name, X_RESULT ContentManager::CreateContent(const std::string_view root_name,
const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data) { const XCONTENT_AGGREGATE_DATA& data) {
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
@ -221,7 +239,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
return X_ERROR_ALREADY_EXISTS; return X_ERROR_ALREADY_EXISTS;
} }
auto package_path = ResolvePackagePath(data); auto package_path = ResolvePackagePath(xuid, data);
if (std::filesystem::exists(package_path)) { if (std::filesystem::exists(package_path)) {
// Exists, must not! // Exists, must not!
return X_ERROR_ALREADY_EXISTS; return X_ERROR_ALREADY_EXISTS;
@ -231,7 +249,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
return X_ERROR_ACCESS_DENIED; return X_ERROR_ACCESS_DENIED;
} }
auto package = ResolvePackage(root_name, data); auto package = ResolvePackage(root_name, xuid, data);
assert_not_null(package); assert_not_null(package);
open_packages_.insert({string_key::create(root_name), package.release()}); open_packages_.insert({string_key::create(root_name), package.release()});
@ -240,6 +258,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
} }
X_RESULT ContentManager::OpenContent(const std::string_view root_name, X_RESULT ContentManager::OpenContent(const std::string_view root_name,
const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data, const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number) { const uint32_t disc_number) {
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
@ -249,14 +268,14 @@ X_RESULT ContentManager::OpenContent(const std::string_view root_name,
return X_ERROR_ALREADY_EXISTS; return X_ERROR_ALREADY_EXISTS;
} }
auto package_path = ResolvePackagePath(data, disc_number); auto package_path = ResolvePackagePath(xuid, data, disc_number);
if (!std::filesystem::exists(package_path)) { if (!std::filesystem::exists(package_path)) {
// Does not exist, must be created. // Does not exist, must be created.
return X_ERROR_FILE_NOT_FOUND; return X_ERROR_FILE_NOT_FOUND;
} }
// Open package. // Open package.
auto package = ResolvePackage(root_name, data, disc_number); auto package = ResolvePackage(root_name, xuid, data, disc_number);
assert_not_null(package); assert_not_null(package);
open_packages_.insert({string_key::create(root_name), package.release()}); open_packages_.insert({string_key::create(root_name), package.release()});
@ -281,9 +300,10 @@ X_RESULT ContentManager::CloseContent(const std::string_view root_name) {
} }
X_RESULT ContentManager::GetContentThumbnail( X_RESULT ContentManager::GetContentThumbnail(
const XCONTENT_AGGREGATE_DATA& data, std::vector<uint8_t>* buffer) { const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t>* buffer) {
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
auto package_path = ResolvePackagePath(data); auto package_path = ResolvePackagePath(xuid, data);
auto thumb_path = package_path / kThumbnailFileName; auto thumb_path = package_path / kThumbnailFileName;
if (std::filesystem::exists(thumb_path)) { if (std::filesystem::exists(thumb_path)) {
auto file = xe::filesystem::OpenFile(thumb_path, "rb"); auto file = xe::filesystem::OpenFile(thumb_path, "rb");
@ -300,9 +320,10 @@ X_RESULT ContentManager::GetContentThumbnail(
} }
X_RESULT ContentManager::SetContentThumbnail( X_RESULT ContentManager::SetContentThumbnail(
const XCONTENT_AGGREGATE_DATA& data, std::vector<uint8_t> buffer) { const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t> buffer) {
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
auto package_path = ResolvePackagePath(data); auto package_path = ResolvePackagePath(xuid, data);
std::filesystem::create_directories(package_path); std::filesystem::create_directories(package_path);
if (std::filesystem::exists(package_path)) { if (std::filesystem::exists(package_path)) {
auto thumb_path = package_path / kThumbnailFileName; auto thumb_path = package_path / kThumbnailFileName;
@ -315,7 +336,8 @@ X_RESULT ContentManager::SetContentThumbnail(
} }
} }
X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) { X_RESULT ContentManager::DeleteContent(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data) {
auto global_lock = global_critical_region_.Acquire(); auto global_lock = global_critical_region_.Acquire();
if (IsContentOpen(data)) { if (IsContentOpen(data)) {
@ -323,7 +345,7 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) {
return X_ERROR_ACCESS_DENIED; return X_ERROR_ACCESS_DENIED;
} }
auto package_path = ResolvePackagePath(data); auto package_path = ResolvePackagePath(xuid, data);
if (std::filesystem::remove_all(package_path) > 0) { if (std::filesystem::remove_all(package_path) > 0) {
return X_ERROR_SUCCESS; return X_ERROR_SUCCESS;
} else { } else {
@ -331,14 +353,21 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) {
} }
} }
std::filesystem::path ContentManager::ResolveGameUserContentPath() { std::filesystem::path ContentManager::ResolveGameUserContentPath(
const uint64_t xuid) {
auto title_id = fmt::format("{:08X}", kernel_state_->title_id()); auto title_id = fmt::format("{:08X}", kernel_state_->title_id());
auto user_name = const UserProfile* user = kernel_state_->user_profile(xuid);
xe::to_path(kernel_state_->user_profile(uint32_t(0))->name()); if (!user) {
auto xuid_str = fmt::format("{:016X}", 0);
// What now?
// Lets assume we save it to common
return root_path_ / xuid_str / title_id / kGameUserContentDirName;
}
auto xuid_str = fmt::format("{:016X}", xuid);
// Per-game per-profile data location: // Per-game per-profile data location:
// content_root/title_id/profile/user_name // content_root/title_id/profile/user_name
return root_path_ / title_id / kGameUserContentDirName / user_name; return root_path_ / xuid_str / title_id / kGameUserContentDirName;
} }
bool ContentManager::IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const { bool ContentManager::IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const {

View File

@ -93,7 +93,7 @@ struct XCONTENT_DATA {
static_assert_size(XCONTENT_DATA, 0x134); static_assert_size(XCONTENT_DATA, 0x134);
struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA { struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA {
be<uint64_t> unk134; // some titles store XUID here? be<uint64_t> xuid; // some titles store XUID here?
be<uint32_t> title_id; be<uint32_t> title_id;
XCONTENT_AGGREGATE_DATA() = default; XCONTENT_AGGREGATE_DATA() = default;
@ -103,7 +103,7 @@ struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA {
set_display_name(other.display_name()); set_display_name(other.display_name());
set_file_name(other.file_name()); set_file_name(other.file_name());
padding[0] = padding[1] = 0; padding[0] = padding[1] = 0;
unk134 = 0; xuid = 0;
title_id = kCurrentlyRunningTitleId; title_id = kCurrentlyRunningTitleId;
} }
@ -141,39 +141,48 @@ class ContentManager {
const std::filesystem::path& root_path); const std::filesystem::path& root_path);
~ContentManager(); ~ContentManager();
std::vector<XCONTENT_AGGREGATE_DATA> ListContent(uint32_t device_id, std::vector<XCONTENT_AGGREGATE_DATA> ListContent(
XContentType content_type, const uint32_t device_id, const uint64_t xuid,
uint32_t title_id = -1); const XContentType content_type, uint32_t title_id = -1);
std::unique_ptr<ContentPackage> ResolvePackage( std::unique_ptr<ContentPackage> ResolvePackage(
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data, const std::string_view root_name, const uint64_t xuid,
const uint32_t disc_number = -1); const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number = -1);
bool ContentExists(const XCONTENT_AGGREGATE_DATA& data); bool ContentExists(const uint64_t xuid,
X_RESULT WriteContentHeaderFile(const XCONTENT_AGGREGATE_DATA* data_raw); const XCONTENT_AGGREGATE_DATA& data);
X_RESULT WriteContentHeaderFile(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA* data_raw);
X_RESULT ReadContentHeaderFile(const std::string_view file_name, X_RESULT ReadContentHeaderFile(const std::string_view file_name,
const uint64_t xuid,
XContentType content_type, XContentType content_type,
XCONTENT_AGGREGATE_DATA& data, XCONTENT_AGGREGATE_DATA& data,
const uint32_t title_id = -1); const uint32_t title_id = -1);
X_RESULT CreateContent(const std::string_view root_name, X_RESULT CreateContent(const std::string_view root_name, const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data); const XCONTENT_AGGREGATE_DATA& data);
X_RESULT OpenContent(const std::string_view root_name, X_RESULT OpenContent(const std::string_view root_name, const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data, const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number = -1); const uint32_t disc_number = -1);
X_RESULT CloseContent(const std::string_view root_name); X_RESULT CloseContent(const std::string_view root_name);
X_RESULT GetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data, X_RESULT GetContentThumbnail(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t>* buffer); std::vector<uint8_t>* buffer);
X_RESULT SetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data, X_RESULT SetContentThumbnail(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
std::vector<uint8_t> buffer); std::vector<uint8_t> buffer);
X_RESULT DeleteContent(const XCONTENT_AGGREGATE_DATA& data); X_RESULT DeleteContent(const uint64_t xuid,
std::filesystem::path ResolveGameUserContentPath(); const XCONTENT_AGGREGATE_DATA& data);
std::filesystem::path ResolveGameUserContentPath(const uint64_t xuid = 0);
bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const; bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const;
void CloseOpenedFilesFromContent(const std::string_view root_name); void CloseOpenedFilesFromContent(const std::string_view root_name);
private: private:
std::filesystem::path ResolvePackageRoot(XContentType content_type, std::filesystem::path ResolvePackageRoot(
const uint64_t xuid,
XContentType content_type,
uint32_t title_id = -1); uint32_t title_id = -1);
std::filesystem::path ResolvePackagePath(const XCONTENT_AGGREGATE_DATA& data, std::filesystem::path ResolvePackagePath(const uint64_t xuid,
const XCONTENT_AGGREGATE_DATA& data,
const uint32_t disc_number = -1); const uint32_t disc_number = -1);
KernelState* kernel_state_; KernelState* kernel_state_;

View File

@ -10,25 +10,53 @@
#include "xenia/kernel/xam/user_profile.h" #include "xenia/kernel/xam/user_profile.h"
#include <sstream> #include <sstream>
#include <xhash>
#include "third_party/fmt/include/fmt/format.h" #include "third_party/fmt/include/fmt/format.h"
#include "xenia/kernel/kernel_state.h" #include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/util/shim_utils.h"
DEFINE_string(gamertag_0, "User_0", "The name of your user profile.",
"Profile");
DEFINE_string(gamertag_1, "User_1", "The name of your user profile.",
"Profile");
DEFINE_string(gamertag_2, "User_2", "The name of your user profile.",
"Profile");
DEFINE_string(gamertag_3, "User_3", "The name of your user profile.",
"Profile");
namespace xe { namespace xe {
namespace kernel { namespace kernel {
namespace xam { namespace xam {
UserProfile::UserProfile(uint8_t index) { UserProfile::UserProfile(uint8_t index) {
const std::vector<std::string> username_list = {
cvars::gamertag_0,
cvars::gamertag_1,
cvars::gamertag_2,
cvars::gamertag_3,
};
std::string current_username = username_list[index];
if (current_username.empty()) {
current_username = "User_" + std::to_string(index);
}
// TODO(Gliniak): Username sanitizer
//
// 58410A1F checks the user XUID against a mask of 0x00C0000000000000 (3<<54), // 58410A1F checks the user XUID against a mask of 0x00C0000000000000 (3<<54),
// if non-zero, it prevents the user from playing the game. // if non-zero, it prevents the user from playing the game.
// "You do not have permissions to perform this operation." // "You do not have permissions to perform this operation."
xuid_ = 0xB13EBABEBABEBABE + index;
name_ = "User";
if (index) {
name_ = "User_" + std::to_string(index);
}
// Get assigned username to current index from config
// Then use lower 4 bytes based on player name for unique xuid
size_t hash = std::hash<std::string>{}(current_username);
xuid_ = 0xB13E000000000000 + (hash & 0xFFFFFFFF);
name_ = current_username;
XELOGE("User (Gamertag: {}) in slot {} have xuid = {:016X}", name_, index,
xuid_);
// https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195 // https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195
// https://github.com/arkem/py360/blob/master/py360/constants.py // https://github.com/arkem/py360/blob/master/py360/constants.py
// XPROFILE_GAMER_YAXIS_INVERSION // XPROFILE_GAMER_YAXIS_INVERSION
@ -137,7 +165,7 @@ UserProfile::Setting* UserProfile::GetSetting(uint32_t setting_id) {
void UserProfile::LoadSetting(UserProfile::Setting* setting) { void UserProfile::LoadSetting(UserProfile::Setting* setting) {
if (setting->is_title_specific()) { if (setting->is_title_specific()) {
auto content_dir = auto content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath(); kernel_state()->content_manager()->ResolveGameUserContentPath(xuid_);
auto setting_id = fmt::format("{:08X}", setting->setting_id); auto setting_id = fmt::format("{:08X}", setting->setting_id);
auto file_path = content_dir / setting_id; auto file_path = content_dir / setting_id;
auto file = xe::filesystem::OpenFile(file_path, "rb"); auto file = xe::filesystem::OpenFile(file_path, "rb");
@ -163,7 +191,7 @@ void UserProfile::SaveSetting(UserProfile::Setting* setting) {
if (setting->is_title_specific()) { if (setting->is_title_specific()) {
auto serialized_setting = setting->Serialize(); auto serialized_setting = setting->Serialize();
auto content_dir = auto content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath(); kernel_state()->content_manager()->ResolveGameUserContentPath(xuid_);
std::filesystem::create_directories(content_dir); std::filesystem::create_directories(content_dir);
auto setting_id = fmt::format("{:08X}", setting->setting_id); auto setting_id = fmt::format("{:08X}", setting->setting_id);
auto file_path = content_dir / setting_id; auto file_path = content_dir / setting_id;

View File

@ -85,6 +85,12 @@ dword_result_t XamContentCreateEnumerator_entry(
return X_E_INVALIDARG; return X_E_INVALIDARG;
} }
const auto user = kernel_state()->user_profile(user_index);
if (!user) {
return X_E_INVALIDARG;
}
if (buffer_size_ptr) { if (buffer_size_ptr) {
*buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate; *buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
} }
@ -99,7 +105,7 @@ dword_result_t XamContentCreateEnumerator_entry(
if (!device_info || device_info->device_id == DummyDeviceId::HDD) { if (!device_info || device_info->device_id == DummyDeviceId::HDD) {
// Get all content data. // Get all content data.
auto content_datas = kernel_state()->content_manager()->ListContent( auto content_datas = kernel_state()->content_manager()->ListContent(
static_cast<uint32_t>(DummyDeviceId::HDD), static_cast<uint32_t>(DummyDeviceId::HDD), user->xuid(),
XContentType(uint32_t(content_type))); XContentType(uint32_t(content_type)));
for (const auto& content_data : content_datas) { for (const auto& content_data : content_datas) {
auto item = e->AppendItem(); auto item = e->AppendItem();
@ -142,7 +148,14 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
*disposition_ptr = 0; *disposition_ptr = 0;
} }
auto run = [content_manager, root_name = root_name.value(), flags, const auto user = kernel_state()->user_profile(user_index);
if (!user) {
return X_ERROR_INVALID_PARAMETER;
}
const uint64_t xuid = user->xuid();
auto run = [content_manager, root_name = root_name.value(), xuid, flags,
content_data, disposition_ptr, license_mask_ptr, overlapped_ptr]( content_data, disposition_ptr, license_mask_ptr, overlapped_ptr](
uint32_t& extended_error, uint32_t& length) -> X_RESULT { uint32_t& extended_error, uint32_t& length) -> X_RESULT {
X_RESULT result = X_ERROR_INVALID_PARAMETER; X_RESULT result = X_ERROR_INVALID_PARAMETER;
@ -151,7 +164,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
switch (flags & 0xF) { switch (flags & 0xF) {
case 1: // CREATE_NEW case 1: // CREATE_NEW
// Fail if exists. // Fail if exists.
if (content_manager->ContentExists(content_data)) { if (content_manager->ContentExists(xuid, content_data)) {
result = X_ERROR_ALREADY_EXISTS; result = X_ERROR_ALREADY_EXISTS;
} else { } else {
create = true; create = true;
@ -159,8 +172,8 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
break; break;
case 2: // CREATE_ALWAYS case 2: // CREATE_ALWAYS
// Overwrite existing, if any. // Overwrite existing, if any.
if (content_manager->ContentExists(content_data)) { if (content_manager->ContentExists(xuid, content_data)) {
content_manager->DeleteContent(content_data); content_manager->DeleteContent(xuid, content_data);
create = true; create = true;
} else { } else {
create = true; create = true;
@ -168,7 +181,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
break; break;
case 3: // OPEN_EXISTING case 3: // OPEN_EXISTING
// Open only if exists. // Open only if exists.
if (!content_manager->ContentExists(content_data)) { if (!content_manager->ContentExists(xuid, content_data)) {
result = X_ERROR_PATH_NOT_FOUND; result = X_ERROR_PATH_NOT_FOUND;
} else { } else {
open = true; open = true;
@ -176,7 +189,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
break; break;
case 4: // OPEN_ALWAYS case 4: // OPEN_ALWAYS
// Create if needed. // Create if needed.
if (!content_manager->ContentExists(content_data)) { if (!content_manager->ContentExists(xuid, content_data)) {
create = true; create = true;
} else { } else {
open = true; open = true;
@ -184,10 +197,10 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
break; break;
case 5: // TRUNCATE_EXISTING case 5: // TRUNCATE_EXISTING
// Fail if doesn't exist, if does exist delete and recreate. // Fail if doesn't exist, if does exist delete and recreate.
if (!content_manager->ContentExists(content_data)) { if (!content_manager->ContentExists(xuid, content_data)) {
result = X_ERROR_PATH_NOT_FOUND; result = X_ERROR_PATH_NOT_FOUND;
} else { } else {
content_manager->DeleteContent(content_data); content_manager->DeleteContent(xuid, content_data);
create = true; create = true;
} }
break; break;
@ -206,12 +219,12 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
} }
if (create) { if (create) {
result = content_manager->CreateContent(root_name, content_data); result = content_manager->CreateContent(root_name, xuid, content_data);
if (XSUCCEEDED(result)) { if (XSUCCEEDED(result)) {
content_manager->WriteContentHeaderFile(&content_data); content_manager->WriteContentHeaderFile(xuid, &content_data);
} }
} else if (open) { } else if (open) {
result = content_manager->OpenContent(root_name, content_data); result = content_manager->OpenContent(root_name, xuid, content_data);
} }
if (license_mask_ptr && XSUCCEEDED(result)) { if (license_mask_ptr && XSUCCEEDED(result)) {
@ -322,8 +335,8 @@ dword_result_t XamContentGetCreator_entry(dword_t user_index,
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>(); XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
bool content_exists = bool content_exists = kernel_state()->content_manager()->ContentExists(
kernel_state()->content_manager()->ContentExists(content_data); user_index, content_data);
if (content_exists) { if (content_exists) {
if (content_data.content_type == XContentType::kSavedGame) { if (content_data.content_type == XContentType::kSavedGame) {
@ -365,10 +378,14 @@ dword_result_t XamContentGetThumbnail_entry(dword_t user_index,
uint32_t buffer_size = *buffer_size_ptr; uint32_t buffer_size = *buffer_size_ptr;
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>(); XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
const UserProfile* user = kernel_state()->user_profile(user_index);
if (!user) {
return X_ERROR_NO_SUCH_USER;
}
// Get thumbnail (if it exists). // Get thumbnail (if it exists).
std::vector<uint8_t> buffer; std::vector<uint8_t> buffer;
auto result = kernel_state()->content_manager()->GetContentThumbnail( auto result = kernel_state()->content_manager()->GetContentThumbnail(
content_data, &buffer); user->xuid(), content_data, &buffer);
*buffer_size_ptr = uint32_t(buffer.size()); *buffer_size_ptr = uint32_t(buffer.size());
@ -406,7 +423,7 @@ dword_result_t XamContentSetThumbnail_entry(dword_t user_index,
auto buffer = std::vector<uint8_t>((uint8_t*)buffer_ptr, auto buffer = std::vector<uint8_t>((uint8_t*)buffer_ptr,
(uint8_t*)buffer_ptr + buffer_size); (uint8_t*)buffer_ptr + buffer_size);
auto result = kernel_state()->content_manager()->SetContentThumbnail( auto result = kernel_state()->content_manager()->SetContentThumbnail(
content_data, std::move(buffer)); user_index, content_data, std::move(buffer));
if (overlapped_ptr) { if (overlapped_ptr) {
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result); kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
@ -422,7 +439,13 @@ dword_result_t XamContentDelete_entry(dword_t user_index,
lpunknown_t overlapped_ptr) { lpunknown_t overlapped_ptr) {
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>(); XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
auto result = kernel_state()->content_manager()->DeleteContent(content_data); const UserProfile* user = kernel_state()->user_profile(user_index);
if (!user) {
return X_ERROR_NO_SUCH_USER;
}
auto result = kernel_state()->content_manager()->DeleteContent(user->xuid(),
content_data);
if (overlapped_ptr) { if (overlapped_ptr) {
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result); kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
@ -451,7 +474,6 @@ static_assert_size(X_SWAPDISC_ERROR_MESSAGE, 12);
dword_result_t XamSwapDisc_entry( dword_result_t XamSwapDisc_entry(
dword_t disc_number, pointer_t<X_KEVENT> completion_handle, dword_t disc_number, pointer_t<X_KEVENT> completion_handle,
pointer_t<X_SWAPDISC_ERROR_MESSAGE> error_message) { pointer_t<X_SWAPDISC_ERROR_MESSAGE> error_message) {
xex2_opt_execution_info* info = nullptr; xex2_opt_execution_info* info = nullptr;
kernel_state()->GetExecutableModule()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, kernel_state()->GetExecutableModule()->GetOptHeader(XEX_HEADER_EXECUTION_INFO,
&info); &info);

View File

@ -112,10 +112,15 @@ dword_result_t XamContentAggregateCreateEnumerator_entry(qword_t xuid,
std::back_inserter(title_ids)); std::back_inserter(title_ids));
} }
auto user = kernel_state()->user_profile(xuid);
if (!user) {
return X_E_NO_SUCH_USER;
}
for (auto& title_id : title_ids) { for (auto& title_id : title_ids) {
// Get all content data. // Get all content data.
auto content_datas = kernel_state()->content_manager()->ListContent( auto content_datas = kernel_state()->content_manager()->ListContent(
static_cast<uint32_t>(DummyDeviceId::HDD), content_type_enum, static_cast<uint32_t>(DummyDeviceId::HDD), xuid, content_type_enum,
title_id); title_id);
for (const auto& content_data : content_datas) { for (const auto& content_data : content_datas) {
auto item = e->AppendItem(); auto item = e->AppendItem();

View File

@ -9,6 +9,7 @@
#include <cstring> #include <cstring>
#include "xenia/app/emulator_window.h"
#include "xenia/base/logging.h" #include "xenia/base/logging.h"
#include "xenia/base/math.h" #include "xenia/base/math.h"
#include "xenia/base/string_util.h" #include "xenia/base/string_util.h"
@ -355,17 +356,6 @@ dword_result_t XamUserWriteProfileSettings_entry(
if (!setting_count || !settings) { if (!setting_count || !settings) {
return X_ERROR_INVALID_PARAMETER; return X_ERROR_INVALID_PARAMETER;
} }
// Skip writing data about users with id != 0 they're not supported
if (user_index > 0) {
if (overlapped) {
kernel_state()->CompleteOverlappedImmediate(
kernel_state()->memory()->HostToGuestVirtual(overlapped),
X_ERROR_NO_SUCH_USER);
return X_ERROR_IO_PENDING;
}
return X_ERROR_SUCCESS;
}
// Update and save settings. // Update and save settings.
const auto& user_profile = kernel_state()->user_profile(user_index); const auto& user_profile = kernel_state()->user_profile(user_index);
@ -561,7 +551,7 @@ dword_result_t XamShowSigninUI_entry(dword_t unk, dword_t unk_mask) {
// Mask values vary. Probably matching user types? Local/remote? // Mask values vary. Probably matching user types? Local/remote?
// Games seem to sit and loop until we trigger this notification: // Games seem to sit and loop until we trigger this notification:
for (uint32_t i = 0; i < 4; i++) { for (uint32_t i = 0; i < MAX_USERS; i++) {
if (kernel_state()->IsUserSignedIn(i)) { if (kernel_state()->IsUserSignedIn(i)) {
// XN_SYS_SIGNINCHANGED // XN_SYS_SIGNINCHANGED
kernel_state()->BroadcastNotification(0xA, i); kernel_state()->BroadcastNotification(0xA, i);