Merge pull request #1178 from Jeremy517/master
Persist title-specific profile content
This commit is contained in:
commit
b7a4abc6f6
|
@ -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
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -285,6 +285,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);
|
||||
|
|
Loading…
Reference in New Issue