diff --git a/include/rex/system/xam/content_manager.h b/include/rex/system/xam/content_manager.h index 0890be5..5c5f92f 100644 --- a/include/rex/system/xam/content_manager.h +++ b/include/rex/system/xam/content_manager.h @@ -183,6 +183,14 @@ class ContentManager { // and writes a .header file for XAM enumeration. X_RESULT InstallContent(const std::filesystem::path& package_path); + // Redirects all content of `content_type` to `root` with a flattened + // single-title layout instead of the shared content tree: + // root/{xuid}/{package}/ data + // root/{xuid}/Headers/{package}.header enumeration header + // The xuid level is kept so per-profile content stays isolated. Set before + // the title enumerates or opens content of this type; no data is migrated. + void SetContentTypeRoot(XContentType content_type, std::filesystem::path root); + private: std::filesystem::path ResolvePackageRoot(uint64_t xuid, XContentType content_type, uint32_t title_id = -1); @@ -199,6 +207,9 @@ class ContentManager { KernelState* kernel_state_; std::filesystem::path root_path_; + // Per-content-type root overrides (SetContentTypeRoot), keyed by the raw + // XContentType value. + std::unordered_map content_type_roots_; // TODO(benvanik): remove use of global lock, it's bad here! rex::thread::global_critical_region global_critical_region_; diff --git a/src/system/xam/content_manager.cpp b/src/system/xam/content_manager.cpp index 7642e3a..c13a404 100644 --- a/src/system/xam/content_manager.cpp +++ b/src/system/xam/content_manager.cpp @@ -85,12 +85,23 @@ ContentManager::ContentManager(KernelState* kernel_state, const std::filesystem: ContentManager::~ContentManager() = default; +void ContentManager::SetContentTypeRoot(XContentType content_type, std::filesystem::path root) { + content_type_roots_[uint32_t(content_type)] = std::move(root); +} + std::filesystem::path ContentManager::ResolvePackageRoot(uint64_t xuid, XContentType content_type, uint32_t title_id) { if (title_id == kCurrentlyRunningTitleId) { title_id = kernel_state_->title_id(); } auto xuid_str = fmt::format("{:016X}", xuid); + + // Overridden types use a flattened single-title layout: root/xuid/ + auto override_it = content_type_roots_.find(uint32_t(content_type)); + if (override_it != content_type_roots_.end()) { + return override_it->second / xuid_str; + } + auto title_id_str = fmt::format("{:08X}", title_id); auto content_type_str = fmt::format("{:08X}", uint32_t(content_type)); @@ -126,9 +137,16 @@ std::filesystem::path ContentManager::ResolvePackageHeaderPath(const std::string } auto xuid_str = fmt::format("{:016X}", xuid); + std::string final_name = std::string(file_name) + ".header"; + + // Overridden types keep headers beside their data: root/xuid/Headers/ + auto override_it = content_type_roots_.find(uint32_t(content_type)); + if (override_it != content_type_roots_.end()) { + return override_it->second / xuid_str / kGameContentHeaderDirName / final_name; + } + auto title_id_str = fmt::format("{:08X}", title_id); auto content_type_str = fmt::format("{:08X}", uint32_t(content_type)); - std::string final_name = std::string(file_name) + ".header"; // Header root path: // content_root/xuid/title_id/Headers/content_type/filename.header