diff options
Diffstat (limited to '')
| -rw-r--r-- | src/core/loader/loader.cpp | 4 | ||||
| -rw-r--r-- | src/core/loader/loader.h | 10 | ||||
| -rw-r--r-- | src/core/loader/nca.cpp | 76 | ||||
| -rw-r--r-- | src/core/loader/nca.h | 2 | ||||
| -rw-r--r-- | src/core/loader/nsp.cpp | 36 | ||||
| -rw-r--r-- | src/core/loader/nsp.h | 2 | ||||
| -rw-r--r-- | src/core/loader/xci.cpp | 34 | ||||
| -rw-r--r-- | src/core/loader/xci.h | 2 | ||||
| -rw-r--r-- | src/yuzu/game_list.cpp | 3 | ||||
| -rw-r--r-- | src/yuzu/game_list.h | 1 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 50 | ||||
| -rw-r--r-- | src/yuzu/main.h | 1 |
12 files changed, 220 insertions, 1 deletions
diff --git a/src/core/loader/loader.cpp b/src/core/loader/loader.cpp index 07c65dc1a..b6e355622 100644 --- a/src/core/loader/loader.cpp +++ b/src/core/loader/loader.cpp | |||
| @@ -108,7 +108,7 @@ std::string GetFileTypeString(FileType type) { | |||
| 108 | return "unknown"; | 108 | return "unknown"; |
| 109 | } | 109 | } |
| 110 | 110 | ||
| 111 | constexpr std::array<const char*, 66> RESULT_MESSAGES{ | 111 | constexpr std::array<const char*, 68> RESULT_MESSAGES{ |
| 112 | "The operation completed successfully.", | 112 | "The operation completed successfully.", |
| 113 | "The loader requested to load is already loaded.", | 113 | "The loader requested to load is already loaded.", |
| 114 | "The operation is not implemented.", | 114 | "The operation is not implemented.", |
| @@ -175,6 +175,8 @@ constexpr std::array<const char*, 66> RESULT_MESSAGES{ | |||
| 175 | "The KIP BLZ decompression of the section failed unexpectedly.", | 175 | "The KIP BLZ decompression of the section failed unexpectedly.", |
| 176 | "The INI file has a bad header.", | 176 | "The INI file has a bad header.", |
| 177 | "The INI file contains more than the maximum allowable number of KIP files.", | 177 | "The INI file contains more than the maximum allowable number of KIP files.", |
| 178 | "Integrity verification could not be performed for this file.", | ||
| 179 | "Integrity verification failed.", | ||
| 178 | }; | 180 | }; |
| 179 | 181 | ||
| 180 | std::string GetResultStatusString(ResultStatus status) { | 182 | std::string GetResultStatusString(ResultStatus status) { |
diff --git a/src/core/loader/loader.h b/src/core/loader/loader.h index 721eb8e8c..b4828f7cd 100644 --- a/src/core/loader/loader.h +++ b/src/core/loader/loader.h | |||
| @@ -3,6 +3,7 @@ | |||
| 3 | 3 | ||
| 4 | #pragma once | 4 | #pragma once |
| 5 | 5 | ||
| 6 | #include <functional> | ||
| 6 | #include <iosfwd> | 7 | #include <iosfwd> |
| 7 | #include <memory> | 8 | #include <memory> |
| 8 | #include <optional> | 9 | #include <optional> |
| @@ -132,6 +133,8 @@ enum class ResultStatus : u16 { | |||
| 132 | ErrorBLZDecompressionFailed, | 133 | ErrorBLZDecompressionFailed, |
| 133 | ErrorBadINIHeader, | 134 | ErrorBadINIHeader, |
| 134 | ErrorINITooManyKIPs, | 135 | ErrorINITooManyKIPs, |
| 136 | ErrorIntegrityVerificationNotImplemented, | ||
| 137 | ErrorIntegrityVerificationFailed, | ||
| 135 | }; | 138 | }; |
| 136 | 139 | ||
| 137 | std::string GetResultStatusString(ResultStatus status); | 140 | std::string GetResultStatusString(ResultStatus status); |
| @@ -170,6 +173,13 @@ public: | |||
| 170 | virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; | 173 | virtual LoadResult Load(Kernel::KProcess& process, Core::System& system) = 0; |
| 171 | 174 | ||
| 172 | /** | 175 | /** |
| 176 | * Try to verify the integrity of the file. | ||
| 177 | */ | ||
| 178 | virtual ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 179 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 180 | } | ||
| 181 | |||
| 182 | /** | ||
| 173 | * Get the code (typically .code section) of the application | 183 | * Get the code (typically .code section) of the application |
| 174 | * | 184 | * |
| 175 | * @param[out] buffer Reference to buffer to store data | 185 | * @param[out] buffer Reference to buffer to store data |
diff --git a/src/core/loader/nca.cpp b/src/core/loader/nca.cpp index 09d40e695..4feb6968a 100644 --- a/src/core/loader/nca.cpp +++ b/src/core/loader/nca.cpp | |||
| @@ -3,6 +3,8 @@ | |||
| 3 | 3 | ||
| 4 | #include <utility> | 4 | #include <utility> |
| 5 | 5 | ||
| 6 | #include "common/hex_util.h" | ||
| 7 | #include "common/scope_exit.h" | ||
| 6 | #include "core/core.h" | 8 | #include "core/core.h" |
| 7 | #include "core/file_sys/content_archive.h" | 9 | #include "core/file_sys/content_archive.h" |
| 8 | #include "core/file_sys/nca_metadata.h" | 10 | #include "core/file_sys/nca_metadata.h" |
| @@ -12,6 +14,7 @@ | |||
| 12 | #include "core/hle/service/filesystem/filesystem.h" | 14 | #include "core/hle/service/filesystem/filesystem.h" |
| 13 | #include "core/loader/deconstructed_rom_directory.h" | 15 | #include "core/loader/deconstructed_rom_directory.h" |
| 14 | #include "core/loader/nca.h" | 16 | #include "core/loader/nca.h" |
| 17 | #include "mbedtls/sha256.h" | ||
| 15 | 18 | ||
| 16 | namespace Loader { | 19 | namespace Loader { |
| 17 | 20 | ||
| @@ -80,6 +83,79 @@ AppLoader_NCA::LoadResult AppLoader_NCA::Load(Kernel::KProcess& process, Core::S | |||
| 80 | return load_result; | 83 | return load_result; |
| 81 | } | 84 | } |
| 82 | 85 | ||
| 86 | ResultStatus AppLoader_NCA::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 87 | using namespace Common::Literals; | ||
| 88 | |||
| 89 | constexpr size_t NcaFileNameWithHashLength = 36; | ||
| 90 | constexpr size_t NcaFileNameHashLength = 32; | ||
| 91 | constexpr size_t NcaSha256HashLength = 32; | ||
| 92 | constexpr size_t NcaSha256HalfHashLength = NcaSha256HashLength / 2; | ||
| 93 | |||
| 94 | // Get the file name. | ||
| 95 | const auto name = file->GetName(); | ||
| 96 | |||
| 97 | // We won't try to verify meta NCAs. | ||
| 98 | if (name.ends_with(".cnmt.nca")) { | ||
| 99 | return ResultStatus::Success; | ||
| 100 | } | ||
| 101 | |||
| 102 | // Check if we can verify this file. NCAs should be named after their hashes. | ||
| 103 | if (!name.ends_with(".nca") || name.size() != NcaFileNameWithHashLength) { | ||
| 104 | LOG_WARNING(Loader, "Unable to validate NCA with name {}", name); | ||
| 105 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 106 | } | ||
| 107 | |||
| 108 | // Get the expected truncated hash of the NCA. | ||
| 109 | const auto input_hash = | ||
| 110 | Common::HexStringToVector(file->GetName().substr(0, NcaFileNameHashLength), false); | ||
| 111 | |||
| 112 | // Declare buffer to read into. | ||
| 113 | std::vector<u8> buffer(4_MiB); | ||
| 114 | |||
| 115 | // Initialize sha256 verification context. | ||
| 116 | mbedtls_sha256_context ctx; | ||
| 117 | mbedtls_sha256_init(&ctx); | ||
| 118 | mbedtls_sha256_starts_ret(&ctx, 0); | ||
| 119 | |||
| 120 | // Ensure we maintain a clean state on exit. | ||
| 121 | SCOPE_EXIT({ mbedtls_sha256_free(&ctx); }); | ||
| 122 | |||
| 123 | // Declare counters. | ||
| 124 | const size_t total_size = file->GetSize(); | ||
| 125 | size_t processed_size = 0; | ||
| 126 | |||
| 127 | // Begin iterating the file. | ||
| 128 | while (processed_size < total_size) { | ||
| 129 | // Refill the buffer. | ||
| 130 | const size_t intended_read_size = std::min(buffer.size(), total_size - processed_size); | ||
| 131 | const size_t read_size = file->Read(buffer.data(), intended_read_size, processed_size); | ||
| 132 | |||
| 133 | // Update the hash function with the buffer contents. | ||
| 134 | mbedtls_sha256_update_ret(&ctx, buffer.data(), read_size); | ||
| 135 | |||
| 136 | // Update counters. | ||
| 137 | processed_size += read_size; | ||
| 138 | |||
| 139 | // Call the progress function. | ||
| 140 | if (!progress_callback(processed_size, total_size)) { | ||
| 141 | return ResultStatus::ErrorIntegrityVerificationFailed; | ||
| 142 | } | ||
| 143 | } | ||
| 144 | |||
| 145 | // Finalize context and compute the output hash. | ||
| 146 | std::array<u8, NcaSha256HashLength> output_hash; | ||
| 147 | mbedtls_sha256_finish_ret(&ctx, output_hash.data()); | ||
| 148 | |||
| 149 | // Compare to expected. | ||
| 150 | if (std::memcmp(input_hash.data(), output_hash.data(), NcaSha256HalfHashLength) != 0) { | ||
| 151 | LOG_ERROR(Loader, "NCA hash mismatch detected for file {}", name); | ||
| 152 | return ResultStatus::ErrorIntegrityVerificationFailed; | ||
| 153 | } | ||
| 154 | |||
| 155 | // File verified. | ||
| 156 | return ResultStatus::Success; | ||
| 157 | } | ||
| 158 | |||
| 83 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { | 159 | ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { |
| 84 | if (nca == nullptr) { | 160 | if (nca == nullptr) { |
| 85 | return ResultStatus::ErrorNotInitialized; | 161 | return ResultStatus::ErrorNotInitialized; |
diff --git a/src/core/loader/nca.h b/src/core/loader/nca.h index cf356ce63..96779e27f 100644 --- a/src/core/loader/nca.h +++ b/src/core/loader/nca.h | |||
| @@ -39,6 +39,8 @@ public: | |||
| 39 | 39 | ||
| 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 40 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 41 | 41 | ||
| 42 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 43 | |||
| 42 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; | 44 | ResultStatus ReadRomFS(FileSys::VirtualFile& dir) override; |
| 43 | ResultStatus ReadProgramId(u64& out_program_id) override; | 45 | ResultStatus ReadProgramId(u64& out_program_id) override; |
| 44 | 46 | ||
diff --git a/src/core/loader/nsp.cpp b/src/core/loader/nsp.cpp index f9b2549a3..fe2af1ae6 100644 --- a/src/core/loader/nsp.cpp +++ b/src/core/loader/nsp.cpp | |||
| @@ -117,6 +117,42 @@ AppLoader_NSP::LoadResult AppLoader_NSP::Load(Kernel::KProcess& process, Core::S | |||
| 117 | return result; | 117 | return result; |
| 118 | } | 118 | } |
| 119 | 119 | ||
| 120 | ResultStatus AppLoader_NSP::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 121 | // Extracted-type NSPs can't be verified. | ||
| 122 | if (nsp->IsExtractedType()) { | ||
| 123 | return ResultStatus::ErrorIntegrityVerificationNotImplemented; | ||
| 124 | } | ||
| 125 | |||
| 126 | // Get list of all NCAs. | ||
| 127 | const auto ncas = nsp->GetNCAsCollapsed(); | ||
| 128 | |||
| 129 | size_t total_size = 0; | ||
| 130 | size_t processed_size = 0; | ||
| 131 | |||
| 132 | // Loop over NCAs, collecting the total size to verify. | ||
| 133 | for (const auto& nca : ncas) { | ||
| 134 | total_size += nca->GetBaseFile()->GetSize(); | ||
| 135 | } | ||
| 136 | |||
| 137 | // Loop over NCAs again, verifying each. | ||
| 138 | for (const auto& nca : ncas) { | ||
| 139 | AppLoader_NCA loader_nca(nca->GetBaseFile()); | ||
| 140 | |||
| 141 | const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { | ||
| 142 | return progress_callback(processed_size + nca_processed_size, total_size); | ||
| 143 | }; | ||
| 144 | |||
| 145 | const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); | ||
| 146 | if (verification_result != ResultStatus::Success) { | ||
| 147 | return verification_result; | ||
| 148 | } | ||
| 149 | |||
| 150 | processed_size += nca->GetBaseFile()->GetSize(); | ||
| 151 | } | ||
| 152 | |||
| 153 | return ResultStatus::Success; | ||
| 154 | } | ||
| 155 | |||
| 120 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { | 156 | ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { |
| 121 | return secondary_loader->ReadRomFS(out_file); | 157 | return secondary_loader->ReadRomFS(out_file); |
| 122 | } | 158 | } |
diff --git a/src/core/loader/nsp.h b/src/core/loader/nsp.h index 79df4586a..7ce436c67 100644 --- a/src/core/loader/nsp.h +++ b/src/core/loader/nsp.h | |||
| @@ -45,6 +45,8 @@ public: | |||
| 45 | 45 | ||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 49 | |||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 50 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 51 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 50 | ResultStatus ReadProgramId(u64& out_program_id) override; | 52 | ResultStatus ReadProgramId(u64& out_program_id) override; |
diff --git a/src/core/loader/xci.cpp b/src/core/loader/xci.cpp index 3a76bc788..12d72c380 100644 --- a/src/core/loader/xci.cpp +++ b/src/core/loader/xci.cpp | |||
| @@ -85,6 +85,40 @@ AppLoader_XCI::LoadResult AppLoader_XCI::Load(Kernel::KProcess& process, Core::S | |||
| 85 | return result; | 85 | return result; |
| 86 | } | 86 | } |
| 87 | 87 | ||
| 88 | ResultStatus AppLoader_XCI::VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) { | ||
| 89 | // Verify secure partition, as it is the only thing we can process. | ||
| 90 | auto secure_partition = xci->GetSecurePartitionNSP(); | ||
| 91 | |||
| 92 | // Get list of all NCAs. | ||
| 93 | const auto ncas = secure_partition->GetNCAsCollapsed(); | ||
| 94 | |||
| 95 | size_t total_size = 0; | ||
| 96 | size_t processed_size = 0; | ||
| 97 | |||
| 98 | // Loop over NCAs, collecting the total size to verify. | ||
| 99 | for (const auto& nca : ncas) { | ||
| 100 | total_size += nca->GetBaseFile()->GetSize(); | ||
| 101 | } | ||
| 102 | |||
| 103 | // Loop over NCAs again, verifying each. | ||
| 104 | for (const auto& nca : ncas) { | ||
| 105 | AppLoader_NCA loader_nca(nca->GetBaseFile()); | ||
| 106 | |||
| 107 | const auto NcaProgressCallback = [&](size_t nca_processed_size, size_t nca_total_size) { | ||
| 108 | return progress_callback(processed_size + nca_processed_size, total_size); | ||
| 109 | }; | ||
| 110 | |||
| 111 | const auto verification_result = loader_nca.VerifyIntegrity(NcaProgressCallback); | ||
| 112 | if (verification_result != ResultStatus::Success) { | ||
| 113 | return verification_result; | ||
| 114 | } | ||
| 115 | |||
| 116 | processed_size += nca->GetBaseFile()->GetSize(); | ||
| 117 | } | ||
| 118 | |||
| 119 | return ResultStatus::Success; | ||
| 120 | } | ||
| 121 | |||
| 88 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { | 122 | ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { |
| 89 | return nca_loader->ReadRomFS(out_file); | 123 | return nca_loader->ReadRomFS(out_file); |
| 90 | } | 124 | } |
diff --git a/src/core/loader/xci.h b/src/core/loader/xci.h index ff05e6f62..b02e136d3 100644 --- a/src/core/loader/xci.h +++ b/src/core/loader/xci.h | |||
| @@ -45,6 +45,8 @@ public: | |||
| 45 | 45 | ||
| 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; | 46 | LoadResult Load(Kernel::KProcess& process, Core::System& system) override; |
| 47 | 47 | ||
| 48 | ResultStatus VerifyIntegrity(std::function<bool(size_t, size_t)> progress_callback) override; | ||
| 49 | |||
| 48 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; | 50 | ResultStatus ReadRomFS(FileSys::VirtualFile& out_file) override; |
| 49 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; | 51 | ResultStatus ReadUpdateRaw(FileSys::VirtualFile& out_file) override; |
| 50 | ResultStatus ReadProgramId(u64& out_program_id) override; | 52 | ResultStatus ReadProgramId(u64& out_program_id) override; |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index b5a02700d..6842ced3e 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -557,6 +557,7 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 557 | QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); | 557 | QMenu* dump_romfs_menu = context_menu.addMenu(tr("Dump RomFS")); |
| 558 | QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); | 558 | QAction* dump_romfs = dump_romfs_menu->addAction(tr("Dump RomFS")); |
| 559 | QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); | 559 | QAction* dump_romfs_sdmc = dump_romfs_menu->addAction(tr("Dump RomFS to SDMC")); |
| 560 | QAction* verify_integrity = context_menu.addAction(tr("Verify Integrity")); | ||
| 560 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); | 561 | QAction* copy_tid = context_menu.addAction(tr("Copy Title ID to Clipboard")); |
| 561 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); | 562 | QAction* navigate_to_gamedb_entry = context_menu.addAction(tr("Navigate to GameDB entry")); |
| 562 | #ifndef WIN32 | 563 | #ifndef WIN32 |
| @@ -628,6 +629,8 @@ void GameList::AddGamePopup(QMenu& context_menu, u64 program_id, const std::stri | |||
| 628 | connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { | 629 | connect(dump_romfs_sdmc, &QAction::triggered, [this, program_id, path]() { |
| 629 | emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); | 630 | emit DumpRomFSRequested(program_id, path, DumpRomFSTarget::SDMC); |
| 630 | }); | 631 | }); |
| 632 | connect(verify_integrity, &QAction::triggered, | ||
| 633 | [this, path]() { emit VerifyIntegrityRequested(path); }); | ||
| 631 | connect(copy_tid, &QAction::triggered, | 634 | connect(copy_tid, &QAction::triggered, |
| 632 | [this, program_id]() { emit CopyTIDRequested(program_id); }); | 635 | [this, program_id]() { emit CopyTIDRequested(program_id); }); |
| 633 | connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { | 636 | connect(navigate_to_gamedb_entry, &QAction::triggered, [this, program_id]() { |
diff --git a/src/yuzu/game_list.h b/src/yuzu/game_list.h index 6c2f75e53..8aea646b2 100644 --- a/src/yuzu/game_list.h +++ b/src/yuzu/game_list.h | |||
| @@ -113,6 +113,7 @@ signals: | |||
| 113 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, | 113 | void RemoveFileRequested(u64 program_id, GameListRemoveTarget target, |
| 114 | const std::string& game_path); | 114 | const std::string& game_path); |
| 115 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 115 | void DumpRomFSRequested(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 116 | void VerifyIntegrityRequested(const std::string& game_path); | ||
| 116 | void CopyTIDRequested(u64 program_id); | 117 | void CopyTIDRequested(u64 program_id); |
| 117 | void CreateShortcut(u64 program_id, const std::string& game_path, | 118 | void CreateShortcut(u64 program_id, const std::string& game_path, |
| 118 | GameListShortcutTarget target); | 119 | GameListShortcutTarget target); |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 4e435c7e2..21df742cb 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -1447,6 +1447,8 @@ void GMainWindow::ConnectWidgetEvents() { | |||
| 1447 | &GMainWindow::OnGameListRemoveInstalledEntry); | 1447 | &GMainWindow::OnGameListRemoveInstalledEntry); |
| 1448 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); | 1448 | connect(game_list, &GameList::RemoveFileRequested, this, &GMainWindow::OnGameListRemoveFile); |
| 1449 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); | 1449 | connect(game_list, &GameList::DumpRomFSRequested, this, &GMainWindow::OnGameListDumpRomFS); |
| 1450 | connect(game_list, &GameList::VerifyIntegrityRequested, this, | ||
| 1451 | &GMainWindow::OnGameListVerifyIntegrity); | ||
| 1450 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); | 1452 | connect(game_list, &GameList::CopyTIDRequested, this, &GMainWindow::OnGameListCopyTID); |
| 1451 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, | 1453 | connect(game_list, &GameList::NavigateToGamedbEntryRequested, this, |
| 1452 | &GMainWindow::OnGameListNavigateToGamedbEntry); | 1454 | &GMainWindow::OnGameListNavigateToGamedbEntry); |
| @@ -2708,6 +2710,54 @@ void GMainWindow::OnGameListDumpRomFS(u64 program_id, const std::string& game_pa | |||
| 2708 | } | 2710 | } |
| 2709 | } | 2711 | } |
| 2710 | 2712 | ||
| 2713 | void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { | ||
| 2714 | const auto NotImplemented = [this] { | ||
| 2715 | QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), | ||
| 2716 | tr("File contents were not checked for validity.")); | ||
| 2717 | }; | ||
| 2718 | const auto Failed = [this] { | ||
| 2719 | QMessageBox::critical(this, tr("Integrity verification failed!"), | ||
| 2720 | tr("File contents may be corrupt.")); | ||
| 2721 | }; | ||
| 2722 | |||
| 2723 | const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); | ||
| 2724 | if (loader == nullptr) { | ||
| 2725 | NotImplemented(); | ||
| 2726 | return; | ||
| 2727 | } | ||
| 2728 | |||
| 2729 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | ||
| 2730 | progress.setWindowModality(Qt::WindowModal); | ||
| 2731 | progress.setMinimumDuration(100); | ||
| 2732 | progress.setAutoClose(false); | ||
| 2733 | progress.setAutoReset(false); | ||
| 2734 | |||
| 2735 | const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) { | ||
| 2736 | if (progress.wasCanceled()) { | ||
| 2737 | return false; | ||
| 2738 | } | ||
| 2739 | |||
| 2740 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); | ||
| 2741 | return true; | ||
| 2742 | }; | ||
| 2743 | |||
| 2744 | const auto status = loader->VerifyIntegrity(QtProgressCallback); | ||
| 2745 | if (progress.wasCanceled() || | ||
| 2746 | status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { | ||
| 2747 | NotImplemented(); | ||
| 2748 | return; | ||
| 2749 | } | ||
| 2750 | |||
| 2751 | if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { | ||
| 2752 | Failed(); | ||
| 2753 | return; | ||
| 2754 | } | ||
| 2755 | |||
| 2756 | progress.close(); | ||
| 2757 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 2758 | tr("The operation completed successfully.")); | ||
| 2759 | } | ||
| 2760 | |||
| 2711 | void GMainWindow::OnGameListCopyTID(u64 program_id) { | 2761 | void GMainWindow::OnGameListCopyTID(u64 program_id) { |
| 2712 | QClipboard* clipboard = QGuiApplication::clipboard(); | 2762 | QClipboard* clipboard = QGuiApplication::clipboard(); |
| 2713 | clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); | 2763 | clipboard->setText(QString::fromStdString(fmt::format("{:016X}", program_id))); |
diff --git a/src/yuzu/main.h b/src/yuzu/main.h index 668dbc3b1..1e4f6e477 100644 --- a/src/yuzu/main.h +++ b/src/yuzu/main.h | |||
| @@ -313,6 +313,7 @@ private slots: | |||
| 313 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, | 313 | void OnGameListRemoveFile(u64 program_id, GameListRemoveTarget target, |
| 314 | const std::string& game_path); | 314 | const std::string& game_path); |
| 315 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); | 315 | void OnGameListDumpRomFS(u64 program_id, const std::string& game_path, DumpRomFSTarget target); |
| 316 | void OnGameListVerifyIntegrity(const std::string& game_path); | ||
| 316 | void OnGameListCopyTID(u64 program_id); | 317 | void OnGameListCopyTID(u64 program_id); |
| 317 | void OnGameListNavigateToGamedbEntry(u64 program_id, | 318 | void OnGameListNavigateToGamedbEntry(u64 program_id, |
| 318 | const CompatibilityList& compatibility_list); | 319 | const CompatibilityList& compatibility_list); |