[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
This commit is contained in:
Gliniak 2024-02-18 12:12:14 +01:00 committed by Radosław Gliński
parent 7d740fb3c1
commit 321305cbf8
4 changed files with 477 additions and 293 deletions

View File

@ -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

View File

@ -32,74 +32,74 @@ UserProfile::UserProfile(uint8_t index) {
// https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195 // 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 // https://github.com/arkem/py360/blob/master/py360/constants.py
// XPROFILE_GAMER_YAXIS_INVERSION // XPROFILE_GAMER_YAXIS_INVERSION
AddSetting(std::make_unique<Int32Setting>(0x10040002, 0)); AddSetting(std::make_unique<UserSetting>(0x10040002, 0));
// XPROFILE_OPTION_CONTROLLER_VIBRATION // XPROFILE_OPTION_CONTROLLER_VIBRATION
AddSetting(std::make_unique<Int32Setting>(0x10040003, 3)); AddSetting(std::make_unique<UserSetting>(0x10040003, 3));
// XPROFILE_GAMERCARD_ZONE // XPROFILE_GAMERCARD_ZONE
AddSetting(std::make_unique<Int32Setting>(0x10040004, 0)); AddSetting(std::make_unique<UserSetting>(0x10040004, 0));
// XPROFILE_GAMERCARD_REGION // XPROFILE_GAMERCARD_REGION
AddSetting(std::make_unique<Int32Setting>(0x10040005, 0)); AddSetting(std::make_unique<UserSetting>(0x10040005, 0));
// XPROFILE_GAMERCARD_CRED // XPROFILE_GAMERCARD_CRED
AddSetting(std::make_unique<Int32Setting>(0x10040006, 0xFA)); AddSetting(std::make_unique<UserSetting>(0x10040006, 0xFA));
// XPROFILE_GAMERCARD_REP
AddSetting(std::make_unique<FloatSetting>(0x5004000B, 0.0f));
// XPROFILE_OPTION_VOICE_MUTED // XPROFILE_OPTION_VOICE_MUTED
AddSetting(std::make_unique<Int32Setting>(0x1004000C, 0)); AddSetting(std::make_unique<UserSetting>(0x1004000C, 3));
// XPROFILE_OPTION_VOICE_THRU_SPEAKERS // XPROFILE_OPTION_VOICE_THRU_SPEAKERS
AddSetting(std::make_unique<Int32Setting>(0x1004000D, 0)); AddSetting(std::make_unique<UserSetting>(0x1004000D, 3));
// XPROFILE_OPTION_VOICE_VOLUME // XPROFILE_OPTION_VOICE_VOLUME
AddSetting(std::make_unique<Int32Setting>(0x1004000E, 0x64)); AddSetting(std::make_unique<UserSetting>(0x1004000E, 0x64));
// XPROFILE_GAMERCARD_MOTTO
AddSetting(std::make_unique<UnicodeSetting>(0x402C0011, u""));
// XPROFILE_GAMERCARD_TITLES_PLAYED // XPROFILE_GAMERCARD_TITLES_PLAYED
AddSetting(std::make_unique<Int32Setting>(0x10040012, 1)); AddSetting(std::make_unique<UserSetting>(0x10040012, 1));
// XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED // XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED
AddSetting(std::make_unique<Int32Setting>(0x10040013, 0)); AddSetting(std::make_unique<UserSetting>(0x10040013, 0));
// XPROFILE_GAMER_DIFFICULTY // XPROFILE_GAMER_DIFFICULTY
AddSetting(std::make_unique<Int32Setting>(0x10040015, 0)); AddSetting(std::make_unique<UserSetting>(0x10040015, 0));
// XPROFILE_GAMER_CONTROL_SENSITIVITY // XPROFILE_GAMER_CONTROL_SENSITIVITY
AddSetting(std::make_unique<Int32Setting>(0x10040018, 0)); AddSetting(std::make_unique<UserSetting>(0x10040018, 0));
// Preferred color 1 // Preferred color 1
AddSetting(std::make_unique<Int32Setting>(0x1004001D, 0xFFFF0000u)); AddSetting(std::make_unique<UserSetting>(0x1004001D, 0xFFFF0000u));
// Preferred color 2 // Preferred color 2
AddSetting(std::make_unique<Int32Setting>(0x1004001E, 0xFF00FF00u)); AddSetting(std::make_unique<UserSetting>(0x1004001E, 0xFF00FF00u));
// XPROFILE_GAMER_ACTION_AUTO_AIM // XPROFILE_GAMER_ACTION_AUTO_AIM
AddSetting(std::make_unique<Int32Setting>(0x10040022, 1)); AddSetting(std::make_unique<UserSetting>(0x10040022, 1));
// XPROFILE_GAMER_ACTION_AUTO_CENTER // XPROFILE_GAMER_ACTION_AUTO_CENTER
AddSetting(std::make_unique<Int32Setting>(0x10040023, 0)); AddSetting(std::make_unique<UserSetting>(0x10040023, 0));
// XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL // XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL
AddSetting(std::make_unique<Int32Setting>(0x10040024, 0)); AddSetting(std::make_unique<UserSetting>(0x10040024, 0));
// XPROFILE_GAMER_RACE_TRANSMISSION // XPROFILE_GAMER_RACE_TRANSMISSION
AddSetting(std::make_unique<Int32Setting>(0x10040026, 0)); AddSetting(std::make_unique<UserSetting>(0x10040026, 0));
// XPROFILE_GAMER_RACE_CAMERA_LOCATION // XPROFILE_GAMER_RACE_CAMERA_LOCATION
AddSetting(std::make_unique<Int32Setting>(0x10040027, 0)); AddSetting(std::make_unique<UserSetting>(0x10040027, 0));
// XPROFILE_GAMER_RACE_BRAKE_CONTROL // XPROFILE_GAMER_RACE_BRAKE_CONTROL
AddSetting(std::make_unique<Int32Setting>(0x10040028, 0)); AddSetting(std::make_unique<UserSetting>(0x10040028, 0));
// XPROFILE_GAMER_RACE_ACCELERATOR_CONTROL // XPROFILE_GAMER_RACE_ACCELERATOR_CONTROL
AddSetting(std::make_unique<Int32Setting>(0x10040029, 0)); AddSetting(std::make_unique<UserSetting>(0x10040029, 0));
// XPROFILE_GAMERCARD_TITLE_CRED_EARNED // XPROFILE_GAMERCARD_TITLE_CRED_EARNED
AddSetting(std::make_unique<Int32Setting>(0x10040038, 0)); AddSetting(std::make_unique<UserSetting>(0x10040038, 0));
// XPROFILE_GAMERCARD_TITLE_ACHIEVEMENTS_EARNED // 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 // XPROFILE_GAMERCARD_PICTURE_KEY
AddSetting( 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 // XPROFILE_TITLE_SPECIFIC1
AddSetting(std::make_unique<BinarySetting>(0x63E83FFF)); AddSetting(std::make_unique<UserSetting>(0x63E83FFF, std::vector<uint8_t>()));
// XPROFILE_TITLE_SPECIFIC2 // XPROFILE_TITLE_SPECIFIC2
AddSetting(std::make_unique<BinarySetting>(0x63E83FFE)); AddSetting(std::make_unique<UserSetting>(0x63E83FFE, std::vector<uint8_t>()));
// XPROFILE_TITLE_SPECIFIC3 // 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) { void UserProfile::AddSetting(std::unique_ptr<UserSetting> setting) {
Setting* previous_setting = setting.get(); UserSetting* previous_setting = setting.get();
std::swap(settings_[setting->setting_id], previous_setting);
if (setting->is_set && setting->is_title_specific()) { std::swap(settings_[setting->GetSettingId()], previous_setting);
if (setting->is_title_specific()) {
SaveSetting(setting.get()); SaveSetting(setting.get());
} }
@ -118,40 +118,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); const auto& it = settings_.find(setting_id);
if (it == settings_.end()) { if (it == settings_.end()) {
return nullptr; return nullptr;
} }
UserProfile::Setting* setting = it->second;
UserSetting* setting = it->second;
if (setting->is_title_specific()) { if (setting->is_title_specific()) {
// If what we have loaded in memory isn't for the title that is running // If what we have loaded in memory isn't for the title that is running
// right now, then load it from disk. // right now, then load it from disk.
if (kernel_state()->title_id() != setting->loaded_title_id) {
LoadSetting(setting); LoadSetting(setting);
} }
}
return setting; return setting;
} }
void UserProfile::LoadSetting(UserProfile::Setting* setting) { void UserProfile::LoadSetting(UserSetting* setting) {
if (setting->is_title_specific()) { if (setting->is_title_specific()) {
auto content_dir = const std::filesystem::path content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath(); kernel_state()->content_manager()->ResolveGameUserContentPath();
auto setting_id = fmt::format("{:08X}", setting->setting_id); const std::string setting_id_str =
auto file_path = content_dir / setting_id; fmt::format("{:08X}", setting->GetSettingId());
auto file = xe::filesystem::OpenFile(file_path, "rb"); const std::filesystem::path file_path = content_dir / setting_id_str;
if (file) { FILE* file = xe::filesystem::OpenFile(file_path, "rb");
fseek(file, 0, SEEK_END); if (!file) {
uint32_t input_file_size = static_cast<uint32_t>(ftell(file)); return;
fseek(file, 0, SEEK_SET); }
std::vector<uint8_t> serialized_data(input_file_size); 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 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); fread(serialized_data.data(), 1, serialized_data.size(), file);
fclose(file); fclose(file);
setting->Deserialize(serialized_data); setting->GetSettingData()->Deserialize(serialized_data);
setting->loaded_title_id = kernel_state()->title_id();
}
} else { } else {
// Unsupported for now. Other settings aren't per-game and need to be // Unsupported for now. Other settings aren't per-game and need to be
// stored some other way. // stored some other way.
@ -159,16 +178,32 @@ void UserProfile::LoadSetting(UserProfile::Setting* setting) {
} }
} }
void UserProfile::SaveSetting(UserProfile::Setting* setting) { void UserProfile::SaveSetting(UserSetting* setting) {
if (setting->is_title_specific()) { if (setting->is_title_specific() &&
auto serialized_setting = setting->Serialize(); setting->GetSettingSource() == X_USER_PROFILE_SETTING_SOURCE::TITLE) {
auto content_dir = const std::filesystem::path content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath(); kernel_state()->content_manager()->ResolveGameUserContentPath();
std::filesystem::create_directories(content_dir); std::filesystem::create_directories(content_dir);
auto setting_id = fmt::format("{:08X}", setting->setting_id);
auto file_path = content_dir / setting_id; const std::string setting_id_str =
auto file = xe::filesystem::OpenFile(file_path, "wb"); fmt::format("{:08X}", setting->GetSettingId());
fwrite(serialized_setting.data(), 1, serialized_setting.size(), file); 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); fclose(file);
} else { } else {
// Unsupported for now. Other settings aren't per-game and need to be // Unsupported for now. Other settings aren't per-game and need to be

View File

@ -17,212 +17,141 @@
#include <vector> #include <vector>
#include "xenia/base/byte_stream.h" #include "xenia/base/byte_stream.h"
#include "xenia/kernel/util/xuserdata.h"
#include "xenia/xbox.h" #include "xenia/xbox.h"
namespace xe { namespace xe {
namespace kernel { namespace kernel {
namespace xam { namespace xam {
struct X_USER_PROFILE_SETTING_DATA { constexpr uint32_t kMaxSettingSize = 0x03E8;
// UserProfile::Setting::Type. Appears to be 8-in-32 field, and the upper 24
// are not always zeroed by the game. enum class X_USER_PROFILE_SETTING_SOURCE : uint32_t {
uint8_t type; NOT_SET = 0,
uint8_t unk_1[3]; DEFAULT = 1,
xe::be<uint32_t> unk_4; TITLE = 2,
// TODO(sabretooth): not sure if this is a union, but it seems likely. UNKNOWN = 3,
// Haven't run into cases other than "binary data" yet. };
// 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 { union {
xe::be<int32_t> s32; // Size is used only for types: CONTENT, WSTRING, BINARY
xe::be<int64_t> s64; be<uint32_t> size;
xe::be<uint32_t> u32; // Raw values that can be written. They do not need to be serialized.
xe::be<double> f64; be<int32_t> s32;
struct { be<int64_t> s64;
xe::be<uint32_t> size; be<uint32_t> u32;
xe::be<uint32_t> ptr; be<double> f64;
} unicode; be<float> f32;
xe::be<float> f32;
struct {
xe::be<uint32_t> size;
xe::be<uint32_t> ptr;
} binary;
xe::be<uint64_t> filetime;
}; };
}; };
static_assert_size(X_USER_PROFILE_SETTING_DATA, 16); static_assert_size(X_USER_PROFILE_SETTING_HEADER, 0x18);
struct X_USER_PROFILE_SETTING { struct X_USER_PROFILE_SETTING {
xe::be<uint32_t> from; xe::be<uint32_t> from;
xe::be<uint32_t> unk04;
union { union {
xe::be<uint32_t> user_index; xe::be<uint32_t> user_index;
xe::be<uint64_t> xuid; xe::be<uint64_t> xuid;
}; };
xe::be<uint32_t> setting_id; xe::be<uint32_t> setting_id;
xe::be<uint32_t> unk14;
union { union {
uint8_t data_bytes[sizeof(X_USER_PROFILE_SETTING_DATA)]; uint8_t data_bytes[sizeof(X_USER_DATA)];
X_USER_PROFILE_SETTING_DATA data; X_USER_DATA data;
}; };
}; };
static_assert_size(X_USER_PROFILE_SETTING, 40); static_assert_size(X_USER_PROFILE_SETTING, 40);
class UserProfile { class UserSetting {
public: public:
class SettingByteStream : public ByteStream { template <typename T>
public: UserSetting(uint32_t setting_id, T data) {
SettingByteStream(uint32_t ptr, uint8_t* data, size_t data_length, header_.setting_id = setting_id;
size_t offset = 0)
: ByteStream(data, data_length, offset), ptr_(ptr) {}
uint32_t ptr() const { return static_cast<uint32_t>(ptr_ + offset()); } 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: private:
uint32_t ptr_; void CreateUserData(uint32_t setting_id, uint32_t data) {
}; header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::INT32);
struct Setting { header_.s64 = data;
enum class Type { user_data_ = std::make_unique<Uint32UserData>(data);
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 { void CreateUserData(uint32_t setting_id, int32_t data) {
return std::vector<uint8_t>(); header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::INT32);
header_.s32 = data;
user_data_ = std::make_unique<Int32UserData>(data);
} }
virtual void Deserialize(std::vector<uint8_t>) {} void CreateUserData(uint32_t setting_id, float data) {
bool is_title_specific() const { return (setting_id & 0x3F00) == 0x3F00; } header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::FLOAT);
}; header_.f32 = data;
struct Int32Setting : public Setting { user_data_ = std::make_unique<FloatUserData>(data);
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;
} }
}; void CreateUserData(uint32_t setting_id, double data) {
struct Int64Setting : public Setting { header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::DOUBLE);
Int64Setting(uint32_t setting_id, int64_t value) header_.f64 = data;
: Setting(setting_id, Type::INT64, 8, true), value(value) {} user_data_ = std::make_unique<DoubleUserData>(data);
int64_t value;
void Append(X_USER_PROFILE_SETTING_DATA* data,
SettingByteStream* stream) override {
Setting::Append(data, stream);
data->s64 = value;
} }
}; void CreateUserData(uint32_t setting_id, int64_t data) {
struct DoubleSetting : public Setting { header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::INT64);
DoubleSetting(uint32_t setting_id, double value) header_.s64 = data;
: Setting(setting_id, Type::DOUBLE, 8, true), value(value) {} user_data_ = std::make_unique<Int64UserData>(data);
double value;
void Append(X_USER_PROFILE_SETTING_DATA* data,
SettingByteStream* stream) override {
Setting::Append(data, stream);
data->f64 = value;
} }
}; void CreateUserData(uint32_t setting_id, const std::u16string& data) {
struct UnicodeSetting : public Setting { header_.setting_type = static_cast<uint8_t>(X_USER_DATA_TYPE::WSTRING);
UnicodeSetting(uint32_t setting_id, const std::u16string& value) header_.size =
: Setting(setting_id, Type::WSTRING, 8, true), value(value) {} std::min(kMaxSettingSize, static_cast<uint32_t>((data.size() + 1) * 2));
std::u16string value; user_data_ = std::make_unique<UnicodeUserData>(data);
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);
} }
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);
} }
};
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;
}
};
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(uint8_t index); UserProfile(uint8_t index);
uint64_t xuid() const { return xuid_; } uint64_t xuid() const { return xuid_; }
@ -230,19 +159,19 @@ class UserProfile {
uint32_t signin_state() const { return 1; } uint32_t signin_state() const { return 1; }
uint32_t type() const { return 1 | 2; /* local | online profile? */ } uint32_t type() const { return 1 | 2; /* local | online profile? */ }
void AddSetting(std::unique_ptr<Setting> setting); void AddSetting(std::unique_ptr<UserSetting> setting);
Setting* GetSetting(uint32_t setting_id); UserSetting* GetSetting(uint32_t setting_id);
std::map<uint32_t, uint32_t> contexts_; std::map<uint32_t, uint32_t> contexts_;
private: private:
uint64_t xuid_; uint64_t xuid_;
std::string name_; std::string name_;
std::vector<std::unique_ptr<Setting>> setting_list_; std::vector<std::unique_ptr<UserSetting>> setting_list_;
std::unordered_map<uint32_t, Setting*> settings_; std::unordered_map<uint32_t, UserSetting*> settings_;
void LoadSetting(UserProfile::Setting*); void LoadSetting(UserSetting*);
void SaveSetting(UserProfile::Setting*); void SaveSetting(UserSetting*);
}; };
} // namespace xam } // namespace xam

