Persist title-specific profile content

This commit is contained in:
Unknown 2018-06-09 16:08:57 -07:00 committed by Jeremy517
parent 5f16e46282
commit 86443dd28a
5 changed files with 99 additions and 3 deletions

View File

@ -23,6 +23,8 @@ namespace xam {
static const wchar_t* kThumbnailFileName = L"__thumbnail.png";
static const wchar_t* kGameUserContentDirName = L"profile";
static int content_device_id_ = 0;
ContentPackage::ContentPackage(KernelState* kernel_state, std::string root_name,
@ -252,6 +254,20 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_DATA& data) {
}
}
std::wstring ContentManager::ResolveGameUserContentPath() {
wchar_t title_id[9] = L"00000000";
std::swprintf(title_id, 9, L"%.8X", kernel_state_->title_id());
auto user_name = xe::to_wstring(kernel_state_->user_profile()->name());
// Per-game per-profile data location:
// content_root/title_id/profile/user_name
auto package_root = xe::join_paths(
root_path_,
xe::join_paths(title_id,
xe::join_paths(kGameUserContentDirName, user_name)));
return package_root + xe::kWPathSeparator;
}
} // namespace xam
} // namespace kernel
} // namespace xe

View File

@ -84,6 +84,7 @@ class ContentManager {
X_RESULT SetContentThumbnail(const XCONTENT_DATA& data,
std::vector<uint8_t> buffer);
X_RESULT DeleteContent(const XCONTENT_DATA& data);
std::wstring ResolveGameUserContentPath();
private:
std::wstring ResolvePackageRoot(uint32_t content_type);

View File

@ -7,6 +7,10 @@
******************************************************************************
*/
#include <sstream>
#include "xenia/kernel/kernel_state.h"
#include "xenia/kernel/util/shim_utils.h"
#include "xenia/kernel/xam/user_profile.h"
namespace xe {
@ -87,6 +91,10 @@ 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());
}
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();
@ -107,7 +115,58 @@ UserProfile::Setting* UserProfile::GetSetting(uint32_t setting_id) {
if (it == settings_.end()) {
return nullptr;
}
return it->second;
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");
}
}
} // namespace xam

View File

@ -25,7 +25,7 @@ class UserProfile {
public:
struct Setting {
enum class Type {
UNKNOWN = 0,
CONTENT = 0,
INT32 = 1,
INT64 = 2,
DOUBLE = 3,
@ -48,8 +48,13 @@ class UserProfile {
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) {}
: 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) {
@ -57,6 +62,10 @@ class UserProfile {
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:
@ -167,6 +176,13 @@ class UserProfile {
}
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)
@ -195,6 +211,9 @@ class UserProfile {
std::string name_;
std::vector<std::unique_ptr<Setting>> setting_list_;
std::unordered_map<uint32_t, Setting*> settings_;
void LoadSetting(UserProfile::Setting*);
void SaveSetting(UserProfile::Setting*);
};
} // namespace xam

View File

@ -290,6 +290,7 @@ dword_result_t XamUserWriteProfileSettings(
static_cast<xam::UserProfile::Setting::Type>(settings_data.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);