diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc index eb2e4bade..97897c68e 100644 --- a/src/xenia/kernel/xam/user_profile.cc +++ b/src/xenia/kernel/xam/user_profile.cc @@ -106,78 +106,6 @@ UserProfile::UserProfile() : dash_gpd_(kDashboardID) { // Try loading profile GPD files... LoadProfile(); - - // 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 - AddSetting(std::make_unique(0x10040002, 0)); - // XPROFILE_OPTION_CONTROLLER_VIBRATION - AddSetting(std::make_unique(0x10040003, 3)); - // XPROFILE_GAMERCARD_ZONE - AddSetting(std::make_unique(0x10040004, 0)); - // XPROFILE_GAMERCARD_REGION - AddSetting(std::make_unique(0x10040005, 0)); - // XPROFILE_GAMERCARD_CRED - AddSetting( - std::make_unique(0x10040006, CalculateUserGamerscore())); - // XPROFILE_GAMERCARD_REP - AddSetting(std::make_unique(0x5004000B, 0.0f)); - // XPROFILE_OPTION_VOICE_MUTED - AddSetting(std::make_unique(0x1004000C, 0)); - // XPROFILE_OPTION_VOICE_THRU_SPEAKERS - AddSetting(std::make_unique(0x1004000D, 0)); - // XPROFILE_OPTION_VOICE_VOLUME - AddSetting(std::make_unique(0x1004000E, 0x64)); - // XPROFILE_GAMERCARD_MOTTO - AddSetting(std::make_unique(0x402C0011, L"")); - // XPROFILE_GAMERCARD_TITLES_PLAYED - AddSetting( - std::make_unique(0x10040012, GetAmountOfPlayedTitles())); - // XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED - AddSetting(std::make_unique(0x10040013, 0)); - // XPROFILE_GAMER_DIFFICULTY - AddSetting(std::make_unique(0x10040015, 0)); - // XPROFILE_GAMER_CONTROL_SENSITIVITY - AddSetting(std::make_unique(0x10040018, 0)); - // Preferred color 1 - AddSetting(std::make_unique(0x1004001D, 0xFFFF0000u)); - // Preferred color 2 - AddSetting(std::make_unique(0x1004001E, 0xFF00FF00u)); - // XPROFILE_GAMER_ACTION_AUTO_AIM - AddSetting(std::make_unique(0x10040022, 1)); - // XPROFILE_GAMER_ACTION_AUTO_CENTER - AddSetting(std::make_unique(0x10040023, 0)); - // XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL - AddSetting(std::make_unique(0x10040024, 0)); - // XPROFILE_GAMER_RACE_TRANSMISSION - AddSetting(std::make_unique(0x10040026, 0)); - // XPROFILE_GAMER_RACE_CAMERA_LOCATION - AddSetting(std::make_unique(0x10040027, 0)); - // XPROFILE_GAMER_RACE_BRAKE_CONTROL - AddSetting(std::make_unique(0x10040028, 0)); - // XPROFILE_GAMER_RACE_ACCELERATOR_CONTROL - AddSetting(std::make_unique(0x10040029, 0)); - // XPROFILE_GAMERCARD_TITLE_CRED_EARNED - AddSetting(std::make_unique(0x10040038, 0)); - // XPROFILE_GAMERCARD_TITLE_ACHIEVEMENTS_EARNED - AddSetting(std::make_unique(0x10040039, 0)); - - // If we set this, games will try to get it. - // XPROFILE_GAMERCARD_PICTURE_KEY - AddSetting( - std::make_unique(0x4064000F, L"gamercard_picture_key")); - - // XPROFILE_TITLE_SPECIFIC1 - AddSetting(std::make_unique(0x63E83FFF)); - // XPROFILE_TITLE_SPECIFIC2 - AddSetting(std::make_unique(0x63E83FFE)); - // XPROFILE_TITLE_SPECIFIC3 - AddSetting(std::make_unique(0x63E83FFD)); - - // Unknown, but on NXE dash it causes profile name & gamerscore appear - AddSetting(std::make_unique(0x63E80044)); - AddSetting(std::make_unique(0x7008004F)); - AddSetting(std::make_unique(0x61180050)); } void UserProfile::LoadProfile() { @@ -519,86 +447,15 @@ bool UserProfile::UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data) { return ret_val; } -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()); +bool UserProfile::AddSettingIfNotExist(xdbf::Setting& setting) { + if (dash_gpd_.GetSetting(setting.id, nullptr)) { + return false; } - - 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(); - ++vec_it) { - if (vec_it->get() == previous_setting) { - vec_it->swap(setting); - break; - } - } - } else { - // new setting: add to the owning list - setting_list_.push_back(std::move(setting)); - } -} - -UserProfile::Setting* UserProfile::GetSetting(uint32_t setting_id) { - const auto& it = settings_.find(setting_id); - if (it == settings_.end()) { - return nullptr; - } - 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"); + if (setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary && + !setting.extraData.size()) { + setting.extraData.resize(XPROFILEID_SIZE(setting.id)); } + return dash_gpd_.UpdateSetting(setting); } xdbf::GpdFile* UserProfile::GetDashboardGpd() { return &dash_gpd_; } @@ -620,18 +477,6 @@ xdbf::SpaFile* UserProfile::GetTitleSpa(uint32_t title_id) { return (game_entry); } -uint32_t UserProfile::CalculateUserGamerscore() const { - uint32_t score = 0; - - std::vector titles; - dash_gpd_.GetTitles(&titles); - - for (auto title : titles) - score += title.gamerscore_earned; - - return score; -} - } // namespace xam } // namespace kernel } // namespace xe diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h index fdd6bc822..37c4cae91 100644 --- a/src/xenia/kernel/xam/user_profile.h +++ b/src/xenia/kernel/xam/user_profile.h @@ -132,180 +132,6 @@ struct X_XAMACCOUNTINFO { class UserProfile { public: - struct Setting { - enum class Type { - CONTENT = 0, - INT32 = 1, - INT64 = 2, - DOUBLE = 3, - WSTRING = 4, - FLOAT = 5, - BINARY = 6, - DATETIME = 7, - INVALID = 0xFF, - }; - union Key { - struct { - uint32_t id : 14; - uint32_t unk : 2; - uint32_t size : 12; - uint32_t type : 4; - }; - uint32_t value; - }; - uint32_t setting_id; - 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), - 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) { - xe::store_and_swap(user_data + kTypeOffset, - 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: - const size_t kTypeOffset = 0; - const size_t kValueOffset = 8; - const size_t kPointerOffset = 12; - }; - struct Int32Setting : public Setting { - Int32Setting(uint32_t setting_id, int32_t value) - : Setting(setting_id, Type::INT32, 4, true), value(value) {} - int32_t value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; - } - }; - struct Int64Setting : public Setting { - Int64Setting(uint32_t setting_id, int64_t value) - : Setting(setting_id, Type::INT64, 8, true), value(value) {} - int64_t value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; - } - }; - struct DoubleSetting : public Setting { - DoubleSetting(uint32_t setting_id, double value) - : Setting(setting_id, Type::DOUBLE, 8, true), value(value) {} - double value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; - } - }; - struct UnicodeSetting : public Setting { - UnicodeSetting(uint32_t setting_id, const std::wstring& value) - : Setting(setting_id, Type::WSTRING, 8, true), value(value) {} - std::wstring value; - size_t extra_size() const override { - return value.empty() ? 0 : 2 * (static_cast(value.size()) + 1); - } - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - int32_t length; - if (value.empty()) { - length = 0; - xe::store_and_swap(user_data + kValueOffset, 0); - xe::store_and_swap(user_data + kPointerOffset, 0); - } else { - length = 2 * (static_cast(value.size()) + 1); - xe::store_and_swap(user_data + kValueOffset, length); - xe::store_and_swap( - user_data + kPointerOffset, - buffer_ptr + static_cast(buffer_offset)); - memcpy(buffer + buffer_offset, value.data(), length); - } - return buffer_offset + length; - } - }; - struct FloatSetting : public Setting { - FloatSetting(uint32_t setting_id, float value) - : Setting(setting_id, Type::FLOAT, 4, true), value(value) {} - float value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; - } - }; - struct BinarySetting : public Setting { - BinarySetting(uint32_t setting_id) - : Setting(setting_id, Type::BINARY, 8, false), value() {} - BinarySetting(uint32_t setting_id, const std::vector& value) - : Setting(setting_id, Type::BINARY, 8, true), value(value) {} - std::vector value; - size_t extra_size() const override { - return static_cast(value.size()); - } - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - int32_t length; - if (value.empty()) { - length = 0; - xe::store_and_swap(user_data + kValueOffset, 0); - xe::store_and_swap(user_data + kPointerOffset, 0); - } else { - length = static_cast(value.size()); - xe::store_and_swap(user_data + kValueOffset, length); - xe::store_and_swap( - user_data + kPointerOffset, - buffer_ptr + static_cast(buffer_offset)); - memcpy(buffer + buffer_offset, value.data(), length); - } - 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) - : Setting(setting_id, Type::DATETIME, 8, true), value(value) {} - int64_t value; - size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr, - size_t buffer_offset) override { - buffer_offset = - Setting::Append(user_data, buffer, buffer_ptr, buffer_offset); - xe::store_and_swap(user_data + kValueOffset, value); - return buffer_offset; - } - }; - static bool DecryptAccountFile(const uint8_t* data, X_XAMACCOUNTINFO* output, bool devkit = false); @@ -317,11 +143,6 @@ class UserProfile { uint64_t xuid() const { return account_.xuid_online; } std::string name() const { return account_.GetGamertagString(); } // uint32_t signin_state() const { return 1; } - uint32_t CalculateUserGamerscore() const; - uint32_t GetAmountOfPlayedTitles() const { return (uint32_t)title_gpds_.size(); } - - void AddSetting(std::unique_ptr setting); - Setting* GetSetting(uint32_t setting_id); xdbf::GpdFile* SetTitleSpaData(const xdbf::SpaFile& spa_data); xdbf::GpdFile* GetTitleGpd(uint32_t title_id = -1); @@ -337,12 +158,9 @@ class UserProfile { void LoadProfile(); bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data); - X_XAMACCOUNTINFO account_; - std::vector> setting_list_; - std::unordered_map settings_; + bool AddSettingIfNotExist(xdbf::Setting& setting); - void LoadSetting(UserProfile::Setting*); - void SaveSetting(UserProfile::Setting*); + X_XAMACCOUNTINFO account_; std::unordered_map title_gpds_; xdbf::GpdFile dash_gpd_; diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc index bf13bfd4a..4785ad5ea 100644 --- a/src/xenia/kernel/xam/xam_user.cc +++ b/src/xenia/kernel/xam/xam_user.cc @@ -219,25 +219,26 @@ static_assert_size(X_USER_READ_PROFILE_SETTINGS, 8); typedef struct { xe::be from; - xe::be unk04; - xe::be user_index; - xe::be unk0C; - xe::be setting_id; - xe::be unk14; - uint8_t setting_data[16]; + union { + xe::be user_index; + xe::be user_xuid; + } user; + xdbf::X_XDBF_GPD_SETTING setting; } X_USER_READ_PROFILE_SETTING; static_assert_size(X_USER_READ_PROFILE_SETTING, 40); // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/Generic/xboxtools.cpp dword_result_t XamUserReadProfileSettings( - dword_t title_id, dword_t user_index, dword_t unk_0, dword_t unk_1, + dword_t title_id, dword_t user_index, dword_t num_xuids, lpqword_t xuids, dword_t setting_count, lpdword_t setting_ids, lpdword_t buffer_size_ptr, lpvoid_t buffer_ptr, dword_t overlapped_ptr) { uint32_t buffer_size = !buffer_size_ptr ? 0u : static_cast(*buffer_size_ptr); - assert_zero(unk_0); - assert_zero(unk_1); + uint64_t xuid = 0; + if (num_xuids && xuids) { + xuid = *xuids; + } // TODO(gibbed): why is this a thing? uint32_t actual_user_index = user_index; @@ -269,32 +270,17 @@ dword_result_t XamUserReadProfileSettings( // Compute required extra size. uint32_t size_needed = base_size_needed; - bool any_missing = false; for (uint32_t n = 0; n < setting_count; ++n) { - uint32_t setting_id = setting_ids[n]; - auto setting = user_profile->GetSetting(setting_id); - if (setting) { - if (setting->is_set) { - auto extra_size = static_cast(setting->extra_size()); - size_needed += extra_size; - } + auto setting_id = (xdbf::X_XDBF_SETTING_ID)(uint32_t)setting_ids[n]; + xdbf::Setting setting; + if (user_profile->GetDashboardGpd()->GetSetting(setting_id, &setting)) { + size_needed += (uint32_t)setting.extraData.size(); } else { - any_missing = true; XELOGE("XamUserReadProfileSettings requested unimplemented setting %.8X", setting_id); } } - if (any_missing) { - // TODO(benvanik): don't fail? most games don't even check! - if (overlapped_ptr) { - kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, - X_ERROR_INVALID_PARAMETER); - return X_ERROR_IO_PENDING; - } - return X_ERROR_INVALID_PARAMETER; - } - *buffer_size_ptr = size_needed; if (!buffer_ptr || buffer_size < size_needed) { if (overlapped_ptr) { @@ -312,27 +298,60 @@ dword_result_t XamUserReadProfileSettings( auto out_setting = reinterpret_cast(buffer_ptr + 8); - size_t buffer_offset = base_size_needed; + uint32_t buffer_offset = base_size_needed; for (uint32_t n = 0; n < setting_count; ++n) { - uint32_t setting_id = setting_ids[n]; - auto setting = user_profile->GetSetting(setting_id); + auto setting_id = (xdbf::X_XDBF_SETTING_ID)(uint32_t)setting_ids[n]; + xdbf::Setting setting; + + auto gpd = user_profile->GetDashboardGpd(); + if (title_id != 0xFFFE07D1) { + gpd = user_profile->GetTitleGpd(title_id); + } + + bool exists = gpd && gpd->GetSetting(setting_id, &setting); + + // TODO: fix binary & unicode settings crashing dash.xex! + if (setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary || + setting.value.type == xdbf::X_XUSER_DATA_TYPE::kUnicode) { + exists = false; + } std::memset(out_setting, 0, sizeof(X_USER_READ_PROFILE_SETTING)); - out_setting->from = - !setting || !setting->is_set ? 0 : setting->is_title_specific() ? 2 : 1; - out_setting->user_index = actual_user_index; - out_setting->setting_id = setting_id; + out_setting->from = !exists ? 0 : setting.IsTitleSpecific() ? 2 : 1; + out_setting->setting.setting_id = setting_id; - if (setting && setting->is_set) { - buffer_offset = - setting->Append(&out_setting->setting_data[0], buffer_ptr, - buffer_ptr.guest_address(), buffer_offset); + if (num_xuids && xuids) { + out_setting->user.user_xuid = xuid; + } else { + out_setting->user.user_index = actual_user_index; + } + + if (exists) { + memcpy(&out_setting->setting.value, &setting.value, + sizeof(xdbf::X_XUSER_DATA)); + + if (setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary) { + memcpy(buffer_ptr.as() + buffer_offset, + setting.extraData.data(), setting.extraData.size()); + + out_setting->setting.value.binary.cbData = + (uint32_t)setting.extraData.size(); + out_setting->setting.value.binary.pbData = + buffer_ptr.guest_address() + buffer_offset; + + buffer_offset += (uint32_t)setting.extraData.size(); + } else if (setting.value.type == xdbf::X_XUSER_DATA_TYPE::kUnicode) { + memcpy(buffer_ptr.as() + buffer_offset, + setting.extraData.data(), setting.extraData.size()); + + out_setting->setting.value.string.cbData = + (uint32_t)setting.extraData.size(); + out_setting->setting.value.string.pwszData = + buffer_ptr.guest_address() + buffer_offset; + + buffer_offset += (uint32_t)setting.extraData.size(); + } } - // TODO(benvanik): why did I do this? - /*else { - std::memset(&out_setting->setting_data[0], 0, - sizeof(out_setting->setting_data)); - }*/ ++out_setting; } @@ -347,29 +366,17 @@ DECLARE_XAM_EXPORT1(XamUserReadProfileSettings, kUserProfiles, kImplemented); typedef struct { xe::be from; - xe::be unk_04; - xe::be unk_08; - xe::be unk_0c; - xe::be setting_id; - xe::be unk_14; - - // UserProfile::Setting::Type. Appears to be 8-in-32 field, and the upper 24 - // are not always zeroed by the game. - uint8_t type; - - xe::be unk_1c; - // TODO(sabretooth): not sure if this is a union, but it seems likely. - // Haven't run into cases other than "binary data" yet. union { - struct { - xe::be length; - xe::be ptr; - } binary; - }; + xe::be user_index; + xe::be user_xuid; + } user; + + xdbf::X_XDBF_GPD_SETTING setting; } X_USER_WRITE_PROFILE_SETTING; +static_assert_size(X_USER_WRITE_PROFILE_SETTING, 0x28); dword_result_t XamUserWriteProfileSettings( - dword_t user_index, dword_t unk, dword_t setting_count, + dword_t title_id, dword_t user_index, dword_t setting_count, pointer_t settings, dword_t overlapped_ptr) { if (!setting_count || !settings) { if (overlapped_ptr) { @@ -387,58 +394,72 @@ dword_result_t XamUserWriteProfileSettings( if (actual_user_index) { // Only support user 0. + if (overlapped_ptr) { + kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, + X_ERROR_NOT_FOUND); + return X_ERROR_IO_PENDING; + } return X_ERROR_NOT_FOUND; } // Update and save settings. const auto& user_profile = kernel_state()->user_profile(); + auto gpd = user_profile->GetDashboardGpd(); + if (title_id != 0xFFFE07D1) { + gpd = user_profile->GetTitleGpd(title_id); + } + + if (!gpd) { + // TODO: find out proper error code for this condition! + if (overlapped_ptr) { + kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, + X_ERROR_INVALID_PARAMETER); + return X_ERROR_IO_PENDING; + } + return X_ERROR_INVALID_PARAMETER; + } + for (uint32_t n = 0; n < setting_count; ++n) { const X_USER_WRITE_PROFILE_SETTING& settings_data = settings[n]; XELOGD( "XamUserWriteProfileSettings: setting index [%d]:" " from=%d setting_id=%.8X data.type=%d", - n, (uint32_t)settings_data.from, (uint32_t)settings_data.setting_id, - settings_data.type); + n, (uint32_t)settings_data.from, + (uint32_t)settings_data.setting.setting_id, + settings_data.setting.value.type); - xam::UserProfile::Setting::Type settingType = - static_cast(settings_data.type); + xdbf::Setting setting; + setting.id = settings_data.setting.setting_id; + setting.value.type = settings_data.setting.value.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); - size_t settings_data_len = settings_data.binary.length; - std::vector data_vec; + // Retrieve any existing setting data if we can + gpd->GetSetting(setting.id, &setting); - if (settings_data.binary.ptr) { - // Copy provided data - data_vec.resize(settings_data_len); - std::memcpy(data_vec.data(), settings_data_ptr, settings_data_len); - } else { - // Data pointer was NULL, so just fill with zeroes - data_vec.resize(settings_data_len, 0); - } + // ... and then overwrite it + memcpy(&setting.value, &settings_data.setting.value, + sizeof(xdbf::X_XUSER_DATA)); - user_profile->AddSetting( - std::make_unique( - settings_data.setting_id, data_vec)); + if (settings_data.setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary) { + if (settings_data.setting.value.binary.pbData) { + setting.extraData.resize(settings_data.setting.value.binary.cbData); + auto* data_ptr = kernel_memory()->TranslateVirtual( + settings_data.setting.value.binary.pbData); + memcpy(setting.extraData.data(), data_ptr, + settings_data.setting.value.binary.cbData); + } + } else if (settings_data.setting.value.type == + xdbf::X_XUSER_DATA_TYPE::kUnicode) { + if (settings_data.setting.value.string.pwszData) { + setting.extraData.resize(settings_data.setting.value.string.cbData); + auto* data_ptr = kernel_memory()->TranslateVirtual( + settings_data.setting.value.string.pwszData); + memcpy(setting.extraData.data(), data_ptr, + settings_data.setting.value.string.cbData); + } + } - } break; - - case UserProfile::Setting::Type::WSTRING: - case UserProfile::Setting::Type::DOUBLE: - case UserProfile::Setting::Type::FLOAT: - case UserProfile::Setting::Type::INT32: - case UserProfile::Setting::Type::INT64: - case UserProfile::Setting::Type::DATETIME: - default: - - XELOGE("XamUserWriteProfileSettings: Unimplemented data type %d", - settingType); - break; - }; + gpd->UpdateSetting(setting); } if (overlapped_ptr) { @@ -710,13 +731,11 @@ dword_result_t XamUserCreateTitlesPlayedEnumerator( for (auto title : titles) { // For some reason dashboard gpd stores info about itself - if (title.title_id == kDashboardID) - continue; + if (title.title_id == kDashboardID) continue; // TODO: Look for better check to provide information about demo title // or system title - if (!title.gamerscore_total || !title.achievements_possible) - continue; + if (!title.gamerscore_total || !title.achievements_possible) continue; auto* details = (xdbf::X_XDBF_GPD_TITLEPLAYED*)e->AppendItem(); title.WriteGPD(details); diff --git a/src/xenia/kernel/xam/xdbf/xdbf.cc b/src/xenia/kernel/xam/xdbf/xdbf.cc index fe79956fb..67227b66b 100644 --- a/src/xenia/kernel/xam/xdbf/xdbf.cc +++ b/src/xenia/kernel/xam/xdbf/xdbf.cc @@ -216,6 +216,7 @@ uint32_t SpaFile::GetAchievements( ach.image_id = ach_data->image_id; ach.gamerscore = ach_data->gamerscore; ach.flags = ach_data->flags; + ach.flags |= static_cast(AchievementPlatform::kX360); ach.label = xe::to_wstring( GetStringTableEntry_(xstr_ptr, ach_data->label_id, xstr_head->count)); @@ -321,6 +322,54 @@ uint32_t GpdFile::GetAchievements( return ach_count; } +bool GpdFile::GetSetting(X_XDBF_SETTING_ID id, Setting* dest) { + for (size_t i = 0; i < entries_.size(); i++) { + auto* entry = (Entry*)&entries_[i]; + if (entry->info.section != static_cast(GpdSection::kSetting) || + entry->info.id != (uint32_t)id) { + continue; + } + + auto* setting_data = + reinterpret_cast(entry->data.data()); + + if (dest) { + dest->ReadGPD(setting_data); + } + return true; + } + + return false; +} + +uint32_t GpdFile::GetSettings(std::vector* settings) const { + uint32_t count = 0; + + for (size_t i = 0; i < entries_.size(); i++) { + auto* entry = (Entry*)&entries_[i]; + if (entry->info.section != static_cast(GpdSection::kSetting)) { + continue; + } + if (entry->info.id == 0x100000000 || entry->info.id == 0x200000000) { + continue; // achievement sync data, ignore it + } + + count++; + + if (settings) { + auto* setting_data = + reinterpret_cast(entry->data.data()); + + Setting setting; + setting.ReadGPD(setting_data); + + settings->push_back(setting); + } + } + + return count; +} + bool GpdFile::GetTitle(uint32_t title_id, TitlePlayed* dest) { for (size_t i = 0; i < entries_.size(); i++) { auto* entry = (Entry*)&entries_[i]; @@ -408,6 +457,36 @@ bool GpdFile::UpdateAchievement(const Achievement& ach) { return UpdateEntry(ent); } +bool GpdFile::UpdateSetting(const Setting& setting) { + Entry ent; + ent.info.section = static_cast(GpdSection::kSetting); + ent.info.id = setting.id; + + // calculate entry size... + size_t est_size = sizeof(X_XDBF_GPD_SETTING); + est_size += setting.extraData.size(); + + ent.data.resize(est_size); + memset(ent.data.data(), 0, est_size); + + auto* setting_data = reinterpret_cast(ent.data.data()); + setting_data->setting_id = setting.id; + memcpy(&setting_data->value, &setting.value, sizeof(X_XUSER_DATA)); + if (setting.value.type == X_XUSER_DATA_TYPE::kBinary) { + setting_data->value.binary.cbData = (uint32_t)setting.extraData.size(); + // todo: check size against size stored inside ID! + memcpy(&setting_data[1], setting.extraData.data(), + setting.extraData.size()); + } else if (setting.value.type == X_XUSER_DATA_TYPE::kUnicode) { + setting_data->value.string.cbData = (uint32_t)setting.extraData.size(); + // todo: check size against size stored inside ID! + memcpy(&setting_data[1], setting.extraData.data(), + setting.extraData.size()); + } + + return UpdateEntry(ent); +} + bool GpdFile::UpdateTitle(const TitlePlayed& title) { Entry ent; ent.info.section = static_cast(GpdSection::kTitle); diff --git a/src/xenia/kernel/xam/xdbf/xdbf.h b/src/xenia/kernel/xam/xdbf/xdbf.h index 5b3d29bb2..1c4d833f0 100644 --- a/src/xenia/kernel/xam/xdbf/xdbf.h +++ b/src/xenia/kernel/xam/xdbf/xdbf.h @@ -224,6 +224,103 @@ struct Achievement { } }; +struct Setting { + X_XDBF_SETTING_ID id = (X_XDBF_SETTING_ID)0; + X_XUSER_DATA value; + std::vector extraData; + + Setting() { value.type = X_XUSER_DATA_TYPE::kNull; } + Setting(X_XDBF_SETTING_ID id, uint32_t value) : id(id) { Value(value); } + Setting(X_XDBF_SETTING_ID id, uint64_t value) : id(id) { Value(value); } + Setting(X_XDBF_SETTING_ID id, float value) : id(id) { Value(value); } + Setting(X_XDBF_SETTING_ID id, const std::wstring& value) : id(id) { + Value(value); + } + Setting(X_XDBF_SETTING_ID id, const std::initializer_list& value) + : id(id), extraData(value) { + this->value.type = X_XUSER_DATA_TYPE::kBinary; + } + + bool IsTitleSpecific() const { + return id == XPROFILE_TITLE_SPECIFIC1 || id == XPROFILE_TITLE_SPECIFIC2 || + id == XPROFILE_TITLE_SPECIFIC3; + } + + void ReadGPD(const X_XDBF_GPD_SETTING* src) { + id = src->setting_id; + memcpy(&value, &src->value, sizeof(X_XUSER_DATA)); + + if (value.type == X_XUSER_DATA_TYPE::kBinary) { + extraData.resize(src->value.binary.cbData); + memcpy(extraData.data(), (uint8_t*)&src[1], src->value.binary.cbData); + } else if (value.type == X_XUSER_DATA_TYPE::kUnicode) { + extraData.resize(src->value.string.cbData); + memcpy(extraData.data(), (uint8_t*)&src[1], src->value.string.cbData); + } + } + + void Value(uint32_t new_value) { + value.type = X_XUSER_DATA_TYPE::kInt32; + assert(XPROFILEID_TYPE(id) == value.type); + + value.nData = new_value; + extraData.clear(); + } + + void Value(uint64_t new_value) { + value.type = X_XUSER_DATA_TYPE::kInt64; + if (XPROFILEID_TYPE(id) == X_XUSER_DATA_TYPE::kDateTime) { + value.type = X_XUSER_DATA_TYPE::kDateTime; + } + + assert(XPROFILEID_TYPE(id) == value.type); + + value.i64Data = new_value; + extraData.clear(); + } + + void Value(float new_value) { + value.type = X_XUSER_DATA_TYPE::kFloat; + assert(XPROFILEID_TYPE(id) == value.type); + + value.fData = new_value; + extraData.clear(); + } + + void Value(double new_value) { + value.type = X_XUSER_DATA_TYPE::kDouble; + assert(XPROFILEID_TYPE(id) == value.type); + + value.dblData = new_value; + extraData.clear(); + } + + void Value(const std::wstring& new_value) { + value.type = X_XUSER_DATA_TYPE::kUnicode; + assert(XPROFILEID_TYPE(id) == value.type); + + value.i64Data = 0; + value.string.cbData = + (uint32_t)((new_value.length() + 1) * sizeof(wchar_t)); + extraData.resize(value.string.cbData); + xe::copy_and_swap((wchar_t*)extraData.data(), new_value.c_str(), + new_value.length()); + *(wchar_t*)(extraData.data() + value.string.cbData - 2) = + 0; // null-terminate + } + + std::wstring ValueString() { + assert(value.type == X_XUSER_DATA_TYPE::kUnicode); + + std::vector swapped; + swapped.resize(extraData.size()); + xe::copy_and_swap((wchar_t*)swapped.data(), + (wchar_t*)extraData.data(), + extraData.size() / sizeof(wchar_t)); + return std::wstring((wchar_t*)swapped.data()); + } +}; + struct Entry { X_XDBF_ENTRY info; std::vector data; @@ -273,13 +370,19 @@ class GpdFile : public XdbfFile { bool GetAchievement(uint16_t id, Achievement* dest); uint32_t GetAchievements(std::vector* achievements) const; + bool GetSetting(X_XDBF_SETTING_ID id, Setting* dest); + uint32_t GetSettings(std::vector* settings) const; + bool GetTitle(uint32_t title_id, TitlePlayed* title); uint32_t GetTitles(std::vector* titles) const; - // Updates (or adds) an achievement + // Updates/adds an achievement bool UpdateAchievement(const Achievement& ach); - // Updates (or adds) a title + // Updates/adds a setting + bool UpdateSetting(const Setting& setting); + + // Updates/adds a title bool UpdateTitle(const TitlePlayed& title); uint32_t GetTitleId() { return title_id_; }