diff --git a/src/xenia/app/emulator_window.cc b/src/xenia/app/emulator_window.cc index 2d6c11b92..c8bca8ba3 100644 --- a/src/xenia/app/emulator_window.cc +++ b/src/xenia/app/emulator_window.cc @@ -936,13 +936,22 @@ void EmulatorWindow::ShowContentDirectory() { std::filesystem::path target_path; auto content_root = emulator_->content_root(); - if (!emulator_->is_title_open() || !emulator_->kernel_state()) { + if (!emulator_->kernel_state()) { 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 { // TODO(gibbed): expose this via ContentManager? + auto xuid_str = fmt::format( + "{:016X}", emulator_->kernel_state()->user_profile((uint32_t)0)->xuid()); auto 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; } diff --git a/src/xenia/emulator.cc b/src/xenia/emulator.cc index 8dbca85a4..411672de7 100644 --- a/src/xenia/emulator.cc +++ b/src/xenia/emulator.cc @@ -368,13 +368,20 @@ X_STATUS Emulator::InstallContentPackage(const std::filesystem::path& path) { 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 = - 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(); std::filesystem::path header_path = - content_root() / fmt::format("{:08X}", device->title_id()) / "Headers" / - fmt::format("{:08X}", device->content_type()) / path.filename(); + content_root() / xuid_str / fmt::format("{:08X}", device->title_id()) / + "Headers" / fmt::format("{:08X}", device->content_type()) / + path.filename(); if (std::filesystem::exists(installation_path)) { // TODO(Gliniak): Popup @@ -631,34 +638,40 @@ bool Emulator::ExceptionCallback(Exception* ex) { std::string crash_msg; crash_msg.append("==== CRASH DUMP ====\n"); crash_msg.append(fmt::format("Thread ID (Host: 0x{:08X} / Guest: 0x{:08X})\n", - current_thread->thread()->system_id(), current_thread->thread_id())); - crash_msg.append(fmt::format("Thread Handle: 0x{:08X}\n", current_thread->handle())); - crash_msg.append(fmt::format("PC: 0x{:08X}\n", - guest_function->MapMachineCodeToGuestAddress(ex->pc()))); + current_thread->thread()->system_id(), + current_thread->thread_id())); + crash_msg.append( + 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"); for (int i = 0; i < 32; i++) { crash_msg.append(fmt::format(" r{:<3} = {:016X}\n", i, context->r[i])); } for (int i = 0; i < 32; i++) { - crash_msg.append(fmt::format(" f{:<3} = {:016X} = (double){} = (float){}\n", i, - *reinterpret_cast(&context->f[i]), context->f[i], - *(float*)&context->f[i])); + crash_msg.append(fmt::format(" f{:<3} = {:016X} = (double){} = (float){}\n", + i, + *reinterpret_cast(&context->f[i]), + context->f[i], *(float*)&context->f[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, - context->v[i].u32[0], context->v[i].u32[1], context->v[i].u32[2], - context->v[i].u32[3])); + crash_msg.append( + fmt::format(" v{:<3} = [0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X}]\n", i, + context->v[i].u32[0], context->v[i].u32[1], + context->v[i].u32[2], context->v[i].u32[3])); } XELOGE("{}", crash_msg); std::string crash_dlg = fmt::format( - "The guest has crashed.\n\n" - "Xenia has now paused itself.\n\n" - "{}", crash_msg); + "The guest has crashed.\n\n" + "Xenia has now paused itself.\n\n" + "{}", + crash_msg); // Display a dialog telling the user the guest has crashed. if (display_window_ && imgui_drawer_) { - display_window_->app_context().CallInUIThreadSynchronous([this, &crash_dlg]() { - xe::ui::ImGuiDialog::ShowMessageBox( - imgui_drawer_, "Uh-oh!", crash_dlg); + display_window_->app_context().CallInUIThreadSynchronous([this, + &crash_dlg]() { + xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_, "Uh-oh!", crash_dlg); }); } diff --git a/src/xenia/kernel/kernel_state.cc b/src/xenia/kernel/kernel_state.cc index 9e6be4764..a06056864 100644 --- a/src/xenia/kernel/kernel_state.cc +++ b/src/xenia/kernel/kernel_state.cc @@ -486,7 +486,7 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref module) { } std::vector tu_list = - content_manager()->ListContent(1, xe::XContentType::kInstaller, + content_manager()->ListContent(1, 0, xe::XContentType::kInstaller, module->title_id()); if (tu_list.empty()) { @@ -500,7 +500,7 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref module) { // TODO(Gliniak): Support for selecting from multiple TUs const xam::XCONTENT_AGGREGATE_DATA& title_update = tu_list.front(); 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 std::filesystem::path patch_xexp = fmt::format("{0}.xexp", module->name()); diff --git a/src/xenia/kernel/kernel_state.h b/src/xenia/kernel/kernel_state.h index 9c8e23766..f34655b6e 100644 --- a/src/xenia/kernel/kernel_state.h +++ b/src/xenia/kernel/kernel_state.h @@ -128,6 +128,13 @@ class KernelState { } xam::UserProfile* user_profile(uint32_t index) const { + if (index == 0xFF) { + index = 0; + } + if (index == 0xFE) { + index = recently_used_profile_; + } + if (!IsUserSignedIn(index)) { return nullptr; } @@ -282,6 +289,8 @@ class KernelState { std::condition_variable_any dispatch_cond_; std::list> dispatch_queue_; + // This should be in ProfileManager, but it is what it is + uint8_t recently_used_profile_ = 0; BitMap tls_bitmap_; uint32_t ke_timestamp_bundle_ptr_ = 0; std::unique_ptr timestamp_timer_; diff --git a/src/xenia/kernel/profile_manager.cc b/src/xenia/kernel/profile_manager.cc new file mode 100644 index 000000000..4bcb381e4 --- /dev/null +++ b/src/xenia/kernel/profile_manager.cc @@ -0,0 +1 @@ +#include "profile_manager.h" diff --git a/src/xenia/kernel/profile_manager.h b/src/xenia/kernel/profile_manager.h new file mode 100644 index 000000000..b5942dc8a --- /dev/null +++ b/src/xenia/kernel/profile_manager.h @@ -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 +#include +#include +#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 profiles_; + std::map logged_profile_; + + //KernelState* kernel_state_; +}; + +} // namespace kernel +} // namespace xe + +#endif // XENIA_KERNEL_PROFILE_MANAGER_H_ \ No newline at end of file diff --git a/src/xenia/kernel/xam/content_manager.cc b/src/xenia/kernel/xam/content_manager.cc index b8d91715a..9d9488ee2 100644 --- a/src/xenia/kernel/xam/content_manager.cc +++ b/src/xenia/kernel/xam/content_manager.cc @@ -61,23 +61,27 @@ ContentManager::ContentManager(KernelState* kernel_state, ContentManager::~ContentManager() = default; 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) { title_id = kernel_state_->title_id(); } + + auto xuid_str = fmt::format("{:016X}", xuid); auto title_id_str = fmt::format("{:08X}", title_id); auto content_type_str = fmt::format("{:08X}", uint32_t(content_type)); // Package root path: // 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( - 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_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::filesystem::path package_path = package_root / xe::to_path(data.file_name()); @@ -89,7 +93,8 @@ std::filesystem::path ContentManager::ResolvePackagePath( } std::vector 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 result; if (title_id == kCurrentlyRunningTitleId) { @@ -97,8 +102,8 @@ std::vector ContentManager::ListContent( } // Search path: - // content_root/title_id/type_name/* - auto package_root = ResolvePackageRoot(content_type, title_id); + // content_root/username/title_id/type_name/* + auto package_root = ResolvePackageRoot(xuid, content_type, title_id); auto file_infos = xe::filesystem::ListFiles(package_root); for (const auto& file_info : file_infos) { if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) { @@ -107,9 +112,9 @@ std::vector ContentManager::ListContent( } XCONTENT_AGGREGATE_DATA content_data; - if (XSUCCEEDED( - ReadContentHeaderFile(xe::path_to_utf8(file_info.name) + ".header", - content_type, content_data, title_id))) { + if (XSUCCEEDED(ReadContentHeaderFile( + xe::path_to_utf8(file_info.name) + ".header", xuid, content_type, + content_data, title_id))) { result.emplace_back(std::move(content_data)); } else { content_data.device_id = device_id; @@ -124,9 +129,9 @@ std::vector ContentManager::ListContent( } std::unique_ptr ContentManager::ResolvePackage( - const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data, - const uint32_t disc_number) { - auto package_path = ResolvePackagePath(data, disc_number); + const std::string_view root_name, const uint64_t xuid, + const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) { + auto package_path = ResolvePackagePath(xuid, data, disc_number); if (!std::filesystem::exists(package_path)) { return nullptr; } @@ -138,18 +143,24 @@ std::unique_ptr ContentManager::ResolvePackage( return package; } -bool ContentManager::ContentExists(const XCONTENT_AGGREGATE_DATA& data) { - auto path = ResolvePackagePath(data); +bool ContentManager::ContentExists(const uint64_t xuid, + const XCONTENT_AGGREGATE_DATA& data) { + auto path = ResolvePackagePath(xuid, data); return std::filesystem::exists(path); } 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 content_type = fmt::format("{:08X}", load_and_swap(&data->content_type)); - auto header_path = - root_path_ / title_id / kGameContentHeaderDirName / content_type; + auto header_path = root_path_ / xuid_str / title_id / + kGameContentHeaderDirName / content_type; if (!std::filesystem::exists(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, + const uint64_t xuid, XContentType content_type, XCONTENT_AGGREGATE_DATA& data, 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()); } + 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 header_file_path = root_path_ / title_id_str / + auto header_file_path = root_path_ / xuid_str / title_id_str / kGameContentHeaderDirName / content_type_directory / file_name; 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 // Kinda simple workaround for that, but still assumption 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_NO_SUCH_FILE; } X_RESULT ContentManager::CreateContent(const std::string_view root_name, + const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data) { 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; } - auto package_path = ResolvePackagePath(data); + auto package_path = ResolvePackagePath(xuid, data); if (std::filesystem::exists(package_path)) { // Exists, must not! return X_ERROR_ALREADY_EXISTS; @@ -231,7 +249,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name, return X_ERROR_ACCESS_DENIED; } - auto package = ResolvePackage(root_name, data); + auto package = ResolvePackage(root_name, xuid, data); assert_not_null(package); 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, + const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) { 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; } - auto package_path = ResolvePackagePath(data, disc_number); + auto package_path = ResolvePackagePath(xuid, data, disc_number); if (!std::filesystem::exists(package_path)) { // Does not exist, must be created. return X_ERROR_FILE_NOT_FOUND; } // Open package. - auto package = ResolvePackage(root_name, data, disc_number); + auto package = ResolvePackage(root_name, xuid, data, disc_number); assert_not_null(package); 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( - const XCONTENT_AGGREGATE_DATA& data, std::vector* buffer) { + const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data, + std::vector* buffer) { auto global_lock = global_critical_region_.Acquire(); - auto package_path = ResolvePackagePath(data); + auto package_path = ResolvePackagePath(xuid, data); auto thumb_path = package_path / kThumbnailFileName; if (std::filesystem::exists(thumb_path)) { auto file = xe::filesystem::OpenFile(thumb_path, "rb"); @@ -300,9 +320,10 @@ X_RESULT ContentManager::GetContentThumbnail( } X_RESULT ContentManager::SetContentThumbnail( - const XCONTENT_AGGREGATE_DATA& data, std::vector buffer) { + const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data, + std::vector buffer) { auto global_lock = global_critical_region_.Acquire(); - auto package_path = ResolvePackagePath(data); + auto package_path = ResolvePackagePath(xuid, data); std::filesystem::create_directories(package_path); if (std::filesystem::exists(package_path)) { 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(); if (IsContentOpen(data)) { @@ -323,7 +345,7 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) { return X_ERROR_ACCESS_DENIED; } - auto package_path = ResolvePackagePath(data); + auto package_path = ResolvePackagePath(xuid, data); if (std::filesystem::remove_all(package_path) > 0) { return X_ERROR_SUCCESS; } 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 user_name = - xe::to_path(kernel_state_->user_profile(uint32_t(0))->name()); + const UserProfile* user = kernel_state_->user_profile(xuid); + 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: // 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 { diff --git a/src/xenia/kernel/xam/content_manager.h b/src/xenia/kernel/xam/content_manager.h index 0d6e2eaa7..592165801 100644 --- a/src/xenia/kernel/xam/content_manager.h +++ b/src/xenia/kernel/xam/content_manager.h @@ -93,7 +93,7 @@ struct XCONTENT_DATA { static_assert_size(XCONTENT_DATA, 0x134); struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA { - be unk134; // some titles store XUID here? + be xuid; // some titles store XUID here? be title_id; XCONTENT_AGGREGATE_DATA() = default; @@ -103,7 +103,7 @@ struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA { set_display_name(other.display_name()); set_file_name(other.file_name()); padding[0] = padding[1] = 0; - unk134 = 0; + xuid = 0; title_id = kCurrentlyRunningTitleId; } @@ -141,39 +141,48 @@ class ContentManager { const std::filesystem::path& root_path); ~ContentManager(); - std::vector ListContent(uint32_t device_id, - XContentType content_type, - uint32_t title_id = -1); + std::vector ListContent( + const uint32_t device_id, const uint64_t xuid, + const XContentType content_type, uint32_t title_id = -1); std::unique_ptr ResolvePackage( - const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data, - const uint32_t disc_number = -1); + const std::string_view root_name, const uint64_t xuid, + const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number = -1); - bool ContentExists(const XCONTENT_AGGREGATE_DATA& data); - X_RESULT WriteContentHeaderFile(const XCONTENT_AGGREGATE_DATA* data_raw); + bool ContentExists(const uint64_t xuid, + 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, + const uint64_t xuid, XContentType content_type, XCONTENT_AGGREGATE_DATA& data, 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); - 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 uint32_t disc_number = -1); 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* buffer); - X_RESULT SetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data, + X_RESULT SetContentThumbnail(const uint64_t xuid, + const XCONTENT_AGGREGATE_DATA& data, std::vector buffer); - X_RESULT DeleteContent(const XCONTENT_AGGREGATE_DATA& data); - std::filesystem::path ResolveGameUserContentPath(); + X_RESULT DeleteContent(const uint64_t xuid, + const XCONTENT_AGGREGATE_DATA& data); + std::filesystem::path ResolveGameUserContentPath(const uint64_t xuid = 0); bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const; void CloseOpenedFilesFromContent(const std::string_view root_name); private: - std::filesystem::path ResolvePackageRoot(XContentType content_type, + std::filesystem::path ResolvePackageRoot( + const uint64_t xuid, + XContentType content_type, 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); KernelState* kernel_state_; diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 4911051d0..ea0437295 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -10,25 +10,53 @@ #include "xenia/kernel/xam/user_profile.h" #include +#include #include "third_party/fmt/include/fmt/format.h" #include "xenia/kernel/kernel_state.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 kernel { namespace xam { UserProfile::UserProfile(uint8_t index) { + const std::vector 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), // if non-zero, it prevents the user from playing the game. // "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{}(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://github.com/arkem/py360/blob/master/py360/constants.py // XPROFILE_GAMER_YAXIS_INVERSION @@ -137,7 +165,7 @@ UserProfile::Setting* UserProfile::GetSetting(uint32_t setting_id) { void UserProfile::LoadSetting(UserProfile::Setting* setting) { if (setting->is_title_specific()) { auto content_dir = - kernel_state()->content_manager()->ResolveGameUserContentPath(); + kernel_state()->content_manager()->ResolveGameUserContentPath(xuid_); auto setting_id = fmt::format("{:08X}", setting->setting_id); auto file_path = content_dir / setting_id; auto file = xe::filesystem::OpenFile(file_path, "rb"); @@ -163,7 +191,7 @@ void UserProfile::SaveSetting(UserProfile::Setting* setting) { if (setting->is_title_specific()) { auto serialized_setting = setting->Serialize(); auto content_dir = - kernel_state()->content_manager()->ResolveGameUserContentPath(); + kernel_state()->content_manager()->ResolveGameUserContentPath(xuid_); std::filesystem::create_directories(content_dir); auto setting_id = fmt::format("{:08X}", setting->setting_id); auto file_path = content_dir / setting_id; diff --git a/src/xenia/kernel/xam/xam_content.cc b/src/xenia/kernel/xam/xam_content.cc index 1b0b9c844..f8772fa5b 100644 --- a/src/xenia/kernel/xam/xam_content.cc +++ b/src/xenia/kernel/xam/xam_content.cc @@ -85,6 +85,12 @@ dword_result_t XamContentCreateEnumerator_entry( return X_E_INVALIDARG; } + const auto user = kernel_state()->user_profile(user_index); + if (!user) { + return X_E_INVALIDARG; + } + + if (buffer_size_ptr) { *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) { // Get all content data. auto content_datas = kernel_state()->content_manager()->ListContent( - static_cast(DummyDeviceId::HDD), + static_cast(DummyDeviceId::HDD), user->xuid(), XContentType(uint32_t(content_type))); for (const auto& content_data : content_datas) { auto item = e->AppendItem(); @@ -142,7 +148,14 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, *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]( uint32_t& extended_error, uint32_t& length) -> X_RESULT { 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) { case 1: // CREATE_NEW // Fail if exists. - if (content_manager->ContentExists(content_data)) { + if (content_manager->ContentExists(xuid, content_data)) { result = X_ERROR_ALREADY_EXISTS; } else { create = true; @@ -159,8 +172,8 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, break; case 2: // CREATE_ALWAYS // Overwrite existing, if any. - if (content_manager->ContentExists(content_data)) { - content_manager->DeleteContent(content_data); + if (content_manager->ContentExists(xuid, content_data)) { + content_manager->DeleteContent(xuid, content_data); create = true; } else { create = true; @@ -168,7 +181,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, break; case 3: // OPEN_EXISTING // Open only if exists. - if (!content_manager->ContentExists(content_data)) { + if (!content_manager->ContentExists(xuid, content_data)) { result = X_ERROR_PATH_NOT_FOUND; } else { open = true; @@ -176,7 +189,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, break; case 4: // OPEN_ALWAYS // Create if needed. - if (!content_manager->ContentExists(content_data)) { + if (!content_manager->ContentExists(xuid, content_data)) { create = true; } else { open = true; @@ -184,10 +197,10 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, break; case 5: // TRUNCATE_EXISTING // 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; } else { - content_manager->DeleteContent(content_data); + content_manager->DeleteContent(xuid, content_data); create = true; } break; @@ -206,12 +219,12 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name, } if (create) { - result = content_manager->CreateContent(root_name, content_data); + result = content_manager->CreateContent(root_name, xuid, content_data); if (XSUCCEEDED(result)) { - content_manager->WriteContentHeaderFile(&content_data); + content_manager->WriteContentHeaderFile(xuid, &content_data); } } 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)) { @@ -322,8 +335,8 @@ dword_result_t XamContentGetCreator_entry(dword_t user_index, XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as(); - bool content_exists = - kernel_state()->content_manager()->ContentExists(content_data); + bool content_exists = kernel_state()->content_manager()->ContentExists( + user_index, content_data); if (content_exists) { 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; XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as(); + const UserProfile* user = kernel_state()->user_profile(user_index); + if (!user) { + return X_ERROR_NO_SUCH_USER; + } // Get thumbnail (if it exists). std::vector buffer; auto result = kernel_state()->content_manager()->GetContentThumbnail( - content_data, &buffer); + user->xuid(), content_data, &buffer); *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*)buffer_ptr, (uint8_t*)buffer_ptr + buffer_size); auto result = kernel_state()->content_manager()->SetContentThumbnail( - content_data, std::move(buffer)); + user_index, content_data, std::move(buffer)); if (overlapped_ptr) { kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result); @@ -422,7 +439,13 @@ dword_result_t XamContentDelete_entry(dword_t user_index, lpunknown_t overlapped_ptr) { XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as(); - 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) { kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result); @@ -451,7 +474,6 @@ static_assert_size(X_SWAPDISC_ERROR_MESSAGE, 12); dword_result_t XamSwapDisc_entry( dword_t disc_number, pointer_t completion_handle, pointer_t error_message) { - xex2_opt_execution_info* info = nullptr; kernel_state()->GetExecutableModule()->GetOptHeader(XEX_HEADER_EXECUTION_INFO, &info); diff --git a/src/xenia/kernel/xam/xam_content_aggregate.cc b/src/xenia/kernel/xam/xam_content_aggregate.cc index eb41c29f7..d9d90fb55 100644 --- a/src/xenia/kernel/xam/xam_content_aggregate.cc +++ b/src/xenia/kernel/xam/xam_content_aggregate.cc @@ -112,10 +112,15 @@ dword_result_t XamContentAggregateCreateEnumerator_entry(qword_t xuid, 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) { // Get all content data. auto content_datas = kernel_state()->content_manager()->ListContent( - static_cast(DummyDeviceId::HDD), content_type_enum, + static_cast(DummyDeviceId::HDD), xuid, content_type_enum, title_id); for (const auto& content_data : content_datas) { auto item = e->AppendItem(); diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 4ba177c38..32744d077 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -9,6 +9,7 @@ #include +#include "xenia/app/emulator_window.h" #include "xenia/base/logging.h" #include "xenia/base/math.h" #include "xenia/base/string_util.h" @@ -355,17 +356,6 @@ dword_result_t XamUserWriteProfileSettings_entry( if (!setting_count || !settings) { 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. 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? // 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)) { // XN_SYS_SIGNINCHANGED kernel_state()->BroadcastNotification(0xA, i);