[XAM] Update profile setting code to use new xdbf::Setting struct
This commit is contained in:
parent
626467951d
commit
fc78fa92cf
|
@ -106,78 +106,6 @@ UserProfile::UserProfile() : dash_gpd_(kDashboardID) {
|
|||
|
||||
// Try loading profile GPD files...
|
||||
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() {
|
||||
|
@ -519,86 +447,15 @@ bool UserProfile::UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data) {
|
|||
return ret_val;
|
||||
}
|
||||
|
||||
void UserProfile::AddSetting(std::unique_ptr<Setting> setting) {
|
||||
Setting* previous_setting = setting.get();
|
||||
std::swap(settings_[setting->setting_id], previous_setting);
|
||||
|
||||
if (setting->is_set && setting->is_title_specific()) {
|
||||
SaveSetting(setting.get());
|
||||
bool UserProfile::AddSettingIfNotExist(xdbf::Setting& setting) {
|
||||
if (dash_gpd_.GetSetting(setting.id, nullptr)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (previous_setting) {
|
||||
// replace: swap out the old setting from the owning list
|
||||
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");
|
||||
if (setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary &&
|
||||
!setting.extraData.size()) {
|
||||
setting.extraData.resize(XPROFILEID_SIZE(setting.id));
|
||||
}
|
||||
return dash_gpd_.UpdateSetting(setting);
|
||||
}
|
||||
|
||||
xdbf::GpdFile* UserProfile::GetDashboardGpd() { return &dash_gpd_; }
|
||||
|
@ -620,18 +477,6 @@ xdbf::SpaFile* UserProfile::GetTitleSpa(uint32_t title_id) {
|
|||
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 kernel
|
||||
} // namespace xe
|
||||
|
|
|
@ -132,180 +132,6 @@ struct X_XAMACCOUNTINFO {
|
|||
|
||||
class UserProfile {
|
||||
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,
|
||||
bool devkit = false);
|
||||
|
||||
|
@ -317,11 +143,6 @@ class UserProfile {
|
|||
uint64_t xuid() const { return account_.xuid_online; }
|
||||
std::string name() const { return account_.GetGamertagString(); }
|
||||
// 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* GetTitleGpd(uint32_t title_id = -1);
|
||||
|
@ -337,12 +158,9 @@ class UserProfile {
|
|||
void LoadProfile();
|
||||
bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data);
|
||||
|
||||
X_XAMACCOUNTINFO account_;
|
||||
std::vector<std::unique_ptr<Setting>> setting_list_;
|
||||
std::unordered_map<uint32_t, Setting*> settings_;
|
||||
bool AddSettingIfNotExist(xdbf::Setting& setting);
|
||||
|
||||
void LoadSetting(UserProfile::Setting*);
|
||||
void SaveSetting(UserProfile::Setting*);
|
||||
X_XAMACCOUNTINFO account_;
|
||||
|
||||
std::unordered_map<uint32_t, xdbf::GpdFile> title_gpds_;
|
||||
xdbf::GpdFile dash_gpd_;
|
||||
|
|
|
@ -219,25 +219,26 @@ static_assert_size(X_USER_READ_PROFILE_SETTINGS, 8);
|
|||
|
||||
typedef struct {
|
||||
xe::be<uint32_t> from;
|
||||
xe::be<uint32_t> unk04;
|
||||
union {
|
||||
xe::be<uint32_t> user_index;
|
||||
xe::be<uint32_t> unk0C;
|
||||
xe::be<uint32_t> setting_id;
|
||||
xe::be<uint32_t> unk14;
|
||||
uint8_t setting_data[16];
|
||||
xe::be<uint64_t> user_xuid;
|
||||
} user;
|
||||
xdbf::X_XDBF_GPD_SETTING setting;
|
||||
} X_USER_READ_PROFILE_SETTING;
|
||||
static_assert_size(X_USER_READ_PROFILE_SETTING, 40);
|
||||
|
||||
// https://github.com/oukiar/freestyledash/blob/master/Freestyle/Tools/Generic/xboxtools.cpp
|
||||
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,
|
||||
lpvoid_t buffer_ptr, dword_t overlapped_ptr) {
|
||||
uint32_t buffer_size =
|
||||
!buffer_size_ptr ? 0u : static_cast<uint32_t>(*buffer_size_ptr);
|
||||
|
||||
assert_zero(unk_0);
|
||||
assert_zero(unk_1);
|
||||
uint64_t xuid = 0;
|
||||
if (num_xuids && xuids) {
|
||||
xuid = *xuids;
|
||||
}
|
||||
|
||||
// TODO(gibbed): why is this a thing?
|
||||
uint32_t actual_user_index = user_index;
|
||||
|
@ -269,32 +270,17 @@ dword_result_t XamUserReadProfileSettings(
|
|||
|
||||
// Compute required extra size.
|
||||
uint32_t size_needed = base_size_needed;
|
||||
bool any_missing = false;
|
||||
for (uint32_t n = 0; n < setting_count; ++n) {
|
||||
uint32_t setting_id = setting_ids[n];
|
||||
auto setting = user_profile->GetSetting(setting_id);
|
||||
if (setting) {
|
||||
if (setting->is_set) {
|
||||
auto extra_size = static_cast<uint32_t>(setting->extra_size());
|
||||
size_needed += extra_size;
|
||||
}
|
||||
auto setting_id = (xdbf::X_XDBF_SETTING_ID)(uint32_t)setting_ids[n];
|
||||
xdbf::Setting setting;
|
||||
if (user_profile->GetDashboardGpd()->GetSetting(setting_id, &setting)) {
|
||||
size_needed += (uint32_t)setting.extraData.size();
|
||||
} else {
|
||||
any_missing = true;
|
||||
XELOGE("XamUserReadProfileSettings requested unimplemented setting %.8X",
|
||||
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;
|
||||
if (!buffer_ptr || buffer_size < size_needed) {
|
||||
if (overlapped_ptr) {
|
||||
|
@ -312,27 +298,60 @@ dword_result_t XamUserReadProfileSettings(
|
|||
auto out_setting =
|
||||
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) {
|
||||
uint32_t setting_id = setting_ids[n];
|
||||
auto setting = user_profile->GetSetting(setting_id);
|
||||
auto setting_id = (xdbf::X_XDBF_SETTING_ID)(uint32_t)setting_ids[n];
|
||||
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));
|
||||
out_setting->from =
|
||||
!setting || !setting->is_set ? 0 : setting->is_title_specific() ? 2 : 1;
|
||||
out_setting->user_index = actual_user_index;
|
||||
out_setting->setting_id = setting_id;
|
||||
out_setting->from = !exists ? 0 : setting.IsTitleSpecific() ? 2 : 1;
|
||||
out_setting->setting.setting_id = setting_id;
|
||||
|
||||
if (setting && setting->is_set) {
|
||||
buffer_offset =
|
||||
setting->Append(&out_setting->setting_data[0], buffer_ptr,
|
||||
buffer_ptr.guest_address(), buffer_offset);
|
||||
if (num_xuids && xuids) {
|
||||
out_setting->user.user_xuid = xuid;
|
||||
} else {
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -347,29 +366,17 @@ DECLARE_XAM_EXPORT1(XamUserReadProfileSettings, kUserProfiles, kImplemented);
|
|||
|
||||
typedef struct {
|
||||
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 {
|
||||
struct {
|
||||
xe::be<uint32_t> length;
|
||||
xe::be<uint32_t> ptr;
|
||||
} binary;
|
||||
};
|
||||
xe::be<uint32_t> user_index;
|
||||
xe::be<uint64_t> user_xuid;
|
||||
} user;
|
||||
|
||||
xdbf::X_XDBF_GPD_SETTING setting;
|
||||
} X_USER_WRITE_PROFILE_SETTING;
|
||||
static_assert_size(X_USER_WRITE_PROFILE_SETTING, 0x28);
|
||||
|
||||
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) {
|
||||
if (!setting_count || !settings) {
|
||||
if (overlapped_ptr) {
|
||||
|
@ -387,58 +394,72 @@ dword_result_t XamUserWriteProfileSettings(
|
|||
|
||||
if (actual_user_index) {
|
||||
// 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;
|
||||
}
|
||||
|
||||
// Update and save settings.
|
||||
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) {
|
||||
const X_USER_WRITE_PROFILE_SETTING& settings_data = settings[n];
|
||||
XELOGD(
|
||||
"XamUserWriteProfileSettings: setting index [%d]:"
|
||||
" from=%d setting_id=%.8X data.type=%d",
|
||||
n, (uint32_t)settings_data.from, (uint32_t)settings_data.setting_id,
|
||||
settings_data.type);
|
||||
n, (uint32_t)settings_data.from,
|
||||
(uint32_t)settings_data.setting.setting_id,
|
||||
settings_data.setting.value.type);
|
||||
|
||||
xam::UserProfile::Setting::Type settingType =
|
||||
static_cast<xam::UserProfile::Setting::Type>(settings_data.type);
|
||||
xdbf::Setting setting;
|
||||
setting.id = settings_data.setting.setting_id;
|
||||
setting.value.type = settings_data.setting.value.type;
|
||||
|
||||
switch (settingType) {
|
||||
case UserProfile::Setting::Type::CONTENT:
|
||||
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;
|
||||
// Retrieve any existing setting data if we can
|
||||
gpd->GetSetting(setting.id, &setting);
|
||||
|
||||
if (settings_data.binary.ptr) {
|
||||
// Copy provided data
|
||||
data_vec.resize(settings_data_len);
|
||||
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);
|
||||
// ... and then overwrite it
|
||||
memcpy(&setting.value, &settings_data.setting.value,
|
||||
sizeof(xdbf::X_XUSER_DATA));
|
||||
|
||||
if (settings_data.setting.value.type == xdbf::X_XUSER_DATA_TYPE::kBinary) {
|
||||
if (settings_data.setting.value.binary.pbData) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
user_profile->AddSetting(
|
||||
std::make_unique<xam::UserProfile::BinarySetting>(
|
||||
settings_data.setting_id, data_vec));
|
||||
|
||||
} 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:
|
||||
default:
|
||||
|
||||
XELOGE("XamUserWriteProfileSettings: Unimplemented data type %d",
|
||||
settingType);
|
||||
break;
|
||||
};
|
||||
gpd->UpdateSetting(setting);
|
||||
}
|
||||
|
||||
if (overlapped_ptr) {
|
||||
|
@ -710,13 +731,11 @@ dword_result_t XamUserCreateTitlesPlayedEnumerator(
|
|||
|
||||
for (auto title : titles) {
|
||||
// For some reason dashboard gpd stores info about itself
|
||||
if (title.title_id == kDashboardID)
|
||||
continue;
|
||||
if (title.title_id == kDashboardID) continue;
|
||||
|
||||
// TODO: Look for better check to provide information about demo title
|
||||
// or system title
|
||||
if (!title.gamerscore_total || !title.achievements_possible)
|
||||
continue;
|
||||
if (!title.gamerscore_total || !title.achievements_possible) continue;
|
||||
|
||||
auto* details = (xdbf::X_XDBF_GPD_TITLEPLAYED*)e->AppendItem();
|
||||
title.WriteGPD(details);
|
||||
|
|
|
@ -216,6 +216,7 @@ uint32_t SpaFile::GetAchievements(
|
|||
ach.image_id = ach_data->image_id;
|
||||
ach.gamerscore = ach_data->gamerscore;
|
||||
ach.flags = ach_data->flags;
|
||||
ach.flags |= static_cast<uint32_t>(AchievementPlatform::kX360);
|
||||
|
||||
ach.label = xe::to_wstring(
|
||||
GetStringTableEntry_(xstr_ptr, ach_data->label_id, xstr_head->count));
|
||||
|
@ -321,6 +322,54 @@ uint32_t GpdFile::GetAchievements(
|
|||
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) {
|
||||
for (size_t i = 0; i < entries_.size(); i++) {
|
||||
auto* entry = (Entry*)&entries_[i];
|
||||
|
@ -408,6 +457,36 @@ bool GpdFile::UpdateAchievement(const Achievement& ach) {
|
|||
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) {
|
||||
Entry ent;
|
||||
ent.info.section = static_cast<uint16_t>(GpdSection::kTitle);
|
||||
|
|
|
@ -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 {
|
||||
X_XDBF_ENTRY info;
|
||||
std::vector<uint8_t> data;
|
||||
|
@ -273,13 +370,19 @@ class GpdFile : public XdbfFile {
|
|||
bool GetAchievement(uint16_t id, Achievement* dest);
|
||||
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);
|
||||
uint32_t GetTitles(std::vector<TitlePlayed>* titles) const;
|
||||
|
||||
// Updates (or adds) an achievement
|
||||
// Updates/adds an achievement
|
||||
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);
|
||||
|
||||
uint32_t GetTitleId() { return title_id_; }
|
||||
|
|
Loading…
Reference in New Issue