diff --git a/src/core/file_sys/patch_manager.cpp b/src/core/file_sys/patch_manager.cpp index ada8c44e78..4fd7dc6c71 100644 --- a/src/core/file_sys/patch_manager.cpp +++ b/src/core/file_sys/patch_manager.cpp @@ -9,6 +9,7 @@ #include #include +#include "common/assert.h" #include "common/hex_util.h" #include "common/logging.h" #include "common/settings.h" @@ -156,12 +157,15 @@ std::string GetUpdateVersionStringFromSlot(const ContentProvider* provider, u64 NACP nacp{nacp_file}; return nacp.GetVersionString(); } +YUZU_NO_INLINE std::optional GetParentApplicationId(const ContentProvider* provider, u64 title_id) { + return provider == nullptr ? std::nullopt : provider->GetParentApplicationId(title_id); +} } // Anonymous namespace PatchManager::PatchManager(u64 title_id_, const Service::FileSystem::FileSystemController& fs_controller_, const ContentProvider& content_provider_) - : title_id{title_id_}, fs_controller{fs_controller_}, content_provider{content_provider_} {} + : title_id{title_id_}, parent_title_id{GetParentApplicationId(std::addressof(content_provider_), title_id_)}, fs_controller{fs_controller_}, content_provider{content_provider_} {} PatchManager::~PatchManager() = default; @@ -169,13 +173,35 @@ u64 PatchManager::GetTitleID() const { return title_id; } +VirtualDir PatchManager::GetModificationLoadRoot(bool sdmc) const { + const auto get_root = [&](u64 id) { + return sdmc ? fs_controller.GetSDMCModificationLoadRoot(id) : fs_controller.GetModificationLoadRoot(id); + }; + auto root = get_root(title_id); + if (!parent_title_id) { + return root; + } + std::vector roots{std::move(root), get_root(*parent_title_id)}; + std::erase(roots, nullptr); + return LayeredVfsDirectory::MakeLayeredDirectory(std::move(roots)); +} + +std::vector PatchManager::GetDisabledAddons() const { + auto disabled = Settings::values.disabled_addons[title_id]; + if (parent_title_id) { + const auto& shared = Settings::values.disabled_addons[*parent_title_id]; + disabled.insert(disabled.end(), shared.begin(), shared.end()); + } + return disabled; +} + VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { LOG_INFO(Loader, "Patching ExeFS for title_id={:016X}", title_id); if (exefs == nullptr) return exefs; - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto disabled = GetDisabledAddons(); bool update_disabled = true; std::optional enabled_version; @@ -303,8 +329,8 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { } // LayeredExeFS - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); - const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); + const auto load_dir = GetModificationLoadRoot(); + const auto sdmc_load_dir = GetModificationLoadRoot(true); std::vector patch_dirs = {sdmc_load_dir}; if (load_dir != nullptr) { @@ -346,7 +372,7 @@ VirtualDir PatchManager::PatchExeFS(VirtualDir exefs) const { } std::vector PatchManager::CollectPatches(const std::vector& patch_dirs, const std::string& build_id) const { - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto disabled = GetDisabledAddons(); const auto nso_build_id = fmt::format("{:0<64}", build_id); std::vector out; @@ -405,7 +431,7 @@ std::vector PatchManager::PatchNSO(const std::vector& nso, const std::st LOG_INFO(Loader, "Patching NSO for name={}, build_id={}", name, build_id); - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); + const auto load_dir = GetModificationLoadRoot(); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return nso; @@ -448,7 +474,7 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) LOG_INFO(Loader, "Querying NSO patch existence for build_id={}, name={}", build_id, name); - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); + const auto load_dir = GetModificationLoadRoot(); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return false; @@ -462,13 +488,13 @@ bool PatchManager::HasNSOPatch(const BuildID& build_id_, std::string_view name) } std::vector PatchManager::CreateCheatList(const BuildID& build_id_) const { - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); + const auto load_dir = GetModificationLoadRoot(); if (load_dir == nullptr) { LOG_ERROR(Loader, "Cannot load mods for invalid title_id={:016X}", title_id); return {}; } - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto disabled = GetDisabledAddons(); auto patch_dirs = load_dir->GetSubdirectories(); std::sort(patch_dirs.begin(), patch_dirs.end(), [](auto const& l, auto const& r) { return l->GetName() < r->GetName(); }); @@ -502,17 +528,16 @@ std::vector PatchManager::CreateCheatList(const BuildI return out; } -static void ApplyLayeredFS(VirtualFile& romfs, u64 title_id, ContentRecordType type, - const Service::FileSystem::FileSystemController& fs_controller) { - const auto load_dir = fs_controller.GetModificationLoadRoot(title_id); - const auto sdmc_load_dir = fs_controller.GetSDMCModificationLoadRoot(title_id); +void PatchManager::ApplyLayeredFS(VirtualFile& romfs, ContentRecordType type) const { + const auto load_dir = GetModificationLoadRoot(); + const auto sdmc_load_dir = GetModificationLoadRoot(true); if ((type != ContentRecordType::Program && type != ContentRecordType::Data && type != ContentRecordType::HtmlDocument) || (load_dir == nullptr && sdmc_load_dir == nullptr)) { return; } - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto disabled = GetDisabledAddons(); std::vector patch_dirs = load_dir->GetSubdirectories(); if (std::find(disabled.cbegin(), disabled.cend(), "SDMC") == disabled.cend()) { patch_dirs.push_back(sdmc_load_dir); @@ -591,7 +616,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs // Game Updates const auto update_tid = GetUpdateTitleID(title_id); - const auto& disabled = Settings::values.disabled_addons[title_id]; + const auto disabled = GetDisabledAddons(); bool update_disabled = true; std::optional enabled_version; @@ -698,7 +723,7 @@ VirtualFile PatchManager::PatchRomFS(const NCA* base_nca, VirtualFile base_romfs // LayeredFS if (apply_layeredfs) { - ApplyLayeredFS(romfs, title_id, type, fs_controller); + ApplyLayeredFS(romfs, type); } return romfs; @@ -1072,15 +1097,22 @@ std::vector PatchManager::GetPatches(VirtualFile update_raw) const { std::optional PatchManager::GetGameVersion() const { const auto update_tid = GetUpdateTitleID(title_id); if (content_provider.HasEntry(update_tid, ContentRecordType::Program)) { - return content_provider.GetEntryVersion(update_tid); + const auto version = content_provider.GetEntryVersion(update_tid); + return version || !parent_title_id ? version : content_provider.GetEntryVersion(GetUpdateTitleID(*parent_title_id)); } - return content_provider.GetEntryVersion(title_id); + const auto version = content_provider.GetEntryVersion(title_id); + return version || !parent_title_id ? version : content_provider.GetEntryVersion(*parent_title_id); } PatchManager::Metadata PatchManager::GetControlMetadata() const { const auto base_control_nca = content_provider.GetEntry(title_id, ContentRecordType::Control); if (base_control_nca == nullptr) { + if (parent_title_id) { + const auto control_id = content_provider.HasEntry(*parent_title_id, ContentRecordType::Control) ? *parent_title_id : GetUpdateTitleID(*parent_title_id); + const PatchManager parent{control_id, fs_controller, content_provider}; + return parent.GetControlMetadata(); + } return {}; } diff --git a/src/core/file_sys/patch_manager.h b/src/core/file_sys/patch_manager.h index 5d71dbcf20..6819a1a3e7 100644 --- a/src/core/file_sys/patch_manager.h +++ b/src/core/file_sys/patch_manager.h @@ -109,10 +109,14 @@ public: [[nodiscard]] static PatchManager::Metadata GetMetadataFromBaseOrUpdate(Core::System& system, u64 application_id) noexcept; private: + [[nodiscard]] VirtualDir GetModificationLoadRoot(bool sdmc = false) const; + [[nodiscard]] std::vector GetDisabledAddons() const; + void ApplyLayeredFS(VirtualFile& romfs, ContentRecordType type) const; [[nodiscard]] std::vector CollectPatches(const std::vector& patch_dirs, const std::string& build_id) const; u64 title_id; + std::optional parent_title_id; const Service::FileSystem::FileSystemController& fs_controller; const ContentProvider& content_provider; }; diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index ce851e4d38..1f430434f7 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -5,6 +5,7 @@ // SPDX-License-Identifier: GPL-2.0-or-later #include +#include #include #include #include @@ -348,6 +349,22 @@ std::vector ContentProvider::ListEntries() const { return ListEntriesFilter(std::nullopt, std::nullopt, std::nullopt); } +std::optional ContentProvider::GetParentApplicationId(u64 program_id) const { + const auto application_id = GetBaseTitleID(program_id); + const auto program_index = program_id - application_id; + if (program_index == 0 || program_index > std::numeric_limits::max()) { + return std::nullopt; + } + if (!ListEntriesFilter(TitleType::Application, ContentRecordType::Meta, program_id).empty()) { + return std::nullopt; + } + if ((!ListEntriesFilter(TitleType::Application, ContentRecordType::Meta, application_id).empty() && HasEntry(program_id, ContentRecordType::Program)) + || (!ListEntriesFilter(TitleType::Update, ContentRecordType::Meta, GetUpdateTitleID(application_id)).empty() && HasEntry(GetUpdateTitleID(program_id), ContentRecordType::Program))) { + return application_id; + } + return std::nullopt; +} + PlaceholderCache::PlaceholderCache(VirtualDir dir_) : dir(std::move(dir_)) {} bool PlaceholderCache::Create(const NcaID& id, u64 size) const { diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index a8e60ad470..89140a2e95 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -100,6 +100,8 @@ public: std::optional title_type = {}, std::optional record_type = {}, std::optional title_id = {}) const = 0; + [[nodiscard]] std::optional GetParentApplicationId(u64 program_id) const; + protected: // A single instance of KeyManager to be used by GetEntry() Core::Crypto::KeyManager& keys = Core::Crypto::KeyManager::Instance(); diff --git a/src/core/file_sys/savedata_factory.cpp b/src/core/file_sys/savedata_factory.cpp index c756734a43..72f022cb41 100644 --- a/src/core/file_sys/savedata_factory.cpp +++ b/src/core/file_sys/savedata_factory.cpp @@ -9,6 +9,7 @@ #include "common/logging.h" #include "common/uuid.h" #include "core/core.h" +#include "core/file_sys/registered_cache.h" #include "core/file_sys/savedata_factory.h" #include "core/file_sys/vfs/vfs.h" @@ -61,17 +62,24 @@ SaveDataFactory::SaveDataFactory(Core::System& system_, ProgramId program_id_, SaveDataFactory::~SaveDataFactory() = default; +std::string SaveDataFactory::GetSaveDataPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id, u64 save_id) const { + if (type == SaveDataType::Account || type == SaveDataType::Device) { + const auto requested_id = title_id != 0 ? title_id : program_id; + const auto parent_id = system.GetContentProvider().GetParentApplicationId(requested_id); + title_id = parent_id.value_or(requested_id); + } + return GetFullPath(program_id, dir, space, type, title_id, user_id, save_id); +} + VirtualDir SaveDataFactory::Create(SaveDataSpaceId space, const SaveDataAttribute& meta) const { - const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id, - meta.user_id, meta.system_save_data_id); + const auto save_directory = GetSaveDataPath(space, meta.type, meta.program_id, meta.user_id, meta.system_save_data_id); return dir->CreateDirectoryRelative(save_directory); } VirtualDir SaveDataFactory::Open(SaveDataSpaceId space, const SaveDataAttribute& meta) const { - const auto save_directory = GetFullPath(program_id, dir, space, meta.type, meta.program_id, - meta.user_id, meta.system_save_data_id); + const auto save_directory = GetSaveDataPath(space, meta.type, meta.program_id, meta.user_id, meta.system_save_data_id); auto out = dir->GetDirectoryRelative(save_directory); @@ -154,8 +162,7 @@ std::string SaveDataFactory::GetUserGameSaveDataRoot(u128 user_id, bool future) SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, u128 user_id) const { - const auto path = - GetFullPath(program_id, dir, SaveDataSpaceId::User, type, title_id, user_id, 0); + const auto path = GetSaveDataPath(SaveDataSpaceId::User, type, title_id, user_id, 0); const auto relative_dir = GetOrCreateDirectoryRelative(dir, path); const auto size_file = relative_dir->GetFile(GetSaveDataSizeFileName()); @@ -173,8 +180,7 @@ SaveDataSize SaveDataFactory::ReadSaveDataSize(SaveDataType type, u64 title_id, void SaveDataFactory::WriteSaveDataSize(SaveDataType type, u64 title_id, u128 user_id, SaveDataSize new_value) const { - const auto path = - GetFullPath(program_id, dir, SaveDataSpaceId::User, type, title_id, user_id, 0); + const auto path = GetSaveDataPath(SaveDataSpaceId::User, type, title_id, user_id, 0); const auto relative_dir = GetOrCreateDirectoryRelative(dir, path); const auto size_file = relative_dir->CreateFile(GetSaveDataSizeFileName()); diff --git a/src/core/file_sys/savedata_factory.h b/src/core/file_sys/savedata_factory.h index 15dd4ec7de..36c09ce2ac 100644 --- a/src/core/file_sys/savedata_factory.h +++ b/src/core/file_sys/savedata_factory.h @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: Copyright 2026 Eden Emulator Project +// SPDX-License-Identifier: GPL-3.0-or-later + // SPDX-FileCopyrightText: Copyright 2018 yuzu Emulator Project // SPDX-License-Identifier: GPL-2.0-or-later @@ -47,6 +50,7 @@ public: void SetAutoCreate(bool state); private: + std::string GetSaveDataPath(SaveDataSpaceId space, SaveDataType type, u64 title_id, u128 user_id, u64 save_id) const; Core::System& system; ProgramId program_id; VirtualDir dir;