Procházet zdrojové kódy

Merge pull request #11486 from liamwhite/system-verification

qt: add verification for installed contents
liamwhite před 2 roky
rodič
revize
3f52b5167b

+ 4 - 0
src/core/file_sys/nca_metadata.cpp

@@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_,
 
 CNMT::~CNMT() = default;
 
+const CNMTHeader& CNMT::GetHeader() const {
+    return header;
+}
+
 u64 CNMT::GetTitleID() const {
     return header.title_id;
 }

+ 1 - 0
src/core/file_sys/nca_metadata.h

@@ -89,6 +89,7 @@ public:
          std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_);
     ~CNMT();
 
+    const CNMTHeader& GetHeader() const;
     u64 GetTitleID() const;
     u32 GetTitleVersion() const;
     TitleType GetType() const;

+ 27 - 1
src/core/file_sys/registered_cache.cpp

@@ -9,6 +9,7 @@
 #include "common/fs/path_util.h"
 #include "common/hex_util.h"
 #include "common/logging/log.h"
+#include "common/scope_exit.h"
 #include "core/crypto/key_manager.h"
 #include "core/file_sys/card_image.h"
 #include "core/file_sys/common_funcs.h"
@@ -625,7 +626,7 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex
             nca->GetTitleId() != title_id) {
             // Create fake cnmt for patch to multiprogram application
             const auto sub_nca_result =
-                InstallEntry(*nca, TitleType::Update, overwrite_if_exists, copy);
+                InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy);
             if (sub_nca_result != InstallResult::Success) {
                 return sub_nca_result;
             }
@@ -672,6 +673,31 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type,
     return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id);
 }
 
+InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header,
+                                            const ContentRecord& base_record,
+                                            bool overwrite_if_exists, const VfsCopyFunction& copy) {
+    const CNMTHeader header{
+        .title_id = nca.GetTitleId(),
+        .title_version = base_header.title_version,
+        .type = base_header.type,
+        .reserved = {},
+        .table_offset = 0x10,
+        .number_content_entries = 1,
+        .number_meta_entries = 0,
+        .attributes = 0,
+        .reserved2 = {},
+        .is_committed = 0,
+        .required_download_system_version = 0,
+        .reserved3 = {},
+    };
+    const OptionalHeader opt_header{0, 0};
+    const CNMT new_cnmt(header, opt_header, {base_record}, {});
+    if (!RawInstallYuzuMeta(new_cnmt)) {
+        return InstallResult::ErrorMetaFailed;
+    }
+    return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id);
+}
+
 bool RegisteredCache::RemoveExistingEntry(u64 title_id) const {
     bool removed_data = false;
 

+ 5 - 0
src/core/file_sys/registered_cache.h

@@ -24,6 +24,7 @@ enum class NCAContentType : u8;
 enum class TitleType : u8;
 
 struct ContentRecord;
+struct CNMTHeader;
 struct MetaRecord;
 class RegisteredCache;
 
@@ -169,6 +170,10 @@ public:
     InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false,
                                const VfsCopyFunction& copy = &VfsRawCopy);
 
+    InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header,
+                               const ContentRecord& base_record, bool overwrite_if_exists = false,
+                               const VfsCopyFunction& copy = &VfsRawCopy);
+
     // Removes an existing entry based on title id
     bool RemoveExistingEntry(u64 title_id) const;
 

+ 104 - 0
src/yuzu/main.cpp

@@ -8,6 +8,7 @@
 #include <iostream>
 #include <memory>
 #include <thread>
+#include "core/loader/nca.h"
 #ifdef __APPLE__
 #include <unistd.h> // for chdir
 #endif
@@ -1554,6 +1555,7 @@ void GMainWindow::ConnectMenuEvents() {
 
     // Help
     connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder);
+    connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents);
     connect_menu(ui->action_About, &GMainWindow::OnAbout);
 }
 
@@ -4006,6 +4008,108 @@ void GMainWindow::OnOpenYuzuFolder() {
         QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir))));
 }
 
