diff options
Diffstat (limited to '')
| -rw-r--r-- | src/frontend_common/content_manager.h | 137 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 138 |
2 files changed, 161 insertions, 114 deletions
diff --git a/src/frontend_common/content_manager.h b/src/frontend_common/content_manager.h index 23f2979db..92276700b 100644 --- a/src/frontend_common/content_manager.h +++ b/src/frontend_common/content_manager.h | |||
| @@ -11,10 +11,12 @@ | |||
| 11 | #include "core/file_sys/content_archive.h" | 11 | #include "core/file_sys/content_archive.h" |
| 12 | #include "core/file_sys/mode.h" | 12 | #include "core/file_sys/mode.h" |
| 13 | #include "core/file_sys/nca_metadata.h" | 13 | #include "core/file_sys/nca_metadata.h" |
| 14 | #include "core/file_sys/patch_manager.h" | ||
| 14 | #include "core/file_sys/registered_cache.h" | 15 | #include "core/file_sys/registered_cache.h" |
| 15 | #include "core/file_sys/submission_package.h" | 16 | #include "core/file_sys/submission_package.h" |
| 16 | #include "core/hle/service/filesystem/filesystem.h" | 17 | #include "core/hle/service/filesystem/filesystem.h" |
| 17 | #include "core/loader/loader.h" | 18 | #include "core/loader/loader.h" |
| 19 | #include "core/loader/nca.h" | ||
| 18 | 20 | ||
| 19 | namespace ContentManager { | 21 | namespace ContentManager { |
| 20 | 22 | ||
| @@ -25,6 +27,12 @@ enum class InstallResult { | |||
| 25 | BaseInstallAttempted, | 27 | BaseInstallAttempted, |
| 26 | }; | 28 | }; |
| 27 | 29 | ||
| 30 | enum class GameVerificationResult { | ||
| 31 | Success, | ||
| 32 | Failed, | ||
| 33 | NotImplemented, | ||
| 34 | }; | ||
| 35 | |||
| 28 | /** | 36 | /** |
| 29 | * \brief Removes a single installed DLC | 37 | * \brief Removes a single installed DLC |
| 30 | * \param fs_controller [FileSystemController] reference from the Core::System instance | 38 | * \param fs_controller [FileSystemController] reference from the Core::System instance |
| @@ -121,7 +129,7 @@ inline bool RemoveMod(const Service::FileSystem::FileSystemController& fs_contro | |||
| 121 | * \param filename Path to the NSP file | 129 | * \param filename Path to the NSP file |
| 122 | * \param callback Optional callback to report the progress of the installation. The first size_t | 130 | * \param callback Optional callback to report the progress of the installation. The first size_t |
| 123 | * parameter is the total size of the virtual file and the second is the current progress. If you | 131 | * parameter is the total size of the virtual file and the second is the current progress. If you |
| 124 | * return false to the callback, it will cancel the installation as soon as possible. | 132 | * return true to the callback, it will cancel the installation as soon as possible. |
| 125 | * \return [InstallResult] representing how the installation finished | 133 | * \return [InstallResult] representing how the installation finished |
| 126 | */ | 134 | */ |
| 127 | inline InstallResult InstallNSP( | 135 | inline InstallResult InstallNSP( |
| @@ -186,7 +194,7 @@ inline InstallResult InstallNSP( | |||
| 186 | * \param title_type Type of NCA package to install | 194 | * \param title_type Type of NCA package to install |
| 187 | * \param callback Optional callback to report the progress of the installation. The first size_t | 195 | * \param callback Optional callback to report the progress of the installation. The first size_t |
| 188 | * parameter is the total size of the virtual file and the second is the current progress. If you | 196 | * parameter is the total size of the virtual file and the second is the current progress. If you |
| 189 | * return false to the callback, it will cancel the installation as soon as possible. | 197 | * return true to the callback, it will cancel the installation as soon as possible. |
| 190 | * \return [InstallResult] representing how the installation finished | 198 | * \return [InstallResult] representing how the installation finished |
| 191 | */ | 199 | */ |
| 192 | inline InstallResult InstallNCA( | 200 | inline InstallResult InstallNCA( |
| @@ -235,4 +243,129 @@ inline InstallResult InstallNCA( | |||
| 235 | } | 243 | } |
| 236 | } | 244 | } |
| 237 | 245 | ||
| 246 | /** | ||
| 247 | * \brief Verifies the installed contents for a given ManualContentProvider | ||
| 248 | * \param system Raw pointer to the system instance | ||
| 249 | * \param provider Raw pointer to the content provider that's tracking indexed games | ||
| 250 | * \param callback Optional callback to report the progress of the installation. The first size_t | ||
| 251 | * parameter is the total size of the installed contents and the second is the current progress. If | ||
| 252 | * you return true to the callback, it will cancel the installation as soon as possible. | ||
| 253 | * \return A list of entries that failed to install. Returns an empty vector if successful. | ||
| 254 | */ | ||
| 255 | inline std::vector<std::string> VerifyInstalledContents( | ||
| 256 | Core::System* system, FileSys::ManualContentProvider* provider, | ||
| 257 | const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | ||
| 258 | // Get content registries. | ||
| 259 | auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); | ||
| 260 | auto user_contents = system->GetFileSystemController().GetUserNANDContents(); | ||
| 261 | |||
| 262 | std::vector<FileSys::RegisteredCache*> content_providers; | ||
| 263 | if (bis_contents) { | ||
| 264 | content_providers.push_back(bis_contents); | ||
| 265 | } | ||
| 266 | if (user_contents) { | ||
| 267 | content_providers.push_back(user_contents); | ||
| 268 | } | ||
| 269 | |||
| 270 | // Get associated NCA files. | ||
| 271 | std::vector<FileSys::VirtualFile> nca_files; | ||
| 272 | |||
| 273 | // Get all installed IDs. | ||
| 274 | size_t total_size = 0; | ||
| 275 | for (auto nca_provider : content_providers) { | ||
| 276 | const auto entries = nca_provider->ListEntriesFilter(); | ||
| 277 | |||
| 278 | for (const auto& entry : entries) { | ||
| 279 | auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); | ||
| 280 | if (!nca_file) { | ||
| 281 | continue; | ||
| 282 | } | ||
| 283 | |||
| 284 | total_size += nca_file->GetSize(); | ||
| 285 | nca_files.push_back(std::move(nca_file)); | ||
| 286 | } | ||
| 287 | } | ||
| 288 | |||
| 289 | // Declare a list of file names which failed to verify. | ||
| 290 | std::vector<std::string> failed; | ||
| 291 | |||
| 292 | size_t processed_size = 0; | ||
| 293 | bool cancelled = false; | ||
| 294 | auto nca_callback = [&](size_t nca_processed, size_t nca_total) { | ||
| 295 | cancelled = callback(total_size, processed_size + nca_processed); | ||
| 296 | return !cancelled; | ||
| 297 | }; | ||
| 298 | |||
| 299 | // Using the NCA loader, determine if all NCAs are valid. | ||
| 300 | for (auto& nca_file : nca_files) { | ||
| 301 | Loader::AppLoader_NCA nca_loader(nca_file); | ||
| 302 | |||
| 303 | auto status = nca_loader.VerifyIntegrity(nca_callback); | ||
| 304 | if (cancelled) { | ||
| 305 | break; | ||
| 306 | } | ||
| 307 | if (status != Loader::ResultStatus::Success) { | ||
| 308 | FileSys::NCA nca(nca_file); | ||
| 309 | const auto title_id = nca.GetTitleId(); | ||
| 310 | std::string title_name = "unknown"; | ||
| 311 | |||
| 312 | const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), | ||
| 313 | FileSys::ContentRecordType::Control); | ||
| 314 | if (control && control->GetStatus() == Loader::ResultStatus::Success) { | ||
| 315 | const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), | ||
| 316 | *provider}; | ||
| 317 | const auto [nacp, logo] = pm.ParseControlNCA(*control); | ||
| 318 | if (nacp) { | ||
| 319 | title_name = nacp->GetApplicationName(); | ||
| 320 | } | ||
| 321 | } | ||
| 322 | |||
| 323 | if (title_id > 0) { | ||
| 324 | failed.push_back( | ||
| 325 | fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); | ||
| 326 | } else { | ||
| 327 | failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); | ||
| 328 | } | ||
| 329 | } | ||
| 330 | |||
| 331 | processed_size += nca_file->GetSize(); | ||
| 332 | } | ||
| 333 | return failed; | ||
| 334 | } | ||
| 335 | |||
| 336 | /** | ||
| 337 | * \brief Verifies the contents of a given game | ||
| 338 | * \param system Raw pointer to the system instance | ||
| 339 | * \param game_path Patch to the game file | ||
| 340 | * \param callback Optional callback to report the progress of the installation. The first size_t | ||
| 341 | * parameter is the total size of the installed contents and the second is the current progress. If | ||
| 342 | * you return true to the callback, it will cancel the installation as soon as possible. | ||
| 343 | * \return GameVerificationResult representing how the verification process finished | ||
| 344 | */ | ||
| 345 | inline GameVerificationResult VerifyGameContents( | ||
| 346 | Core::System* system, const std::string& game_path, | ||
| 347 | const std::function<bool(size_t, size_t)>& callback = std::function<bool(size_t, size_t)>()) { | ||
| 348 | const auto loader = Loader::GetLoader( | ||
| 349 | *system, system->GetFilesystem()->OpenFile(game_path, FileSys::Mode::Read)); | ||
| 350 | if (loader == nullptr) { | ||
| 351 | return GameVerificationResult::NotImplemented; | ||
| 352 | } | ||
| 353 | |||
| 354 | bool cancelled = false; | ||
| 355 | auto loader_callback = [&](size_t processed, size_t total) { | ||
| 356 | cancelled = callback(total, processed); | ||
| 357 | return !cancelled; | ||
| 358 | }; | ||
| 359 | |||
| 360 | const auto status = loader->VerifyIntegrity(loader_callback); | ||
| 361 | if (cancelled || status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { | ||
| 362 | return GameVerificationResult::NotImplemented; | ||
| 363 | } | ||
| 364 | |||
| 365 | if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { | ||
| 366 | return GameVerificationResult::Failed; | ||
| 367 | } | ||
| 368 | return GameVerificationResult::Success; | ||
| 369 | } | ||
| 370 | |||
| 238 | } // namespace ContentManager | 371 | } // namespace ContentManager |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index 05bd4174c..d8b0beadf 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -2786,16 +2786,6 @@ void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { | |||
| 2786 | QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), | 2786 | QMessageBox::warning(this, tr("Integrity verification couldn't be performed!"), |
| 2787 | tr("File contents were not checked for validity.")); | 2787 | tr("File contents were not checked for validity.")); |
| 2788 | }; | 2788 | }; |
| 2789 | const auto Failed = [this] { | ||
| 2790 | QMessageBox::critical(this, tr("Integrity verification failed!"), | ||
| 2791 | tr("File contents may be corrupt.")); | ||
| 2792 | }; | ||
| 2793 | |||
| 2794 | const auto loader = Loader::GetLoader(*system, vfs->OpenFile(game_path, FileSys::Mode::Read)); | ||
| 2795 | if (loader == nullptr) { | ||
| 2796 | NotImplemented(); | ||
| 2797 | return; | ||
| 2798 | } | ||
| 2799 | 2789 | ||
| 2800 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | 2790 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); |
| 2801 | progress.setWindowModality(Qt::WindowModal); | 2791 | progress.setWindowModality(Qt::WindowModal); |
| @@ -2803,30 +2793,26 @@ void GMainWindow::OnGameListVerifyIntegrity(const std::string& game_path) { | |||
| 2803 | progress.setAutoClose(false); | 2793 | progress.setAutoClose(false); |
| 2804 | progress.setAutoReset(false); | 2794 | progress.setAutoReset(false); |
| 2805 | 2795 | ||
| 2806 | const auto QtProgressCallback = [&](size_t processed_size, size_t total_size) { | 2796 | const auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { |
| 2807 | if (progress.wasCanceled()) { | ||
| 2808 | return false; | ||
| 2809 | } | ||
| 2810 | |||
| 2811 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); | 2797 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); |
| 2812 | return true; | 2798 | return progress.wasCanceled(); |
| 2813 | }; | 2799 | }; |
| 2814 | 2800 | ||
| 2815 | const auto status = loader->VerifyIntegrity(QtProgressCallback); | 2801 | const auto result = |
| 2816 | if (progress.wasCanceled() || | 2802 | ContentManager::VerifyGameContents(system.get(), game_path, QtProgressCallback); |
| 2817 | status == Loader::ResultStatus::ErrorIntegrityVerificationNotImplemented) { | 2803 | progress.close(); |
| 2804 | switch (result) { | ||
| 2805 | case ContentManager::GameVerificationResult::Success: | ||
| 2806 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 2807 | tr("The operation completed successfully.")); | ||
| 2808 | break; | ||
| 2809 | case ContentManager::GameVerificationResult::Failed: | ||
| 2810 | QMessageBox::critical(this, tr("Integrity verification failed!"), | ||
| 2811 | tr("File contents may be corrupt.")); | ||
| 2812 | break; | ||
| 2813 | case ContentManager::GameVerificationResult::NotImplemented: | ||
| 2818 | NotImplemented(); | 2814 | NotImplemented(); |
| 2819 | return; | ||
| 2820 | } | ||
| 2821 | |||
| 2822 | if (status == Loader::ResultStatus::ErrorIntegrityVerificationFailed) { | ||
| 2823 | Failed(); | ||
| 2824 | return; | ||
| 2825 | } | 2815 | } |
| 2826 | |||
| 2827 | progress.close(); | ||
| 2828 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 2829 | tr("The operation completed successfully.")); | ||
| 2830 | } | 2816 | } |
| 2831 | 2817 | ||
| 2832 | void GMainWindow::OnGameListCopyTID(u64 program_id) { | 2818 | void GMainWindow::OnGameListCopyTID(u64 program_id) { |
| @@ -4121,10 +4107,6 @@ void GMainWindow::OnOpenYuzuFolder() { | |||
| 4121 | } | 4107 | } |
| 4122 | 4108 | ||
| 4123 | void GMainWindow::OnVerifyInstalledContents() { | 4109 | void GMainWindow::OnVerifyInstalledContents() { |
| 4124 | // Declare sizes. | ||
| 4125 | size_t total_size = 0; | ||
| 4126 | size_t processed_size = 0; | ||
| 4127 | |||
| 4128 | // Initialize a progress dialog. | 4110 | // Initialize a progress dialog. |
| 4129 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); | 4111 | QProgressDialog progress(tr("Verifying integrity..."), tr("Cancel"), 0, 100, this); |
| 4130 | progress.setWindowModality(Qt::WindowModal); | 4112 | progress.setWindowModality(Qt::WindowModal); |
| @@ -4132,93 +4114,25 @@ void GMainWindow::OnVerifyInstalledContents() { | |||
| 4132 | progress.setAutoClose(false); | 4114 | progress.setAutoClose(false); |
| 4133 | progress.setAutoReset(false); | 4115 | progress.setAutoReset(false); |
| 4134 | 4116 | ||
| 4135 | // Declare a list of file names which failed to verify. | ||
| 4136 | std::vector<std::string> failed; | ||
| 4137 | |||
| 4138 | // Declare progress callback. | 4117 | // Declare progress callback. |
| 4139 | auto QtProgressCallback = [&](size_t nca_processed, size_t nca_total) { | 4118 | auto QtProgressCallback = [&](size_t total_size, size_t processed_size) { |
| 4140 | if (progress.wasCanceled()) { | 4119 | progress.setValue(static_cast<int>((processed_size * 100) / total_size)); |
| 4141 | return false; | 4120 | return progress.wasCanceled(); |
| 4142 | } | ||
| 4143 | progress.setValue(static_cast<int>(((processed_size + nca_processed) * 100) / total_size)); | ||
| 4144 | return true; | ||
| 4145 | }; | 4121 | }; |
| 4146 | 4122 | ||
| 4147 | // Get content registries. | 4123 | const std::vector<std::string> result = |
| 4148 | auto bis_contents = system->GetFileSystemController().GetSystemNANDContents(); | 4124 | ContentManager::VerifyInstalledContents(system.get(), provider.get(), QtProgressCallback); |
| 4149 | auto user_contents = system->GetFileSystemController().GetUserNANDContents(); | ||
| 4150 | |||
| 4151 | std::vector<FileSys::RegisteredCache*> content_providers; | ||
| 4152 | if (bis_contents) { | ||
| 4153 | content_providers.push_back(bis_contents); | ||
| 4154 | } | ||
| 4155 | if (user_contents) { | ||
| 4156 | content_providers.push_back(user_contents); | ||
| 4157 | } | ||
| 4158 | |||
| 4159 | // Get associated NCA files. | ||
| 4160 | std::vector<FileSys::VirtualFile> nca_files; | ||
| 4161 | |||
| 4162 | // Get all installed IDs. | ||
| 4163 | for (auto nca_provider : content_providers) { | ||
| 4164 | const auto entries = nca_provider->ListEntriesFilter(); | ||
| 4165 | |||
| 4166 | for (const auto& entry : entries) { | ||
| 4167 | auto nca_file = nca_provider->GetEntryRaw(entry.title_id, entry.type); | ||
| 4168 | if (!nca_file) { | ||
| 4169 | continue; | ||
| 4170 | } | ||
| 4171 | |||
| 4172 | total_size += nca_file->GetSize(); | ||
| 4173 | nca_files.push_back(std::move(nca_file)); | ||
| 4174 | } | ||
| 4175 | } | ||
| 4176 | |||
| 4177 | // Using the NCA loader, determine if all NCAs are valid. | ||
| 4178 | for (auto& nca_file : nca_files) { | ||
| 4179 | Loader::AppLoader_NCA nca_loader(nca_file); | ||
| 4180 | |||
| 4181 | auto status = nca_loader.VerifyIntegrity(QtProgressCallback); | ||
| 4182 | if (progress.wasCanceled()) { | ||
| 4183 | break; | ||
| 4184 | } | ||
| 4185 | if (status != Loader::ResultStatus::Success) { | ||
| 4186 | FileSys::NCA nca(nca_file); | ||
| 4187 | const auto title_id = nca.GetTitleId(); | ||
| 4188 | std::string title_name = "unknown"; | ||
| 4189 | |||
| 4190 | const auto control = provider->GetEntry(FileSys::GetBaseTitleID(title_id), | ||
| 4191 | FileSys::ContentRecordType::Control); | ||
| 4192 | if (control && control->GetStatus() == Loader::ResultStatus::Success) { | ||
| 4193 | const FileSys::PatchManager pm{title_id, system->GetFileSystemController(), | ||
| 4194 | *provider}; | ||
| 4195 | const auto [nacp, logo] = pm.ParseControlNCA(*control); | ||
| 4196 | if (nacp) { | ||
| 4197 | title_name = nacp->GetApplicationName(); | ||
| 4198 | } | ||
| 4199 | } | ||
| 4200 | |||
| 4201 | if (title_id > 0) { | ||
| 4202 | failed.push_back( | ||
| 4203 | fmt::format("{} ({:016X}) ({})", nca_file->GetName(), title_id, title_name)); | ||
| 4204 | } else { | ||
| 4205 | failed.push_back(fmt::format("{} (unknown)", nca_file->GetName())); | ||
| 4206 | } | ||
| 4207 | } | ||
| 4208 | |||
| 4209 | processed_size += nca_file->GetSize(); | ||
| 4210 | } | ||
| 4211 | |||
| 4212 | progress.close(); | 4125 | progress.close(); |
| 4213 | 4126 | ||
| 4214 | if (failed.size() > 0) { | 4127 | if (result.empty()) { |
| 4215 | auto failed_names = QString::fromStdString(fmt::format("{}", fmt::join(failed, "\n"))); | 4128 | QMessageBox::information(this, tr("Integrity verification succeeded!"), |
| 4129 | tr("The operation completed successfully.")); | ||
| 4130 | } else { | ||
| 4131 | const auto failed_names = | ||
| 4132 | QString::fromStdString(fmt::format("{}", fmt::join(result, "\n"))); | ||
| 4216 | QMessageBox::critical( | 4133 | QMessageBox::critical( |
| 4217 | this, tr("Integrity verification failed!"), | 4134 | this, tr("Integrity verification failed!"), |
| 4218 | tr("Verification failed for the following files:\n\n%1").arg(failed_names)); | 4135 | tr("Verification failed for the following files:\n\n%1").arg(failed_names)); |
| 4219 | } else { | ||
| 4220 | QMessageBox::information(this, tr("Integrity verification succeeded!"), | ||
| 4221 | tr("The operation completed successfully.")); | ||
| 4222 | } | 4136 | } |
| 4223 | } | 4137 | } |
| 4224 | 4138 | ||