[XAM] Update profile setting code to use new xdbf::Setting struct

This commit is contained in:
emoose 2019-12-24 16:14:28 +00:00 committed by illusion98
parent 626467951d
commit fc78fa92cf
5 changed files with 315 additions and 451 deletions

View File

@ -106,78 +106,6 @@ UserProfile::UserProfile() : dash_gpd_(kDashboardID) {
// Try loading profile GPD files... // Try loading profile GPD files...
LoadProfile(); LoadProfile();
// 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));
// XPROFILE_OPTION_CONTROLLER_VIBRATION
AddSetting(std::make_unique<Int32Setting>(0x10040003, 3));
// XPROFILE_GAMERCARD_ZONE
AddSetting(std::make_unique<Int32Setting>(0x10040004, 0));
// XPROFILE_GAMERCARD_REGION
AddSetting(std::make_unique<Int32Setting>(0x10040005, 0));
// XPROFILE_GAMERCARD_CRED
AddSetting(
std::make_unique<Int32Setting>(0x10040006, CalculateUserGamerscore()));
// XPROFILE_GAMERCARD_REP
AddSetting(std::make_unique<FloatSetting>(0x5004000B, 0.0f));
// XPROFILE_OPTION_VOICE_MUTED
AddSetting(std::make_unique<Int32Setting>(0x1004000C, 0));
// XPROFILE_OPTION_VOICE_THRU_SPEAKERS
AddSetting(std::make_unique<Int32Setting>(0x1004000D, 0));
// XPROFILE_OPTION_VOICE_VOLUME
AddSetting(std::make_unique<Int32Setting>(0x1004000E, 0x64));
// XPROFILE_GAMERCARD_MOTTO
AddSetting(std::make_unique<UnicodeSetting>(0x402C0011, L""));
// XPROFILE_GAMERCARD_TITLES_PLAYED
AddSetting(
std::make_unique<Int32Setting>(0x10040012, GetAmountOfPlayedTitles()));
// XPROFILE_GAMERCARD_ACHIEVEMENTS_EARNED
AddSetting(std::make_unique<Int32Setting>(0x10040013, 0));
// XPROFILE_GAMER_DIFFICULTY
AddSetting(std::make_unique<Int32Setting>(0x10040015, 0));
// XPROFILE_GAMER_CONTROL_SENSITIVITY
AddSetting(std::make_unique<Int32Setting>(0x10040018, 0));
// Preferred color 1
AddSetting(std::make_unique<Int32Setting>(0x1004001D, 0xFFFF0000u));
// Preferred color 2
AddSetting(std::make_unique<Int32Setting>(0x1004001E, 0xFF00FF00u));
// XPROFILE_GAMER_ACTION_AUTO_AIM
AddSetting(std::make_unique<Int32Setting>(0x10040022, 1));
// XPROFILE_GAMER_ACTION_AUTO_CENTER
AddSetting(std::make_unique<Int32Setting>(0x10040023, 0));
// XPROFILE_GAMER_ACTION_MOVEMENT_CONTROL
AddSetting(std::make_unique<Int32Setting>(0x10040024, 0));
// XPROFILE_GAMER_RACE_TRANSMISSION
AddSetting(std::make_unique<Int32Setting>(0x10040026, 0));
// XPROFILE_GAMER_RACE_CAMERA_LOCATION
AddSetting(std::make_unique<Int32Setting>(0x10040027, 0));
// XPROFILE_GAMER_RACE_BRAKE_CONTROL
AddSetting(std::make_unique<Int32Setting>(0x10040028, 0));
// XPROFILE_GAMER_RACE_ACCELERATOR_CONTROL
AddSetting(std::make_unique<Int32Setting>(0x10040029, 0));
// XPROFILE_GAMERCARD_TITLE_CRED_EARNED
AddSetting(std::make_unique<Int32Setting>(0x10040038, 0));
// XPROFILE_GAMERCARD_TITLE_ACHIEVEMENTS_EARNED
AddSetting(std::make_unique<Int32Setting>(0x10040039, 0));
// If we set this, games will try to get it.
// XPROFILE_GAMERCARD_PICTURE_KEY
AddSetting(
std::make_unique<UnicodeSetting>(0x4064000F, L"gamercard_picture_key"));
// XPROFILE_TITLE_SPECIFIC1
AddSetting(std::make_unique<BinarySetting>(0x63E83FFF));
// XPROFILE_TITLE_SPECIFIC2
AddSetting(std::make_unique<BinarySetting>(0x63E83FFE));
// XPROFILE_TITLE_SPECIFIC3
AddSetting(std::make_unique<BinarySetting>(0x63E83FFD));
// Unknown, but on NXE dash it causes profile name & gamerscore appear
AddSetting(std::make_unique<BinarySetting>(0x63E80044));
AddSetting(std::make_unique<BinarySetting>(0x7008004F));
AddSetting(std::make_unique<BinarySetting>(0x61180050));
} }
void UserProfile::LoadProfile() { void UserProfile::LoadProfile() {
@ -519,86 +447,15 @@ bool UserProfile::UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data) {
return ret_val; return ret_val;
} }
void UserProfile::AddSetting(std::unique_ptr<Setting> setting) { bool UserProfile::AddSettingIfNotExist(xdbf::Setting& setting) {
Setting* previous_setting = setting.get(); if (dash_gpd_.GetSetting(setting.id, nullptr)) {
std::swap(settings_[setting->setting_id], previous_setting); return false;
if (setting->is_set && setting->is_title_specific()) {
SaveSetting(setting.get());
} }
if (setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary &&
if (previous_setting) { !setting.extraData.size()) {
// replace: swap out the old setting from the owning list setting.extraData.resize(XPROFILEID_SIZE(setting.id));
for (auto vec_it = setting_list_.begin(); vec_it != setting_list_.end();
++vec_it) {
if (vec_it->get() == previous_setting) {
vec_it->swap(setting);
break;
}
}
} else {
// new setting: add to the owning list
setting_list_.push_back(std::move(setting));
}
}
UserProfile::Setting* UserProfile::GetSetting(uint32_t setting_id) {
const auto& it = settings_.find(setting_id);
if (it == settings_.end()) {
return nullptr;
}
UserProfile::Setting* 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);
}
}
return setting;
}
void UserProfile::LoadSetting(UserProfile::Setting* setting) {
if (setting->is_title_specific()) {
auto content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath();
auto setting_id = xe::format_string(L"%.8X", setting->setting_id);
auto file_path = xe::join_paths(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();
}
} else {
// Unsupported for now. Other settings aren't per-game and need to be
// stored some other way.
XELOGW("Attempting to load unsupported profile setting from disk");
}
}
void UserProfile::SaveSetting(UserProfile::Setting* setting) {
if (setting->is_title_specific()) {
auto serialized_setting = setting->Serialize();
auto content_dir =
kernel_state()->content_manager()->ResolveGameUserContentPath();
xe::filesystem::CreateFolder(content_dir);
auto setting_id = xe::format_string(L"%.8X", setting->setting_id);
auto file_path = xe::join_paths(content_dir, setting_id);
auto file = xe::filesystem::OpenFile(file_path, "wb");
fwrite(serialized_setting.data(), 1, serialized_setting.size(), file);
fclose(file);
} else {
// Unsupported for now. Other settings aren't per-game and need to be
// stored some other way.
XELOGW("Attempting to save unsupported profile setting to disk");
} }
return dash_gpd_.UpdateSetting(setting);
} }
xdbf::GpdFile* UserProfile::GetDashboardGpd() { return &dash_gpd_; } xdbf::GpdFile* UserProfile::GetDashboardGpd() { return &dash_gpd_; }
@ -620,18 +477,6 @@ xdbf::SpaFile* UserProfile::GetTitleSpa(uint32_t title_id) {
return (game_entry); return (game_entry);
} }
uint32_t UserProfile::CalculateUserGamerscore() const {
uint32_t score = 0;
std::vector<xdbf::TitlePlayed> titles;
dash_gpd_.GetTitles(&titles);
for (auto title : titles)
score += title.gamerscore_earned;
return score;
}
} // namespace xam } // namespace xam
} // namespace kernel } // namespace kernel
} // namespace xe } // namespace xe

