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);
openSUSE Build Service is sponsored by