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] 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 27c1b0233..39bfbdfd3 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 f339cd17b..8598f0543 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<bool(const VirtualFile&, const VirtualFile
 
 enum class InstallResult {
     Success,
+    OverwriteExisting,
     ErrorAlreadyExists,
     ErrorCopyFailed,
     ErrorMetaFailed,
@@ -132,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;
@@ -154,6 +155,9 @@ public:
         std::optional<TitleType> title_type = {}, std::optional<ContentRecordType> record_type = {},
         std::optional<u64> 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,