[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:
parent
7d740fb3c1
commit
321305cbf8
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
Loading…
Reference in New Issue