From a82fdea1ac84693288ed5656f3ff89ceaaaeea1b Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sun, 5 Jul 2020 09:37:50 -0400 Subject: [PATCH 1/5] registered_cache: Remove previous update/dlc if it exists on install - This checks for and removes old updates or dlc based on title id. If a content meta nca exists within the registered cache, it will attempt to remove all the ncas associated with the content meta before installing a new update/dlc --- src/core/file_sys/registered_cache.cpp | 88 ++++++++++++++++++++++---- src/core/file_sys/registered_cache.h | 10 ++- 2 files changed, 84 insertions(+), 14 deletions(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 27c1b02330..39bfbdfd32 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -547,6 +547,57 @@ InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_ex return InstallEntry(*xci.GetSecurePartitionNSP(), overwrite_if_exists, copy); } +bool RegisteredCache::RemoveExistingEntry(const u64 title_id) { + const auto delete_nca = [this](const NcaID& id) { + const auto path = GetRelativePathFromNcaID(id, false, true, false); + + if (dir->GetFileRelative(path) == nullptr) { + return false; + } + + Core::Crypto::SHA256Hash hash{}; + mbedtls_sha256_ret(id.data(), id.size(), hash.data(), 0); + const auto dirname = fmt::format("000000{:02X}", hash[0]); + + const auto dir2 = GetOrCreateDirectoryRelative(dir, dirname); + + const auto res = dir2->DeleteFile(fmt::format("{}.nca", Common::HexToString(id, false))); + + return res; + }; + + // Get the Content Provider + const auto& installed = Core::System::GetInstance().GetContentProvider(); + // If an update exists, remove + if (installed.HasEntry(title_id, ContentRecordType::Meta)) { + LOG_INFO(Loader, + "Previous Update (v{}) for title_id={:016X} detected! Attempting to remove...", + installed.GetEntryVersion(title_id).value_or(0), title_id); + // Get all the ncas associated with the current update CNMT and delete them + const auto& meta_old_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{}); + const auto& program_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{}); + const auto& data_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{}); + const auto& control_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{}); + const auto& html_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{}); + const auto& legal_id = + GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{}); + + delete_nca(meta_old_id); + delete_nca(program_id); + delete_nca(data_id); + delete_nca(control_id); + delete_nca(html_id); + delete_nca(legal_id); + return true; + } + return false; +} + InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_exists, const VfsCopyFunction& copy) { const auto ncas = nsp.GetNCAsCollapsed(); @@ -560,31 +611,44 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex return InstallResult::ErrorMetaFailed; } - // Install Metadata File const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); const auto meta_id = Common::HexStringToArray<16>(meta_id_raw); - const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id); - if (res != InstallResult::Success) - return res; - - // Install all the other NCAs const auto section0 = (*meta_iter)->GetSubdirectories()[0]; const auto cnmt_file = section0->GetFiles()[0]; const CNMT cnmt(cnmt_file); + + // Get the title id stored within the CNMT + const auto title_id = cnmt.GetTitleID(); + // Removes an entry if it exists + const auto result = RemoveExistingEntry(title_id); + + // Install Metadata File + const auto res = RawInstallNCA(**meta_iter, copy, overwrite_if_exists, meta_id); + if (res != InstallResult::Success) { + return res; + } + + // Install all the other NCAs for (const auto& record : cnmt.GetContentRecords()) { // Ignore DeltaFragments, they are not useful to us - if (record.type == ContentRecordType::DeltaFragment) + if (record.type == ContentRecordType::DeltaFragment) { continue; + } const auto nca = GetNCAFromNSPForID(nsp, record.nca_id); - if (nca == nullptr) + if (nca == nullptr) { return InstallResult::ErrorCopyFailed; + } const auto res2 = RawInstallNCA(*nca, copy, overwrite_if_exists, record.nca_id); - if (res2 != InstallResult::Success) + if (res2 != InstallResult::Success) { return res2; + } } Refresh(); + if (result) { + return InstallResult::ErrorAlreadyExists; + } return InstallResult::Success; } @@ -610,8 +674,9 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, mbedtls_sha256_ret(data.data(), data.size(), c_rec.hash.data(), 0); memcpy(&c_rec.nca_id, &c_rec.hash, 16); const CNMT new_cnmt(header, opt_header, {c_rec}, {}); - if (!RawInstallYuzuMeta(new_cnmt)) + if (!RawInstallYuzuMeta(new_cnmt)) { return InstallResult::ErrorMetaFailed; + } return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); } @@ -649,8 +714,9 @@ InstallResult RegisteredCache::RawInstallNCA(const NCA& nca, const VfsCopyFuncti } auto out = dir->CreateFileRelative(path); - if (out == nullptr) + if (out == nullptr) { return InstallResult::ErrorCopyFailed; + } return copy(in, out, VFS_RC_LARGE_COPY_BLOCK) ? InstallResult::Success : InstallResult::ErrorCopyFailed; } diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index f339cd17b9..8598f05432 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -34,6 +34,7 @@ using VfsCopyFunction = std::function title_type = {}, std::optional record_type = {}, std::optional title_id = {}) const override; + // Removes an existing entry based on title id + bool RemoveExistingEntry(const u64 title_id); + // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure // there is a meta NCA and all of them are accessible. InstallResult InstallEntry(const XCI& xci, bool overwrite_if_exists = false, From 8794e623d99d6179ab15f76ddf473002d112fc3a Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sun, 5 Jul 2020 10:38:34 -0400 Subject: [PATCH 2/5] Remove global system instance and address feedback --- src/core/file_sys/registered_cache.cpp | 22 +++++++++------------- src/core/file_sys/registered_cache.h | 2 +- 2 files changed, 10 insertions(+), 14 deletions(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 39bfbdfd32..0f4d52d35f 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -547,7 +547,7 @@ InstallResult RegisteredCache::InstallEntry(const XCI& xci, bool overwrite_if_ex return InstallEntry(*xci.GetSecurePartitionNSP(), overwrite_if_exists, copy); } -bool RegisteredCache::RemoveExistingEntry(const u64 title_id) { +bool RegisteredCache::RemoveExistingEntry(u64 title_id) { const auto delete_nca = [this](const NcaID& id) { const auto path = GetRelativePathFromNcaID(id, false, true, false); @@ -566,25 +566,23 @@ bool RegisteredCache::RemoveExistingEntry(const u64 title_id) { return res; }; - // Get the Content Provider - const auto& installed = Core::System::GetInstance().GetContentProvider(); // If an update exists, remove - if (installed.HasEntry(title_id, ContentRecordType::Meta)) { + if (HasEntry(title_id, ContentRecordType::Meta)) { LOG_INFO(Loader, "Previous Update (v{}) for title_id={:016X} detected! Attempting to remove...", - installed.GetEntryVersion(title_id).value_or(0), title_id); + GetEntryVersion(title_id).value_or(0), title_id); // Get all the ncas associated with the current update CNMT and delete them - const auto& meta_old_id = + const auto meta_old_id = GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{}); - const auto& program_id = + const auto program_id = GetNcaIDFromMetadata(title_id, ContentRecordType::Program).value_or(NcaID{}); - const auto& data_id = + const auto data_id = GetNcaIDFromMetadata(title_id, ContentRecordType::Data).value_or(NcaID{}); - const auto& control_id = + const auto control_id = GetNcaIDFromMetadata(title_id, ContentRecordType::Control).value_or(NcaID{}); - const auto& html_id = + const auto html_id = GetNcaIDFromMetadata(title_id, ContentRecordType::HtmlDocument).value_or(NcaID{}); - const auto& legal_id = + const auto legal_id = GetNcaIDFromMetadata(title_id, ContentRecordType::LegalInformation).value_or(NcaID{}); delete_nca(meta_old_id); @@ -618,9 +616,7 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex const auto cnmt_file = section0->GetFiles()[0]; const CNMT cnmt(cnmt_file); - // Get the title id stored within the CNMT const auto title_id = cnmt.GetTitleID(); - // Removes an entry if it exists const auto result = RemoveExistingEntry(title_id); // Install Metadata File diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 8598f05432..3459c695bd 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -156,7 +156,7 @@ public: std::optional title_id = {}) const override; // Removes an existing entry based on title id - bool RemoveExistingEntry(const u64 title_id); + bool RemoveExistingEntry(u64 title_id); // Raw copies all the ncas from the xci/nsp to the csache. Does some quick checks to make sure // there is a meta NCA and all of them are accessible. From 1bbc61f5f1f65478727fbe6351240331d77f104d Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sun, 12 Jul 2020 12:28:18 -0400 Subject: [PATCH 3/5] Use proper install result when overwriting files --- src/core/file_sys/registered_cache.cpp | 2 +- src/yuzu/main.cpp | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 0f4d52d35f..92417ef705 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -643,7 +643,7 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex Refresh(); if (result) { - return InstallResult::ErrorAlreadyExists; + return InstallResult::OverwriteExisting; } return InstallResult::Success; } diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4323797050..d51cb2bcb0 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp @@ -1755,7 +1755,7 @@ InstallResult GMainWindow::InstallNSPXCI(const QString& filename) { *nsp, true, qt_raw_copy); if (res == FileSys::InstallResult::Success) { return InstallResult::Success; - } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { + } else if (res == FileSys::InstallResult::OverwriteExisting) { return InstallResult::Overwrite; } else { return InstallResult::Failure; @@ -1842,7 +1842,7 @@ InstallResult GMainWindow::InstallNCA(const QString& filename) { if (res == FileSys::InstallResult::Success) { return InstallResult::Success; - } else if (res == FileSys::InstallResult::ErrorAlreadyExists) { + } else if (res == FileSys::InstallResult::OverwriteExisting) { return InstallResult::Overwrite; } else { return InstallResult::Failure; From 0ca7b8269af70ed80c60148fc966343f85034222 Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Sun, 5 Jul 2020 09:45:07 -0400 Subject: [PATCH 4/5] clang format --- src/core/file_sys/registered_cache.h | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index 3459c695bd..29cf0d40c7 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h @@ -133,9 +133,9 @@ public: // Parsing function defines the conversion from raw file to NCA. If there are other steps // besides creating the NCA from the file (e.g. NAX0 on SD Card), that should go in a custom // parsing function. - explicit RegisteredCache( - VirtualDir dir, ContentProviderParsingFunction parsing_function = - [](const VirtualFile& file, const NcaID& id) { return file; }); + explicit RegisteredCache(VirtualDir dir, + ContentProviderParsingFunction parsing_function = + [](const VirtualFile& file, const NcaID& id) { return file; }); ~RegisteredCache() override; void Refresh() override; From f66e3181dc3ead065330aa81a07e530643579f7f Mon Sep 17 00:00:00 2001 From: Morph <39850852+Morph1984@users.noreply.github.com> Date: Thu, 16 Jul 2020 05:22:51 -0400 Subject: [PATCH 5/5] Check for empty section0 and CNMT prior to install --- src/core/file_sys/registered_cache.cpp | 22 +++++++++++++++++++--- 1 file changed, 19 insertions(+), 3 deletions(-) diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 92417ef705..37351c5610 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp @@ -566,12 +566,13 @@ bool RegisteredCache::RemoveExistingEntry(u64 title_id) { return res; }; - // If an update exists, remove + // If an entry exists in the registered cache, remove it if (HasEntry(title_id, ContentRecordType::Meta)) { LOG_INFO(Loader, - "Previous Update (v{}) for title_id={:016X} detected! Attempting to remove...", + "Previously installed entry (v{}) for title_id={:016X} detected! " + "Attempting to remove...", GetEntryVersion(title_id).value_or(0), title_id); - // Get all the ncas associated with the current update CNMT and delete them + // Get all the ncas associated with the current CNMT and delete them const auto meta_old_id = GetNcaIDFromMetadata(title_id, ContentRecordType::Meta).value_or(NcaID{}); const auto program_id = @@ -612,7 +613,22 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); const auto meta_id = Common::HexStringToArray<16>(meta_id_raw); + if ((*meta_iter)->GetSubdirectories().empty()) { + LOG_ERROR(Loader, + "The file you are attempting to install does not contain a section0 within the " + "metadata NCA and is therefore malformed. Verify that the file is valid."); + return InstallResult::ErrorMetaFailed; + } + const auto section0 = (*meta_iter)->GetSubdirectories()[0]; + + if (section0->GetFiles().empty()) { + LOG_ERROR(Loader, + "The file you are attempting to install does not contain a CNMT within the " + "metadata NCA and is therefore malformed. Verify that the file is valid."); + return InstallResult::ErrorMetaFailed; + } + const auto cnmt_file = section0->GetFiles()[0]; const CNMT cnmt(cnmt_file);