View File

@ -132,180 +132,6 @@ struct X_XAMACCOUNTINFO {
class UserProfile { class UserProfile {
public: public:
struct Setting {
enum class Type {
CONTENT = 0,
INT32 = 1,
INT64 = 2,
DOUBLE = 3,
WSTRING = 4,
FLOAT = 5,
BINARY = 6,
DATETIME = 7,
INVALID = 0xFF,
};
union Key {
struct {
uint32_t id : 14;
uint32_t unk : 2;
uint32_t size : 12;
uint32_t type : 4;
};
uint32_t value;
};
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 size_t extra_size() const { return 0; }
virtual size_t Append(uint8_t* user_data, uint8_t* buffer,
uint32_t buffer_ptr, size_t buffer_offset) {
xe::store_and_swap<uint8_t>(user_data + kTypeOffset,
static_cast<uint8_t>(type));
return buffer_offset;
}
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; }
protected:
const size_t kTypeOffset = 0;
const size_t kValueOffset = 8;
const size_t kPointerOffset = 12;
};
struct Int32Setting : public Setting {
Int32Setting(uint32_t setting_id, int32_t value)
: Setting(setting_id, Type::INT32, 4, true), value(value) {}
int32_t value;
size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr,
size_t buffer_offset) override {
buffer_offset =
Setting::Append(user_data, buffer, buffer_ptr, buffer_offset);
xe::store_and_swap<int32_t>(user_data + kValueOffset, value);
return buffer_offset;
}
};
struct Int64Setting : public Setting {
Int64Setting(uint32_t setting_id, int64_t value)
: Setting(setting_id, Type::INT64, 8, true), value(value) {}
int64_t value;
size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr,
size_t buffer_offset) override {
buffer_offset =
Setting::Append(user_data, buffer, buffer_ptr, buffer_offset);
xe::store_and_swap<int64_t>(user_data + kValueOffset, value);
return buffer_offset;
}
};
struct DoubleSetting : public Setting {
DoubleSetting(uint32_t setting_id, double value)
: Setting(setting_id, Type::DOUBLE, 8, true), value(value) {}
double value;
size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr,
size_t buffer_offset) override {
buffer_offset =
Setting::Append(user_data, buffer, buffer_ptr, buffer_offset);
xe::store_and_swap<double>(user_data + kValueOffset, value);
return buffer_offset;
}
};
struct UnicodeSetting : public Setting {
UnicodeSetting(uint32_t setting_id, const std::wstring& value)
: Setting(setting_id, Type::WSTRING, 8, true), value(value) {}
std::wstring value;
size_t extra_size() const override {
return value.empty() ? 0 : 2 * (static_cast<int32_t>(value.size()) + 1);
}
size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr,
size_t buffer_offset) override {
buffer_offset =
Setting::Append(user_data, buffer, buffer_ptr, buffer_offset);
int32_t length;
if (value.empty()) {
length = 0;
xe::store_and_swap<int32_t>(user_data + kValueOffset, 0);
xe::store_and_swap<uint32_t>(user_data + kPointerOffset, 0);
} else {
length = 2 * (static_cast<int32_t>(value.size()) + 1);
xe::store_and_swap<int32_t>(user_data + kValueOffset, length);
xe::store_and_swap<uint32_t>(
user_data + kPointerOffset,
buffer_ptr + static_cast<uint32_t>(buffer_offset));
memcpy(buffer + buffer_offset, value.data(), length);
}
return buffer_offset + length;
}
};
struct FloatSetting : public Setting {
FloatSetting(uint32_t setting_id, float value)
: Setting(setting_id, Type::FLOAT, 4, true), value(value) {}
float value;
size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr,
size_t buffer_offset) override {
buffer_offset =
Setting::Append(user_data, buffer, buffer_ptr, buffer_offset);
xe::store_and_swap<float>(user_data + kValueOffset, value);
return buffer_offset;
}
};
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;
size_t extra_size() const override {
return static_cast<int32_t>(value.size());
}
size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr,
size_t buffer_offset) override {
buffer_offset =
Setting::Append(user_data, buffer, buffer_ptr, buffer_offset);
int32_t length;
if (value.empty()) {
length = 0;
xe::store_and_swap<int32_t>(user_data + kValueOffset, 0);
xe::store_and_swap<int32_t>(user_data + kPointerOffset, 0);
} else {
length = static_cast<int32_t>(value.size());
xe::store_and_swap<int32_t>(user_data + kValueOffset, length);
xe::store_and_swap<uint32_t>(
user_data + kPointerOffset,
buffer_ptr + static_cast<uint32_t>(buffer_offset));
memcpy(buffer + buffer_offset, value.data(), length);
}
return buffer_offset + length;
}
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;
size_t Append(uint8_t* user_data, uint8_t* buffer, uint32_t buffer_ptr,
size_t buffer_offset) override {
buffer_offset =
Setting::Append(user_data, buffer, buffer_ptr, buffer_offset);
xe::store_and_swap<int64_t>(user_data + kValueOffset, value);
return buffer_offset;
}
};
static bool DecryptAccountFile(const uint8_t* data, X_XAMACCOUNTINFO* output, static bool DecryptAccountFile(const uint8_t* data, X_XAMACCOUNTINFO* output,
bool devkit = false); bool devkit = false);
@ -317,11 +143,6 @@ class UserProfile {
uint64_t xuid() const { return account_.xuid_online; } uint64_t xuid() const { return account_.xuid_online; }
std::string name() const { return account_.GetGamertagString(); } std::string name() const { return account_.GetGamertagString(); }
// uint32_t signin_state() const { return 1; } // uint32_t signin_state() const { return 1; }
uint32_t CalculateUserGamerscore() const;
uint32_t GetAmountOfPlayedTitles() const { return (uint32_t)title_gpds_.size(); }
void AddSetting(std::unique_ptr<Setting> setting);
Setting* GetSetting(uint32_t setting_id);
xdbf::GpdFile* SetTitleSpaData(const xdbf::SpaFile& spa_data); xdbf::GpdFile* SetTitleSpaData(const xdbf::SpaFile& spa_data);
xdbf::GpdFile* GetTitleGpd(uint32_t title_id = -1); xdbf::GpdFile* GetTitleGpd(uint32_t title_id = -1);
@ -337,12 +158,9 @@ class UserProfile {
void LoadProfile(); void LoadProfile();
bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data); bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data);
X_XAMACCOUNTINFO account_; bool AddSettingIfNotExist(xdbf::Setting& setting);
std::vector<std::unique_ptr<Setting>> setting_list_;
std::unordered_map<uint32_t, Setting*> settings_;
void LoadSetting(UserProfile::Setting*); X_XAMACCOUNTINFO account_;
void SaveSetting(UserProfile::Setting*);
std::unordered_map<uint32_t, xdbf::GpdFile> title_gpds_; std::unordered_map<uint32_t, xdbf::GpdFile> title_gpds_;
xdbf::GpdFile dash_gpd_; xdbf::GpdFile dash_gpd_;

