File xenia_PR2249.patch of Package xenia
From 79d2d3ba55c382ab9a280f0bf7416ffd1dab5296 Mon Sep 17 00:00:00 2001
From: Gliniak <Gliniak93@gmail.com>
Date: Sun, 18 Feb 2024 12:12:14 +0100
Subject: [PATCH] [XAM] Changes to settings handling
- Fixed incorrect X_USER_PROFILE_SETTING structure
- Added X_USER_PROFILE_SETTING_HEADER based on entries from console
- Removed X_USER_PROFILE_SETTING_DATA in favor of X_USER_DATA
X_USER_DATA is used also in Properties in exactly the same way
- Removed is_set in favor of X_USER_PROFILE_SETTING_SOURCE
- Prevent setting from storing settings longer than 0x3E8 bytes. Some games try to write bigger value which causes them to crash
---
src/xenia/kernel/util/xuserdata.h | 234 +++++++++++++++++++++
src/xenia/kernel/xam/user_profile.cc | 162 ++++++++------
src/xenia/kernel/xam/user_profile.h | 303 ++++++++++-----------------
src/xenia/kernel/xam/xam_user.cc | 51 ++---
4 files changed, 474 insertions(+), 276 deletions(-)
create mode 100644 src/xenia/kernel/util/xuserdata.h
diff --git a/src/xenia/kernel/util/xuserdata.h b/src/xenia/kernel/util/xuserdata.h
new file mode 100644
index 0000000000..794882b6d2
--- /dev/null
+++ b/src/xenia/kernel/util/xuserdata.h
@@ -0,0 +1,234 @@
+/**
+ ******************************************************************************
+ * Xenia : Xbox 360 Emulator Research Project *
+ ******************************************************************************
+ * Copyright 2024 Ben Vanik. All rights reserved. *
+ * Released under the BSD license - see LICENSE in the root for more details. *
+ ******************************************************************************
+ */
+
+#ifndef XENIA_KERNEL_UTIL_XUSERDATA_H_
+#define XENIA_KERNEL_UTIL_XUSERDATA_H_
+
+#include "xenia/base/byte_stream.h"
+#include "xenia/xbox.h"
+
+namespace xe {
+namespace kernel {
+
+enum class X_USER_DATA_TYPE : uint8_t {
+ CONTENT = 0,
+ INT32 = 1,
+ INT64 = 2,
+ DOUBLE = 3,
+ WSTRING = 4,
+ FLOAT = 5,
+ BINARY = 6,
+ DATETIME = 7,
+ UNSET = 0xFF,
+};
+
+struct X_USER_DATA {
+ X_USER_DATA_TYPE type;
+
+ union {
+ be<int32_t> s32;
+ be<int64_t> s64;
+ be<uint32_t> u32;
+ be<double> f64;
+ struct {
+ be<uint32_t> size;
+ be<uint32_t> ptr;
+ } unicode;
+ be<float> f32;
+ struct {
+ be<uint32_t> size;
+ be<uint32_t> ptr;
+ } binary;
+ be<uint64_t> filetime;
+ };
+};
+static_assert_size(X_USER_DATA, 16);
+
+class DataByteStream : public ByteStream {
+ public:
+ DataByteStream(uint32_t ptr, uint8_t* data, size_t data_length,
+ size_t offset = 0)
+ : ByteStream(data, data_length, offset), ptr_(ptr) {}
+
+ uint32_t ptr() const { return static_cast<uint32_t>(ptr_ + offset()); }
+
+ private:
+ uint32_t ptr_;
+};
+
+class UserData {
+ public:
+ union Key {
+ uint32_t value;
+ struct {
+ uint32_t id : 14;
+ uint32_t unk : 2;
+ uint32_t size : 12;
+ uint32_t type : 4;
+ };
+ };
+
+ UserData(){};
+ UserData(X_USER_DATA_TYPE type) { data_.type = type; }
+
+ virtual void Append(X_USER_DATA* data, DataByteStream* stream) {
+ data->type = data_.type;
+ }
+
+ virtual std::vector<uint8_t> Serialize() const {
+ return std::vector<uint8_t>();
+ }
+ virtual void Deserialize(std::vector<uint8_t>) {}
+
+ private:
+ X_USER_DATA data_ = {};
+};
+
+class Int32UserData : public UserData {
+ public:
+ Int32UserData(int32_t value)
+ : UserData(X_USER_DATA_TYPE::INT32), value_(value) {}
+ void Append(X_USER_DATA* data, DataByteStream* stream) override {
+ UserData::Append(data, stream);
+ data->s32 = value_;
+ }
+
+ private:
+ int32_t value_;
+};
+
+class Uint32UserData : public UserData {
+ public:
+ Uint32UserData(uint32_t value)
+ : UserData(X_USER_DATA_TYPE::INT32), value_(value) {}
+ void Append(X_USER_DATA* data, DataByteStream* stream) override {
+ UserData::Append(data, stream);
+ data->u32 = value_;
+ }
+
+ private:
+ uint32_t value_;
+};
+
+class Int64UserData : public UserData {
+ public:
+ Int64UserData(int64_t value)
+ : UserData(X_USER_DATA_TYPE::INT64), value_(value) {}
+ void Append(X_USER_DATA* data, DataByteStream* stream) override {
+ UserData::Append(data, stream);
+ data->s64 = value_;
+ }
+
+ private:
+ int64_t value_;
+};
+
+class FloatUserData : public UserData {
+ public:
+ FloatUserData(float value)
+ : UserData(X_USER_DATA_TYPE::FLOAT), value_(value) {}
+
+ void Append(X_USER_DATA* data, DataByteStream* stream) override {
+ UserData::Append(data, stream);
+ data->f32 = value_;
+ }
+
+ private:
+ float value_;
+};
+
+class DoubleUserData : public UserData {
+ public:
+ DoubleUserData(double value)
+ : UserData(X_USER_DATA_TYPE::DOUBLE), value_(value) {}
+ void Append(X_USER_DATA* data, DataByteStream* stream) override {
+ UserData::Append(data, stream);
+ data->f64 = value_;
+ }
+
+ private:
+ double value_;
+};
+
+class UnicodeUserData : public UserData {
+ public:
+ UnicodeUserData(const std::u16string& value)
+ : UserData(X_USER_DATA_TYPE::WSTRING), value_(value) {}
+ void Append(X_USER_DATA* data, DataByteStream* stream) override {
+ UserData::Append(data, stream);
+
+ if (value_.empty()) {
+ data->unicode.size = 0;
+ data->unicode.ptr = 0;
+ return;
+ }
+
+ size_t count = value_.size() + 1;
+ size_t size = 2 * count;
+ assert_true(size <= std::numeric_limits<uint32_t>::max());
+ data->unicode.size = static_cast<uint32_t>(size);
+ data->unicode.ptr = stream->ptr();
+ auto buffer =
+ reinterpret_cast<uint16_t*>(&stream->data()[stream->offset()]);
+ stream->Advance(size);
+ copy_and_swap(buffer, (uint16_t*)value_.data(), count);
+ }
+
+ private:
+ std::u16string value_;
+};
+
+class BinaryUserData : public UserData {
+ public:
+ BinaryUserData(const std::vector<uint8_t>& value)
+ : UserData(X_USER_DATA_TYPE::BINARY), value_(value) {}
+ void Append(X_USER_DATA* data, DataByteStream* stream) override {
+ UserData::Append(data, stream);
+
+ if (value_.empty()) {
+ data->binary.size = 0;
+ data->binary.ptr = 0;
+ return;
+ }
+
+ size_t size = value_.size();
+ assert_true(size <= std::numeric_limits<uint32_t>::max());
+ data->binary.size = static_cast<uint32_t>(size);
+ data->binary.ptr = stream->ptr();
+ stream->Write(value_.data(), size);
+ }
+
+ std::vector<uint8_t> Serialize() const override {
+ return std::vector<uint8_t>(value_.data(), value_.data() + value_.size());
+ }
+
+ void Deserialize(std::vector<uint8_t> data) override { value_ = data; }
+
+ private:
+ std::vector<uint8_t> value_;
+};
+
+class DateTimeUserData : public UserData {
+ public:
+ DateTimeUserData(int64_t value)
+ : UserData(X_USER_DATA_TYPE::DATETIME), value_(value) {}
+
+ void Append(X_USER_DATA* data, DataByteStream* stream) override {
+ UserData::Append(data, stream);
+ data->filetime = value_;
+ }
+
+ private:
+ int64_t value_;
+};
+
+} // namespace kernel
+} // namespace xe
+
+#endif
diff --git a/src/xenia/kernel/xam/user_profile.cc b/src/xenia/kernel/xam/user_profile.cc
index 3bfa40f48a..fab4439c01 100644
--- a/src/xenia/kernel/xam/user_profile.cc
+++ b/src/xenia/kernel/xam/user_profile.cc
@@ -25,78 +25,77 @@ UserProfile::UserProfile() {
// "You do not have permissions to perform this operation."
xuid_ = 0xB13EBABEBABEBABE;
name_ = "User";
-
// 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<Int32Setting>(0x10040002, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040002, 0));
// XPROFILE_OPTION_CONTROLLER_VIBRATION
- AddSetting(std::make_unique<Int32Setting>(0x10040003, 3));
+ AddSetting(std::make_unique<UserSetting>(0x10040003, 3));
// XPROFILE_GAMERCARD_ZONE
- AddSetting(std::make_unique<Int32Setting>(0x10040004, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040004, 0));
// XPROFILE_GAMERCARD_REGION
- AddSetting(std::make_unique<Int32Setting>(0x10040005, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040005, 0));
// XPROFILE_GAMERCARD_CRED
- AddSetting(std::make_unique<Int32Setting>(0x10040006, 0xFA));
- // XPROFILE_GAMERCARD_REP
- AddSetting(std::make_unique<FloatSetting>(0x5004000B, 0.0f));
+ AddSetting(std::make_unique<UserSetting>(0x10040006, 0xFA));
// XPROFILE_OPTION_VOICE_MUTED
- AddSetting(std::make_unique<Int32Setting>(0x1004000C, 0));
+ AddSetting(std::make_unique<UserSetting>(0x1004000C, 3));
// XPROFILE_OPTION_VOICE_THRU_SPEAKERS
- AddSetting(std::make_unique<Int32Setting>(0x1004000D, 0));
+ AddSetting(std::make_unique<UserSetting>(0x1004000D, 3));
// XPROFILE_OPTION_VOICE_VOLUME
- AddSetting(std::make_unique<Int32Setting>(0x1004000E, 0x64));
- // XPROFILE_GAMERCARD_MOTTO
- AddSetting(std::make_unique<UnicodeSetting>(0x402C0011, u""));
+ AddSetting(std::make_unique<UserSetting>(0x1004000E, 0x64));
// XPROFILE_GAMERCARD_TITLES_PLAYED
- AddSetting(std::make_unique<Int32Setting>(0x10040012, 1));
+ AddSetting(std::make_unique<UserSetting>(0x10040012, 1));
// XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED
- AddSetting(std::make_unique<Int32Setting>(0x10040013, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040013, 0));
// XPROFILE_GAMER_DIFFICULTY
- AddSetting(std::make_unique<Int32Setting>(0x10040015, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040015, 0));
// XPROFILE_GAMER_CONTROL_SENSITIVITY
- AddSetting(std::make_unique<Int32Setting>(0x10040018, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040018, 0));
// Preferred color 1
- AddSetting(std::make_unique<Int32Setting>(0x1004001D, 0xFFFF0000u));
+ AddSetting(std::make_unique<UserSetting>(0x1004001D, 0xFFFF0000u));
// Preferred color 2
- AddSetting(std::make_unique<Int32Setting>(0x1004001E, 0xFF00FF00u));
+ AddSetting(std::make_unique<UserSetting>(0x1004001E, 0xFF00FF00u));
// XPROFILE_GAMER_ACTION_AUTO_AIM
- AddSetting(std::make_unique<Int32Setting>(0x10040022, 1));
+ AddSetting(std::make_unique<UserSetting>(0x10040022, 1));
// XPROFILE_GAMER_ACTION_AUTO_CENTER
- AddSetting(std::make_unique<Int32Setting>(0x10040023, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040023, 0));
// XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL
- AddSetting(std::make_unique<Int32Setting>(0x10040024, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040024, 0));
// XPROFILE_GAMER_RACE_TRANSMISSION
- AddSetting(std::make_unique<Int32Setting>(0x10040026, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040026, 0));
// XPROFILE_GAMER_RACE_CAMERA_LOCATION
- AddSetting(std::make_unique<Int32Setting>(0x10040027, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040027, 0));
// XPROFILE_GAMER_RACE_BRAKE_CONTROL
- AddSetting(std::make_unique<Int32Setting>(0x10040028, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040028, 0));
// XPROFILE_GAMER_RACE_ACCELERATOR_CONTROL
- AddSetting(std::make_unique<Int32Setting>(0x10040029, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040029, 0));
// XPROFILE_GAMERCARD_TITLE_CRED_EARNED
- AddSetting(std::make_unique<Int32Setting>(0x10040038, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040038, 0));
// XPROFILE_GAMERCARD_TITLE_ACHIEVEMENTS_EARNED
- AddSetting(std::make_unique<Int32Setting>(0x10040039, 0));
+ AddSetting(std::make_unique<UserSetting>(0x10040039, 0));
- // If we set this, games will try to get it.
+ // XPROFILE_GAMERCARD_MOTTO
+ AddSetting(std::make_unique<UserSetting>(0x402C0011, u""));
// XPROFILE_GAMERCARD_PICTURE_KEY
AddSetting(
- std::make_unique<UnicodeSetting>(0x4064000F, u"gamercard_picture_key"));
+ std::make_unique<UserSetting>(0x4064000F, u"gamercard_picture_key"));
+ // XPROFILE_GAMERCARD_REP
+ AddSetting(std::make_unique<UserSetting>(0x5004000B, 0.0f));
// XPROFILE_TITLE_SPECIFIC1
- AddSetting(std::make_unique<BinarySetting>(0x63E83FFF));
+ AddSetting(std::make_unique<UserSetting>(0x63E83FFF, std::vector<uint8_t>()));
// XPROFILE_TITLE_SPECIFIC2
- AddSetting(std::make_unique<BinarySetting>(0x63E83FFE));
+ AddSetting(std::make_unique<UserSetting>(0x63E83FFE, std::vector<uint8_t>()));
// XPROFILE_TITLE_SPECIFIC3
- AddSetting(std::make_unique<BinarySetting>(0x63E83FFD));
+ AddSetting(std::make_unique<UserSetting>(0x63E83FFD, std::vector<uint8_t>()));
}
-void UserProfile::AddSetting(std::unique_ptr<Setting> setting) {
- Setting* previous_setting = setting.get();
- std::swap(settings_[setting->setting_id], previous_setting);
+void UserProfile::AddSetting(std::unique_ptr<UserSetting> setting) {
+ UserSetting* previous_setting = setting.get();
+
+ std::swap(settings_[setting->GetSettingId()], previous_setting);
- if (setting->is_set && setting->is_title_specific()) {
+ if (setting->is_title_specific()) {
SaveSetting(setting.get());
}
@@ -115,40 +114,59 @@ void UserProfile::AddSetting(std::unique_ptr<Setting> setting) {
}
}
-UserProfile::Setting* UserProfile::GetSetting(uint32_t setting_id) {
+UserSetting* UserProfile::GetSetting(uint32_t setting_id) {
const auto& it = settings_.find(setting_id);
if (it == settings_.end()) {
return nullptr;
}
- UserProfile::Setting* setting = it->second;
+
+ UserSetting* 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);
- }
+ LoadSetting(setting);
}
return setting;
}
-void UserProfile::LoadSetting(UserProfile::Setting* setting) {
+void UserProfile::LoadSetting(UserSetting* setting) {
if (setting->is_title_specific()) {
- auto content_dir =
+ const std::filesystem::path content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath();
- auto setting_id = fmt::format("{:08X}", setting->setting_id);
- auto file_path = 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<uint32_t>(ftell(file));
- fseek(file, 0, SEEK_SET);
-
- std::vector<uint8_t> serialized_data(input_file_size);
- fread(serialized_data.data(), 1, serialized_data.size(), file);
+ const std::string setting_id_str =
+ fmt::format("{:08X}", setting->GetSettingId());
+ const std::filesystem::path file_path = content_dir / setting_id_str;
+ FILE* file = xe::filesystem::OpenFile(file_path, "rb");
+ if (!file) {
+ return;
+ }
+
+ const uint32_t input_file_size =
+ static_cast<uint32_t>(std::filesystem::file_size(file_path));
+
+ if (input_file_size < sizeof(X_USER_PROFILE_SETTING_HEADER)) {
fclose(file);
- setting->Deserialize(serialized_data);
- setting->loaded_title_id = kernel_state()->title_id();
+ // Setting seems to be invalid, remove it.
+ std::filesystem::remove(file_path);
+ return;
}
+
+ X_USER_PROFILE_SETTING_HEADER header;
+ fread(&header, sizeof(X_USER_PROFILE_SETTING_HEADER), 1, file);
+ if (header.setting_id != setting->GetSettingId()) {
+ // It's setting with different ID? Corrupted perhaps.
+ fclose(file);
+ std::filesystem::remove(file_path);
+ return;
+ }
+
+ // TODO(Gliniak): Right now we only care about CONTENT, WSTRING, BINARY
+ setting->SetNewSettingHeader(&header);
+ setting->SetNewSettingSource(X_USER_PROFILE_SETTING_SOURCE::TITLE);
+ std::vector<uint8_t> serialized_data(setting->GetSettingHeader()->size);
+ fread(serialized_data.data(), 1, serialized_data.size(), file);
+ fclose(file);
+ setting->GetSettingData()->Deserialize(serialized_data);
} else {
// Unsupported for now. Other settings aren't per-game and need to be
// stored some other way.
@@ -156,16 +174,32 @@ void UserProfile::LoadSetting(UserProfile::Setting* setting) {
}
}
-void UserProfile::SaveSetting(UserProfile::Setting* setting) {
- if (setting->is_title_specific()) {
- auto serialized_setting = setting->Serialize();
- auto content_dir =
+void UserProfile::SaveSetting(UserSetting* setting) {
+ if (setting->is_title_specific() &&
+ setting->GetSettingSource() == X_USER_PROFILE_SETTING_SOURCE::TITLE) {
+ const std::filesystem::path content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath();
+
std::filesystem::create_directories(content_dir);
- auto setting_id = fmt::format("{:08X}", setting->setting_id);
- auto file_path = content_dir / setting_id;
- auto file = xe::filesystem::OpenFile(file_path, "wb");
- fwrite(serialized_setting.data(), 1, serialized_setting.size(), file);
+
+ const std::string setting_id_str =
+ fmt::format("{:08X}", setting->GetSettingId());
+ std::filesystem::path file_path = content_dir / setting_id_str;
+ FILE* file = xe::filesystem::OpenFile(file_path, "wb");
+
+ if (!file) {
+ return;
+ }
+
+ const std::vector<uint8_t> serialized_setting =
+ setting->GetSettingData()->Serialize();
+ const uint32_t serialized_setting_length = std::min(
+ kMaxSettingSize, static_cast<uint32_t>(serialized_setting.size()));
+
+ fwrite(setting->GetSettingHeader(), sizeof(X_USER_PROFILE_SETTING_HEADER),
+ 1, file);
+ // Writing data
+ fwrite(serialized_setting.data(), 1, serialized_setting_length, file);
fclose(file);
} else {
// Unsupported for now. Other settings aren't per-game and need to be
diff --git a/src/xenia/kernel/xam/user_profile.h b/src/xenia/kernel/xam/user_profile.h
index ad24b003d1..2c79b963af 100644
--- a/src/xenia/kernel/xam/user_profile.h
+++ b/src/xenia/kernel/xam/user_profile.h
@@ -16,212 +16,141 @@
#include <vector>
#include "xenia/base/byte_stream.h"
+#include "xenia/kernel/util/xuserdata.h"
#include "xenia/xbox.h"
namespace xe {
namespace kernel {
namespace xam {
-struct X_USER_PROFILE_SETTING_DATA {
- // UserProfile::Setting::Type. Appears to be 8-in-32 field, and the upper 24
- // are not always zeroed by the game.
- uint8_t type;
- uint8_t unk_1[3];
- xe::be<uint32_t> unk_4;
- // TODO(sabretooth): not sure if this is a union, but it seems likely.
- // Haven't run into cases other than "binary data" yet.
+constexpr uint32_t kMaxSettingSize = 0x03E8;
+
+enum class X_USER_PROFILE_SETTING_SOURCE : uint32_t {
+ NOT_SET = 0,
+ DEFAULT = 1,
+ TITLE = 2,
+ UNKNOWN = 3,
+};
+
+// Each setting contains 0x18 bytes long header
+struct X_USER_PROFILE_SETTING_HEADER {
+ xe::be<uint32_t> setting_id;
+ xe::be<uint32_t> unknown_1;
+ xe::be<uint8_t> setting_type;
+ char unknown_2[3];
+ xe::be<uint32_t> unknown_3;
+
union {
- xe::be<int32_t> s32;
- xe::be<int64_t> s64;
- xe::be<uint32_t> u32;
- xe::be<double> f64;
- struct {
- xe::be<uint32_t> size;
- xe::be<uint32_t> ptr;
- } unicode;
- xe::be<float> f32;
- struct {
- xe::be<uint32_t> size;
- xe::be<uint32_t> ptr;
- } binary;
- xe::be<uint64_t> filetime;
+ // Size is used only for types: CONTENT, WSTRING, BINARY
+ be<uint32_t> size;
+ // Raw values that can be written. They do not need to be serialized.
+ be<int32_t> s32;
+ be<int64_t> s64;
+ be<uint32_t> u32;
+ be<double> f64;
+ be<float> f32;
};
};
-static_assert_size(X_USER_PROFILE_SETTING_DATA, 16);
+static_assert_size(X_USER_PROFILE_SETTING_HEADER, 0x18);
struct X_USER_PROFILE_SETTING {
xe::be<uint32_t> from;
- xe::be<uint32_t> unk04;
union {
xe::be<uint32_t> user_index;
xe::be<uint64_t> xuid;
};
xe::be<uint32_t> setting_id;
- xe::be<uint32_t> unk14;
union {
- uint8_t data_bytes[sizeof(X_USER_PROFILE_SETTING_DATA)];
- X_USER_PROFILE_SETTING_DATA data;
+ uint8_t data_bytes[sizeof(X_USER_DATA)];
+ X_USER_DATA data;
};
};
static_assert_size(X_USER_PROFILE_SETTING, 40);
-class UserProfile {
+class UserSetting {
public:
- class SettingByteStream : public ByteStream {
- public:
- SettingByteStream(uint32_t ptr, uint8_t* data, size_t data_length,
- size_t offset = 0)
- : ByteStream(data, data_length, offset), ptr_(ptr) {}
-
- uint32_t ptr() const { return static_cast<uint32_t>(ptr_ + offset()); }
+ template <typename T>
+ UserSetting(uint32_t setting_id, T data) {
+ header_.setting_id = setting_id;
+
+ setting_id_.value = setting_id;
+ CreateUserData(setting_id, data);
+ }
+
+ static bool is_title_specific(uint32_t setting_id) {
+ return (setting_id & 0x3F00) == 0x3F00;
+ }
+
+ bool is_title_specific() const {
+ return is_title_specific(setting_id_.value);
+ }
+
+ const uint32_t GetSettingId() const { return setting_id_.value; }
+ const X_USER_PROFILE_SETTING_SOURCE GetSettingSource() const {
+ return created_by_;
+ }
+ const X_USER_PROFILE_SETTING_HEADER* GetSettingHeader() const {
+ return &header_;
+ }
+ UserData* GetSettingData() { return user_data_.get(); }
+
+ void SetNewSettingSource(X_USER_PROFILE_SETTING_SOURCE new_source) {
+ created_by_ = new_source;
+ }
+
+ void SetNewSettingHeader(X_USER_PROFILE_SETTING_HEADER* header) {
+ header_ = *header;
+ }
- private:
- uint32_t ptr_;
- };
- struct Setting {
- enum class Type {
- CONTENT = 0,
- INT32 = 1,
- INT64 = 2,
- DOUBLE = 3,
- WSTRING = 4,
- FLOAT = 5,
- BINARY = 6,
- DATETIME = 7,
- UNSET = 0xFF,
- };
- union Key {
- uint32_t value;
- struct {
- uint32_t id : 14;
- uint32_t unk : 2;
- uint32_t size : 12;
- uint32_t type : 4;
- };
- };
- 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 void Append(X_USER_PROFILE_SETTING_DATA* data,
- SettingByteStream* stream) {
- data->type = static_cast<uint8_t>(type);
- }
- virtual std::vector<uint8_t> Serialize() const {
- return std::vector<uint8_t>();
- }
- virtual void Deserialize(std::vector<uint8_t>) {}
- bool is_title_specific() const { return (setting_id & 0x3F00) == 0x3F00; }
- };
- struct Int32Setting : public Setting {
- Int32Setting(uint32_t setting_id, int32_t value)
- : Setting(setting_id, Type::INT32, 4, true), value(value) {}
- int32_t value;
- void Append(X_USER_PROFILE_SETTING_DATA* data,
- SettingByteStream* stream) override {
- Setting::Append(data, stream);
- data->s32 = value;
- }
- };
- struct Int64Setting : public Setting {
- Int64Setting(uint32_t setting_id, int64_t value)
- : Setting(setting_id, Type::INT64, 8, true), value(value) {}
- int64_t value;
- void Append(X_USER_PROFILE_SETTING_DATA* data,
- SettingByteStream* stream) override {
- Setting::Append(data, stream);
- data->s64 = value;
- }
- };
- struct DoubleSetting : public Setting {
- DoubleSetting(uint32_t setting_id, double value)
- : Setting(setting_id, Type::DOUBLE, 8, true), value(value) {}
- double value;
- void Append(X_USER_PROFILE_SETTING_DATA* data,
- SettingByteStream* stream) override {
- Setting::Append(data, stream);
- data->f64 = value;
- }
- };
- struct UnicodeSetting : public Setting {
- UnicodeSetting(uint32_t setting_id, const std::u16string& value)
- : Setting(setting_id, Type::WSTRING, 8, true), value(value) {}
- std::u16string value;
- void Append(X_USER_PROFILE_SETTING_DATA* data,
- SettingByteStream* stream) override {
- Setting::Append(data, stream);
- if (value.empty()) {
- data->unicode.size = 0;
- data->unicode.ptr = 0;
- } else {
- size_t count = value.size() + 1;
- size_t size = 2 * count;
- assert_true(size <= std::numeric_limits<uint32_t>::max());
- data->unicode.size = static_cast<uint32_t>(size);
- data->unicode.ptr = stream->ptr();
- auto buffer =
- reinterpret_cast<uint16_t*>(&stream->data()[stream->offset()]);
- stream->Advance(size);
- xe::copy_and_swap(buffer, (uint16_t*)value.data(), count);
- }
- }
- };
- struct FloatSetting : public Setting {
- FloatSetting(uint32_t setting_id, float value)
- : Setting(setting_id, Type::FLOAT, 4, true), value(value) {}
- float value;
- void Append(X_USER_PROFILE_SETTING_DATA* data,
- SettingByteStream* stream) override {
- Setting::Append(data, stream);
- data->f32 = value;
- }
- };
- 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<uint8_t>& value)
- : Setting(setting_id, Type::BINARY, 8, true), value(value) {}
- std::vector<uint8_t> value;
- void Append(X_USER_PROFILE_SETTING_DATA* data,
- SettingByteStream* stream) override {
- Setting::Append(data, stream);
- if (value.empty()) {
- data->binary.size = 0;
- data->binary.ptr = 0;
- } else {
- size_t size = value.size();
- assert_true(size <= std::numeric_limits<uint32_t>::max());
- data->binary.size = static_cast<uint32_t>(size);
- data->binary.ptr = stream->ptr();
- stream->Write(value.data(), size);
- }
- }
- std::vector<uint8_t> Serialize() const override {
- return std::vector<uint8_t>(value.data(), value.data() + value.size());
- }
- void Deserialize(std::vector<uint8_t> 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;
- void Append(X_USER_PROFILE_SETTING_DATA* data,
- SettingByteStream* stream) override {
- Setting::Append(data, stream);
- data->filetime = value;
- }
- };
+ private:
+ void CreateUserData(uint32_t setting_id, uint32_t data) {
+ header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::INT32);
+ header_.s64 = data;
+ user_data_ = std::make_unique<Uint32UserData>(data);
+ }
+ void CreateUserData(uint32_t setting_id, int32_t data) {
+ header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::INT32);
+ header_.s32 = data;
+ user_data_ = std::make_unique<Int32UserData>(data);
+ }
+ void CreateUserData(uint32_t setting_id, float data) {
+ header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::FLOAT);
+ header_.f32 = data;
+ user_data_ = std::make_unique<FloatUserData>(data);
+ }
+ void CreateUserData(uint32_t setting_id, double data) {
+ header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::DOUBLE);
+ header_.f64 = data;
+ user_data_ = std::make_unique<DoubleUserData>(data);
+ }
+ void CreateUserData(uint32_t setting_id, int64_t data) {
+ header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::INT64);
+ header_.s64 = data;
+ user_data_ = std::make_unique<Int64UserData>(data);
+ }
+ void CreateUserData(uint32_t setting_id, const std::u16string& data) {
+ header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::WSTRING);
+ header_.size =
+ std::min(kMaxSettingSize, static_cast<uint32_t>((data.size() + 1) * 2));
+ user_data_ = std::make_unique<UnicodeUserData>(data);
+ }
+ void CreateUserData(uint32_t setting_id, const std::vector<uint8_t>& data) {
+ header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::BINARY);
+ header_.size =
+ std::min(kMaxSettingSize, static_cast<uint32_t>(data.size()));
+ user_data_ = std::make_unique<BinaryUserData>(data);
+ }
+
+ X_USER_PROFILE_SETTING_SOURCE created_by_ =
+ X_USER_PROFILE_SETTING_SOURCE::DEFAULT;
+
+ X_USER_PROFILE_SETTING_HEADER header_ = {};
+ UserData::Key setting_id_ = {};
+ std::unique_ptr<UserData> user_data_ = nullptr;
+};
+class UserProfile {
+ public:
UserProfile();
uint64_t xuid() const { return xuid_; }
@@ -229,17 +158,17 @@ class UserProfile {
uint32_t signin_state() const { return 1; }
uint32_t type() const { return 1 | 2; /* local | online profile? */ }
- void AddSetting(std::unique_ptr<Setting> setting);
- Setting* GetSetting(uint32_t setting_id);
+ void AddSetting(std::unique_ptr<UserSetting> setting);
+ UserSetting* GetSetting(uint32_t setting_id);
private:
uint64_t xuid_;
std::string name_;
- std::vector<std::unique_ptr<Setting>> setting_list_;
- std::unordered_map<uint32_t, Setting*> settings_;
+ std::vector<std::unique_ptr<UserSetting>> setting_list_;
+ std::unordered_map<uint32_t, UserSetting*> settings_;
- void LoadSetting(UserProfile::Setting*);
- void SaveSetting(UserProfile::Setting*);
+ void LoadSetting(UserSetting*);
+ void SaveSetting(UserSetting*);
};
} // namespace xam
diff --git a/src/xenia/kernel/xam/xam_user.cc b/src/xenia/kernel/xam/xam_user.cc
index f9ff584399..0b4df70e63 100644
--- a/src/xenia/kernel/xam/xam_user.cc
+++ b/src/xenia/kernel/xam/xam_user.cc
@@ -191,11 +191,11 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
uint32_t needed_data_size = 0;
for (uint32_t i = 0; i < setting_count; ++i) {
needed_header_size += sizeof(X_USER_PROFILE_SETTING);
- UserProfile::Setting::Key setting_key;
+ UserData::Key setting_key;
setting_key.value = static_cast<uint32_t>(setting_ids[i]);
- switch (static_cast<UserProfile::Setting::Type>(setting_key.type)) {
- case UserProfile::Setting::Type::WSTRING:
- case UserProfile::Setting::Type::BINARY:
+ switch (static_cast<X_USER_DATA_TYPE>(setting_key.type)) {
+ case X_USER_DATA_TYPE::WSTRING:
+ case X_USER_DATA_TYPE::BINARY:
needed_data_size += setting_key.size;
break;
default:
@@ -266,7 +266,7 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
out_header->settings_ptr =
kernel_state()->memory()->HostToGuestVirtual(out_setting);
- UserProfile::SettingByteStream out_stream(
+ DataByteStream out_stream(
kernel_state()->memory()->HostToGuestVirtual(buffer), buffer, buffer_size,
needed_header_size);
for (uint32_t n = 0; n < setting_count; ++n) {
@@ -274,18 +274,17 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
auto setting = user_profile->GetSetting(setting_id);
std::memset(out_setting, 0, sizeof(X_USER_PROFILE_SETTING));
- out_setting->from = !setting || !setting->is_set ? 0
- : setting->is_title_specific() ? 2
- : 1;
+ out_setting->from =
+ !setting ? 0 : static_cast<uint32_t>(setting->GetSettingSource());
if (xuids) {
out_setting->xuid = user_profile->xuid();
} else {
- out_setting->user_index = static_cast<uint32_t>(user_index);
+ out_setting->user_index = user_index;
}
out_setting->setting_id = setting_id;
- if (setting && setting->is_set) {
- setting->Append(&out_setting->data, &out_stream);
+ if (setting) {
+ setting->GetSettingData()->Append(&out_setting->data, &out_stream);
}
++out_setting;
}
@@ -343,9 +342,8 @@ dword_result_t XamUserWriteProfileSettings_entry(
for (uint32_t n = 0; n < setting_count; ++n) {
const X_USER_PROFILE_SETTING& setting = settings[n];
- auto setting_type =
- static_cast<UserProfile::Setting::Type>(setting.data.type);
- if (setting_type == UserProfile::Setting::Type::UNSET) {
+ auto setting_type = static_cast<X_USER_DATA_TYPE>(setting.data.type);
+ if (setting_type == X_USER_DATA_TYPE::UNSET) {
continue;
}
@@ -356,8 +354,8 @@ dword_result_t XamUserWriteProfileSettings_entry(
setting.data.type);
switch (setting_type) {
- case UserProfile::Setting::Type::CONTENT:
- case UserProfile::Setting::Type::BINARY: {
+ case X_USER_DATA_TYPE::CONTENT:
+ case X_USER_DATA_TYPE::BINARY: {
uint8_t* binary_ptr =
kernel_state()->memory()->TranslateVirtual(setting.data.binary.ptr);
size_t binary_size = setting.data.binary.size;
@@ -370,16 +368,19 @@ dword_result_t XamUserWriteProfileSettings_entry(
// Data pointer was NULL, so just fill with zeroes
bytes.resize(binary_size, 0);
}
- user_profile->AddSetting(
- std::make_unique<xam::UserProfile::BinarySetting>(
- setting.setting_id, bytes));
+
+ auto user_setting =
+ std::make_unique<UserSetting>(setting.setting_id, bytes);
+
+ user_setting->SetNewSettingSource(X_USER_PROFILE_SETTING_SOURCE::TITLE);
+ user_profile->AddSetting(std::move(user_setting));
} 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:
+ case X_USER_DATA_TYPE::WSTRING:
+ case X_USER_DATA_TYPE::DOUBLE:
+ case X_USER_DATA_TYPE::FLOAT:
+ case X_USER_DATA_TYPE::INT32:
+ case X_USER_DATA_TYPE::INT64:
+ case X_USER_DATA_TYPE::DATETIME:
default: {
XELOGE("XamUserWriteProfileSettings: Unimplemented data type {}",
setting_type);