diff --git a/src/core/decryptor.cpp b/src/core/decryptor.cpp index 6bdcbcf..68c9fd3 100644 --- a/src/core/decryptor.cpp +++ b/src/core/decryptor.cpp @@ -49,6 +49,10 @@ std::array GetFileCTR(const std::string& path) { } } // namespace +void SDMCDecryptor::Reset(std::size_t total_size) { + quick_decryptor.Reset(total_size); +} + bool SDMCDecryptor::DecryptAndWriteFile(const std::string& source, const std::string& destination, const QuickDecryptor::ProgressCallback& callback) { return quick_decryptor.DecryptAndWriteFile(source, destination, callback); @@ -83,7 +87,7 @@ std::vector SDMCDecryptor::DecryptFile(const std::string& source) const { return data; } -SDMCFile::SDMCFile() {} +SDMCFile::SDMCFile() = default; SDMCFile::SDMCFile(std::string root_folder, const std::string& filename, const char openmode[], int flags) { diff --git a/src/core/decryptor.h b/src/core/decryptor.h index 8423ac7..b10802b 100644 --- a/src/core/decryptor.h +++ b/src/core/decryptor.h @@ -41,6 +41,14 @@ public: */ std::vector DecryptFile(const std::string& source) const; + /** + * Marks the beginning of a new content, resetting imported_size counter, and setting an new + * total_size for the next content. + * This doesn't affect at all how the contents will be imported, but will make sure the callback + * is properly invoked. + */ + void Reset(std::size_t total_size); + private: std::string root_folder; QuickDecryptor quick_decryptor; diff --git a/src/core/importer.cpp b/src/core/importer.cpp index 16f45e2..94e08a0 100644 --- a/src/core/importer.cpp +++ b/src/core/importer.cpp @@ -67,7 +67,7 @@ bool SDMCImporter::ImportContent(const ContentSpecifier& specifier, case ContentType::Application: case ContentType::Update: case ContentType::DLC: - return ImportTitle(specifier.id, callback); + return ImportTitle(specifier, callback); case ContentType::Savegame: return ImportSavegame(specifier.id, callback); case ContentType::Extdata: @@ -81,23 +81,34 @@ bool SDMCImporter::ImportContent(const ContentSpecifier& specifier, } } -bool SDMCImporter::ImportTitle(u64 id, const ProgressCallback& callback) { - const auto path = fmt::format("title/{:08x}/{:08x}/content/", (id >> 32), (id & 0xFFFFFFFF)); - return FileUtil::ForeachDirectoryEntry( - nullptr, config.sdmc_path + path, - [this, &path, callback](u64* /*num_entries_out*/, const std::string& directory, - const std::string& virtual_name) { - if (FileUtil::IsDirectory(directory + virtual_name)) { - return true; +bool SDMCImporter::ImportTitle(const ContentSpecifier& specifier, + const ProgressCallback& callback) { + decryptor->Reset(specifier.maximum_size); + const FileUtil::DirectoryEntryCallable DirectoryEntryCallback = + [this, size = config.sdmc_path.size(), callback, + &DirectoryEntryCallback](u64* /*num_entries_out*/, const std::string& directory, + const std::string& virtual_name) { + if (FileUtil::IsDirectory(directory + virtual_name + "/")) { + if (virtual_name == "cmd") { + return true; // Skip cmd (not used in Citra) + } + // Recursive call (necessary for DLCs) + return FileUtil::ForeachDirectoryEntry(nullptr, directory + virtual_name + "/", + DirectoryEntryCallback); } + const auto filepath = (directory + virtual_name).substr(size - 1); return decryptor->DecryptAndWriteFile( - "/" + path + virtual_name, + filepath, FileUtil::GetUserPath(FileUtil::UserPath::SDMCDir) + "Nintendo " - "3DS/00000000000000000000000000000000/00000000000000000000000000000000/" + - path + virtual_name, + "3DS/00000000000000000000000000000000/00000000000000000000000000000000" + + filepath, callback); - }); + }; + const auto path = fmt::format("title/{:08x}/{:08x}/content/", (specifier.id >> 32), + (specifier.id & 0xFFFFFFFF)); + return FileUtil::ForeachDirectoryEntry(nullptr, config.sdmc_path + path, + DirectoryEntryCallback); } bool SDMCImporter::ImportSavegame(u64 id, [[maybe_unused]] const ProgressCallback& callback) { diff --git a/src/core/importer.h b/src/core/importer.h index ecc29d9..0bbd176 100644 --- a/src/core/importer.h +++ b/src/core/importer.h @@ -108,7 +108,7 @@ public: private: bool Init(); - bool ImportTitle(u64 id, const ProgressCallback& callback); + bool ImportTitle(const ContentSpecifier& specifier, const ProgressCallback& callback); bool ImportSavegame(u64 id, const ProgressCallback& callback); bool ImportExtdata(u64 id, const ProgressCallback& callback); bool ImportSystemArchive(u64 id, const ProgressCallback& callback); diff --git a/src/core/quick_decryptor.cpp b/src/core/quick_decryptor.cpp index ba4ebbe..b975f17 100644 --- a/src/core/quick_decryptor.cpp +++ b/src/core/quick_decryptor.cpp @@ -74,8 +74,8 @@ bool QuickDecryptor::DecryptAndWriteFile(const std::string& source_, destination = destination_; callback = callback_; - total_size = FileUtil::GetSize(root_folder + source); - if (total_size == 0) { + current_total_size = FileUtil::GetSize(root_folder + source); + if (current_total_size == 0) { LOG_ERROR(Core, "Could not open file {}", root_folder + source); return false; } @@ -109,7 +109,7 @@ void QuickDecryptor::DataReadLoop() { return; } - std::size_t file_size = total_size; + std::size_t file_size = current_total_size; while (is_running && file_size > 0) { if (is_first_run) { @@ -140,7 +140,7 @@ void QuickDecryptor::DataDecryptLoop() { aes.SetKeyWithIV(key.data(), key.size(), ctr.data()); std::size_t current_buffer = 0; - std::size_t file_size = total_size; + std::size_t file_size = current_total_size; while (is_running && file_size > 0) { data_read_event[current_buffer].Wait(); @@ -166,17 +166,18 @@ void QuickDecryptor::DataWriteLoop() { return; } - std::size_t file_size = total_size; + std::size_t file_size = current_total_size; std::size_t iteration = 0; /// The number of iterations each progress report covers. 32 * 16K = 512K constexpr std::size_t ProgressReportFreq = 32; while (is_running && file_size > 0) { - iteration++; if (iteration % ProgressReportFreq == 0) { - callback(iteration * BufferSize, total_size); + callback(imported_size, total_size); } + iteration++; + data_decrypted_event[current_buffer].Wait(); const auto bytes_to_write = std::min(BufferSize, file_size); @@ -186,6 +187,7 @@ void QuickDecryptor::DataWriteLoop() { return; } file_size -= bytes_to_write; + imported_size += bytes_to_write; data_written_event[current_buffer].Set(); current_buffer = (current_buffer + 1) % buffers.size(); @@ -201,4 +203,9 @@ void QuickDecryptor::Abort() { } } +void QuickDecryptor::Reset(std::size_t total_size_) { + total_size = total_size_; + imported_size = 0; +} + } // namespace Core diff --git a/src/core/quick_decryptor.h b/src/core/quick_decryptor.h index a645c19..d82977a 100644 --- a/src/core/quick_decryptor.h +++ b/src/core/quick_decryptor.h @@ -35,13 +35,22 @@ public: void Abort(); + /// Reset the imported_size counter for this content and set a new total_size. + void Reset(std::size_t total_size); + private: static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB std::string root_folder; std::string source; std::string destination; + + // Total size of this content, may consist of multiple files std::size_t total_size{}; + // Total size of the current file to process + std::size_t current_total_size{}; + // Total imported size for this content + std::size_t imported_size{}; std::array, 3> buffers; std::array data_read_event;