From 86443dd28af10064a6c4e5ee0c23def14ec8e879 Mon Sep 17 00:00:00 2001 From: Unknown Date: Sat, 9 Jun 2018 16:08:57 -0700 Subject: [PATCH] Persist title-specific profile content --- src/xenia/kernel/xam/content_manager.cc | 16 +++++++ src/xenia/kernel/xam/content_manager.h | 1 + src/xenia/kernel/xam/user_profile.cc | 61 ++++++++++++++++++++++++- src/xenia/kernel/xam/user_profile.h | 23 +++++++++- src/xenia/kernel/xam/xam_user.cc | 1 + 5 files changed, 99 insertions(+), 3 deletions(-) diff --git a/src/xenia/kernel/xam/content_manager.cc b/src/xenia/kernel/xam/content_manager.cc index 4b5306e39..2ca34f716 100644 --- a/src/xenia/kernel/xam/content_manager.cc +++ b/src/xenia/kernel/xam/content_manager.cc @@ -23,6 +23,8 @@ namespace xam { static const wchar_t* kThumbnailFileName = L"__thumbnail.png"; +static const wchar_t* kGameUserContentDirName = L"profile"; + static int content_device_id_ = 0; ContentPackage::ContentPackage(KernelState* kernel_state, std::string root_name, @@ -252,6 +254,20 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_DATA& data) { } } +std::wstring ContentManager::ResolveGameUserContentPath() { + wchar_t title_id[9] = L"00000000"; + std::swprintf(title_id, 9, L"%.8X", kernel_state_->title_id()); + auto user_name = xe::to_wstring(kernel_state_->user_profile()->name()); + + // Per-game per-profile data location: + // content_root/title_id/profile/user_name + auto package_root = xe::join_paths( + root_path_, + xe::join_paths(title_id, + xe::join_paths(kGameUserContentDirName, user_name))); + return package_root + xe::kWPathSeparator; +} + } // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xam/content_manager.h b/src/xenia/kernel/xam/content_manager.h index 9a0bd1948..179939b80 100644 --- a/src/xenia/kernel/xam/content_manager.h +++ b/src/xenia/kernel/xam/content_manager.h @@ -84,6 +84,7 @@ class ContentManager { X_RESULT SetContentThumbnail(const XCONTENT_DATA& data, std::vector buffer); X_RESULT DeleteContent(const XCONTENT_DATA& data); + std::wstring ResolveGameUserContentPath(); private: std::wstring ResolvePackageRoot(uint32_t content_type); diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index 9e555405d..ba6c4d045 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -7,6 +7,10 @@ ****************************************************************************** */ +#include + +#include "xenia/kernel/kernel_state.h" +#include "xenia/kernel/util/shim_utils.h" #include "xenia/kernel/xam/user_profile.h" namespace xe { @@ -87,6 +91,10 @@ void UserProfile::AddSetting(std::unique_ptr setting) { Setting* previous_setting = setting.get(); std::swap(settings_[setting->setting_id], previous_setting); + if (setting->is_set && setting->is_title_specific()) { + SaveSetting(setting.get()); + } + if (previous_setting) { // replace: swap out the old setting from the owning list for (auto vec_it = setting_list_.begin(); vec_it != setting_list_.end(); @@ -107,7 +115,58 @@ UserProfile::Setting* UserProfile::GetSetting(uint32_t setting_id) { if (it == settings_.end()) { return nullptr; } - return it->second; + UserProfile::Setting* setting = it->second; + if (setting->is_title_specific()) { + // If what we have loaded in memory isn't for the title that is running + // right now, then load it from disk. + if (kernel_state()->title_id() != setting->loaded_title_id) { + LoadSetting(setting); + } + } + return setting; +} + +void UserProfile::LoadSetting(UserProfile::Setting* setting) { + if (setting->is_title_specific()) { + auto content_dir = + kernel_state()->content_manager()->ResolveGameUserContentPath(); + auto setting_id = xe::format_string(L"%.8X", setting->setting_id); + auto file_path = xe::join_paths(content_dir, setting_id); + auto file = xe::filesystem::OpenFile(file_path, "rb"); + if (file) { + fseek(file, 0, SEEK_END); + uint32_t input_file_size = static_cast(ftell(file)); + fseek(file, 0, SEEK_SET); + + std::vector serialized_data(input_file_size); + fread(serialized_data.data(), 1, serialized_data.size(), file); + fclose(file); + setting->Deserialize(serialized_data); + setting->loaded_title_id = kernel_state()->title_id(); + } + } else { + // Unsupported for now. Other settings aren't per-game and need to be + // stored some other way. + XELOGW("Attempting to load unsupported profile setting from disk"); + } +} + +void UserProfile::SaveSetting(UserProfile::Setting* setting) { + if (setting->is_title_specific()) { + auto serialized_setting = setting->Serialize(); + auto content_dir = + kernel_state()->content_manager()->ResolveGameUserContentPath(); + xe::filesystem::CreateFolder(content_dir); + auto setting_id = xe::format_string(L"%.8X", setting->setting_id); + auto file_path = xe::join_paths(content_dir, setting_id); + auto file = xe::filesystem::OpenFile(file_path, "wb"); + fwrite(serialized_setting.data(), 1, serialized_setting.size(), file); + fclose(file); + } else { + // Unsupported for now. Other settings aren't per-game and need to be + // stored some other way. + XELOGW("Attempting to save unsupported profile setting to disk"); + } } } // namespace xam diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index 0afe2db8c..f8f2dacd8 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -25,7 +25,7 @@ class UserProfile { public: struct Setting { enum class Type { - UNKNOWN = 0, + CONTENT = 0, INT32 = 1, INT64 = 2, DOUBLE = 3, @@ -48,8 +48,13 @@ class UserProfile { Type type; size_t size; bool is_set; + uint32_t loaded_title_id; Setting(uint32_t setting_id, Type type, size_t size, bool is_set) - : setting_id(setting_id), type(type), size(size), is_set(is_set) {} + : setting_id(setting_id), + type(type), + size(size), + is_set(is_set), + loaded_title_id(0) {} virtual size_t extra_size() const { return 0; } virtual size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, size_t buffer_offset) { @@ -57,6 +62,10 @@ class UserProfile { static_cast(type)); return buffer_offset; } + virtual std::vector Serialize() const { + return std::vector(); + } + virtual void Deserialize(std::vector) {} bool is_title_specific() const { return (setting_id & 0x3F00) == 0x3F00; } protected: @@ -167,6 +176,13 @@ class UserProfile { } return buffer_offset + length; } + std::vector Serialize() const override { + return std::vector(value.data(), value.data() + value.size()); + } + void Deserialize(std::vector data) override { + value = data; + is_set = true; + } }; struct DateTimeSetting : public Setting { DateTimeSetting(uint32_t setting_id, int64_t value) @@ -195,6 +211,9 @@ class UserProfile { std::string name_; std::vector> setting_list_; std::unordered_map settings_; + + void LoadSetting(UserProfile::Setting*); + void SaveSetting(UserProfile::Setting*); }; } // namespace xam diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index 4a15d3662..8400699a0 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -290,6 +290,7 @@ dword_result_t XamUserWriteProfileSettings( static_cast(settings_data.type); switch (settingType) { + case UserProfile::Setting::Type::CONTENT: case UserProfile::Setting::Type::BINARY: { uint8_t* settings_data_ptr = kernel_state()->memory()->TranslateVirtual( settings_data.binary.ptr);