[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://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();
if (setting->is_set && setting->is_title_specific()) {
std::swap(settings_[setting->GetSettingId()], previous_setting);
if (setting->is_title_specific()) {
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);
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);
fclose(file);
setting->Deserialize(serialized_data);
setting->loaded_title_id = kernel_state()->title_id();
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 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.
@ -159,16 +178,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

View File

@ -17,212 +17,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 UserSetting {
public:
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:
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:
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()); }
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;
}
};
UserProfile(uint8_t index);
uint64_t xuid() const { return xuid_; }
@ -230,19 +159,19 @@ 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);
std::map<uint32_t, uint32_t> contexts_;
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

View File

@ -199,11 +199,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:
@ -286,7 +286,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) {
@ -294,27 +294,20 @@ 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->xuid = -1;
out_setting->user_index = static_cast<uint32_t>(user_index);
out_setting->user_index = user_index;
}
out_setting->setting_id = setting_id;
if (setting) {
out_setting->from = 1;
out_setting->data.type = uint8_t(setting->type);
if (setting->is_set) {
if (setting->is_title_specific()) {
out_setting->from = 2;
}
setting->Append(&out_setting->data, &out_stream);
}
out_setting->data.type = static_cast<X_USER_DATA_TYPE>(
setting->GetSettingHeader()->setting_type.value);
setting->GetSettingData()->Append(&out_setting->data, &out_stream);
}
++out_setting;
}
@ -372,9 +365,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;
}
@ -385,21 +377,12 @@ dword_result_t XamUserWriteProfileSettings_entry(
setting.data.type);
switch (setting_type) {
case UserProfile::Setting::Type::CONTENT:
case UserProfile::Setting::Type::BINARY: {
UserProfile::Setting::Key setting_key;
setting_key.value = static_cast<uint32_t>(setting.setting_id);
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;
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;
if (setting.data.binary.ptr) {
// Copy provided data
@ -409,16 +392,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);