summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/core/loader/loader.cpp4
-rw-r--r--src/core/loader/loader.h10
-rw-r--r--src/core/loader/nca.cpp76
-rw-r--r--src/core/loader/nca.h2
-rw-r--r--src/core/loader/nsp.cpp36
-rw-r--r--src/core/loader/nsp.h2
-rw-r--r--src/core/loader/xci.cpp34
-rw-r--r--src/core/loader/xci.h2
-rw-r--r--src/yuzu/game_list.cpp3
-rw-r--r--src/yuzu/game_list.h1
-rw-r--r--src/yuzu/main.cpp50
-rw-r--r--src/yuzu/main.h1
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
111constexpr std::array<const char*, 66> RESULT_MESSAGES{ 111constexpr 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
180std::string GetResultStatusString(ResultStatus status) { 182std::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
137std::string GetResultStatusString(ResultStatus status); 140std::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
16namespace Loader { 19namespace 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
86ResultStatus 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
83ResultStatus AppLoader_NCA::ReadRomFS(FileSys::VirtualFile& dir) { 159ResultStatus 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
120ResultStatus 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
120ResultStatus AppLoader_NSP::ReadRomFS(FileSys::VirtualFile& out_file) { 156ResultStatus 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
88ResultStatus 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
88ResultStatus AppLoader_XCI::ReadRomFS(FileSys::VirtualFile& out_file) { 122ResultStatus 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
2713void 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
2711void GMainWindow::OnGameListCopyTID(u64 program_id) { 2761void 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);