diff --git a/src/core/decryptor.cpp b/src/core/decryptor.cpp index 2af126f..fc04c48 100644 --- a/src/core/decryptor.cpp +++ b/src/core/decryptor.cpp @@ -59,9 +59,9 @@ bool SDMCDecryptor::DecryptAndWriteFile(const std::string& source, const std::st return false; } - auto source_file = std::make_unique(root_folder + source, "rb"); + auto source_file = std::make_shared(root_folder + source, "rb"); auto size = source_file->GetSize(); - auto destination_file = std::make_unique(destination, "wb"); + auto destination_file = std::make_shared(destination, "wb"); auto key = Key::GetNormalKey(Key::SDKey); auto ctr = GetFileCTR(source); return quick_decryptor.DecryptAndWriteFile(std::move(source_file), size, diff --git a/src/core/ncch/ncch_container.cpp b/src/core/ncch/ncch_container.cpp index 95043ce..e2b4ecd 100644 --- a/src/core/ncch/ncch_container.cpp +++ b/src/core/ncch/ncch_container.cpp @@ -18,6 +18,7 @@ #include "core/key/key.h" #include "core/ncch/ncch_container.h" #include "core/ncch/seed_db.h" +#include "core/quick_decryptor.h" namespace Core { @@ -30,15 +31,15 @@ static const int kBlockSize = 0x200; ///< Size of ExeFS blocks (in bytes) NCCHContainer::NCCHContainer(const std::string& root_folder, const std::string& filepath) : root_folder(root_folder), filepath(filepath) { - file = SDMCFile(root_folder, filepath, "rb"); + file = std::make_shared(root_folder, filepath, "rb"); } ResultStatus NCCHContainer::OpenFile(const std::string& root_folder, const std::string& filepath) { this->root_folder = root_folder; this->filepath = filepath; - file = SDMCFile(root_folder, filepath, "rb"); + file = std::make_shared(root_folder, filepath, "rb"); - if (!file.IsOpen()) { + if (!file->IsOpen()) { LOG_WARNING(Service_FS, "Failed to open {}", filepath); return ResultStatus::Error; } @@ -52,11 +53,11 @@ ResultStatus NCCHContainer::Load() { if (is_loaded) return ResultStatus::Success; - if (file.IsOpen()) { + if (file->IsOpen()) { // Reset read pointer in case this file has been read before. - file.Seek(0, SEEK_SET); + file->Seek(0, SEEK_SET); - if (file.ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) + if (file->ReadBytes(&ncch_header, sizeof(NCCH_Header)) != sizeof(NCCH_Header)) return ResultStatus::Error; // Verify we are loading the correct file type... @@ -192,7 +193,7 @@ ResultStatus NCCHContainer::Load() { return file && file.ReadBytes(&exheader_header, size) == size; }; - if (!read_exheader(file)) { + if (!read_exheader(*file)) { return ResultStatus::Error; } @@ -248,8 +249,8 @@ ResultStatus NCCHContainer::Load() { LOG_DEBUG(Service_FS, "ExeFS offset: 0x{:08X}", exefs_offset); LOG_DEBUG(Service_FS, "ExeFS size: 0x{:08X}", exefs_size); - file.Seek(exefs_offset, SEEK_SET); - if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) + file->Seek(exefs_offset, SEEK_SET); + if (file->ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header)) return ResultStatus::Error; if (is_encrypted) { @@ -259,7 +260,7 @@ ResultStatus NCCHContainer::Load() { .ProcessData(data, data, sizeof(exefs_header)); } - exefs_file = SDMCFile(root_folder, filepath, "rb"); + exefs_file = std::make_shared(root_folder, filepath, "rb"); has_exefs = true; } @@ -276,7 +277,7 @@ ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vector& if (result != ResultStatus::Success) return result; - if (!exefs_file.IsOpen()) + if (!exefs_file->IsOpen()) return ResultStatus::Error; LOG_DEBUG(Service_FS, "{} sections:", kMaxSections); @@ -289,14 +290,14 @@ ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vector& section.offset, section.size, section.name); s64 section_offset = (section.offset + exefs_offset + sizeof(ExeFs_Header)); - exefs_file.Seek(section_offset, SEEK_SET); + exefs_file->Seek(section_offset, SEEK_SET); CryptoPP::CTR_Mode::Decryption dec(primary_key.data(), primary_key.size(), exefs_ctr.data()); dec.Seek(section.offset + sizeof(ExeFs_Header)); buffer.resize(section.size); - if (exefs_file.ReadBytes(&buffer[0], section.size) != section.size) + if (exefs_file->ReadBytes(&buffer[0], section.size) != section.size) return ResultStatus::Error; if (is_encrypted) { dec.ProcessData(&buffer[0], &buffer[0], section.size); @@ -418,6 +419,123 @@ ResultStatus NCCHContainer::ReadSeedCrypto(bool& used) { return ResultStatus::Success; } +ResultStatus NCCHContainer::DecryptToFile(const std::string& destination, + const ProgressCallback& callback) { + ResultStatus result = Load(); + if (result != ResultStatus::Success) + return result; + + if (!has_header || !has_exheader) + return ResultStatus::ErrorNotUsed; + + if (!FileUtil::CreateFullPath(destination)) { + LOG_ERROR(Core, "Could not create path {}", destination); + return ResultStatus::Error; + } + auto dest_file = std::make_shared(destination, "wb"); + if (!*dest_file) { + LOG_ERROR(Core, "Could not open file {}", destination); + return ResultStatus::Error; + } + + if (!is_encrypted) { + // Simply copy everything + QuickDecryptor decryptor; + const auto size = file->GetSize(); + decryptor.Reset(size); + decryptor.DecryptAndWriteFile(file, size, dest_file, callback); + } + + // Write NCCH header + NCCH_Header modified_header = ncch_header; + modified_header.no_crypto.Assign(1); + modified_header.secondary_key_slot = 0; + if (dest_file->WriteBytes(&modified_header, sizeof(modified_header)) != + sizeof(modified_header)) { + LOG_ERROR(Core, "Could not write NCCH header to file {}", destination); + return ResultStatus::Error; + } + + // Write Exheader + if (dest_file->WriteBytes(&exheader_header, sizeof(exheader_header)) != + sizeof(exheader_header)) { + LOG_ERROR(Core, "Could not write Exheader to file {}", destination); + return ResultStatus::Error; + } + + QuickDecryptor decryptor; + const auto total_size = + file->GetSize() - sizeof(NCCH_Header) - sizeof(ExHeader_Header) - sizeof(ExeFs_Header); + decryptor.Reset(total_size); + + std::size_t written = sizeof(NCCH_Header) + sizeof(ExHeader_Header); + + const auto Write = [&](std::string_view name, std::size_t offset, std::size_t size, + bool decrypt = false, const Key::AESKey& key = {}, + const Key::AESKey& ctr = {}, std::size_t aes_seek_pos = 0) { + if (offset == 0 || size == 0) { + return true; + } + + file->Seek(written, SEEK_SET); + ASSERT_MSG(written <= offset, "Offsets are not in increasing order"); + if (!decryptor.DecryptAndWriteFile(file, offset - written, dest_file, callback)) { + LOG_ERROR(Core, "Could not write data before {} to {}", name, destination); + return false; + } + if (!decryptor.DecryptAndWriteFile(file, size, dest_file, callback, decrypt, key, ctr, + aes_seek_pos)) { + LOG_ERROR(Core, "Could not write {} to {}", name, destination); + return false; + } + written = offset + size; + return true; + }; + + if (!Write("plain region", ncch_header.plain_region_offset * 0x200, + ncch_header.plain_region_size * 0x200)) { + return ResultStatus::Error; + } + + // Write ExeFS header + if (dest_file->WriteBytes(&exefs_header, sizeof(exefs_header)) != sizeof(exefs_header)) { + LOG_ERROR(Core, "Could not write ExeFS header to file {}", destination); + return ResultStatus::Error; + } + written += sizeof(ExeFs_Header); + + for (unsigned section_number = 0; section_number < kMaxSections; section_number++) { + const auto& section = exefs_header.section[section_number]; + if (section.offset == 0 && section.size == 0) { // not used + continue; + } + + Key::AESKey key; + if (strcmp(section.name, "icon") == 0 || strcmp(section.name, "banner") == 0) { + key = primary_key; + } else { + key = secondary_key; + } + + // Plus 1 for the ExeFS header + if (!Write(section.name, section.offset + (ncch_header.exefs_offset + 1) * 0x200, + section.size, true, key, exefs_ctr, section.offset + sizeof(exefs_header))) { + return ResultStatus::Error; + } + } + + if (!Write("romfs", ncch_header.romfs_offset * 0x200, ncch_header.romfs_size * 0x200, true, + secondary_key, romfs_ctr)) { + return ResultStatus::Error; + } + if (written < file->GetSize()) { + LOG_WARNING(Core, "Data after {} ignored", written); + } + + callback(total_size, total_size); + return ResultStatus::Success; +} + #pragma pack(push, 1) struct RomFSIVFCHeader { u32_le magic; diff --git a/src/core/ncch/ncch_container.h b/src/core/ncch/ncch_container.h index fdc079a..26afeda 100644 --- a/src/core/ncch/ncch_container.h +++ b/src/core/ncch/ncch_container.h @@ -265,6 +265,13 @@ public: */ ResultStatus ReadSeedCrypto(bool& used); + /** + * Decrypts this NCCH and write to the destination file. + * @return ResultStatus result of function. + */ + ResultStatus DecryptToFile(const std::string& destination, + const ProgressCallback& callback = [](std::size_t, std::size_t) {}); + NCCH_Header ncch_header; ExHeader_Header exheader_header; ExeFs_Header exefs_header; @@ -289,8 +296,8 @@ private: std::string root_folder; std::string filepath; - SDMCFile file; - SDMCFile exefs_file; + std::shared_ptr file; + std::shared_ptr exefs_file; }; /** diff --git a/src/core/quick_decryptor.cpp b/src/core/quick_decryptor.cpp index b7585b9..efe42a3 100644 --- a/src/core/quick_decryptor.cpp +++ b/src/core/quick_decryptor.cpp @@ -23,15 +23,20 @@ template QuickDecryptor::~QuickDecryptor() = default; template -bool QuickDecryptor::DecryptAndWriteFile(std::unique_ptr source_, std::size_t size, - std::unique_ptr destination_, +bool QuickDecryptor::DecryptAndWriteFile(std::shared_ptr source_, std::size_t size, + std::shared_ptr destination_, const ProgressCallback& callback_, bool decrypt_, - Core::Key::AESKey key_, Core::Key::AESKey ctr_) { + Core::Key::AESKey key_, Core::Key::AESKey ctr_, + std::size_t aes_seek_pos_) { if (is_running) { LOG_ERROR(Core, "Decryptor is running"); return false; } + if (size == 0) { + return true; + } + for (auto& event : data_read_event) { event.Reset(); } @@ -48,6 +53,7 @@ bool QuickDecryptor::DecryptAndWriteFile(std::unique_ptr source_, s decrypt = decrypt_; key = std::move(key_); ctr = std::move(ctr_); + aes_seek_pos = aes_seek_pos_; callback = callback_; current_total_size = size; @@ -117,6 +123,7 @@ template void QuickDecryptor::DataDecryptLoop() { CryptoPP::CTR_Mode::Decryption aes; aes.SetKeyWithIV(key.data(), key.size(), ctr.data()); + aes.Seek(aes_seek_pos); std::size_t current_buffer = 0; std::size_t file_size = current_total_size; diff --git a/src/core/quick_decryptor.h b/src/core/quick_decryptor.h index 5346b07..16dc7bb 100644 --- a/src/core/quick_decryptor.h +++ b/src/core/quick_decryptor.h @@ -33,11 +33,23 @@ public: explicit QuickDecryptor(); ~QuickDecryptor(); - bool DecryptAndWriteFile(std::unique_ptr source, std::size_t size, - std::unique_ptr destination, + /** + * Decrypts and writes a file. + * + * @param source Source file + * @param size Size to read, decrypt and write + * @param destination Destination file + * @param callback Progress callback + * @param decrypt Whether to perform decryption or not + * @param key AES Key for decryption + * @param ctr AES CTR for decryption + * @param aes_seek_pos The position to seek to for decryption. + */ + bool DecryptAndWriteFile(std::shared_ptr source, std::size_t size, + std::shared_ptr destination, const ProgressCallback& callback = [](std::size_t, std::size_t) {}, bool decrypt = false, Core::Key::AESKey key = {}, - Core::Key::AESKey ctr = {}); + Core::Key::AESKey ctr = {}, std::size_t aes_seek_pos = 0); void DataReadLoop(); void DataDecryptLoop(); @@ -51,11 +63,12 @@ public: private: static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB - std::unique_ptr source; - std::unique_ptr destination; + std::shared_ptr source; + std::shared_ptr destination; bool decrypt{}; Core::Key::AESKey key; Core::Key::AESKey ctr; + std::size_t aes_seek_pos; // Total size of this content, may consist of multiple files std::size_t total_size{};