diff --git a/Source/Android/jni/WiiUtils.cpp b/Source/Android/jni/WiiUtils.cpp index 77ef3e3f7f..995f0d05b6 100644 --- a/Source/Android/jni/WiiUtils.cpp +++ b/Source/Android/jni/WiiUtils.cpp @@ -102,10 +102,10 @@ JNIEXPORT void JNICALL Java_org_dolphinemu_dolphinemu_utils_WiiUtils_importNANDB return DiscIO::NANDImporter().ImportNANDBin( path, - [] { - // This callback gets called every now and then in case we want to update the GUI. However, - // we have no way of knowing what the current progress is, so we can't do anything - // especially useful. DolphinQt chooses to show the elapsed time, for reference. + [](DiscIO::NANDImporter::Step, int, int) { + // This callback gets called every now and then in case we want to update the GUI. + // TODO + return false; }, [] { // This callback gets called if the NAND file does not have decryption keys appended to it. diff --git a/Source/Core/DiscIO/NANDImporter.cpp b/Source/Core/DiscIO/NANDImporter.cpp index 54122d0626..f4c94bbb2c 100644 --- a/Source/Core/DiscIO/NANDImporter.cpp +++ b/Source/Core/DiscIO/NANDImporter.cpp @@ -23,8 +23,7 @@ NANDImporter::NANDImporter() : m_nand_root(File::GetUserPath(D_WIIROOT_IDX)) } NANDImporter::~NANDImporter() = default; -void NANDImporter::ImportNANDBin(const std::string& path_to_bin, - std::function update_callback, +void NANDImporter::ImportNANDBin(const std::string& path_to_bin, UpdateCallback update_callback, const std::function& get_otp_dump_path) { m_update_callback = std::move(update_callback); @@ -35,8 +34,13 @@ void NANDImporter::ImportNANDBin(const std::string& path_to_bin, return; ExportKeys(); - ProcessEntry(0, ""); + + if (!ExtractFiles()) + return; + ExtractCertificates(); + + std::ignore = m_update_callback(Step::Extracting, m_progress_cur, m_progress_max); } bool NANDImporter::ReadNANDBin(const std::string& path_to_bin, @@ -60,10 +64,8 @@ bool NANDImporter::ReadNANDBin(const std::string& path_to_bin, for (size_t i = 0; i < NAND_TOTAL_BLOCKS; i++) { - // Instead of updating on every cycle, we only update every 1000 cycles for a balance between - // not updating fast enough vs updating too fast - if (i % 1000 == 0) - m_update_callback(); + if (m_update_callback(Step::Loading, int(i), int(NAND_TOTAL_BLOCKS))) + return false; file.ReadBytes(&m_nand[i * NAND_BLOCK_SIZE], NAND_BLOCK_SIZE); @@ -121,6 +123,14 @@ bool NANDImporter::FindSuperblock() return true; } +bool NANDImporter::ExtractFiles() +{ + constexpr u16 CLUSTER_CHAIN_END = 0xFFFB; + m_progress_cur = 0; + m_progress_max = std::ranges::count(m_superblock->fat, CLUSTER_CHAIN_END); + return ProcessEntry(0, ""); +} + std::string NANDImporter::GetPath(const NANDFSTEntry& entry, const std::string& parent_path) { std::string name(entry.name, strnlen(entry.name, sizeof(NANDFSTEntry::name))); @@ -131,25 +141,26 @@ std::string NANDImporter::GetPath(const NANDFSTEntry& entry, const std::string& return parent_path + '/' + name; } -void NANDImporter::ProcessEntry(u16 entry_number, const std::string& parent_path) +bool NANDImporter::ProcessEntry(u16 entry_number, const std::string& parent_path) { while (entry_number != 0xffff) { if (entry_number >= m_superblock->fst.size()) { ERROR_LOG_FMT(DISCIO, "FST entry number {} out of range", entry_number); - return; + return false; } const NANDFSTEntry entry = m_superblock->fst[entry_number]; const std::string path = GetPath(entry, parent_path); INFO_LOG_FMT(DISCIO, "Entry: {} Path: {}", entry, path); - m_update_callback(); Type type = static_cast(entry.mode & 3); if (type == Type::File) { + if (m_update_callback(Step::Extracting, m_progress_cur++, m_progress_max)) + return false; std::vector data = GetEntryData(entry); File::IOFile file(m_nand_root + path, "wb"); file.WriteBytes(data.data(), data.size()); @@ -157,7 +168,8 @@ void NANDImporter::ProcessEntry(u16 entry_number, const std::string& parent_path else if (type == Type::Directory) { File::CreateDir(m_nand_root + path); - ProcessEntry(entry.sub, path); + if (!ProcessEntry(entry.sub, path)) + return false; } else { @@ -166,6 +178,7 @@ void NANDImporter::ProcessEntry(u16 entry_number, const std::string& parent_path entry_number = entry.sib; } + return true; } std::vector NANDImporter::GetEntryData(const NANDFSTEntry& entry) const diff --git a/Source/Core/DiscIO/NANDImporter.h b/Source/Core/DiscIO/NANDImporter.h index 164d009f5a..f22f043709 100644 --- a/Source/Core/DiscIO/NANDImporter.h +++ b/Source/Core/DiscIO/NANDImporter.h @@ -23,10 +23,18 @@ public: NANDImporter(); ~NANDImporter(); + enum class Step + { + Loading, + Extracting, + }; + // Return true to cancel. + using UpdateCallback = std::function; + // Extract a NAND image to the configured NAND root. // If the associated OTP/SEEPROM dump (keys.bin) is not included in the image, // get_otp_dump_path will be called to get a path to it. - void ImportNANDBin(const std::string& path_to_bin, std::function update_callback, + void ImportNANDBin(const std::string& path_to_bin, UpdateCallback update_callback, const std::function& get_otp_dump_path); bool ExtractCertificates(); @@ -67,9 +75,10 @@ private: bool ReadNANDBin(const std::string& path_to_bin, const std::function& get_otp_dump_path); bool FindSuperblock(); + bool ExtractFiles(); std::string GetPath(const NANDFSTEntry& entry, const std::string& parent_path); std::string FormatDebugString(const NANDFSTEntry& entry); - void ProcessEntry(u16 entry_number, const std::string& parent_path); + bool ProcessEntry(u16 entry_number, const std::string& parent_path); std::vector GetEntryData(const NANDFSTEntry& entry) const; void ExportKeys(); @@ -78,7 +87,9 @@ private: std::vector m_nand_keys; std::unique_ptr m_aes_ctx; std::unique_ptr m_superblock; - std::function m_update_callback; + UpdateCallback m_update_callback; + u16 m_progress_cur = 0; + u16 m_progress_max = 0; }; } // namespace DiscIO diff --git a/Source/Core/DolphinQt/MainWindow.cpp b/Source/Core/DolphinQt/MainWindow.cpp index c3f5b4148c..2713157a74 100644 --- a/Source/Core/DolphinQt/MainWindow.cpp +++ b/Source/Core/DolphinQt/MainWindow.cpp @@ -1881,20 +1881,24 @@ void MainWindow::OnImportNANDBackup() return; ParallelProgressDialog dialog(this); - dialog.GetRaw()->setMinimum(0); - dialog.GetRaw()->setMaximum(0); - dialog.GetRaw()->setLabelText(tr("Importing NAND backup")); - dialog.GetRaw()->setCancelButton(nullptr); - - auto beginning = QDateTime::currentDateTime().toMSecsSinceEpoch(); + dialog.GetRaw()->setWindowTitle(tr("Importing NAND backup")); std::future result = std::async(std::launch::async, [&] { DiscIO::NANDImporter().ImportNANDBin( file.toStdString(), - [&dialog, beginning] { - dialog.SetLabelText( - tr("Importing NAND backup\n Time elapsed: %1s") - .arg((QDateTime::currentDateTime().toMSecsSinceEpoch() - beginning) / 1000)); + [&dialog](DiscIO::NANDImporter::Step step, u32 cur, u32 max) { + switch (step) + { + case DiscIO::NANDImporter::Step::Loading: + dialog.SetLabelText(tr("Loading NAND...")); + break; + case DiscIO::NANDImporter::Step::Extracting: + dialog.SetLabelText(tr("Extracting NAND...")); + break; + } + dialog.SetValue(cur); + dialog.SetMaximum(max); + return dialog.WasCanceled(); }, [this] { std::optional keys_file = RunOnObject(this, [this] {