[XAM] Allow custom profiles - This breaks current content visibility
This includes creating many profiles
This commit is contained in:
parent
1ae524a8d5
commit
bb5db2e3da
|
@ -936,13 +936,22 @@ void EmulatorWindow::ShowContentDirectory() {
|
||||||
std::filesystem::path target_path;
|
std::filesystem::path target_path;
|
||||||
|
|
||||||
auto content_root = emulator_->content_root();
|
auto content_root = emulator_->content_root();
|
||||||
if (!emulator_->is_title_open() || !emulator_->kernel_state()) {
|
if (!emulator_->kernel_state()) {
|
||||||
target_path = content_root;
|
target_path = content_root;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!emulator_->is_title_open()) {
|
||||||
|
auto xuid_str = fmt::format(
|
||||||
|
"{:016X}", emulator_->kernel_state()->user_profile((uint32_t)0)->xuid());
|
||||||
|
auto package_root = content_root / xuid_str;
|
||||||
|
target_path = package_root;
|
||||||
} else {
|
} else {
|
||||||
// TODO(gibbed): expose this via ContentManager?
|
// TODO(gibbed): expose this via ContentManager?
|
||||||
|
auto xuid_str = fmt::format(
|
||||||
|
"{:016X}", emulator_->kernel_state()->user_profile((uint32_t)0)->xuid());
|
||||||
auto title_id =
|
auto title_id =
|
||||||
fmt::format("{:08X}", emulator_->kernel_state()->title_id());
|
fmt::format("{:08X}", emulator_->kernel_state()->title_id());
|
||||||
auto package_root = content_root / title_id;
|
auto package_root = content_root / xuid_str / title_id;
|
||||||
target_path = package_root;
|
target_path = package_root;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -368,13 +368,20 @@ X_STATUS Emulator::InstallContentPackage(const std::filesystem::path& path) {
|
||||||
return X_STATUS_INVALID_PARAMETER;
|
return X_STATUS_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto xuid_str = fmt::format("{:016X}", 0);
|
||||||
|
if (device->content_type() == XContentType::kSavedGame) {
|
||||||
|
xuid_str = fmt::format("{:016X}",
|
||||||
|
kernel_state()->user_profile((uint32_t)0)->xuid());
|
||||||
|
}
|
||||||
|
|
||||||
std::filesystem::path installation_path =
|
std::filesystem::path installation_path =
|
||||||
content_root() / fmt::format("{:08X}", device->title_id()) /
|
content_root() / xuid_str / fmt::format("{:08X}", device->title_id()) /
|
||||||
fmt::format("{:08X}", device->content_type()) / path.filename();
|
fmt::format("{:08X}", device->content_type()) / path.filename();
|
||||||
|
|
||||||
std::filesystem::path header_path =
|
std::filesystem::path header_path =
|
||||||
content_root() / fmt::format("{:08X}", device->title_id()) / "Headers" /
|
content_root() / xuid_str / fmt::format("{:08X}", device->title_id()) /
|
||||||
fmt::format("{:08X}", device->content_type()) / path.filename();
|
"Headers" / fmt::format("{:08X}", device->content_type()) /
|
||||||
|
path.filename();
|
||||||
|
|
||||||
if (std::filesystem::exists(installation_path)) {
|
if (std::filesystem::exists(installation_path)) {
|
||||||
// TODO(Gliniak): Popup
|
// TODO(Gliniak): Popup
|
||||||
|
@ -631,34 +638,40 @@ bool Emulator::ExceptionCallback(Exception* ex) {
|
||||||
std::string crash_msg;
|
std::string crash_msg;
|
||||||
crash_msg.append("==== CRASH DUMP ====\n");
|
crash_msg.append("==== CRASH DUMP ====\n");
|
||||||
crash_msg.append(fmt::format("Thread ID (Host: 0x{:08X} / Guest: 0x{:08X})\n",
|
crash_msg.append(fmt::format("Thread ID (Host: 0x{:08X} / Guest: 0x{:08X})\n",
|
||||||
current_thread->thread()->system_id(), current_thread->thread_id()));
|
current_thread->thread()->system_id(),
|
||||||
crash_msg.append(fmt::format("Thread Handle: 0x{:08X}\n", current_thread->handle()));
|
current_thread->thread_id()));
|
||||||
crash_msg.append(fmt::format("PC: 0x{:08X}\n",
|
crash_msg.append(
|
||||||
guest_function->MapMachineCodeToGuestAddress(ex->pc())));
|
fmt::format("Thread Handle: 0x{:08X}\n", current_thread->handle()));
|
||||||
|
crash_msg.append(
|
||||||
|
fmt::format("PC: 0x{:08X}\n",
|
||||||
|
guest_function->MapMachineCodeToGuestAddress(ex->pc())));
|
||||||
crash_msg.append("Registers:\n");
|
crash_msg.append("Registers:\n");
|
||||||
for (int i = 0; i < 32; i++) {
|
for (int i = 0; i < 32; i++) {
|
||||||
crash_msg.append(fmt::format(" r{:<3} = {:016X}\n", i, context->r[i]));
|
crash_msg.append(fmt::format(" r{:<3} = {:016X}\n", i, context->r[i]));
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 32; i++) {
|
for (int i = 0; i < 32; i++) {
|
||||||
crash_msg.append(fmt::format(" f{:<3} = {:016X} = (double){} = (float){}\n", i,
|
crash_msg.append(fmt::format(" f{:<3} = {:016X} = (double){} = (float){}\n",
|
||||||
*reinterpret_cast<uint64_t*>(&context->f[i]), context->f[i],
|
i,
|
||||||
*(float*)&context->f[i]));
|
*reinterpret_cast<uint64_t*>(&context->f[i]),
|
||||||
|
context->f[i], *(float*)&context->f[i]));
|
||||||
}
|
}
|
||||||
for (int i = 0; i < 128; i++) {
|
for (int i = 0; i < 128; i++) {
|
||||||
crash_msg.append(fmt::format(" v{:<3} = [0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X}]\n", i,
|
crash_msg.append(
|
||||||
context->v[i].u32[0], context->v[i].u32[1], context->v[i].u32[2],
|
fmt::format(" v{:<3} = [0x{:08X}, 0x{:08X}, 0x{:08X}, 0x{:08X}]\n", i,
|
||||||
context->v[i].u32[3]));
|
context->v[i].u32[0], context->v[i].u32[1],
|
||||||
|
context->v[i].u32[2], context->v[i].u32[3]));
|
||||||
}
|
}
|
||||||
XELOGE("{}", crash_msg);
|
XELOGE("{}", crash_msg);
|
||||||
std::string crash_dlg = fmt::format(
|
std::string crash_dlg = fmt::format(
|
||||||
"The guest has crashed.\n\n"
|
"The guest has crashed.\n\n"
|
||||||
"Xenia has now paused itself.\n\n"
|
"Xenia has now paused itself.\n\n"
|
||||||
"{}", crash_msg);
|
"{}",
|
||||||
|
crash_msg);
|
||||||
// Display a dialog telling the user the guest has crashed.
|
// Display a dialog telling the user the guest has crashed.
|
||||||
if (display_window_ && imgui_drawer_) {
|
if (display_window_ && imgui_drawer_) {
|
||||||
display_window_->app_context().CallInUIThreadSynchronous([this, &crash_dlg]() {
|
display_window_->app_context().CallInUIThreadSynchronous([this,
|
||||||
xe::ui::ImGuiDialog::ShowMessageBox(
|
&crash_dlg]() {
|
||||||
imgui_drawer_, "Uh-oh!", crash_dlg);
|
xe::ui::ImGuiDialog::ShowMessageBox(imgui_drawer_, "Uh-oh!", crash_dlg);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -486,7 +486,7 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref<UserModule> module) {
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<xam::XCONTENT_AGGREGATE_DATA> tu_list =
|
std::vector<xam::XCONTENT_AGGREGATE_DATA> tu_list =
|
||||||
content_manager()->ListContent(1, xe::XContentType::kInstaller,
|
content_manager()->ListContent(1, 0, xe::XContentType::kInstaller,
|
||||||
module->title_id());
|
module->title_id());
|
||||||
|
|
||||||
if (tu_list.empty()) {
|
if (tu_list.empty()) {
|
||||||
|
@ -500,7 +500,7 @@ X_RESULT KernelState::ApplyTitleUpdate(const object_ref<UserModule> module) {
|
||||||
// TODO(Gliniak): Support for selecting from multiple TUs
|
// TODO(Gliniak): Support for selecting from multiple TUs
|
||||||
const xam::XCONTENT_AGGREGATE_DATA& title_update = tu_list.front();
|
const xam::XCONTENT_AGGREGATE_DATA& title_update = tu_list.front();
|
||||||
X_RESULT open_status =
|
X_RESULT open_status =
|
||||||
content_manager()->OpenContent("UPDATE", title_update, disc_number);
|
content_manager()->OpenContent("UPDATE", 0, title_update, disc_number);
|
||||||
|
|
||||||
// Use the corresponding patch for the launch module
|
// Use the corresponding patch for the launch module
|
||||||
std::filesystem::path patch_xexp = fmt::format("{0}.xexp", module->name());
|
std::filesystem::path patch_xexp = fmt::format("{0}.xexp", module->name());
|
||||||
|
|
|
@ -128,6 +128,13 @@ class KernelState {
|
||||||
}
|
}
|
||||||
|
|
||||||
xam::UserProfile* user_profile(uint32_t index) const {
|
xam::UserProfile* user_profile(uint32_t index) const {
|
||||||
|
if (index == 0xFF) {
|
||||||
|
index = 0;
|
||||||
|
}
|
||||||
|
if (index == 0xFE) {
|
||||||
|
index = recently_used_profile_;
|
||||||
|
}
|
||||||
|
|
||||||
if (!IsUserSignedIn(index)) {
|
if (!IsUserSignedIn(index)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -282,6 +289,8 @@ class KernelState {
|
||||||
std::condition_variable_any dispatch_cond_;
|
std::condition_variable_any dispatch_cond_;
|
||||||
std::list<std::function<void()>> dispatch_queue_;
|
std::list<std::function<void()>> dispatch_queue_;
|
||||||
|
|
||||||
|
// This should be in ProfileManager, but it is what it is
|
||||||
|
uint8_t recently_used_profile_ = 0;
|
||||||
BitMap tls_bitmap_;
|
BitMap tls_bitmap_;
|
||||||
uint32_t ke_timestamp_bundle_ptr_ = 0;
|
uint32_t ke_timestamp_bundle_ptr_ = 0;
|
||||||
std::unique_ptr<xe::threading::HighResolutionTimer> timestamp_timer_;
|
std::unique_ptr<xe::threading::HighResolutionTimer> timestamp_timer_;
|
||||||
|
|
|
@ -0,0 +1 @@
|
||||||
|
#include "profile_manager.h"
|
|
@ -0,0 +1,69 @@
|
||||||
|
/**
|
||||||
|
******************************************************************************
|
||||||
|
* Xenia : Xbox 360 Emulator Research Project *
|
||||||
|
******************************************************************************
|
||||||
|
* Copyright 2023 Ben Vanik. All rights reserved. *
|
||||||
|
* Released under the BSD license - see LICENSE in the root for more details. *
|
||||||
|
******************************************************************************
|
||||||
|
*/
|
||||||
|
|
||||||
|
#ifndef XENIA_KERNEL_PROFILE_MANAGER_H_
|
||||||
|
#define XENIA_KERNEL_PROFILE_MANAGER_H_
|
||||||
|
|
||||||
|
#include <random>
|
||||||
|
#include <map>
|
||||||
|
#include <vector>
|
||||||
|
#include "xenia/kernel/xam/user_profile.h"
|
||||||
|
|
||||||
|
namespace xe {
|
||||||
|
namespace kernel {
|
||||||
|
|
||||||
|
class ProfileIOHandler {
|
||||||
|
public:
|
||||||
|
virtual bool LoadProfile(uint64_t xuid);
|
||||||
|
virtual bool SaveProfile(uint64_t xuid);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProfileIOHandlerToml : public ProfileIOHandler {
|
||||||
|
public:
|
||||||
|
bool LoadProfile(uint64_t xuid);
|
||||||
|
bool SaveProfile(uint64_t xuid);
|
||||||
|
};
|
||||||
|
|
||||||
|
class ProfileManager {
|
||||||
|
public:
|
||||||
|
|
||||||
|
ProfileManager(){};
|
||||||
|
~ProfileManager(){};
|
||||||
|
|
||||||
|
void CreateProfile();
|
||||||
|
void DeleteProfile(uint64_t xuid);
|
||||||
|
|
||||||
|
void Login(uint64_t xuid, uint32_t user_index);
|
||||||
|
void Logout(uint64_t xuid);
|
||||||
|
|
||||||
|
xam::UserProfile* GetProfile(uint32_t user_index);
|
||||||
|
xam::UserProfile* GetProfile(uint64_t xuid);
|
||||||
|
|
||||||
|
private:
|
||||||
|
static uint64_t GenerateXuid() {
|
||||||
|
std::random_device rd;
|
||||||
|
std::mt19937 gen(rd());
|
||||||
|
|
||||||
|
return ((uint64_t)0xE03 << 52) + (gen() % (1 << 31));
|
||||||
|
}
|
||||||
|
|
||||||
|
void LoadProfile(ProfileIOHandler* io, uint64_t xuid);
|
||||||
|
void SaveProfile(ProfileIOHandler* io, uint64_t xuid);
|
||||||
|
|
||||||
|
|
||||||
|
std::vector<uint32_t> profiles_;
|
||||||
|
std::map<uint8_t, xam::UserProfile*> logged_profile_;
|
||||||
|
|
||||||
|
//KernelState* kernel_state_;
|
||||||
|
};
|
||||||
|
|
||||||
|
} // namespace kernel
|
||||||
|
} // namespace xe
|
||||||
|
|
||||||
|
#endif // XENIA_KERNEL_PROFILE_MANAGER_H_
|
|
@ -61,23 +61,27 @@ ContentManager::ContentManager(KernelState* kernel_state,
|
||||||
ContentManager::~ContentManager() = default;
|
ContentManager::~ContentManager() = default;
|
||||||
|
|
||||||
std::filesystem::path ContentManager::ResolvePackageRoot(
|
std::filesystem::path ContentManager::ResolvePackageRoot(
|
||||||
XContentType content_type, uint32_t title_id) {
|
const uint64_t xuid, XContentType content_type, uint32_t title_id) {
|
||||||
if (title_id == kCurrentlyRunningTitleId) {
|
if (title_id == kCurrentlyRunningTitleId) {
|
||||||
title_id = kernel_state_->title_id();
|
title_id = kernel_state_->title_id();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto xuid_str = fmt::format("{:016X}", xuid);
|
||||||
auto title_id_str = fmt::format("{:08X}", title_id);
|
auto title_id_str = fmt::format("{:08X}", title_id);
|
||||||
auto content_type_str = fmt::format("{:08X}", uint32_t(content_type));
|
auto content_type_str = fmt::format("{:08X}", uint32_t(content_type));
|
||||||
|
|
||||||
// Package root path:
|
// Package root path:
|
||||||
// content_root/title_id/content_type/
|
// content_root/title_id/content_type/
|
||||||
return root_path_ / title_id_str / content_type_str;
|
return root_path_ / xuid_str / title_id_str / content_type_str;
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path ContentManager::ResolvePackagePath(
|
std::filesystem::path ContentManager::ResolvePackagePath(
|
||||||
const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) {
|
const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
|
||||||
|
const uint32_t disc_number) {
|
||||||
// Content path:
|
// Content path:
|
||||||
// content_root/title_id/content_type/data_file_name/
|
// content_root/title_id/content_type/data_file_name/
|
||||||
auto package_root = ResolvePackageRoot(data.content_type, data.title_id);
|
auto package_root =
|
||||||
|
ResolvePackageRoot(xuid, data.content_type, data.title_id);
|
||||||
std::string disc_directory = "";
|
std::string disc_directory = "";
|
||||||
std::filesystem::path package_path =
|
std::filesystem::path package_path =
|
||||||
package_root / xe::to_path(data.file_name());
|
package_root / xe::to_path(data.file_name());
|
||||||
|
@ -89,7 +93,8 @@ std::filesystem::path ContentManager::ResolvePackagePath(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
|
std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
|
||||||
uint32_t device_id, XContentType content_type, uint32_t title_id) {
|
uint32_t device_id, const uint64_t xuid, XContentType content_type,
|
||||||
|
uint32_t title_id) {
|
||||||
std::vector<XCONTENT_AGGREGATE_DATA> result;
|
std::vector<XCONTENT_AGGREGATE_DATA> result;
|
||||||
|
|
||||||
if (title_id == kCurrentlyRunningTitleId) {
|
if (title_id == kCurrentlyRunningTitleId) {
|
||||||
|
@ -97,8 +102,8 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search path:
|
// Search path:
|
||||||
// content_root/title_id/type_name/*
|
// content_root/username/title_id/type_name/*
|
||||||
auto package_root = ResolvePackageRoot(content_type, title_id);
|
auto package_root = ResolvePackageRoot(xuid, content_type, title_id);
|
||||||
auto file_infos = xe::filesystem::ListFiles(package_root);
|
auto file_infos = xe::filesystem::ListFiles(package_root);
|
||||||
for (const auto& file_info : file_infos) {
|
for (const auto& file_info : file_infos) {
|
||||||
if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) {
|
if (file_info.type != xe::filesystem::FileInfo::Type::kDirectory) {
|
||||||
|
@ -107,9 +112,9 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
XCONTENT_AGGREGATE_DATA content_data;
|
XCONTENT_AGGREGATE_DATA content_data;
|
||||||
if (XSUCCEEDED(
|
if (XSUCCEEDED(ReadContentHeaderFile(
|
||||||
ReadContentHeaderFile(xe::path_to_utf8(file_info.name) + ".header",
|
xe::path_to_utf8(file_info.name) + ".header", xuid, content_type,
|
||||||
content_type, content_data, title_id))) {
|
content_data, title_id))) {
|
||||||
result.emplace_back(std::move(content_data));
|
result.emplace_back(std::move(content_data));
|
||||||
} else {
|
} else {
|
||||||
content_data.device_id = device_id;
|
content_data.device_id = device_id;
|
||||||
|
@ -124,9 +129,9 @@ std::vector<XCONTENT_AGGREGATE_DATA> ContentManager::ListContent(
|
||||||
}
|
}
|
||||||
|
|
||||||
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
|
std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
|
||||||
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data,
|
const std::string_view root_name, const uint64_t xuid,
|
||||||
const uint32_t disc_number) {
|
const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number) {
|
||||||
auto package_path = ResolvePackagePath(data, disc_number);
|
auto package_path = ResolvePackagePath(xuid, data, disc_number);
|
||||||
if (!std::filesystem::exists(package_path)) {
|
if (!std::filesystem::exists(package_path)) {
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
@ -138,18 +143,24 @@ std::unique_ptr<ContentPackage> ContentManager::ResolvePackage(
|
||||||
return package;
|
return package;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContentManager::ContentExists(const XCONTENT_AGGREGATE_DATA& data) {
|
bool ContentManager::ContentExists(const uint64_t xuid,
|
||||||
auto path = ResolvePackagePath(data);
|
const XCONTENT_AGGREGATE_DATA& data) {
|
||||||
|
auto path = ResolvePackagePath(xuid, data);
|
||||||
return std::filesystem::exists(path);
|
return std::filesystem::exists(path);
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::WriteContentHeaderFile(
|
X_RESULT ContentManager::WriteContentHeaderFile(
|
||||||
const XCONTENT_AGGREGATE_DATA* data) {
|
const uint64_t xuid, const XCONTENT_AGGREGATE_DATA* data) {
|
||||||
|
if (!kernel_state_->IsUserSignedIn(xuid)) {
|
||||||
|
return X_STATUS_INVALID_PARAMETER_1;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xuid_str = fmt::format("{:016X}", xuid);
|
||||||
auto title_id = fmt::format("{:08X}", kernel_state_->title_id());
|
auto title_id = fmt::format("{:08X}", kernel_state_->title_id());
|
||||||
auto content_type =
|
auto content_type =
|
||||||
fmt::format("{:08X}", load_and_swap<uint32_t>(&data->content_type));
|
fmt::format("{:08X}", load_and_swap<uint32_t>(&data->content_type));
|
||||||
auto header_path =
|
auto header_path = root_path_ / xuid_str / title_id /
|
||||||
root_path_ / title_id / kGameContentHeaderDirName / content_type;
|
kGameContentHeaderDirName / content_type;
|
||||||
|
|
||||||
if (!std::filesystem::exists(header_path)) {
|
if (!std::filesystem::exists(header_path)) {
|
||||||
if (!std::filesystem::create_directories(header_path)) {
|
if (!std::filesystem::create_directories(header_path)) {
|
||||||
|
@ -170,6 +181,7 @@ X_RESULT ContentManager::WriteContentHeaderFile(
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name,
|
X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name,
|
||||||
|
const uint64_t xuid,
|
||||||
XContentType content_type,
|
XContentType content_type,
|
||||||
XCONTENT_AGGREGATE_DATA& data,
|
XCONTENT_AGGREGATE_DATA& data,
|
||||||
const uint32_t title_id) {
|
const uint32_t title_id) {
|
||||||
|
@ -178,8 +190,13 @@ X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name,
|
||||||
title_id_str = fmt::format("{:08X}", kernel_state_->title_id());
|
title_id_str = fmt::format("{:08X}", kernel_state_->title_id());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!kernel_state_->IsUserSignedIn(xuid)) {
|
||||||
|
return X_STATUS_NO_SUCH_FILE;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xuid_str = fmt::format("{:016X}", xuid);
|
||||||
auto content_type_directory = fmt::format("{:08X}", content_type);
|
auto content_type_directory = fmt::format("{:08X}", content_type);
|
||||||
auto header_file_path = root_path_ / title_id_str /
|
auto header_file_path = root_path_ / xuid_str / title_id_str /
|
||||||
kGameContentHeaderDirName / content_type_directory /
|
kGameContentHeaderDirName / content_type_directory /
|
||||||
file_name;
|
file_name;
|
||||||
constexpr uint32_t header_size = sizeof(XCONTENT_AGGREGATE_DATA);
|
constexpr uint32_t header_size = sizeof(XCONTENT_AGGREGATE_DATA);
|
||||||
|
@ -206,13 +223,14 @@ X_RESULT ContentManager::ReadContentHeaderFile(const std::string_view file_name,
|
||||||
// usually requires title_id to be provided
|
// usually requires title_id to be provided
|
||||||
// Kinda simple workaround for that, but still assumption
|
// Kinda simple workaround for that, but still assumption
|
||||||
data.title_id = title_id;
|
data.title_id = title_id;
|
||||||
data.unk134 = kernel_state_->user_profile(uint32_t(0))->xuid();
|
data.xuid = xuid;
|
||||||
return X_STATUS_SUCCESS;
|
return X_STATUS_SUCCESS;
|
||||||
}
|
}
|
||||||
return X_STATUS_NO_SUCH_FILE;
|
return X_STATUS_NO_SUCH_FILE;
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::CreateContent(const std::string_view root_name,
|
X_RESULT ContentManager::CreateContent(const std::string_view root_name,
|
||||||
|
const uint64_t xuid,
|
||||||
const XCONTENT_AGGREGATE_DATA& data) {
|
const XCONTENT_AGGREGATE_DATA& data) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
|
@ -221,7 +239,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
|
||||||
return X_ERROR_ALREADY_EXISTS;
|
return X_ERROR_ALREADY_EXISTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
auto package_path = ResolvePackagePath(xuid, data);
|
||||||
if (std::filesystem::exists(package_path)) {
|
if (std::filesystem::exists(package_path)) {
|
||||||
// Exists, must not!
|
// Exists, must not!
|
||||||
return X_ERROR_ALREADY_EXISTS;
|
return X_ERROR_ALREADY_EXISTS;
|
||||||
|
@ -231,7 +249,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
|
||||||
return X_ERROR_ACCESS_DENIED;
|
return X_ERROR_ACCESS_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto package = ResolvePackage(root_name, data);
|
auto package = ResolvePackage(root_name, xuid, data);
|
||||||
assert_not_null(package);
|
assert_not_null(package);
|
||||||
|
|
||||||
open_packages_.insert({string_key::create(root_name), package.release()});
|
open_packages_.insert({string_key::create(root_name), package.release()});
|
||||||
|
@ -240,6 +258,7 @@ X_RESULT ContentManager::CreateContent(const std::string_view root_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::OpenContent(const std::string_view root_name,
|
X_RESULT ContentManager::OpenContent(const std::string_view root_name,
|
||||||
|
const uint64_t xuid,
|
||||||
const XCONTENT_AGGREGATE_DATA& data,
|
const XCONTENT_AGGREGATE_DATA& data,
|
||||||
const uint32_t disc_number) {
|
const uint32_t disc_number) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
@ -249,14 +268,14 @@ X_RESULT ContentManager::OpenContent(const std::string_view root_name,
|
||||||
return X_ERROR_ALREADY_EXISTS;
|
return X_ERROR_ALREADY_EXISTS;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto package_path = ResolvePackagePath(data, disc_number);
|
auto package_path = ResolvePackagePath(xuid, data, disc_number);
|
||||||
if (!std::filesystem::exists(package_path)) {
|
if (!std::filesystem::exists(package_path)) {
|
||||||
// Does not exist, must be created.
|
// Does not exist, must be created.
|
||||||
return X_ERROR_FILE_NOT_FOUND;
|
return X_ERROR_FILE_NOT_FOUND;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Open package.
|
// Open package.
|
||||||
auto package = ResolvePackage(root_name, data, disc_number);
|
auto package = ResolvePackage(root_name, xuid, data, disc_number);
|
||||||
assert_not_null(package);
|
assert_not_null(package);
|
||||||
|
|
||||||
open_packages_.insert({string_key::create(root_name), package.release()});
|
open_packages_.insert({string_key::create(root_name), package.release()});
|
||||||
|
@ -281,9 +300,10 @@ X_RESULT ContentManager::CloseContent(const std::string_view root_name) {
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::GetContentThumbnail(
|
X_RESULT ContentManager::GetContentThumbnail(
|
||||||
const XCONTENT_AGGREGATE_DATA& data, std::vector<uint8_t>* buffer) {
|
const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
|
||||||
|
std::vector<uint8_t>* buffer) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
auto package_path = ResolvePackagePath(data);
|
auto package_path = ResolvePackagePath(xuid, data);
|
||||||
auto thumb_path = package_path / kThumbnailFileName;
|
auto thumb_path = package_path / kThumbnailFileName;
|
||||||
if (std::filesystem::exists(thumb_path)) {
|
if (std::filesystem::exists(thumb_path)) {
|
||||||
auto file = xe::filesystem::OpenFile(thumb_path, "rb");
|
auto file = xe::filesystem::OpenFile(thumb_path, "rb");
|
||||||
|
@ -300,9 +320,10 @@ X_RESULT ContentManager::GetContentThumbnail(
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::SetContentThumbnail(
|
X_RESULT ContentManager::SetContentThumbnail(
|
||||||
const XCONTENT_AGGREGATE_DATA& data, std::vector<uint8_t> buffer) {
|
const uint64_t xuid, const XCONTENT_AGGREGATE_DATA& data,
|
||||||
|
std::vector<uint8_t> buffer) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
auto package_path = ResolvePackagePath(data);
|
auto package_path = ResolvePackagePath(xuid, data);
|
||||||
std::filesystem::create_directories(package_path);
|
std::filesystem::create_directories(package_path);
|
||||||
if (std::filesystem::exists(package_path)) {
|
if (std::filesystem::exists(package_path)) {
|
||||||
auto thumb_path = package_path / kThumbnailFileName;
|
auto thumb_path = package_path / kThumbnailFileName;
|
||||||
|
@ -315,7 +336,8 @@ X_RESULT ContentManager::SetContentThumbnail(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) {
|
X_RESULT ContentManager::DeleteContent(const uint64_t xuid,
|
||||||
|
const XCONTENT_AGGREGATE_DATA& data) {
|
||||||
auto global_lock = global_critical_region_.Acquire();
|
auto global_lock = global_critical_region_.Acquire();
|
||||||
|
|
||||||
if (IsContentOpen(data)) {
|
if (IsContentOpen(data)) {
|
||||||
|
@ -323,7 +345,7 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) {
|
||||||
return X_ERROR_ACCESS_DENIED;
|
return X_ERROR_ACCESS_DENIED;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto package_path = ResolvePackagePath(data);
|
auto package_path = ResolvePackagePath(xuid, data);
|
||||||
if (std::filesystem::remove_all(package_path) > 0) {
|
if (std::filesystem::remove_all(package_path) > 0) {
|
||||||
return X_ERROR_SUCCESS;
|
return X_ERROR_SUCCESS;
|
||||||
} else {
|
} else {
|
||||||
|
@ -331,14 +353,21 @@ X_RESULT ContentManager::DeleteContent(const XCONTENT_AGGREGATE_DATA& data) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::filesystem::path ContentManager::ResolveGameUserContentPath() {
|
std::filesystem::path ContentManager::ResolveGameUserContentPath(
|
||||||
|
const uint64_t xuid) {
|
||||||
auto title_id = fmt::format("{:08X}", kernel_state_->title_id());
|
auto title_id = fmt::format("{:08X}", kernel_state_->title_id());
|
||||||
auto user_name =
|
const UserProfile* user = kernel_state_->user_profile(xuid);
|
||||||
xe::to_path(kernel_state_->user_profile(uint32_t(0))->name());
|
if (!user) {
|
||||||
|
auto xuid_str = fmt::format("{:016X}", 0);
|
||||||
|
// What now?
|
||||||
|
// Lets assume we save it to common
|
||||||
|
return root_path_ / xuid_str / title_id / kGameUserContentDirName;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto xuid_str = fmt::format("{:016X}", xuid);
|
||||||
// Per-game per-profile data location:
|
// Per-game per-profile data location:
|
||||||
// content_root/title_id/profile/user_name
|
// content_root/title_id/profile/user_name
|
||||||
return root_path_ / title_id / kGameUserContentDirName / user_name;
|
return root_path_ / xuid_str / title_id / kGameUserContentDirName;
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ContentManager::IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const {
|
bool ContentManager::IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const {
|
||||||
|
|
|
@ -93,7 +93,7 @@ struct XCONTENT_DATA {
|
||||||
static_assert_size(XCONTENT_DATA, 0x134);
|
static_assert_size(XCONTENT_DATA, 0x134);
|
||||||
|
|
||||||
struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA {
|
struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA {
|
||||||
be<uint64_t> unk134; // some titles store XUID here?
|
be<uint64_t> xuid; // some titles store XUID here?
|
||||||
be<uint32_t> title_id;
|
be<uint32_t> title_id;
|
||||||
|
|
||||||
XCONTENT_AGGREGATE_DATA() = default;
|
XCONTENT_AGGREGATE_DATA() = default;
|
||||||
|
@ -103,7 +103,7 @@ struct XCONTENT_AGGREGATE_DATA : XCONTENT_DATA {
|
||||||
set_display_name(other.display_name());
|
set_display_name(other.display_name());
|
||||||
set_file_name(other.file_name());
|
set_file_name(other.file_name());
|
||||||
padding[0] = padding[1] = 0;
|
padding[0] = padding[1] = 0;
|
||||||
unk134 = 0;
|
xuid = 0;
|
||||||
title_id = kCurrentlyRunningTitleId;
|
title_id = kCurrentlyRunningTitleId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -141,39 +141,48 @@ class ContentManager {
|
||||||
const std::filesystem::path& root_path);
|
const std::filesystem::path& root_path);
|
||||||
~ContentManager();
|
~ContentManager();
|
||||||
|
|
||||||
std::vector<XCONTENT_AGGREGATE_DATA> ListContent(uint32_t device_id,
|
std::vector<XCONTENT_AGGREGATE_DATA> ListContent(
|
||||||
XContentType content_type,
|
const uint32_t device_id, const uint64_t xuid,
|
||||||
uint32_t title_id = -1);
|
const XContentType content_type, uint32_t title_id = -1);
|
||||||
|
|
||||||
std::unique_ptr<ContentPackage> ResolvePackage(
|
std::unique_ptr<ContentPackage> ResolvePackage(
|
||||||
const std::string_view root_name, const XCONTENT_AGGREGATE_DATA& data,
|
const std::string_view root_name, const uint64_t xuid,
|
||||||
const uint32_t disc_number = -1);
|
const XCONTENT_AGGREGATE_DATA& data, const uint32_t disc_number = -1);
|
||||||
|
|
||||||
bool ContentExists(const XCONTENT_AGGREGATE_DATA& data);
|
bool ContentExists(const uint64_t xuid,
|
||||||
X_RESULT WriteContentHeaderFile(const XCONTENT_AGGREGATE_DATA* data_raw);
|
const XCONTENT_AGGREGATE_DATA& data);
|
||||||
|
X_RESULT WriteContentHeaderFile(const uint64_t xuid,
|
||||||
|
const XCONTENT_AGGREGATE_DATA* data_raw);
|
||||||
X_RESULT ReadContentHeaderFile(const std::string_view file_name,
|
X_RESULT ReadContentHeaderFile(const std::string_view file_name,
|
||||||
|
const uint64_t xuid,
|
||||||
XContentType content_type,
|
XContentType content_type,
|
||||||
XCONTENT_AGGREGATE_DATA& data,
|
XCONTENT_AGGREGATE_DATA& data,
|
||||||
const uint32_t title_id = -1);
|
const uint32_t title_id = -1);
|
||||||
X_RESULT CreateContent(const std::string_view root_name,
|
X_RESULT CreateContent(const std::string_view root_name, const uint64_t xuid,
|
||||||
const XCONTENT_AGGREGATE_DATA& data);
|
const XCONTENT_AGGREGATE_DATA& data);
|
||||||
X_RESULT OpenContent(const std::string_view root_name,
|
X_RESULT OpenContent(const std::string_view root_name, const uint64_t xuid,
|
||||||
const XCONTENT_AGGREGATE_DATA& data,
|
const XCONTENT_AGGREGATE_DATA& data,
|
||||||
const uint32_t disc_number = -1);
|
const uint32_t disc_number = -1);
|
||||||
X_RESULT CloseContent(const std::string_view root_name);
|
X_RESULT CloseContent(const std::string_view root_name);
|
||||||
X_RESULT GetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data,
|
X_RESULT GetContentThumbnail(const uint64_t xuid,
|
||||||
|
const XCONTENT_AGGREGATE_DATA& data,
|
||||||
std::vector<uint8_t>* buffer);
|
std::vector<uint8_t>* buffer);
|
||||||
X_RESULT SetContentThumbnail(const XCONTENT_AGGREGATE_DATA& data,
|
X_RESULT SetContentThumbnail(const uint64_t xuid,
|
||||||
|
const XCONTENT_AGGREGATE_DATA& data,
|
||||||
std::vector<uint8_t> buffer);
|
std::vector<uint8_t> buffer);
|
||||||
X_RESULT DeleteContent(const XCONTENT_AGGREGATE_DATA& data);
|
X_RESULT DeleteContent(const uint64_t xuid,
|
||||||
std::filesystem::path ResolveGameUserContentPath();
|
const XCONTENT_AGGREGATE_DATA& data);
|
||||||
|
std::filesystem::path ResolveGameUserContentPath(const uint64_t xuid = 0);
|
||||||
bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const;
|
bool IsContentOpen(const XCONTENT_AGGREGATE_DATA& data) const;
|
||||||
void CloseOpenedFilesFromContent(const std::string_view root_name);
|
void CloseOpenedFilesFromContent(const std::string_view root_name);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::filesystem::path ResolvePackageRoot(XContentType content_type,
|
std::filesystem::path ResolvePackageRoot(
|
||||||
|
const uint64_t xuid,
|
||||||
|
XContentType content_type,
|
||||||
uint32_t title_id = -1);
|
uint32_t title_id = -1);
|
||||||
std::filesystem::path ResolvePackagePath(const XCONTENT_AGGREGATE_DATA& data,
|
std::filesystem::path ResolvePackagePath(const uint64_t xuid,
|
||||||
|
const XCONTENT_AGGREGATE_DATA& data,
|
||||||
const uint32_t disc_number = -1);
|
const uint32_t disc_number = -1);
|
||||||
|
|
||||||
KernelState* kernel_state_;
|
KernelState* kernel_state_;
|
||||||
|
|
|
@ -10,25 +10,53 @@
|
||||||
#include "xenia/kernel/xam/user_profile.h"
|
#include "xenia/kernel/xam/user_profile.h"
|
||||||
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
#include <xhash>
|
||||||
|
|
||||||
#include "third_party/fmt/include/fmt/format.h"
|
#include "third_party/fmt/include/fmt/format.h"
|
||||||
#include "xenia/kernel/kernel_state.h"
|
#include "xenia/kernel/kernel_state.h"
|
||||||
#include "xenia/kernel/util/shim_utils.h"
|
#include "xenia/kernel/util/shim_utils.h"
|
||||||
|
|
||||||
|
DEFINE_string(gamertag_0, "User_0", "The name of your user profile.",
|
||||||
|
"Profile");
|
||||||
|
DEFINE_string(gamertag_1, "User_1", "The name of your user profile.",
|
||||||
|
"Profile");
|
||||||
|
DEFINE_string(gamertag_2, "User_2", "The name of your user profile.",
|
||||||
|
"Profile");
|
||||||
|
DEFINE_string(gamertag_3, "User_3", "The name of your user profile.",
|
||||||
|
"Profile");
|
||||||
|
|
||||||
namespace xe {
|
namespace xe {
|
||||||
namespace kernel {
|
namespace kernel {
|
||||||
namespace xam {
|
namespace xam {
|
||||||
|
|
||||||
UserProfile::UserProfile(uint8_t index) {
|
UserProfile::UserProfile(uint8_t index) {
|
||||||
|
const std::vector<std::string> username_list = {
|
||||||
|
cvars::gamertag_0,
|
||||||
|
cvars::gamertag_1,
|
||||||
|
cvars::gamertag_2,
|
||||||
|
cvars::gamertag_3,
|
||||||
|
};
|
||||||
|
|
||||||
|
std::string current_username = username_list[index];
|
||||||
|
if (current_username.empty()) {
|
||||||
|
current_username = "User_" + std::to_string(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO(Gliniak): Username sanitizer
|
||||||
|
//
|
||||||
// 58410A1F checks the user XUID against a mask of 0x00C0000000000000 (3<<54),
|
// 58410A1F checks the user XUID against a mask of 0x00C0000000000000 (3<<54),
|
||||||
// if non-zero, it prevents the user from playing the game.
|
// if non-zero, it prevents the user from playing the game.
|
||||||
// "You do not have permissions to perform this operation."
|
// "You do not have permissions to perform this operation."
|
||||||
xuid_ = 0xB13EBABEBABEBABE + index;
|
|
||||||
name_ = "User";
|
|
||||||
if (index) {
|
|
||||||
name_ = "User_" + std::to_string(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
// Get assigned username to current index from config
|
||||||
|
// Then use lower 4 bytes based on player name for unique xuid
|
||||||
|
size_t hash = std::hash<std::string>{}(current_username);
|
||||||
|
|
||||||
|
xuid_ = 0xB13E000000000000 + (hash & 0xFFFFFFFF);
|
||||||
|
name_ = current_username;
|
||||||
|
|
||||||
|
XELOGE("User (Gamertag: {}) in slot {} have xuid = {:016X}", name_, index,
|
||||||
|
xuid_);
|
||||||
// https://cs.rin.ru/forum/viewtopic.php?f=38&t=60668&hilit=gfwl+live&start=195
|
// 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
|
// https://github.com/arkem/py360/blob/master/py360/constants.py
|
||||||
// XPROFILE_GAMER_YAXIS_INVERSION
|
// XPROFILE_GAMER_YAXIS_INVERSION
|
||||||
|
@ -137,7 +165,7 @@ UserProfile::Setting* UserProfile::GetSetting(uint32_t setting_id) {
|
||||||
void UserProfile::LoadSetting(UserProfile::Setting* setting) {
|
void UserProfile::LoadSetting(UserProfile::Setting* setting) {
|
||||||
if (setting->is_title_specific()) {
|
if (setting->is_title_specific()) {
|
||||||
auto content_dir =
|
auto content_dir =
|
||||||
kernel_state()->content_manager()->ResolveGameUserContentPath();
|
kernel_state()->content_manager()->ResolveGameUserContentPath(xuid_);
|
||||||
auto setting_id = fmt::format("{:08X}", setting->setting_id);
|
auto setting_id = fmt::format("{:08X}", setting->setting_id);
|
||||||
auto file_path = content_dir / setting_id;
|
auto file_path = content_dir / setting_id;
|
||||||
auto file = xe::filesystem::OpenFile(file_path, "rb");
|
auto file = xe::filesystem::OpenFile(file_path, "rb");
|
||||||
|
@ -163,7 +191,7 @@ void UserProfile::SaveSetting(UserProfile::Setting* setting) {
|
||||||
if (setting->is_title_specific()) {
|
if (setting->is_title_specific()) {
|
||||||
auto serialized_setting = setting->Serialize();
|
auto serialized_setting = setting->Serialize();
|
||||||
auto content_dir =
|
auto content_dir =
|
||||||
kernel_state()->content_manager()->ResolveGameUserContentPath();
|
kernel_state()->content_manager()->ResolveGameUserContentPath(xuid_);
|
||||||
std::filesystem::create_directories(content_dir);
|
std::filesystem::create_directories(content_dir);
|
||||||
auto setting_id = fmt::format("{:08X}", setting->setting_id);
|
auto setting_id = fmt::format("{:08X}", setting->setting_id);
|
||||||
auto file_path = content_dir / setting_id;
|
auto file_path = content_dir / setting_id;
|
||||||
|
|
|
@ -85,6 +85,12 @@ dword_result_t XamContentCreateEnumerator_entry(
|
||||||
return X_E_INVALIDARG;
|
return X_E_INVALIDARG;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const auto user = kernel_state()->user_profile(user_index);
|
||||||
|
if (!user) {
|
||||||
|
return X_E_INVALIDARG;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
if (buffer_size_ptr) {
|
if (buffer_size_ptr) {
|
||||||
*buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
|
*buffer_size_ptr = sizeof(XCONTENT_DATA) * items_per_enumerate;
|
||||||
}
|
}
|
||||||
|
@ -99,7 +105,7 @@ dword_result_t XamContentCreateEnumerator_entry(
|
||||||
if (!device_info || device_info->device_id == DummyDeviceId::HDD) {
|
if (!device_info || device_info->device_id == DummyDeviceId::HDD) {
|
||||||
// Get all content data.
|
// Get all content data.
|
||||||
auto content_datas = kernel_state()->content_manager()->ListContent(
|
auto content_datas = kernel_state()->content_manager()->ListContent(
|
||||||
static_cast<uint32_t>(DummyDeviceId::HDD),
|
static_cast<uint32_t>(DummyDeviceId::HDD), user->xuid(),
|
||||||
XContentType(uint32_t(content_type)));
|
XContentType(uint32_t(content_type)));
|
||||||
for (const auto& content_data : content_datas) {
|
for (const auto& content_data : content_datas) {
|
||||||
auto item = e->AppendItem();
|
auto item = e->AppendItem();
|
||||||
|
@ -142,7 +148,14 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
|
||||||
*disposition_ptr = 0;
|
*disposition_ptr = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto run = [content_manager, root_name = root_name.value(), flags,
|
const auto user = kernel_state()->user_profile(user_index);
|
||||||
|
if (!user) {
|
||||||
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
|
}
|
||||||
|
|
||||||
|
const uint64_t xuid = user->xuid();
|
||||||
|
|
||||||
|
auto run = [content_manager, root_name = root_name.value(), xuid, flags,
|
||||||
content_data, disposition_ptr, license_mask_ptr, overlapped_ptr](
|
content_data, disposition_ptr, license_mask_ptr, overlapped_ptr](
|
||||||
uint32_t& extended_error, uint32_t& length) -> X_RESULT {
|
uint32_t& extended_error, uint32_t& length) -> X_RESULT {
|
||||||
X_RESULT result = X_ERROR_INVALID_PARAMETER;
|
X_RESULT result = X_ERROR_INVALID_PARAMETER;
|
||||||
|
@ -151,7 +164,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
|
||||||
switch (flags & 0xF) {
|
switch (flags & 0xF) {
|
||||||
case 1: // CREATE_NEW
|
case 1: // CREATE_NEW
|
||||||
// Fail if exists.
|
// Fail if exists.
|
||||||
if (content_manager->ContentExists(content_data)) {
|
if (content_manager->ContentExists(xuid, content_data)) {
|
||||||
result = X_ERROR_ALREADY_EXISTS;
|
result = X_ERROR_ALREADY_EXISTS;
|
||||||
} else {
|
} else {
|
||||||
create = true;
|
create = true;
|
||||||
|
@ -159,8 +172,8 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
|
||||||
break;
|
break;
|
||||||
case 2: // CREATE_ALWAYS
|
case 2: // CREATE_ALWAYS
|
||||||
// Overwrite existing, if any.
|
// Overwrite existing, if any.
|
||||||
if (content_manager->ContentExists(content_data)) {
|
if (content_manager->ContentExists(xuid, content_data)) {
|
||||||
content_manager->DeleteContent(content_data);
|
content_manager->DeleteContent(xuid, content_data);
|
||||||
create = true;
|
create = true;
|
||||||
} else {
|
} else {
|
||||||
create = true;
|
create = true;
|
||||||
|
@ -168,7 +181,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
|
||||||
break;
|
break;
|
||||||
case 3: // OPEN_EXISTING
|
case 3: // OPEN_EXISTING
|
||||||
// Open only if exists.
|
// Open only if exists.
|
||||||
if (!content_manager->ContentExists(content_data)) {
|
if (!content_manager->ContentExists(xuid, content_data)) {
|
||||||
result = X_ERROR_PATH_NOT_FOUND;
|
result = X_ERROR_PATH_NOT_FOUND;
|
||||||
} else {
|
} else {
|
||||||
open = true;
|
open = true;
|
||||||
|
@ -176,7 +189,7 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
|
||||||
break;
|
break;
|
||||||
case 4: // OPEN_ALWAYS
|
case 4: // OPEN_ALWAYS
|
||||||
// Create if needed.
|
// Create if needed.
|
||||||
if (!content_manager->ContentExists(content_data)) {
|
if (!content_manager->ContentExists(xuid, content_data)) {
|
||||||
create = true;
|
create = true;
|
||||||
} else {
|
} else {
|
||||||
open = true;
|
open = true;
|
||||||
|
@ -184,10 +197,10 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
|
||||||
break;
|
break;
|
||||||
case 5: // TRUNCATE_EXISTING
|
case 5: // TRUNCATE_EXISTING
|
||||||
// Fail if doesn't exist, if does exist delete and recreate.
|
// Fail if doesn't exist, if does exist delete and recreate.
|
||||||
if (!content_manager->ContentExists(content_data)) {
|
if (!content_manager->ContentExists(xuid, content_data)) {
|
||||||
result = X_ERROR_PATH_NOT_FOUND;
|
result = X_ERROR_PATH_NOT_FOUND;
|
||||||
} else {
|
} else {
|
||||||
content_manager->DeleteContent(content_data);
|
content_manager->DeleteContent(xuid, content_data);
|
||||||
create = true;
|
create = true;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -206,12 +219,12 @@ dword_result_t xeXamContentCreate(dword_t user_index, lpstring_t root_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
if (create) {
|
if (create) {
|
||||||
result = content_manager->CreateContent(root_name, content_data);
|
result = content_manager->CreateContent(root_name, xuid, content_data);
|
||||||
if (XSUCCEEDED(result)) {
|
if (XSUCCEEDED(result)) {
|
||||||
content_manager->WriteContentHeaderFile(&content_data);
|
content_manager->WriteContentHeaderFile(xuid, &content_data);
|
||||||
}
|
}
|
||||||
} else if (open) {
|
} else if (open) {
|
||||||
result = content_manager->OpenContent(root_name, content_data);
|
result = content_manager->OpenContent(root_name, xuid, content_data);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (license_mask_ptr && XSUCCEEDED(result)) {
|
if (license_mask_ptr && XSUCCEEDED(result)) {
|
||||||
|
@ -322,8 +335,8 @@ dword_result_t XamContentGetCreator_entry(dword_t user_index,
|
||||||
|
|
||||||
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
|
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
|
||||||
|
|
||||||
bool content_exists =
|
bool content_exists = kernel_state()->content_manager()->ContentExists(
|
||||||
kernel_state()->content_manager()->ContentExists(content_data);
|
user_index, content_data);
|
||||||
|
|
||||||
if (content_exists) {
|
if (content_exists) {
|
||||||
if (content_data.content_type == XContentType::kSavedGame) {
|
if (content_data.content_type == XContentType::kSavedGame) {
|
||||||
|
@ -365,10 +378,14 @@ dword_result_t XamContentGetThumbnail_entry(dword_t user_index,
|
||||||
uint32_t buffer_size = *buffer_size_ptr;
|
uint32_t buffer_size = *buffer_size_ptr;
|
||||||
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
|
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
|
||||||
|
|
||||||
|
const UserProfile* user = kernel_state()->user_profile(user_index);
|
||||||
|
if (!user) {
|
||||||
|
return X_ERROR_NO_SUCH_USER;
|
||||||
|
}
|
||||||
// Get thumbnail (if it exists).
|
// Get thumbnail (if it exists).
|
||||||
std::vector<uint8_t> buffer;
|
std::vector<uint8_t> buffer;
|
||||||
auto result = kernel_state()->content_manager()->GetContentThumbnail(
|
auto result = kernel_state()->content_manager()->GetContentThumbnail(
|
||||||
content_data, &buffer);
|
user->xuid(), content_data, &buffer);
|
||||||
|
|
||||||
*buffer_size_ptr = uint32_t(buffer.size());
|
*buffer_size_ptr = uint32_t(buffer.size());
|
||||||
|
|
||||||
|
@ -406,7 +423,7 @@ dword_result_t XamContentSetThumbnail_entry(dword_t user_index,
|
||||||
auto buffer = std::vector<uint8_t>((uint8_t*)buffer_ptr,
|
auto buffer = std::vector<uint8_t>((uint8_t*)buffer_ptr,
|
||||||
(uint8_t*)buffer_ptr + buffer_size);
|
(uint8_t*)buffer_ptr + buffer_size);
|
||||||
auto result = kernel_state()->content_manager()->SetContentThumbnail(
|
auto result = kernel_state()->content_manager()->SetContentThumbnail(
|
||||||
content_data, std::move(buffer));
|
user_index, content_data, std::move(buffer));
|
||||||
|
|
||||||
if (overlapped_ptr) {
|
if (overlapped_ptr) {
|
||||||
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
|
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
|
||||||
|
@ -422,7 +439,13 @@ dword_result_t XamContentDelete_entry(dword_t user_index,
|
||||||
lpunknown_t overlapped_ptr) {
|
lpunknown_t overlapped_ptr) {
|
||||||
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
|
XCONTENT_AGGREGATE_DATA content_data = *content_data_ptr.as<XCONTENT_DATA*>();
|
||||||
|
|
||||||
auto result = kernel_state()->content_manager()->DeleteContent(content_data);
|
const UserProfile* user = kernel_state()->user_profile(user_index);
|
||||||
|
if (!user) {
|
||||||
|
return X_ERROR_NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto result = kernel_state()->content_manager()->DeleteContent(user->xuid(),
|
||||||
|
content_data);
|
||||||
|
|
||||||
if (overlapped_ptr) {
|
if (overlapped_ptr) {
|
||||||
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
|
kernel_state()->CompleteOverlappedImmediate(overlapped_ptr, result);
|
||||||
|
@ -451,7 +474,6 @@ static_assert_size(X_SWAPDISC_ERROR_MESSAGE, 12);
|
||||||
dword_result_t XamSwapDisc_entry(
|
dword_result_t XamSwapDisc_entry(
|
||||||
dword_t disc_number, pointer_t<X_KEVENT> completion_handle,
|
dword_t disc_number, pointer_t<X_KEVENT> completion_handle,
|
||||||
pointer_t<X_SWAPDISC_ERROR_MESSAGE> error_message) {
|
pointer_t<X_SWAPDISC_ERROR_MESSAGE> error_message) {
|
||||||
|
|
||||||
xex2_opt_execution_info* info = nullptr;
|
xex2_opt_execution_info* info = nullptr;
|
||||||
kernel_state()->GetExecutableModule()->GetOptHeader(XEX_HEADER_EXECUTION_INFO,
|
kernel_state()->GetExecutableModule()->GetOptHeader(XEX_HEADER_EXECUTION_INFO,
|
||||||
&info);
|
&info);
|
||||||
|
|
|
@ -112,10 +112,15 @@ dword_result_t XamContentAggregateCreateEnumerator_entry(qword_t xuid,
|
||||||
std::back_inserter(title_ids));
|
std::back_inserter(title_ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto user = kernel_state()->user_profile(xuid);
|
||||||
|
if (!user) {
|
||||||
|
return X_E_NO_SUCH_USER;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& title_id : title_ids) {
|
for (auto& title_id : title_ids) {
|
||||||
// Get all content data.
|
// Get all content data.
|
||||||
auto content_datas = kernel_state()->content_manager()->ListContent(
|
auto content_datas = kernel_state()->content_manager()->ListContent(
|
||||||
static_cast<uint32_t>(DummyDeviceId::HDD), content_type_enum,
|
static_cast<uint32_t>(DummyDeviceId::HDD), xuid, content_type_enum,
|
||||||
title_id);
|
title_id);
|
||||||
for (const auto& content_data : content_datas) {
|
for (const auto& content_data : content_datas) {
|
||||||
auto item = e->AppendItem();
|
auto item = e->AppendItem();
|
||||||
|
|
|
@ -9,6 +9,7 @@
|
||||||
|
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
|
#include "xenia/app/emulator_window.h"
|
||||||
#include "xenia/base/logging.h"
|
#include "xenia/base/logging.h"
|
||||||
#include "xenia/base/math.h"
|
#include "xenia/base/math.h"
|
||||||
#include "xenia/base/string_util.h"
|
#include "xenia/base/string_util.h"
|
||||||
|
@ -355,17 +356,6 @@ dword_result_t XamUserWriteProfileSettings_entry(
|
||||||
if (!setting_count || !settings) {
|
if (!setting_count || !settings) {
|
||||||
return X_ERROR_INVALID_PARAMETER;
|
return X_ERROR_INVALID_PARAMETER;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip writing data about users with id != 0 they're not supported
|
|
||||||
if (user_index > 0) {
|
|
||||||
if (overlapped) {
|
|
||||||
kernel_state()->CompleteOverlappedImmediate(
|
|
||||||
kernel_state()->memory()->HostToGuestVirtual(overlapped),
|
|
||||||
X_ERROR_NO_SUCH_USER);
|
|
||||||
return X_ERROR_IO_PENDING;
|
|
||||||
}
|
|
||||||
return X_ERROR_SUCCESS;
|
|
||||||
}
|
|
||||||
// Update and save settings.
|
// Update and save settings.
|
||||||
const auto& user_profile = kernel_state()->user_profile(user_index);
|
const auto& user_profile = kernel_state()->user_profile(user_index);
|
||||||
|
|
||||||
|
@ -561,7 +551,7 @@ dword_result_t XamShowSigninUI_entry(dword_t unk, dword_t unk_mask) {
|
||||||
// Mask values vary. Probably matching user types? Local/remote?
|
// Mask values vary. Probably matching user types? Local/remote?
|
||||||
// Games seem to sit and loop until we trigger this notification:
|
// Games seem to sit and loop until we trigger this notification:
|
||||||
|
|
||||||
for (uint32_t i = 0; i < 4; i++) {
|
for (uint32_t i = 0; i < MAX_USERS; i++) {
|
||||||
if (kernel_state()->IsUserSignedIn(i)) {
|
if (kernel_state()->IsUserSignedIn(i)) {
|
||||||
// XN_SYS_SIGNINCHANGED
|
// XN_SYS_SIGNINCHANGED
|
||||||
kernel_state()->BroadcastNotification(0xA, i);
|
kernel_state()->BroadcastNotification(0xA, i);
|
||||||
|
|
Loading…
Reference in New Issue