diff options
| -rw-r--r-- | src/core/file_sys/registered_cache.cpp | 50 | ||||
| -rw-r--r-- | src/core/file_sys/registered_cache.h | 20 | ||||
| -rw-r--r-- | src/yuzu/game_list.cpp | 1 | ||||
| -rw-r--r-- | src/yuzu/main.cpp | 88 |
4 files changed, 106 insertions, 53 deletions
diff --git a/src/core/file_sys/registered_cache.cpp b/src/core/file_sys/registered_cache.cpp index 20fec2391..e916d5610 100644 --- a/src/core/file_sys/registered_cache.cpp +++ b/src/core/file_sys/registered_cache.cpp | |||
| @@ -60,7 +60,7 @@ static std::string GetCNMTName(TitleType type, u64 title_id) { | |||
| 60 | auto index = static_cast<size_t>(type); | 60 | auto index = static_cast<size_t>(type); |
| 61 | // If the index is after the jump in TitleType, subtract it out. | 61 | // If the index is after the jump in TitleType, subtract it out. |
| 62 | if (index >= static_cast<size_t>(TitleType::Application)) | 62 | if (index >= static_cast<size_t>(TitleType::Application)) |
| 63 | index -= static_cast<size_t>(TitleType::Application); | 63 | index -= 0x7B; |
| 64 | return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); | 64 | return fmt::format("{}_{:016x}.cnmt", TITLE_TYPE_NAMES[index], title_id); |
| 65 | } | 65 | } |
| 66 | 66 | ||
| @@ -343,7 +343,8 @@ static std::shared_ptr<NCA> GetNCAFromXCIForID(std::shared_ptr<XCI> xci, const N | |||
| 343 | return iter == xci->GetNCAs().end() ? nullptr : *iter; | 343 | return iter == xci->GetNCAs().end() ? nullptr : *iter; |
| 344 | } | 344 | } |
| 345 | 345 | ||
| 346 | bool RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, const VfsCopyFunction& copy) { | 346 | InstallResult RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists, |
| 347 | const VfsCopyFunction& copy) { | ||
| 347 | const auto& ncas = xci->GetNCAs(); | 348 | const auto& ncas = xci->GetNCAs(); |
| 348 | const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) { | 349 | const auto& meta_iter = std::find_if(ncas.begin(), ncas.end(), [](std::shared_ptr<NCA> nca) { |
| 349 | return nca->GetType() == NCAContentType::Meta; | 350 | return nca->GetType() == NCAContentType::Meta; |
| @@ -352,14 +353,16 @@ bool RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, const VfsCopyFuncti | |||
| 352 | if (meta_iter == ncas.end()) { | 353 | if (meta_iter == ncas.end()) { |
| 353 | LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and " | 354 | LOG_ERROR(Loader, "The XCI you are attempting to install does not have a metadata NCA and " |
| 354 | "is therefore malformed. Double check your encryption keys."); | 355 | "is therefore malformed. Double check your encryption keys."); |
| 355 | return false; | 356 | return InstallResult::ErrorMetaFailed; |
| 356 | } | 357 | } |
| 357 | 358 | ||
| 358 | // Install Metadata File | 359 | // Install Metadata File |
| 359 | const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); | 360 | const auto meta_id_raw = (*meta_iter)->GetName().substr(0, 32); |
| 360 | const auto meta_id = HexStringToArray<16>(meta_id_raw); | 361 | const auto meta_id = HexStringToArray<16>(meta_id_raw); |
| 361 | if (!RawInstallNCA(*meta_iter, copy, meta_id)) | 362 | |
| 362 | return false; | 363 | const auto res = RawInstallNCA(*meta_iter, copy, overwrite_if_exists, meta_id); |
| 364 | if (res != InstallResult::Success) | ||
| 365 | return res; | ||
| 363 | 366 | ||
| 364 | // Install all the other NCAs | 367 | // Install all the other NCAs |
| 365 | const auto section0 = (*meta_iter)->GetSubdirectories()[0]; | 368 | const auto section0 = (*meta_iter)->GetSubdirectories()[0]; |
| @@ -367,16 +370,19 @@ bool RegisteredCache::InstallEntry(std::shared_ptr<XCI> xci, const VfsCopyFuncti | |||
| 367 | const CNMT cnmt(cnmt_file); | 370 | const CNMT cnmt(cnmt_file); |
| 368 | for (const auto& record : cnmt.GetContentRecords()) { | 371 | for (const auto& record : cnmt.GetContentRecords()) { |
| 369 | const auto nca = GetNCAFromXCIForID(xci, record.nca_id); | 372 | const auto nca = GetNCAFromXCIForID(xci, record.nca_id); |
| 370 | if (nca == nullptr || !RawInstallNCA(nca, copy, record.nca_id)) | 373 | if (nca == nullptr) |
| 371 | return false; | 374 | return InstallResult::ErrorCopyFailed; |
| 375 | const auto res2 = RawInstallNCA(nca, copy, overwrite_if_exists, record.nca_id); | ||
| 376 | if (res2 != InstallResult::Success) | ||
| 377 | return res2; | ||
| 372 | } | 378 | } |
| 373 | 379 | ||
| 374 | Refresh(); | 380 | Refresh(); |
| 375 | return true; | 381 | return InstallResult::Success; |
| 376 | } | 382 | } |
| 377 | 383 | ||
| 378 | bool RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type, | 384 | InstallResult RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type, |
| 379 | const VfsCopyFunction& copy) { | 385 | bool overwrite_if_exists, const VfsCopyFunction& copy) { |
| 380 | CNMTHeader header{ | 386 | CNMTHeader header{ |
| 381 | nca->GetTitleId(), ///< Title ID | 387 | nca->GetTitleId(), ///< Title ID |
| 382 | 0, ///< Ignore/Default title version | 388 | 0, ///< Ignore/Default title version |
| @@ -393,11 +399,14 @@ bool RegisteredCache::InstallEntry(std::shared_ptr<NCA> nca, TitleType type, | |||
| 393 | mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0); | 399 | mbedtls_sha256(data.data(), data.size(), c_rec.hash.data(), 0); |
| 394 | memcpy(&c_rec.nca_id, &c_rec.hash, 16); | 400 | memcpy(&c_rec.nca_id, &c_rec.hash, 16); |
| 395 | const CNMT new_cnmt(header, opt_header, {c_rec}, {}); | 401 | const CNMT new_cnmt(header, opt_header, {c_rec}, {}); |
| 396 | return RawInstallYuzuMeta(new_cnmt) && RawInstallNCA(nca, copy, c_rec.nca_id); | 402 | if (!RawInstallYuzuMeta(new_cnmt)) |
| 403 | return InstallResult::ErrorMetaFailed; | ||
| 404 | return RawInstallNCA(nca, copy, overwrite_if_exists, c_rec.nca_id); | ||
| 397 | } | 405 | } |
| 398 | 406 | ||
| 399 | bool RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, | 407 | InstallResult RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, |
| 400 | boost::optional<NcaID> override_id) { | 408 | bool overwrite_if_exists, |
| 409 | boost::optional<NcaID> override_id) { | ||
| 401 | const auto in = nca->GetBaseFile(); | 410 | const auto in = nca->GetBaseFile(); |
| 402 | Core::Crypto::SHA256Hash hash{}; | 411 | Core::Crypto::SHA256Hash hash{}; |
| 403 | 412 | ||
| @@ -416,15 +425,22 @@ bool RegisteredCache::RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunct | |||
| 416 | 425 | ||
| 417 | std::string path = GetRelativePathFromNcaID(id, false, true); | 426 | std::string path = GetRelativePathFromNcaID(id, false, true); |
| 418 | 427 | ||
| 419 | if (GetFileAtID(id) != nullptr) { | 428 | if (GetFileAtID(id) != nullptr && !overwrite_if_exists) { |
| 420 | LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping..."); | 429 | LOG_WARNING(Loader, "Attempting to overwrite existing NCA. Skipping..."); |
| 421 | return false; | 430 | return InstallResult::ErrorAlreadyExists; |
| 431 | } | ||
| 432 | |||
| 433 | if (GetFileAtID(id) != nullptr) { | ||
| 434 | LOG_WARNING(Loader, "Overwriting existing NCA..."); | ||
| 435 | VirtualDir c_dir; | ||
| 436 | { c_dir = dir->GetFileRelative(path)->GetContainingDirectory(); } | ||
| 437 | c_dir->DeleteFile(FileUtil::GetFilename(path)); | ||
| 422 | } | 438 | } |
| 423 | 439 | ||
| 424 | auto out = dir->CreateFileRelative(path); | 440 | auto out = dir->CreateFileRelative(path); |
| 425 | if (out == nullptr) | 441 | if (out == nullptr) |
| 426 | return false; | 442 | return InstallResult::ErrorCopyFailed; |
| 427 | return copy(in, out); | 443 | return copy(in, out) ? InstallResult::Success : InstallResult::ErrorCopyFailed; |
| 428 | } | 444 | } |
| 429 | 445 | ||
| 430 | bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { | 446 | bool RegisteredCache::RawInstallYuzuMeta(const CNMT& cnmt) { |
diff --git a/src/core/file_sys/registered_cache.h b/src/core/file_sys/registered_cache.h index f2b07eec8..a7c51a59c 100644 --- a/src/core/file_sys/registered_cache.h +++ b/src/core/file_sys/registered_cache.h | |||
| @@ -25,6 +25,13 @@ using NcaID = std::array<u8, 0x10>; | |||
| 25 | using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; | 25 | using RegisteredCacheParsingFunction = std::function<VirtualFile(const VirtualFile&, const NcaID&)>; |
| 26 | using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; | 26 | using VfsCopyFunction = std::function<bool(VirtualFile, VirtualFile)>; |
| 27 | 27 | ||
| 28 | enum class InstallResult { | ||
| 29 | Success, | ||
| 30 | ErrorAlreadyExists, | ||
| 31 | ErrorCopyFailed, | ||
| 32 | ErrorMetaFailed, | ||
| 33 | }; | ||
| 34 | |||
| 28 | struct RegisteredCacheEntry { | 35 | struct RegisteredCacheEntry { |
| 29 | u64 title_id; | 36 | u64 title_id; |
| 30 | ContentRecordType type; | 37 | ContentRecordType type; |
| @@ -77,14 +84,16 @@ public: | |||
| 77 | 84 | ||
| 78 | // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there | 85 | // Raw copies all the ncas from the xci to the csache. Does some quick checks to make sure there |
| 79 | // is a meta NCA and all of them are accessible. | 86 | // is a meta NCA and all of them are accessible. |
| 80 | bool InstallEntry(std::shared_ptr<XCI> xci, const VfsCopyFunction& copy = &VfsRawCopy); | 87 | InstallResult InstallEntry(std::shared_ptr<XCI> xci, bool overwrite_if_exists = false, |
| 88 | const VfsCopyFunction& copy = &VfsRawCopy); | ||
| 81 | 89 | ||
| 82 | // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this | 90 | // Due to the fact that we must use Meta-type NCAs to determine the existance of files, this |
| 83 | // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a | 91 | // poses quite a challenge. Instead of creating a new meta NCA for this file, yuzu will create a |
| 84 | // dir inside the NAND called 'yuzu_meta' and store the raw CNMT there. | 92 | // dir inside the NAND called 'yuzu_meta' and store the raw CNMT there. |
| 85 | // TODO(DarkLordZach): Author real meta-type NCAs and install those. | 93 | // TODO(DarkLordZach): Author real meta-type NCAs and install those. |
| 86 | bool InstallEntry(std::shared_ptr<NCA> nca, TitleType type, | 94 | InstallResult InstallEntry(std::shared_ptr<NCA> nca, TitleType type, |
| 87 | const VfsCopyFunction& copy = &VfsRawCopy); | 95 | bool overwrite_if_exists = false, |
| 96 | const VfsCopyFunction& copy = &VfsRawCopy); | ||
| 88 | 97 | ||
| 89 | private: | 98 | private: |
| 90 | template <typename T> | 99 | template <typename T> |
| @@ -97,8 +106,9 @@ private: | |||
| 97 | boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const; | 106 | boost::optional<NcaID> GetNcaIDFromMetadata(u64 title_id, ContentRecordType type) const; |
| 98 | VirtualFile GetFileAtID(NcaID id) const; | 107 | VirtualFile GetFileAtID(NcaID id) const; |
| 99 | VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const; | 108 | VirtualFile OpenFileOrDirectoryConcat(const VirtualDir& dir, std::string_view path) const; |
| 100 | bool RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, | 109 | InstallResult RawInstallNCA(std::shared_ptr<NCA> nca, const VfsCopyFunction& copy, |
| 101 | boost::optional<NcaID> override_id = boost::none); | 110 | bool overwrite_if_exists, |
| 111 | boost::optional<NcaID> override_id = boost::none); | ||
| 102 | bool RawInstallYuzuMeta(const CNMT& cnmt); | 112 | bool RawInstallYuzuMeta(const CNMT& cnmt); |
| 103 | 113 | ||
| 104 | VirtualDir dir; | 114 | VirtualDir dir; |
diff --git a/src/yuzu/game_list.cpp b/src/yuzu/game_list.cpp index faaeda63d..f867118d9 100644 --- a/src/yuzu/game_list.cpp +++ b/src/yuzu/game_list.cpp | |||
| @@ -405,7 +405,6 @@ void GameList::RefreshGameDirectory() { | |||
| 405 | 405 | ||
| 406 | static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca, | 406 | static void GetMetadataFromControlNCA(const std::shared_ptr<FileSys::NCA>& nca, |
| 407 | std::vector<u8>& icon, std::string& name) { | 407 | std::vector<u8>& icon, std::string& name) { |
| 408 | |||
| 409 | const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); | 408 | const auto control_dir = FileSys::ExtractRomFS(nca->GetRomFS()); |
| 410 | if (control_dir == nullptr) | 409 | if (control_dir == nullptr) |
| 411 | return; | 410 | return; |
diff --git a/src/yuzu/main.cpp b/src/yuzu/main.cpp index e8254c30f..b5f97f332 100644 --- a/src/yuzu/main.cpp +++ b/src/yuzu/main.cpp | |||
| @@ -650,37 +650,59 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 650 | return true; | 650 | return true; |
| 651 | }; | 651 | }; |
| 652 | 652 | ||
| 653 | const auto success = [this]() { | ||
| 654 | QMessageBox::information(this, tr("Successfully Installed"), | ||
| 655 | tr("The file was successfully installed.")); | ||
| 656 | game_list->PopulateAsync(UISettings::values.gamedir, UISettings::values.gamedir_deepscan); | ||
| 657 | }; | ||
| 658 | |||
| 659 | const auto failed = [this]() { | ||
| 660 | QMessageBox::warning( | ||
| 661 | this, tr("Failed to Install"), | ||
| 662 | tr("There was an error while attempting to install the provided file. It " | ||
| 663 | "could have an incorrect format or be missing metadata. Please " | ||
| 664 | "double-check your file and try again.")); | ||
| 665 | }; | ||
| 666 | |||
| 667 | const auto overwrite = [this]() { | ||
| 668 | return QMessageBox::question(this, "Failed to Install", | ||
| 669 | "The file you are attempting to install already exists " | ||
| 670 | "in the cache. Would you like to overwrite it?") == | ||
| 671 | QMessageBox::Yes; | ||
| 672 | }; | ||
| 673 | |||
| 653 | if (!filename.isEmpty()) { | 674 | if (!filename.isEmpty()) { |
| 654 | if (filename.endsWith("xci", Qt::CaseInsensitive)) { | 675 | if (filename.endsWith("xci", Qt::CaseInsensitive)) { |
| 655 | const auto xci = std::make_shared<FileSys::XCI>( | 676 | const auto xci = std::make_shared<FileSys::XCI>( |
| 656 | vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); | 677 | vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); |
| 657 | if (xci->GetStatus() != Loader::ResultStatus::Success) { | 678 | if (xci->GetStatus() != Loader::ResultStatus::Success) { |
| 658 | QMessageBox::warning( | 679 | failed(); |
| 659 | this, tr("Failed to Install XCI"), | ||
| 660 | tr("The XCI file you provided is invalid. Please double-check your encryption " | ||
| 661 | "keys and the file and try again.")); | ||
| 662 | return; | 680 | return; |
| 663 | } | 681 | } |
| 664 | if (Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, qt_raw_copy)) { | 682 | const auto res = |
| 665 | QMessageBox::information(this, tr("Successfully Installed XCI"), | 683 | Service::FileSystem::GetUserNANDContents()->InstallEntry(xci, false, qt_raw_copy); |
| 666 | tr("The file was successfully installed.")); | 684 | if (res == FileSys::InstallResult::Success) { |
| 667 | game_list->PopulateAsync(UISettings::values.gamedir, | 685 | success(); |
| 668 | UISettings::values.gamedir_deepscan); | ||
| 669 | } else { | 686 | } else { |
| 670 | QMessageBox::warning( | 687 | if (res == FileSys::InstallResult::ErrorAlreadyExists) { |
| 671 | this, tr("Failed to Install XCI"), | 688 | if (overwrite()) { |
| 672 | tr("There was an error while attempting to install the provided XCI file. It " | 689 | const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( |
| 673 | "could have an incorrect format or be missing a metadata entry. Please " | 690 | xci, true, qt_raw_copy); |
| 674 | "double-check your file and try again.")); | 691 | if (res2 == FileSys::InstallResult::Success) { |
| 692 | success(); | ||
| 693 | } else { | ||
| 694 | failed(); | ||
| 695 | } | ||
| 696 | } | ||
| 697 | } else { | ||
| 698 | failed(); | ||
| 699 | } | ||
| 675 | } | 700 | } |
| 676 | } else { | 701 | } else { |
| 677 | const auto nca = std::make_shared<FileSys::NCA>( | 702 | const auto nca = std::make_shared<FileSys::NCA>( |
| 678 | vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); | 703 | vfs->OpenFile(filename.toStdString(), FileSys::Mode::Read)); |
| 679 | if (nca->GetStatus() != Loader::ResultStatus::Success) { | 704 | if (nca->GetStatus() != Loader::ResultStatus::Success) { |
| 680 | QMessageBox::warning( | 705 | failed(); |
| 681 | this, tr("Failed to Install NCA"), | ||
| 682 | tr("The NCA file you provided is invalid. Please double-check your encryption " | ||
| 683 | "keys and the file and try again.")); | ||
| 684 | return; | 706 | return; |
| 685 | } | 707 | } |
| 686 | 708 | ||
| @@ -702,7 +724,7 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 702 | 724 | ||
| 703 | auto index = tt_options.indexOf(item); | 725 | auto index = tt_options.indexOf(item); |
| 704 | if (!ok || index == -1) { | 726 | if (!ok || index == -1) { |
| 705 | QMessageBox::warning(this, tr("Failed to Install NCA"), | 727 | QMessageBox::warning(this, tr("Failed to Install"), |
| 706 | tr("The title type you selected for the NCA is invalid.")); | 728 | tr("The title type you selected for the NCA is invalid.")); |
| 707 | return; | 729 | return; |
| 708 | } | 730 | } |
| @@ -710,18 +732,24 @@ void GMainWindow::OnMenuInstallToNAND() { | |||
| 710 | if (index >= 5) | 732 | if (index >= 5) |
| 711 | index += 0x7B; | 733 | index += 0x7B; |
| 712 | 734 | ||
| 713 | if (Service::FileSystem::GetUserNANDContents()->InstallEntry( | 735 | const auto res = Service::FileSystem::GetUserNANDContents()->InstallEntry( |
| 714 | nca, static_cast<FileSys::TitleType>(index), qt_raw_copy)) { | 736 | nca, static_cast<FileSys::TitleType>(index), false, qt_raw_copy); |
| 715 | QMessageBox::information(this, tr("Successfully Installed NCA"), | 737 | if (res == FileSys::InstallResult::Success) { |
| 716 | tr("The file was successfully installed.")); | 738 | success(); |
| 717 | game_list->PopulateAsync(UISettings::values.gamedir, | ||
| 718 | UISettings::values.gamedir_deepscan); | ||
| 719 | } else { | 739 | } else { |
| 720 | QMessageBox::warning(this, tr("Failed to Install NCA"), | 740 | if (res == FileSys::InstallResult::ErrorAlreadyExists) { |
| 721 | tr("There was an error while attempting to install the " | 741 | if (overwrite()) { |
| 722 | "provided NCA file. An error might have occured creating " | 742 | const auto res2 = Service::FileSystem::GetUserNANDContents()->InstallEntry( |
| 723 | "the metadata file or parsing the NCA. Please " | 743 | nca, static_cast<FileSys::TitleType>(index), true, qt_raw_copy); |
| 724 | "double-check your file and try again.")); | 744 | if (res2 == FileSys::InstallResult::Success) { |
| 745 | success(); | ||
| 746 | } else { | ||
| 747 | failed(); | ||
| 748 | } | ||
| 749 | } | ||
| 750 | } else { | ||
| 751 | failed(); | ||
| 752 | } | ||
| 725 | } | 753 | } |
| 726 | } | 754 | } |
| 727 | } | 755 | } |