[XAM] Allow loading profile from STFS (extracts package automatically!)
Profiles can now be placed as either an extracted folder with GPDs, or an STFS package, inside the Documents\Xenia\content\FFFE07D1\00010000\ directory eg. Documents\Xenia\content\FFFE07D1\00010000\E0000E07FA53D7F1 (this roughly matches the same location as X360 stores it) If loading an STFS package the package will first get extracted to <path>.dir/, and then the profile is loaded/saved into that directory. (originally was going to mount the package and read everything in-memory, but then realized how hard adding new files/modifying/etc would be.. VFS doesn't allow mixing two devices into the same mount_path afaik) Code for extraction is taken from xenia-vfs-dump (as StfsContainerDevice::ExtractToFolder) A [XAM]profile_xuid config option is added too, which should let you pick which profile to load from the FFFE07D1\00010000\ folder if you have multiple there. (at least I hope it should - something like "profile_xuid = 0xE0000E07FA53D7F1" will work I hope... cpptoml might have issues with hex digits though, not sure, will investigate later...) If profile_xuid isn't set (left at -1), Xenia will just load whatever the first file/folder inside there is.
This commit is contained in:
parent
41b94145e2
commit
12674aa529
|
@ -48,12 +48,13 @@ KernelState::KernelState(Emulator* emulator)
|
|||
file_system_ = emulator->file_system();
|
||||
|
||||
app_manager_ = std::make_unique<xam::AppManager>();
|
||||
user_profile_ = std::make_unique<xam::UserProfile>(this);
|
||||
|
||||
auto content_root = emulator_->content_root();
|
||||
content_root = xe::to_absolute_path(content_root);
|
||||
content_manager_ = std::make_unique<xam::ContentManager>(this, content_root);
|
||||
|
||||
user_profile_ = std::make_unique<xam::UserProfile>(this);
|
||||
|
||||
assert_null(shared_kernel_state_);
|
||||
shared_kernel_state_ = this;
|
||||
|
||||
|
|
|
@ -44,37 +44,15 @@ uint32_t ContentManager::title_id() {
|
|||
return kernel_state_->title_id();
|
||||
}
|
||||
|
||||
std::wstring ContentManager::ResolvePackageRoot(uint32_t content_type) {
|
||||
std::wstring ContentManager::ResolvePackageRoot(uint32_t content_type,
|
||||
uint32_t title_id) {
|
||||
wchar_t title_id_str[9] = L"00000000";
|
||||
std::swprintf(title_id_str, 9, L"%.8X", title_id());
|
||||
std::swprintf(title_id_str, 9, L"%.8X",
|
||||
title_id == -1 ? this->title_id() : title_id);
|
||||
|
||||
wchar_t content_type_str[9] = L"00000000";
|
||||
std::swprintf(content_type_str, 9, L"%.8X", content_type);
|
||||
|
||||
std::wstring type_name;
|
||||
switch (content_type) {
|
||||
case 1:
|
||||
// Save games.
|
||||
type_name = L"00000001";
|
||||
break;
|
||||
case 2:
|
||||
// DLC from the marketplace.
|
||||
type_name = L"00000002";
|
||||
break;
|
||||
case 3:
|
||||
// Publisher content?
|
||||
type_name = L"00000003";
|
||||
break;
|
||||
case 0x000D0000:
|
||||
// ???
|
||||
type_name = L"000D0000";
|
||||
break;
|
||||
default:
|
||||
type_name = L"00000000";
|
||||
//assert_unhandled_case(data.content_type);
|
||||
//return nullptr;
|
||||
}
|
||||
|
||||
// Package root path:
|
||||
// content_root/title_id/type_name/
|
||||
auto package_root = xe::join_paths(
|
||||
|
|
|
@ -65,6 +65,8 @@ class ContentManager {
|
|||
uint32_t content_type);
|
||||
|
||||
ContentPackage* ResolvePackage(const XCONTENT_DATA& data);
|
||||
std::wstring ResolvePackageRoot(uint32_t content_type,
|
||||
uint32_t title_id = -1);
|
||||
|
||||
bool ContentExists(const XCONTENT_DATA& data);
|
||||
X_RESULT CreateContent(std::string root_name, const XCONTENT_DATA& data);
|
||||
|
@ -82,7 +84,6 @@ class ContentManager {
|
|||
private:
|
||||
uint32_t title_id();
|
||||
|
||||
std::wstring ResolvePackageRoot(uint32_t content_type);
|
||||
std::wstring ResolvePackagePath(const XCONTENT_DATA& data);
|
||||
|
||||
KernelState* kernel_state_;
|
||||
|
|
|
@ -20,9 +20,12 @@
|
|||
#include "xenia/kernel/kernel_state.h"
|
||||
#include "xenia/kernel/util/crypto_utils.h"
|
||||
#include "xenia/kernel/util/shim_utils.h"
|
||||
#include "xenia/vfs/devices/host_path_device.h"
|
||||
|
||||
DECLARE_int32(license_mask);
|
||||
|
||||
DEFINE_int64(profile_xuid, -1, "XUID of the profile to load in (E0...)", "XAM");
|
||||
|
||||
namespace xe {
|
||||
namespace kernel {
|
||||
namespace xam {
|
||||
|
@ -31,9 +34,18 @@ std::string X_XAMACCOUNTINFO::GetGamertagString() const {
|
|||
return xe::to_string(std::wstring(gamertag));
|
||||
}
|
||||
|
||||
std::wstring UserProfile::directory() const {
|
||||
return xe::to_absolute_path(kernel_state_->emulator()->content_root() +
|
||||
L"\\profile\\");
|
||||
std::wstring UserProfile::path() const {
|
||||
if (!profile_path_.empty()) {
|
||||
return profile_path_;
|
||||
}
|
||||
|
||||
return base_path_;
|
||||
}
|
||||
|
||||
std::wstring UserProfile::path(uint64_t xuid) const {
|
||||
wchar_t path[4096];
|
||||
swprintf_s(path, L"%s%llX", base_path_.c_str(), xuid);
|
||||
return path;
|
||||
}
|
||||
|
||||
bool UserProfile::DecryptAccountFile(const uint8_t* data,
|
||||
|
@ -108,18 +120,75 @@ void UserProfile::EncryptAccountFile(const X_XAMACCOUNTINFO* input,
|
|||
|
||||
UserProfile::UserProfile(KernelState* kernel_state)
|
||||
: kernel_state_(kernel_state), dash_gpd_(kDashboardID) {
|
||||
account_.xuid_online = 0xE000BABEBABEBABE;
|
||||
base_path_ =
|
||||
xe::to_absolute_path(kernel_state_->content_manager()->ResolvePackageRoot(
|
||||
0x10000, 0xFFFE07D1));
|
||||
|
||||
account_.xuid_online = cvars::profile_xuid;
|
||||
wcscpy(account_.gamertag, L"XeniaUser");
|
||||
|
||||
// Try loading profile GPD files...
|
||||
LoadProfile();
|
||||
}
|
||||
|
||||
void UserProfile::LoadProfile() {
|
||||
std::wstring UserProfile::MountProfile(const std::wstring& path) {
|
||||
auto package = path;
|
||||
auto package_dir = package + L".dir\\";
|
||||
if (filesystem::PathExists(package_dir)) {
|
||||
return package_dir;
|
||||
}
|
||||
|
||||
filesystem::FileInfo info;
|
||||
// Get info for the original path too, in case we changed to the .dir above
|
||||
if (!filesystem::GetInfo(package, &info)) {
|
||||
return L"";
|
||||
}
|
||||
auto file_system = kernel_state_->file_system();
|
||||
|
||||
auto file_name = xe::to_string(info.name);
|
||||
auto mount_path = "\\Device\\Profile_" + file_name;
|
||||
|
||||
// If package still points to a file, open it as STFS and extract
|
||||
if (info.type == filesystem::FileInfo::Type::kFile) {
|
||||
XELOGI("MountProfile: extracting STFS profile %S", package.c_str());
|
||||
|
||||
// Register the container in the virtual filesystem.
|
||||
auto device =
|
||||
std::make_unique<vfs::StfsContainerDevice>(mount_path, package);
|
||||
if (!device->Initialize()) {
|
||||
XELOGE(
|
||||
"MountProfile: Unable to mount %S as STFS; file not found or "
|
||||
"corrupt.",
|
||||
package.c_str());
|
||||
return L"";
|
||||
}
|
||||
device->ExtractToFolder(package_dir);
|
||||
return package_dir;
|
||||
}
|
||||
|
||||
return package + L"\\";
|
||||
}
|
||||
|
||||
bool UserProfile::LoadProfile() {
|
||||
auto profile_path = path(cvars::profile_xuid);
|
||||
if (cvars::profile_xuid == -1) {
|
||||
auto files = filesystem::ListFiles(path());
|
||||
// TODO: allow choosing by index maybe?
|
||||
for (auto f : files) {
|
||||
profile_path = f.path + f.name;
|
||||
break; // use first found dir/package as profile
|
||||
}
|
||||
}
|
||||
|
||||
profile_path_ = MountProfile(profile_path);
|
||||
if (profile_path_.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
auto mmap_ =
|
||||
MappedMemory::Open(directory() + L"Account", MappedMemory::Mode::kRead);
|
||||
MappedMemory::Open(path() + L"Account", MappedMemory::Mode::kRead);
|
||||
if (mmap_) {
|
||||
XELOGI("Loading Account file from path %SAccount", directory().c_str());
|
||||
XELOGI("Loading Account file from path %SAccount", path().c_str());
|
||||
|
||||
X_XAMACCOUNTINFO tmp_acct;
|
||||
bool success = DecryptAccountFile(mmap_->data(), &tmp_acct);
|
||||
|
@ -137,10 +206,10 @@ void UserProfile::LoadProfile() {
|
|||
mmap_->Close();
|
||||
}
|
||||
|
||||
XELOGI("Loading profile GPDs from path %S", directory().c_str());
|
||||
XELOGI("Loading profile GPDs from path %S", path().c_str());
|
||||
|
||||
mmap_ = MappedMemory::Open(directory() + L"FFFE07D1.gpd",
|
||||
MappedMemory::Mode::kRead);
|
||||
mmap_ =
|
||||
MappedMemory::Open(path() + L"FFFE07D1.gpd", MappedMemory::Mode::kRead);
|
||||
if (mmap_) {
|
||||
dash_gpd_.Read(mmap_->data(), mmap_->size());
|
||||
mmap_->Close();
|
||||
|
@ -229,7 +298,7 @@ void UserProfile::LoadProfile() {
|
|||
for (auto title : titles) {
|
||||
wchar_t fname[256];
|
||||
swprintf(fname, 256, L"%X.gpd", title.title_id);
|
||||
mmap_ = MappedMemory::Open(directory() + fname, MappedMemory::Mode::kRead);
|
||||
mmap_ = MappedMemory::Open(path() + fname, MappedMemory::Mode::kRead);
|
||||
if (!mmap_) {
|
||||
XELOGE("Failed to open GPD for title %X (%s)!", title.title_id,
|
||||
xe::to_string(title.title_name).c_str());
|
||||
|
@ -250,6 +319,7 @@ void UserProfile::LoadProfile() {
|
|||
}
|
||||
|
||||
XELOGI("Loaded %d profile GPDs", title_gpds_.size());
|
||||
return true;
|
||||
}
|
||||
|
||||
xdbf::GpdFile* UserProfile::SetTitleSpaData(const xdbf::SpaFile& spa_data) {
|
||||
|
@ -535,16 +605,16 @@ bool UserProfile::UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data) {
|
|||
return false;
|
||||
}
|
||||
|
||||
if (!filesystem::PathExists(directory())) {
|
||||
filesystem::CreateFolder(directory());
|
||||
if (!filesystem::PathExists(path())) {
|
||||
filesystem::CreateFolder(path());
|
||||
}
|
||||
|
||||
wchar_t fname[256];
|
||||
swprintf(fname, 256, L"%X.gpd", title_id);
|
||||
|
||||
filesystem::CreateFile(directory() + fname);
|
||||
filesystem::CreateFile(path() + fname);
|
||||
auto mmap_ = MappedMemory::Open(
|
||||
directory() + fname, MappedMemory::Mode::kReadWrite, 0, gpd_length);
|
||||
path() + fname, MappedMemory::Mode::kReadWrite, 0, gpd_length);
|
||||
if (!mmap_) {
|
||||
XELOGE("Failed to open %X.gpd for writing!", title_id);
|
||||
return false;
|
||||
|
|
|
@ -175,7 +175,8 @@ class UserProfile {
|
|||
|
||||
uint64_t xuid() const { return account_.xuid_online; }
|
||||
std::string name() const { return account_.GetGamertagString(); }
|
||||
std::wstring directory() const;
|
||||
std::wstring path() const;
|
||||
std::wstring path(uint64_t xuid) const;
|
||||
// uint32_t signin_state() const { return 1; }
|
||||
|
||||
xdbf::GpdFile* SetTitleSpaData(const xdbf::SpaFile& spa_data);
|
||||
|
@ -188,13 +189,17 @@ class UserProfile {
|
|||
bool UpdateAllGpds();
|
||||
|
||||
private:
|
||||
void LoadProfile();
|
||||
bool LoadProfile();
|
||||
std::wstring MountProfile(const std::wstring& path);
|
||||
|
||||
bool UpdateGpd(uint32_t title_id, xdbf::GpdFile& gpd_data);
|
||||
|
||||
bool AddSettingIfNotExist(xdbf::Setting& setting);
|
||||
|
||||
KernelState* kernel_state_;
|
||||
|
||||
std::wstring profile_path_;
|
||||
std::wstring base_path_;
|
||||
X_XAMACCOUNTINFO account_;
|
||||
|
||||
std::unordered_map<uint32_t, xdbf::GpdFile> title_gpds_;
|
||||
|
|
|
@ -786,7 +786,7 @@ dword_result_t XamReadTile(dword_t tile_type, dword_t game_id, qword_t item_id,
|
|||
if (kTileFileNames.count(type)) {
|
||||
// image_id = XUID of profile to retrieve from
|
||||
|
||||
auto file_path = kernel_state()->user_profile()->directory();
|
||||
auto file_path = kernel_state()->user_profile()->path();
|
||||
file_path += kTileFileNames.at(type);
|
||||
|
||||
mmap = MappedMemory::Open(file_path, MappedMemory::Mode::kRead);
|
||||
|
|
|
@ -777,5 +777,83 @@ bool StfsContainerDevice::ResolveFromFolder(const std::wstring& path) {
|
|||
return true;
|
||||
}
|
||||
|
||||
uint32_t StfsContainerDevice::ExtractToFolder(const std::wstring& base_path) {
|
||||
XELOGD("Unpacking to %S", base_path.c_str());
|
||||
|
||||
// Create path if it doesn't exist
|
||||
if (!filesystem::PathExists(base_path)) {
|
||||
filesystem::CreateFolder(base_path);
|
||||
}
|
||||
|
||||
// Run through all the files, breadth-first style.
|
||||
std::queue<vfs::Entry*> queue;
|
||||
auto root = ResolvePath("/");
|
||||
queue.push(root);
|
||||
|
||||
// Allocate a buffer when needed.
|
||||
size_t buffer_size = 0;
|
||||
uint8_t* buffer = nullptr;
|
||||
uint32_t extracted = 0;
|
||||
|
||||
while (!queue.empty()) {
|
||||
auto entry = queue.front();
|
||||
queue.pop();
|
||||
for (auto& entry : entry->children()) {
|
||||
queue.push(entry.get());
|
||||
}
|
||||
|
||||
XELOGD(" %s", entry->path().c_str());
|
||||
auto dest_name = xe::join_paths(base_path, xe::to_wstring(entry->path()));
|
||||
if (entry->attributes() & kFileAttributeDirectory) {
|
||||
xe::filesystem::CreateFolder(dest_name + L"\\");
|
||||
continue;
|
||||
}
|
||||
|
||||
vfs::File* in_file = nullptr;
|
||||
if (entry->Open(FileAccess::kFileReadData, &in_file) != X_STATUS_SUCCESS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
auto file = xe::filesystem::OpenFile(dest_name, "wb");
|
||||
if (!file) {
|
||||
in_file->Destroy();
|
||||
continue;
|
||||
}
|
||||
|
||||
if (entry->can_map()) {
|
||||
auto map = entry->OpenMapped(xe::MappedMemory::Mode::kRead);
|
||||
fwrite(map->data(), map->size(), 1, file);
|
||||
map->Close();
|
||||
} else {
|
||||
// Can't map the file into memory. Read it into a temporary buffer.
|
||||
if (!buffer || entry->size() > buffer_size) {
|
||||
// Resize the buffer.
|
||||
if (buffer) {
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
// Allocate a buffer rounded up to the nearest 512MB.
|
||||
buffer_size = xe::round_up(entry->size(), 512 * 1024 * 1024);
|
||||
buffer = new uint8_t[buffer_size];
|
||||
}
|
||||
|
||||
size_t bytes_read = 0;
|
||||
in_file->ReadSync(buffer, entry->size(), 0, &bytes_read);
|
||||
fwrite(buffer, bytes_read, 1, file);
|
||||
}
|
||||
|
||||
extracted++;
|
||||
|
||||
fclose(file);
|
||||
in_file->Destroy();
|
||||
}
|
||||
|
||||
if (buffer) {
|
||||
delete[] buffer;
|
||||
}
|
||||
|
||||
return extracted;
|
||||
}
|
||||
|
||||
} // namespace vfs
|
||||
} // namespace xe
|
|
@ -185,6 +185,8 @@ class StfsContainerDevice : public Device {
|
|||
|
||||
StfsHeader& header() { return header_; }
|
||||
|
||||
uint32_t ExtractToFolder(const std::wstring& dest_path);
|
||||
|
||||
private:
|
||||
enum class Error {
|
||||
kSuccess = 0,
|
||||
|
|
Loading…
Reference in New Issue