View File

@ -199,11 +199,11 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
uint32_t needed_data_size = 0; uint32_t needed_data_size = 0;
for (uint32_t i = 0; i < setting_count; ++i) { for (uint32_t i = 0; i < setting_count; ++i) {
needed_header_size += sizeof(X_USER_PROFILE_SETTING); 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]); setting_key.value = static_cast<uint32_t>(setting_ids[i]);
switch (static_cast<UserProfile::Setting::Type>(setting_key.type)) { switch (static_cast<X_USER_DATA_TYPE>(setting_key.type)) {
case UserProfile::Setting::Type::WSTRING: case X_USER_DATA_TYPE::WSTRING:
case UserProfile::Setting::Type::BINARY: case X_USER_DATA_TYPE::BINARY:
needed_data_size += setting_key.size; needed_data_size += setting_key.size;
break; break;
default: default:
@ -286,7 +286,7 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
out_header->settings_ptr = out_header->settings_ptr =
kernel_state()->memory()->HostToGuestVirtual(out_setting); kernel_state()->memory()->HostToGuestVirtual(out_setting);
UserProfile::SettingByteStream out_stream( DataByteStream out_stream(
kernel_state()->memory()->HostToGuestVirtual(buffer), buffer, buffer_size, kernel_state()->memory()->HostToGuestVirtual(buffer), buffer, buffer_size,
needed_header_size); needed_header_size);
for (uint32_t n = 0; n < setting_count; ++n) { for (uint32_t n = 0; n < setting_count; ++n) {
@ -294,27 +294,20 @@ uint32_t XamUserReadProfileSettingsEx(uint32_t title_id, uint32_t user_index,
auto setting = user_profile->GetSetting(setting_id); auto setting = user_profile->GetSetting(setting_id);
std::memset(out_setting, 0, sizeof(X_USER_PROFILE_SETTING)); std::memset(out_setting, 0, sizeof(X_USER_PROFILE_SETTING));
out_setting->from = !setting || !setting->is_set ? 0 out_setting->from =
: setting->is_title_specific() ? 2 !setting ? 0 : static_cast<uint32_t>(setting->GetSettingSource());
: 1;
if (xuids) { if (xuids) {
out_setting->xuid = user_profile->xuid(); out_setting->xuid = user_profile->xuid();
} else { } else {
out_setting->xuid = -1; out_setting->xuid = -1;
out_setting->user_index = static_cast<uint32_t>(user_index); out_setting->user_index = user_index;
} }
out_setting->setting_id = setting_id; out_setting->setting_id = setting_id;
if (setting) { if (setting) {
out_setting->from = 1; out_setting->data.type = static_cast<X_USER_DATA_TYPE>(
out_setting->data.type = uint8_t(setting->type); setting->GetSettingHeader()->setting_type.value);
if (setting->is_set) { setting->GetSettingData()->Append(&out_setting->data, &out_stream);
if (setting->is_title_specific()) {
out_setting->from = 2;
}
setting->Append(&out_setting->data, &out_stream);
}
} }
++out_setting; ++out_setting;
} }
@ -372,9 +365,8 @@ dword_result_t XamUserWriteProfileSettings_entry(
for (uint32_t n = 0; n < setting_count; ++n) { for (uint32_t n = 0; n < setting_count; ++n) {
const X_USER_PROFILE_SETTING& setting = settings[n]; const X_USER_PROFILE_SETTING& setting = settings[n];
auto setting_type = auto setting_type = static_cast<X_USER_DATA_TYPE>(setting.data.type);
static_cast<UserProfile::Setting::Type>(setting.data.type); if (setting_type == X_USER_DATA_TYPE::UNSET) {
if (setting_type == UserProfile::Setting::Type::UNSET) {
continue; continue;
} }
@ -385,21 +377,12 @@ dword_result_t XamUserWriteProfileSettings_entry(
setting.data.type); setting.data.type);
switch (setting_type) { switch (setting_type) {
case UserProfile::Setting::Type::CONTENT: case X_USER_DATA_TYPE::CONTENT:
case UserProfile::Setting::Type::BINARY: { case X_USER_DATA_TYPE::BINARY: {
UserProfile::Setting::Key setting_key;
setting_key.value = static_cast<uint32_t>(setting.setting_id);
uint8_t* binary_ptr = uint8_t* binary_ptr =
kernel_state()->memory()->TranslateVirtual(setting.data.binary.ptr); kernel_state()->memory()->TranslateVirtual(setting.data.binary.ptr);
size_t binary_size = setting.data.binary.size; size_t binary_size = setting.data.binary.size;
if (setting_key.size < binary_size) {
XELOGW(
"XamUserWriteProfileSettings: binary size > key size. Shrinking "
"binary size!");
binary_size = setting_key.size;
}
std::vector<uint8_t> bytes; std::vector<uint8_t> bytes;
if (setting.data.binary.ptr) { if (setting.data.binary.ptr) {
// Copy provided data // Copy provided data
@ -409,16 +392,19 @@ dword_result_t XamUserWriteProfileSettings_entry(
// Data pointer was NULL, so just fill with zeroes // Data pointer was NULL, so just fill with zeroes
bytes.resize(binary_size, 0); bytes.resize(binary_size, 0);
} }
user_profile->AddSetting(
std::make_unique<xam::UserProfile::BinarySetting>( auto user_setting =
setting.setting_id, bytes)); 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; } break;
case UserProfile::Setting::Type::WSTRING: case X_USER_DATA_TYPE::WSTRING:
case UserProfile::Setting::Type::DOUBLE: case X_USER_DATA_TYPE::DOUBLE:
case UserProfile::Setting::Type::FLOAT: case X_USER_DATA_TYPE::FLOAT:
case UserProfile::Setting::Type::INT32: case X_USER_DATA_TYPE::INT32:
case UserProfile::Setting::Type::INT64: case X_USER_DATA_TYPE::INT64:
case UserProfile::Setting::Type::DATETIME: case X_USER_DATA_TYPE::DATETIME:
default: { default: {
XELOGE("XamUserWriteProfileSettings: Unimplemented data type {}", XELOGE("XamUserWriteProfileSettings: Unimplemented data type {}",
setting_type); setting_type);