View File

@ -219,25 +219,26 @@ static_assert_size(X_USER_READ_PROFILE_SETTINGS, 8);
typedef struct { typedef struct {
xe::be<uint32_t> from; xe::be<uint32_t> from;
xe::be<uint32_t> unk04; union {
xe::be<uint32_t> user_index; xe::be<uint32_t> user_index;
xe::be<uint32_t> unk0C; xe::be<uint64_t> user_xuid;
xe::be<uint32_t> setting_id; } user;
xe::be<uint32_t> unk14; xdbf::X_XDBF_GPD_SETTING setting;
uint8_t setting_data[16];
} X_USER_READ_PROFILE_SETTING; } X_USER_READ_PROFILE_SETTING;
static_assert_size(X_USER_READ_PROFILE_SETTING, 40); static_assert_size(X_USER_READ_PROFILE_SETTING, 40);
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/Generic/xboxtools.cpp // https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/Generic/xboxtools.cpp
dword_result_t XamUserReadProfileSettings( dword_result_t XamUserReadProfileSettings(
dword_t title_id, dword_t user_index, dword_t unk_0, dword_t unk_1, dword_t title_id, dword_t user_index, dword_t num_xuids, lpqword_t xuids,
dword_t setting_count, lpdword_t setting_ids, lpdword_t buffer_size_ptr, dword_t setting_count, lpdword_t setting_ids, lpdword_t buffer_size_ptr,
lpvoid_t buffer_ptr, dword_t overlapped_ptr) { lpvoid_t buffer_ptr, dword_t overlapped_ptr) {
uint32_t buffer_size = uint32_t buffer_size =
!buffer_size_ptr ? 0u : static_cast<uint32_t>(*buffer_size_ptr); !buffer_size_ptr ? 0u : static_cast<uint32_t>(*buffer_size_ptr);
assert_zero(unk_0); uint64_t xuid = 0;
assert_zero(unk_1); if (num_xuids && xuids) {
xuid = *xuids;
}
// TODO(gibbed): why is this a thing? // TODO(gibbed): why is this a thing?
uint32_t actual_user_index = user_index; uint32_t actual_user_index = user_index;
@ -269,32 +270,17 @@ dword_result_t XamUserReadProfileSettings(
// Compute required extra size. // Compute required extra size.
uint32_t size_needed = base_size_needed; uint32_t size_needed = base_size_needed;
bool any_missing = false;
for (uint32_t n = 0; n < setting_count; ++n) { for (uint32_t n = 0; n < setting_count; ++n) {
uint32_t setting_id = setting_ids[n]; auto setting_id = (xdbf::X_XDBF_SETTING_ID)(uint32_t)setting_ids[n];
auto setting = user_profile->GetSetting(setting_id); xdbf::Setting setting;
if (setting) { if (user_profile->GetDashboardGpd()->GetSetting(setting_id, &setting)) {
if (setting->is_set) { size_needed += (uint32_t)setting.extraData.size();
auto extra_size = static_cast<uint32_t>(setting->extra_size());
size_needed += extra_size;
}
} else { } else {
any_missing = true;
XELOGE("XamUserReadProfileSettings requested unimplemented setting %.8X", XELOGE("XamUserReadProfileSettings requested unimplemented setting %.8X",
setting_id); setting_id);
} }
} }
if (any_missing) {
// TODO(benvanik): don't fail? most games don't even check!
if (overlapped_ptr) {
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr,
X_ERROR_INVALID_PARAMETER);
return X_ERROR_IO_PENDING;
}
return X_ERROR_INVALID_PARAMETER;
}
*buffer_size_ptr = size_needed; *buffer_size_ptr = size_needed;
if (!buffer_ptr || buffer_size < size_needed) { if (!buffer_ptr || buffer_size < size_needed) {
if (overlapped_ptr) { if (overlapped_ptr) {
@ -312,27 +298,60 @@ dword_result_t XamUserReadProfileSettings(
auto out_setting = auto out_setting =
reinterpret_cast<X_USER_READ_PROFILE_SETTING*>(buffer_ptr + 8); reinterpret_cast<X_USER_READ_PROFILE_SETTING*>(buffer_ptr + 8);
size_t buffer_offset = base_size_needed; uint32_t buffer_offset = base_size_needed;
for (uint32_t n = 0; n < setting_count; ++n) { for (uint32_t n = 0; n < setting_count; ++n) {
uint32_t setting_id = setting_ids[n]; auto setting_id = (xdbf::X_XDBF_SETTING_ID)(uint32_t)setting_ids[n];
auto setting = user_profile->GetSetting(setting_id); xdbf::Setting setting;
auto gpd = user_profile->GetDashboardGpd();
if (title_id != 0xFFFE07D1) {
gpd = user_profile->GetTitleGpd(title_id);
}
bool exists = gpd && gpd->GetSetting(setting_id, &setting);
// TODO: fix binary & unicode settings crashing dash.xex!
if (setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary ||
setting.value.type == xdbf::X_XUSER_DATA_TYPE::kUnicode) {
exists = false;
}
std::memset(out_setting, 0, sizeof(X_USER_READ_PROFILE_SETTING)); std::memset(out_setting, 0, sizeof(X_USER_READ_PROFILE_SETTING));
out_setting->from = out_setting->from = !exists ? 0 : setting.IsTitleSpecific() ? 2 : 1;
!setting || !setting->is_set ? 0 : setting->is_title_specific() ? 2 : 1; out_setting->setting.setting_id = setting_id;
out_setting->user_index = actual_user_index;
out_setting->setting_id = setting_id;
if (setting && setting->is_set) { if (num_xuids && xuids) {
buffer_offset = out_setting->user.user_xuid = xuid;
setting->Append(&out_setting->setting_data[0], buffer_ptr, } else {
buffer_ptr.guest_address(), buffer_offset); out_setting->user.user_index = actual_user_index;
}
if (exists) {
memcpy(&out_setting->setting.value, &setting.value,
sizeof(xdbf::X_XUSER_DATA));
if (setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary) {
memcpy(buffer_ptr.as<uint8_t*>() + buffer_offset,
setting.extraData.data(), setting.extraData.size());
out_setting->setting.value.binary.cbData =
(uint32_t)setting.extraData.size();
out_setting->setting.value.binary.pbData =
buffer_ptr.guest_address() + buffer_offset;
buffer_offset += (uint32_t)setting.extraData.size();
} else if (setting.value.type == xdbf::X_XUSER_DATA_TYPE::kUnicode) {
memcpy(buffer_ptr.as<uint8_t*>() + buffer_offset,
setting.extraData.data(), setting.extraData.size());
out_setting->setting.value.string.cbData =
(uint32_t)setting.extraData.size();
out_setting->setting.value.string.pwszData =
buffer_ptr.guest_address() + buffer_offset;
buffer_offset += (uint32_t)setting.extraData.size();
}
} }
// TODO(benvanik): why did I do this?
/*else {
std::memset(&out_setting->setting_data[0], 0,
sizeof(out_setting->setting_data));
}*/
++out_setting; ++out_setting;
} }
@ -347,29 +366,17 @@ DECLARE_XAM_EXPORT1(XamUserReadProfileSettings, kUserProfiles, kImplemented);
typedef struct { typedef struct {
xe::be<uint32_t> from; xe::be<uint32_t> from;
xe::be<uint32_t> unk_04;
xe::be<uint32_t> unk_08;
xe::be<uint32_t> unk_0c;
xe::be<uint32_t> setting_id;
xe::be<uint32_t> unk_14;
// UserProfile::Setting::Type. Appears to be 8-in-32 field, and the upper 24
// are not always zeroed by the game.
uint8_t type;
xe::be<uint32_t> unk_1c;
// TODO(sabretooth): not sure if this is a union, but it seems likely.
// Haven't run into cases other than "binary data" yet.
union { union {
struct { xe::be<uint32_t> user_index;
xe::be<uint32_t> length; xe::be<uint64_t> user_xuid;
xe::be<uint32_t> ptr; } user;
} binary;
}; xdbf::X_XDBF_GPD_SETTING setting;
} X_USER_WRITE_PROFILE_SETTING; } X_USER_WRITE_PROFILE_SETTING;
static_assert_size(X_USER_WRITE_PROFILE_SETTING, 0x28);
dword_result_t XamUserWriteProfileSettings( dword_result_t XamUserWriteProfileSettings(
dword_t user_index, dword_t unk, dword_t setting_count, dword_t title_id, dword_t user_index, dword_t setting_count,
pointer_t<X_USER_WRITE_PROFILE_SETTING> settings, dword_t overlapped_ptr) { pointer_t<X_USER_WRITE_PROFILE_SETTING> settings, dword_t overlapped_ptr) {
if (!setting_count || !settings) { if (!setting_count || !settings) {
if (overlapped_ptr) { if (overlapped_ptr) {
@ -387,58 +394,72 @@ dword_result_t XamUserWriteProfileSettings(
if (actual_user_index) { if (actual_user_index) {
// Only support user 0. // Only support user 0.
if (overlapped_ptr) {
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr,
X_ERROR_NOT_FOUND);
return X_ERROR_IO_PENDING;
}
return X_ERROR_NOT_FOUND; return X_ERROR_NOT_FOUND;
} }
// Update and save settings. // Update and save settings.
const auto& user_profile = kernel_state()->user_profile(); const auto& user_profile = kernel_state()->user_profile();
auto gpd = user_profile->GetDashboardGpd();
if (title_id != 0xFFFE07D1) {
gpd = user_profile->GetTitleGpd(title_id);
}
if (!gpd) {
// TODO: find out proper error code for this condition!
if (overlapped_ptr) {
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr,
X_ERROR_INVALID_PARAMETER);
return X_ERROR_IO_PENDING;
}
return X_ERROR_INVALID_PARAMETER;
}
for (uint32_t n = 0; n < setting_count; ++n) { for (uint32_t n = 0; n < setting_count; ++n) {
const X_USER_WRITE_PROFILE_SETTING& settings_data = settings[n]; const X_USER_WRITE_PROFILE_SETTING& settings_data = settings[n];
XELOGD( XELOGD(
"XamUserWriteProfileSettings: setting index [%d]:" "XamUserWriteProfileSettings: setting index [%d]:"
" from=%d setting_id=%.8X data.type=%d", " from=%d setting_id=%.8X data.type=%d",
n, (uint32_t)settings_data.from, (uint32_t)settings_data.setting_id, n, (uint32_t)settings_data.from,
settings_data.type); (uint32_t)settings_data.setting.setting_id,
settings_data.setting.value.type);
xam::UserProfile::Setting::Type settingType = xdbf::Setting setting;
static_cast<xam::UserProfile::Setting::Type>(settings_data.type); setting.id = settings_data.setting.setting_id;
setting.value.type = settings_data.setting.value.type;
switch (settingType) { // Retrieve any existing setting data if we can
case UserProfile::Setting::Type::CONTENT: gpd->GetSetting(setting.id, &setting);
case UserProfile::Setting::Type::BINARY: {
uint8_t* settings_data_ptr = kernel_state()->memory()->TranslateVirtual(
settings_data.binary.ptr);
size_t settings_data_len = settings_data.binary.length;
std::vector<uint8_t> data_vec;
if (settings_data.binary.ptr) { // ... and then overwrite it
// Copy provided data memcpy(&setting.value, &settings_data.setting.value,
data_vec.resize(settings_data_len); sizeof(xdbf::X_XUSER_DATA));
std::memcpy(data_vec.data(), settings_data_ptr, settings_data_len);
} else {
// Data pointer was NULL, so just fill with zeroes
data_vec.resize(settings_data_len, 0);
}
user_profile->AddSetting( if (settings_data.setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary) {
std::make_unique<xam::UserProfile::BinarySetting>( if (settings_data.setting.value.binary.pbData) {
settings_data.setting_id, data_vec)); setting.extraData.resize(settings_data.setting.value.binary.cbData);
auto* data_ptr = kernel_memory()->TranslateVirtual(
settings_data.setting.value.binary.pbData);
memcpy(setting.extraData.data(), data_ptr,
settings_data.setting.value.binary.cbData);
}
} else if (settings_data.setting.value.type ==
xdbf::X_XUSER_DATA_TYPE::kUnicode) {
if (settings_data.setting.value.string.pwszData) {
setting.extraData.resize(settings_data.setting.value.string.cbData);
auto* data_ptr = kernel_memory()->TranslateVirtual(
settings_data.setting.value.string.pwszData);
memcpy(setting.extraData.data(), data_ptr,
settings_data.setting.value.string.cbData);
}
}
} break; gpd->UpdateSetting(setting);
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:
default:
XELOGE("XamUserWriteProfileSettings: Unimplemented data type %d",
settingType);
break;
};
} }
if (overlapped_ptr) { if (overlapped_ptr) {
@ -710,13 +731,11 @@ dword_result_t XamUserCreateTitlesPlayedEnumerator(
for (auto title : titles) { for (auto title : titles) {
// For some reason dashboard gpd stores info about itself // For some reason dashboard gpd stores info about itself
if (title.title_id == kDashboardID) if (title.title_id == kDashboardID) continue;
continue;
// TODO: Look for better check to provide information about demo title // TODO: Look for better check to provide information about demo title
// or system title // or system title
if (!title.gamerscore_total || !title.achievements_possible) if (!title.gamerscore_total || !title.achievements_possible) continue;
continue;
auto* details = (xdbf::X_XDBF_GPD_TITLEPLAYED*)e->AppendItem(); auto* details = (xdbf::X_XDBF_GPD_TITLEPLAYED*)e->AppendItem();
title.WriteGPD(details); title.WriteGPD(details);

View File

@ -216,6 +216,7 @@ uint32_t SpaFile::GetAchievements(
ach.image_id = ach_data->image_id; ach.image_id = ach_data->image_id;
ach.gamerscore = ach_data->gamerscore; ach.gamerscore = ach_data->gamerscore;
ach.flags = ach_data->flags; ach.flags = ach_data->flags;
ach.flags |= static_cast<uint32_t>(AchievementPlatform::kX360);
ach.label = xe::to_wstring( ach.label = xe::to_wstring(
GetStringTableEntry_(xstr_ptr, ach_data->label_id, xstr_head->count)); GetStringTableEntry_(xstr_ptr, ach_data->label_id, xstr_head->count));
@ -321,6 +322,54 @@ uint32_t GpdFile::GetAchievements(
return ach_count; return ach_count;
} }
bool GpdFile::GetSetting(X_XDBF_SETTING_ID id, Setting* dest) {
for (size_t i = 0; i < entries_.size(); i++) {
auto* entry = (Entry*)&entries_[i];
if (entry->info.section != static_cast<uint16_t>(GpdSection::kSetting) ||
entry->info.id != (uint32_t)id) {
continue;
}
auto* setting_data =
reinterpret_cast<const X_XDBF_GPD_SETTING*>(entry->data.data());
if (dest) {
dest->ReadGPD(setting_data);
}
return true;
}
return false;
}
uint32_t GpdFile::GetSettings(std::vector<Setting>* settings) const {
uint32_t count = 0;
for (size_t i = 0; i < entries_.size(); i++) {
auto* entry = (Entry*)&entries_[i];
if (entry->info.section != static_cast<uint16_t>(GpdSection::kSetting)) {
continue;
}
if (entry->info.id == 0x100000000 || entry->info.id == 0x200000000) {
continue; // achievement sync data, ignore it
}
count++;
if (settings) {
auto* setting_data =
reinterpret_cast<const X_XDBF_GPD_SETTING*>(entry->data.data());
Setting setting;
setting.ReadGPD(setting_data);
settings->push_back(setting);
}
}
return count;
}
bool GpdFile::GetTitle(uint32_t title_id, TitlePlayed* dest) { bool GpdFile::GetTitle(uint32_t title_id, TitlePlayed* dest) {
for (size_t i = 0; i < entries_.size(); i++) { for (size_t i = 0; i < entries_.size(); i++) {
auto* entry = (Entry*)&entries_[i]; auto* entry = (Entry*)&entries_[i];
@ -408,6 +457,36 @@ bool GpdFile::UpdateAchievement(const Achievement& ach) {
return UpdateEntry(ent); return UpdateEntry(ent);
} }
bool GpdFile::UpdateSetting(const Setting& setting) {
Entry ent;
ent.info.section = static_cast<uint16_t>(GpdSection::kSetting);
ent.info.id = setting.id;
// calculate entry size...
size_t est_size = sizeof(X_XDBF_GPD_SETTING);
est_size += setting.extraData.size();
ent.data.resize(est_size);
memset(ent.data.data(), 0, est_size);
auto* setting_data = reinterpret_cast<X_XDBF_GPD_SETTING*>(ent.data.data());
setting_data->setting_id = setting.id;
memcpy(&setting_data->value, &setting.value, sizeof(X_XUSER_DATA));
if (setting.value.type == X_XUSER_DATA_TYPE::kBinary) {
setting_data->value.binary.cbData = (uint32_t)setting.extraData.size();
// todo: check size against size stored inside ID!
memcpy(&setting_data[1], setting.extraData.data(),
setting.extraData.size());
} else if (setting.value.type == X_XUSER_DATA_TYPE::kUnicode) {
setting_data->value.string.cbData = (uint32_t)setting.extraData.size();
// todo: check size against size stored inside ID!
memcpy(&setting_data[1], setting.extraData.data(),
setting.extraData.size());
}
return UpdateEntry(ent);
}
bool GpdFile::UpdateTitle(const TitlePlayed& title) { bool GpdFile::UpdateTitle(const TitlePlayed& title) {
Entry ent; Entry ent;
ent.info.section = static_cast<uint16_t>(GpdSection::kTitle); ent.info.section = static_cast<uint16_t>(GpdSection::kTitle);

View File

@ -224,6 +224,103 @@ struct Achievement {
} }
}; };
struct Setting {
X_XDBF_SETTING_ID id = (X_XDBF_SETTING_ID)0;
X_XUSER_DATA value;
std::vector<uint8_t> extraData;
Setting() { value.type = X_XUSER_DATA_TYPE::kNull; }
Setting(X_XDBF_SETTING_ID id, uint32_t value) : id(id) { Value(value); }
Setting(X_XDBF_SETTING_ID id, uint64_t value) : id(id) { Value(value); }
Setting(X_XDBF_SETTING_ID id, float value) : id(id) { Value(value); }
Setting(X_XDBF_SETTING_ID id, const std::wstring& value) : id(id) {
Value(value);
}
Setting(X_XDBF_SETTING_ID id, const std::initializer_list<uint8_t>& value)
: id(id), extraData(value) {
this->value.type = X_XUSER_DATA_TYPE::kBinary;
}
bool IsTitleSpecific() const {
return id == XPROFILE_TITLE_SPECIFIC1 || id == XPROFILE_TITLE_SPECIFIC2 ||
id == XPROFILE_TITLE_SPECIFIC3;
}
void ReadGPD(const X_XDBF_GPD_SETTING* src) {
id = src->setting_id;
memcpy(&value, &src->value, sizeof(X_XUSER_DATA));
if (value.type == X_XUSER_DATA_TYPE::kBinary) {
extraData.resize(src->value.binary.cbData);
memcpy(extraData.data(), (uint8_t*)&src[1], src->value.binary.cbData);
} else if (value.type == X_XUSER_DATA_TYPE::kUnicode) {
extraData.resize(src->value.string.cbData);
memcpy(extraData.data(), (uint8_t*)&src[1], src->value.string.cbData);
}
}
void Value(uint32_t new_value) {
value.type = X_XUSER_DATA_TYPE::kInt32;
assert(XPROFILEID_TYPE(id) == value.type);
value.nData = new_value;
extraData.clear();
}
void Value(uint64_t new_value) {
value.type = X_XUSER_DATA_TYPE::kInt64;
if (XPROFILEID_TYPE(id) == X_XUSER_DATA_TYPE::kDateTime) {
value.type = X_XUSER_DATA_TYPE::kDateTime;
}
assert(XPROFILEID_TYPE(id) == value.type);
value.i64Data = new_value;
extraData.clear();
}
void Value(float new_value) {
value.type = X_XUSER_DATA_TYPE::kFloat;
assert(XPROFILEID_TYPE(id) == value.type);
value.fData = new_value;
extraData.clear();
}
void Value(double new_value) {
value.type = X_XUSER_DATA_TYPE::kDouble;
assert(XPROFILEID_TYPE(id) == value.type);
value.dblData = new_value;
extraData.clear();
}
void Value(const std::wstring& new_value) {
value.type = X_XUSER_DATA_TYPE::kUnicode;
assert(XPROFILEID_TYPE(id) == value.type);
value.i64Data = 0;
value.string.cbData =
(uint32_t)((new_value.length() + 1) * sizeof(wchar_t));
extraData.resize(value.string.cbData);
xe::copy_and_swap<wchar_t>((wchar_t*)extraData.data(), new_value.c_str(),
new_value.length());
*(wchar_t*)(extraData.data() + value.string.cbData - 2) =
0; // null-terminate
}
std::wstring ValueString() {
assert(value.type == X_XUSER_DATA_TYPE::kUnicode);
std::vector<uint8_t> swapped;
swapped.resize(extraData.size());
xe::copy_and_swap<wchar_t>((wchar_t*)swapped.data(),
(wchar_t*)extraData.data(),
extraData.size() / sizeof(wchar_t));
return std::wstring((wchar_t*)swapped.data());
}
};
struct Entry { struct Entry {
X_XDBF_ENTRY info; X_XDBF_ENTRY info;
std::vector<uint8_t> data; std::vector<uint8_t> data;
@ -273,13 +370,19 @@ class GpdFile : public XdbfFile {
bool GetAchievement(uint16_t id, Achievement* dest); bool GetAchievement(uint16_t id, Achievement* dest);
uint32_t GetAchievements(std::vector<Achievement>* achievements) const; uint32_t GetAchievements(std::vector<Achievement>* achievements) const;
bool GetSetting(X_XDBF_SETTING_ID id, Setting* dest);
uint32_t GetSettings(std::vector<Setting>* settings) const;
bool GetTitle(uint32_t title_id, TitlePlayed* title); bool GetTitle(uint32_t title_id, TitlePlayed* title);
uint32_t GetTitles(std::vector<TitlePlayed>* titles) const; uint32_t GetTitles(std::vector<TitlePlayed>* titles) const;
// Updates (or adds) an achievement // Updates/adds an achievement
bool UpdateAchievement(const Achievement& ach); bool UpdateAchievement(const Achievement& ach);
// Updates (or adds) a title // Updates/adds a setting
bool UpdateSetting(const Setting& setting);
// Updates/adds a title
bool UpdateTitle(const TitlePlayed& title); bool UpdateTitle(const TitlePlayed& title);
uint32_t GetTitleId() { return title_id_; } uint32_t GetTitleId() { return title_id_; }