+void GMainWindow::OnVerifyInstalledContents() {
+    // Declare sizes.
+    size_t total_size = 0;
+    size_t processed_size = 0;
+
+    // Initialize a progress dialog.
+    QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this);
+    progress.setWindowModality(Qt::WindowModal);
+    progress.setMinimumDuration(100);
+    progress.setAutoClose(false);
+    progress.setAutoReset(false);
+
+    // Declare a list of file names which failed to verify.
+    std::vector<std::string> failed;
+
+    // Declare progress callback.
+    auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) {
+        if (progress.wasCanceled()) {
+            return false;
+        }
+        progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size));
+        return true;
+    };
+
+    // Get content registries.
+    auto bis_contents = system->GetFileSystemController().GetSystemNANDContents();
+    auto user_contents = system->GetFileSystemController().GetUserNANDContents();
+
+    std::vector<FileSys::RegisteredCache*> content_providers;
+    if (bis_contents) {
+        content_providers.push_back(bis_contents);
+    }
+    if (user_contents) {
+        content_providers.push_back(user_contents);
+    }
+
+    // Get associated NCA files.
+    std::vector<FileSys::VirtualFile> nca_files;
+
+    // Get all installed IDs.
+    for (auto nca_provider : content_providers) {
+        const auto entries = nca_provider->ListEntriesFilter();
+
+        for (const auto& entry : entries) {
+            auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type);
+            if (!nca_file) {
+                continue;
+            }
+
+            total_size += nca_file->GetSize();
+            nca_files.push_back(std::move(nca_file));
+        }
+    }
+
+    // Using the NCA loader, determine if all NCAs are valid.
+    for (auto& nca_file : nca_files) {
+        Loader::AppLoader_NCA nca_loader(nca_file);
+
+        auto status = nca_loader.VerifyIntegrity(QtProgressCallback);
+        if (progress.wasCanceled()) {
+            break;
+        }
+        if (status != Loader::ResultStatus::Success) {
+            FileSys::NCA nca(nca_file);
+            const auto title_id = nca.GetTitleId();
+            std::string title_name = "unknown";
+
+            const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id),
+                                                    FileSys::ContentRecordType::Control);
+            if (control && control->GetStatus() == Loader::ResultStatus::Success) {
+                const FileSys::PatchManager pm{title_id, system->GetFileSystemController(),
+                                               *provider};
+                const auto [nacp, logo] = pm.ParseControlNCA(*control);
+                if (nacp) {
+                    title_name = nacp->GetApplicationName();
+                }
+            }
+
+            if (title_id > 0) {
+                failed.push_back(
+                    fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name));
+            } else {
+                failed.push_back(fmt::format("{} (unknown)", nca_file->GetName()));
+            }
+        }
+
+        processed_size += nca_file->GetSize();
+    }
+
+    progress.close();
+
+    if (failed.size() > 0) {
+        auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n")));
+        QMessageBox::critical(
+            this, tr("Integrity verification failed!"),
+            tr("Verification failed for the following files:\n\n%1").arg(failed_names));
+    } else {
+        QMessageBox::information(this, tr("Integrity verification succeeded!"),
+                                 tr("The operation completed successfully."));
+    }
+}
+
 void GMainWindow::OnAbout() {
     AboutDialog aboutDialog(this);
     aboutDialog.exec();

+ 1 - 0
src/yuzu/main.h

@@ -350,6 +350,7 @@ private slots:
     void OnConfigurePerGame();
     void OnLoadAmiibo();
     void OnOpenYuzuFolder();
+    void OnVerifyInstalledContents();
     void OnAbout();
     void OnToggleFilterBar();
     void OnToggleStatusBar();

+ 6 - 0
src/yuzu/main.ui

@@ -148,6 +148,7 @@
      <addaction name="action_Configure_Tas"/>
     </widget>
     <addaction name="action_Rederive"/>
+    <addaction name="action_Verify_installed_contents"/>
     <addaction name="separator"/>
     <addaction name="action_Capture_Screenshot"/>
     <addaction name="menuTAS"/>
@@ -214,6 +215,11 @@
     <string>&amp;Reinitialize keys...</string>
    </property>
   </action>
+  <action name="action_Verify_installed_contents">
+   <property name="text">
+    <string>Verify installed contents</string>
+   </property>
+  </action>
   <action name="action_About">
    <property name="text">
     <string>&amp;About yuzu</string>