diff options
| author | 2023-09-11 23:50:36 -0400 | |
|---|---|---|
| committer | 2023-09-12 09:20:50 -0400 | |
| commit | f8985d1cc5f7378ab2bca2526a61d6aff778d36e (patch) | |
| tree | e7f492f817486b30549351951163df3ac0b8f20e | |
| parent | ci: fix msvc when used with LTO (#11459) (diff) | |
| download | yuzu-f8985d1cc5f7378ab2bca2526a61d6aff778d36e.tar.gz yuzu-f8985d1cc5f7378ab2bca2526a61d6aff778d36e.tar.xz yuzu-f8985d1cc5f7378ab2bca2526a61d6aff778d36e.zip | |
qt: add verification for installed contents
Diffstat (limited to '')
| -rw-r--r-- | src/core/file_sys/nca_metadata.cpp | 4 | ||||
| -rw-r--r-- | src/core/file_sys/nca_metadata.h | 1 | ||||
| -rw-r--r-- | src/core/file_sys/registered_cache.cpp | 28 | ||||
| -rw-r--r-- | src/core/file_sys/registered_cache.h | 5 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 104 | ||||
| -rw-r--r-- | src/yuzu/main.h | 1 | ||||
| -rw-r--r-- | src/yuzu/main.ui | 6 |
7 files changed, 148 insertions, 1 deletions
diff --git a/src/core/file_sys/nca_metadata.cpp b/src/core/file_sys/nca_metadata.cpp index 52c78020c..f4a774675 100644 --- a/src/core/file_sys/nca_metadata.cpp +++ b/src/core/file_sys/nca_metadata.cpp | |||
| @@ -45,6 +45,10 @@ CNMT::CNMT(CNMTHeader header_, OptionalHeader opt_header_, | |||
| 45 | 45 | ||
| 46 | CNMT::~CNMT() = default; | 46 | CNMT::~CNMT() = default; |
| 47 | 47 | ||
| 48 | const CNMTHeader& CNMT::GetHeader() const { | ||
| 49 | return header; | ||
| 50 | } | ||
| 51 | |||
| 48 | u64 CNMT::GetTitleID() const { | 52 | u64 CNMT::GetTitleID() const { |
| 49 | return header.title_id; | 53 | return header.title_id; |
| 50 | } | 54 | } |
diff --git a/src/core/file_sys/nca_metadata.h b/src/core/file_sys/nca_metadata.h index c59ece010..68e463b5f 100644 --- a/src/core/file_sys/nca_metadata.h +++ b/src/core/file_sys/nca_metadata.h | |||
| @@ -89,6 +89,7 @@ public: | |||
| 89 | std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_); | 89 | std::vector<ContentRecord> content_records_, std::vector<MetaRecord> meta_records_); |
| 90 | ~CNMT(); | 90 | ~CNMT(); |
| 91 | 91 | ||
| 92 | const CNMTHeader& GetHeader() const; | ||
| 92 | u64 GetTitleID() const; | 93 | u64 GetTitleID() const; |
| 93 | u32 GetTitleVersion() const; | 94 | u32 GetTitleVersion() const; |
| 94 | TitleType GetType() const; | 95 | TitleType GetType() const; |
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index f70adab82..e33b00d89 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp | |||
| @@ -9,6 +9,7 @@ | |||
| 9 | #include "common/fs/path_util.h" | 9 | #include "common/fs/path_util.h" |
| 10 | #include "common/hex_util.h" | 10 | #include "common/hex_util.h" |
| 11 | #include "common/logging/log.h" | 11 | #include "common/logging/log.h" |
| 12 | #include "common/scope_exit.h" | ||
| 12 | #include "core/crypto/key_manager.h" | 13 | #include "core/crypto/key_manager.h" |
| 13 | #include "core/file_sys/card_image.h" | 14 | #include "core/file_sys/card_image.h" |
| 14 | #include "core/file_sys/common_funcs.h" | 15 | #include "core/file_sys/common_funcs.h" |
| @@ -625,7 +626,7 @@ InstallResult RegisteredCache::InstallEntry(const NSP& nsp, bool overwrite_if_ex | |||
| 625 | nca->GetTitleId() != title_id) { | 626 | nca->GetTitleId() != title_id) { |
| 626 | // Create fake cnmt for patch to multiprogram application | 627 | // Create fake cnmt for patch to multiprogram application |
| 627 | const auto sub_nca_result = | 628 | const auto sub_nca_result = |
| 628 | InstallEntry(*nca, TitleType::Update, overwrite_if_exists, copy); | 629 | InstallEntry(*nca, cnmt.GetHeader(), record, overwrite_if_exists, copy); |
| 629 | if (sub_nca_result != InstallResult::Success) { | 630 | if (sub_nca_result != InstallResult::Success) { |
| 630 | return sub_nca_result; | 631 | return sub_nca_result; |
| 631 | } | 632 | } |
| @@ -672,6 +673,31 @@ InstallResult RegisteredCache::InstallEntry(const NCA& nca, TitleType type, | |||
| 672 | return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); | 673 | return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); |
| 673 | } | 674 | } |
| 674 | 675 | ||
| 676 | InstallResult RegisteredCache::InstallEntry(const NCA& nca, const CNMTHeader& base_header, | ||
| 677 | const ContentRecord& base_record, | ||
| 678 | bool overwrite_if_exists, const VfsCopyFunction& copy) { | ||
| 679 | const CNMTHeader header{ | ||
| 680 | .title_id = nca.GetTitleId(), | ||
| 681 | .title_version = base_header.title_version, | ||
| 682 | .type = base_header.type, | ||
| 683 | .reserved = {}, | ||
| 684 | .table_offset = 0x10, | ||
| 685 | .number_content_entries = 1, | ||
| 686 | .number_meta_entries = 0, | ||
| 687 | .attributes = 0, | ||
| 688 | .reserved2 = {}, | ||
| 689 | .is_committed = 0, | ||
| 690 | .required_download_system_version = 0, | ||
| 691 | .reserved3 = {}, | ||
| 692 | }; | ||
| 693 | const OptionalHeader opt_header{0, 0}; | ||
| 694 | const CNMT new_cnmt(header, opt_header, {base_record}, {}); | ||
| 695 | if (!RawInstallYuzuMeta(new_cnmt)) { | ||
| 696 | return InstallResult::ErrorMetaFailed; | ||
| 697 | } | ||
| 698 | return RawInstallNCA(nca, copy, overwrite_if_exists, base_record.nca_id); | ||
| 699 | } | ||
| 700 | |||
| 675 | bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { | 701 | bool RegisteredCache::RemoveExistingEntry(u64 title_id) const { |
| 676 | bool removed_data = false; | 702 | bool removed_data = false; |
| 677 | 703 | ||
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index bd7f53eaf..64815a845 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h | |||
| @@ -24,6 +24,7 @@ enum class NCAContentType : u8; | |||
| 24 | enum class TitleType : u8; | 24 | enum class TitleType : u8; |
| 25 | 25 | ||
| 26 | struct ContentRecord; | 26 | struct ContentRecord; |
| 27 | struct CNMTHeader; | ||
| 27 | struct MetaRecord; | 28 | struct MetaRecord; |
| 28 | class RegisteredCache; | 29 | class RegisteredCache; |
| 29 | 30 | ||
| @@ -169,6 +170,10 @@ public: | |||
| 169 | InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, | 170 | InstallResult InstallEntry(const NCA& nca, TitleType type, bool overwrite_if_exists = false, |
| 170 | const VfsCopyFunction& copy = &VfsRawCopy); | 171 | const VfsCopyFunction& copy = &VfsRawCopy); |
| 171 | 172 | ||
| 173 | InstallResult InstallEntry(const NCA& nca, const CNMTHeader& base_header, | ||
| 174 | const ContentRecord& base_record, bool overwrite_if_exists = false, | ||
| 175 | const VfsCopyFunction& copy = &VfsRawCopy); | ||
| 176 | |||
| 172 | // Removes an existing entry based on title id | 177 | // Removes an existing entry based on title id |
| 173 | bool RemoveExistingEntry(u64 title_id) const; | 178 | bool RemoveExistingEntry(u64 title_id) const; |
| 174 | 179 | ||
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 9cea60c32..fc2055a7b 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -8,6 +8,7 @@ | |||
| 8 | #include <iostream> | 8 | #include <iostream> |
| 9 | #include <memory> | 9 | #include <memory> |
| 10 | #include <thread> | 10 | #include <thread> |
| 11 | #include "core/loader/nca.h" | ||
| 11 | #ifdef __APPLE__ | 12 | #ifdef __APPLE__ |
| 12 | #include <unistd.h> // for chdir | 13 | #include <unistd.h> // for chdir |
| 13 | #endif | 14 | #endif |
| @@ -1554,6 +1555,7 @@ void GMainWindow::ConnectMenuEvents() { | |||
| 1554 | 1555 | ||
| 1555 | // Help | 1556 | // Help |
| 1556 | connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); | 1557 | connect_menu(ui->action_Open_yuzu_Folder, &GMainWindow::OnOpenYuzuFolder); |
| 1558 | connect_menu(ui->action_Verify_installed_contents, &GMainWindow::OnVerifyInstalledContents); | ||
| 1557 | connect_menu(ui->action_About, &GMainWindow::OnAbout); | 1559 | connect_menu(ui->action_About, &GMainWindow::OnAbout); |
| 1558 | } | 1560 | } |
| 1559 | 1561 | ||
| @@ -4000,6 +4002,108 @@ void GMainWindow::OnOpenYuzuFolder() { | |||
| 4000 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir)))); | 4002 | QString::fromStdString(Common::FS::GetYuzuPathString(Common::FS::YuzuPath::YuzuDir)))); |
| 4001 | } | 4003 | } |
| 4002 | 4004 | ||
| 4005 | void GMainWindow::OnVerifyInstalledContents() { | ||
| 4006 | // Declare sizes. | ||
| 4007 | size_t total_size = 0; | ||
| 4008 | size_t processed_size = 0; | ||
| 4009 | |||
| 4010 | // Initialize a progress dialog. | ||
| 4011 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | ||
| 4012 | progress.setWindowModality(Qt::WindowModal); | ||
| 4013 | progress.setMinimumDuration(100); | ||
| 4014 | progress.setAutoClose(false); | ||
| 4015 | progress.setAutoReset(false); | ||
| 4016 | |||
| 4017 | // Declare a list of file names which failed to verify. | ||
| 4018 | std::vector<std::string> failed; | ||
| 4019 | |||
| 4020 | // Declare progress callback. | ||
| 4021 | auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) { | ||
| 4022 | if (progress.wasCanceled()) { | ||
| 4023 | return false; | ||
| 4024 | } | ||
| 4025 | progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size)); | ||
| 4026 | return true; | ||
| 4027 | }; | ||
| 4028 | |||
| 4029 | // Get content registries. | ||
| 4030 | auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); | ||
| 4031 | auto user_contents = system->GetFileSystemController().GetUserNANDContents(); | ||
| 4032 | |||
| 4033 | std::vector<FileSys::RegisteredCache*> content_providers; | ||
| 4034 | if (bis_contents) { | ||
| 4035 | content_providers.push_back(bis_contents); | ||
| 4036 | } | ||
| 4037 | if (user_contents) { | ||
| 4038 | content_providers.push_back(user_contents); | ||
| 4039 | } | ||
| 4040 | |||
| 4041 | // Get associated NCA files. | ||
| 4042 | std::vector<FileSys::VirtualFile> nca_files; | ||
| 4043 | |||
| 4044 | // Get all installed IDs. | ||
| 4045 | for (auto nca_provider : content_providers) { | ||
| 4046 | const auto entries = nca_provider->ListEntriesFilter(); | ||
| 4047 | |||
| 4048 | for (const auto& entry : entries) { | ||
| 4049 | auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); | ||
| 4050 | if (!nca_file) { | ||
| 4051 | continue; | ||
| 4052 | } | ||
| 4053 | |||
| 4054 | total_size += nca_file->GetSize(); | ||
| 4055 | nca_files.push_back(std::move(nca_file)); | ||
| 4056 | } | ||
| 4057 | } | ||
| 4058 | |||
| 4059 | // Using the NCA loader, determine if all NCAs are valid. | ||
| 4060 | for (auto& nca_file : nca_files) { | ||
| 4061 | Loader::AppLoader_NCA nca_loader(nca_file); | ||
| 4062 | |||
| 4063 | auto status = nca_loader.VerifyIntegrity(QtProgressCallback); | ||
| 4064 | if (progress.wasCanceled()) { | ||
| 4065 | break; | ||
| 4066 | } | ||
| 4067 | if (status != Loader::ResultStatus::Success) { | ||
| 4068 | FileSys::NCA nca(nca_file); | ||
| 4069 | const auto title_id = nca.GetTitleId(); | ||
| 4070 | std::string title_name = "unknown"; | ||
| 4071 | |||
| 4072 | const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), | ||
| 4073 | FileSys::ContentRecordType::Control); | ||
| 4074 | if (control && control->GetStatus() == Loader::ResultStatus::Success) { | ||
| 4075 | const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), | ||
| 4076 | *provider}; | ||
| 4077 | const auto [nacp, logo] = pm.ParseControlNCA(*control); | ||
| 4078 | if (nacp) { | ||
| 4079 | title_name = nacp->GetApplicationName(); | ||
| 4080 | } | ||
| 4081 | } | ||
| 4082 | |||
| 4083 | if (title_id > 0) { | ||
| 4084 | failed.push_back( | ||
| 4085 | fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); | ||
| 4086 | } else { | ||
| 4087 | failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); | ||
| 4088 | } | ||
| 4089 | } | ||
| 4090 | |||
| 4091 | processed_size += nca_file->GetSize(); | ||
| 4092 | } | ||
| 4093 | |||
| 4094 | progress.close(); | ||
| 4095 | |||
| 4096 | if (failed.size() > 0) { | ||
| 4097 | auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n"))); | ||
| 4098 | QMessageBox::critical( | ||
| 4099 | this, tr("Integrity verification failed!"), | ||
| 4100 | tr("Verification failed for the following files:\n\n%1").arg(failed_names)); | ||
| 4101 | } else { | ||
| 4102 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 4103 | tr("The operation completed successfully.")); | ||
| 4104 | } | ||
| 4105 | } | ||
| 4106 | |||
| 4003 | void GMainWindow::OnAbout() { | 4107 | void GMainWindow::OnAbout() { |
| 4004 | AboutDialog aboutDialog(this); | 4108 | AboutDialog aboutDialog(this); |
| 4005 | aboutDialog.exec(); | 4109 | aboutDialog.exec(); |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 1e4f6e477..7b4820ecd 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -343,6 +343,7 @@ private slots: | |||
| 343 | void OnConfigurePerGame(); | 343 | void OnConfigurePerGame(); |
| 344 | void OnLoadAmiibo(); | 344 | void OnLoadAmiibo(); |
| 345 | void OnOpenYuzuFolder(); | 345 | void OnOpenYuzuFolder(); |
| 346 | void OnVerifyInstalledContents(); | ||
| 346 | void OnAbout(); | 347 | void OnAbout(); |
| 347 | void OnToggleFilterBar(); | 348 | void OnToggleFilterBar(); |
| 348 | void OnToggleStatusBar(); | 349 | void OnToggleStatusBar(); |
diff --git a/src/yuzu/main.ui b/src/yuzu/main.ui index 013ba0ceb..e54d7d75d 100644 --- a/src/yuzu/main.ui +++ b/src/yuzu/main.ui | |||
| @@ -148,6 +148,7 @@ | |||
| 148 | <addaction name="action_Configure_Tas"/> | 148 | <addaction name="action_Configure_Tas"/> |
| 149 | </widget> | 149 | </widget> |
| 150 | <addaction name="action_Rederive"/> | 150 | <addaction name="action_Rederive"/> |
| 151 | <addaction name="action_Verify_installed_contents"/> | ||
| 151 | <addaction name="separator"/> | 152 | <addaction name="separator"/> |
| 152 | <addaction name="action_Capture_Screenshot"/> | 153 | <addaction name="action_Capture_Screenshot"/> |
| 153 | <addaction name="menuTAS"/> | 154 | <addaction name="menuTAS"/> |
| @@ -214,6 +215,11 @@ | |||
| 214 | <string>&Reinitialize keys...</string> | 215 | <string>&Reinitialize keys...</string> |
| 215 | </property> | 216 | </property> |
| 216 | </action> | 217 | </action> |
| 218 | <action name="action_Verify_installed_contents"> | ||
| 219 | <property name="text"> | ||
| 220 | <string>Verify installed contents</string> | ||
| 221 | </property> | ||
| 222 | </action> | ||
| 217 | <action name="action_About"> | 223 | <action name="action_About"> |
| 218 | <property name="text"> | 224 | <property name="text"> |
| 219 | <string>&About yuzu</string> | 225 | <string>&About yuzu</string> |