mirror of
https://github.com/Dark98/threeSD.git
synced 2026-07-03 00:38:58 +00:00
core: Add DecryptToFile in NCCHContainer
This commit is contained in:
@@ -59,9 +59,9 @@ bool SDMCDecryptor::DecryptAndWriteFile(const std::string& source, const std::st
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
auto source_file = std::make_unique<FileUtil::IOFile>(root_folder + source, "rb");
|
auto source_file = std::make_shared<FileUtil::IOFile>(root_folder + source, "rb");
|
||||||
auto size = source_file->GetSize();
|
auto size = source_file->GetSize();
|
||||||
auto destination_file = std::make_unique<FileUtil::IOFile>(destination, "wb");
|
auto destination_file = std::make_shared<FileUtil::IOFile>(destination, "wb");
|
||||||
auto key = Key::GetNormalKey(Key::SDKey);
|
auto key = Key::GetNormalKey(Key::SDKey);
|
||||||
auto ctr = GetFileCTR(source);
|
auto ctr = GetFileCTR(source);
|
||||||
return quick_decryptor.DecryptAndWriteFile(std::move(source_file), size,
|
return quick_decryptor.DecryptAndWriteFile(std::move(source_file), size,
|
||||||
|
|||||||
@@ -18,6 +18,7 @@
|
|||||||
#include "core/key/key.h"
|
#include "core/key/key.h"
|
||||||
#include "core/ncch/ncch_container.h"
|
#include "core/ncch/ncch_container.h"
|
||||||
#include "core/ncch/seed_db.h"
|
#include "core/ncch/seed_db.h"
|
||||||
|
#include "core/quick_decryptor.h"
|
||||||
|
|
||||||
namespace Core {
|
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)
|
NCCHContainer::NCCHContainer(const std::string& root_folder, const std::string& filepath)
|
||||||
: root_folder(root_folder), filepath(filepath) {
|
: root_folder(root_folder), filepath(filepath) {
|
||||||
file = SDMCFile(root_folder, filepath, "rb");
|
file = std::make_shared<SDMCFile>(root_folder, filepath, "rb");
|
||||||
}
|
}
|
||||||
|
|
||||||
ResultStatus NCCHContainer::OpenFile(const std::string& root_folder, const std::string& filepath) {
|
ResultStatus NCCHContainer::OpenFile(const std::string& root_folder, const std::string& filepath) {
|
||||||
this->root_folder = root_folder;
|
this->root_folder = root_folder;
|
||||||
this->filepath = filepath;
|
this->filepath = filepath;
|
||||||
file = SDMCFile(root_folder, filepath, "rb");
|
file = std::make_shared<SDMCFile>(root_folder, filepath, "rb");
|
||||||
|
|
||||||
if (!file.IsOpen()) {
|
if (!file->IsOpen()) {
|
||||||
LOG_WARNING(Service_FS, "Failed to open {}", filepath);
|
LOG_WARNING(Service_FS, "Failed to open {}", filepath);
|
||||||
return ResultStatus::Error;
|
return ResultStatus::Error;
|
||||||
}
|
}
|
||||||
@@ -52,11 +53,11 @@ ResultStatus NCCHContainer::Load() {
|
|||||||
if (is_loaded)
|
if (is_loaded)
|
||||||
return ResultStatus::Success;
|
return ResultStatus::Success;
|
||||||
|
|
||||||
if (file.IsOpen()) {
|
if (file->IsOpen()) {
|
||||||
// Reset read pointer in case this file has been read before.
|
// 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;
|
return ResultStatus::Error;
|
||||||
|
|
||||||
// Verify we are loading the correct file type...
|
// Verify we are loading the correct file type...
|
||||||
@@ -192,7 +193,7 @@ ResultStatus NCCHContainer::Load() {
|
|||||||
return file && file.ReadBytes(&exheader_header, size) == size;
|
return file && file.ReadBytes(&exheader_header, size) == size;
|
||||||
};
|
};
|
||||||
|
|
||||||
if (!read_exheader(file)) {
|
if (!read_exheader(*file)) {
|
||||||
return ResultStatus::Error;
|
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 offset: 0x{:08X}", exefs_offset);
|
||||||
LOG_DEBUG(Service_FS, "ExeFS size: 0x{:08X}", exefs_size);
|
LOG_DEBUG(Service_FS, "ExeFS size: 0x{:08X}", exefs_size);
|
||||||
file.Seek(exefs_offset, SEEK_SET);
|
file->Seek(exefs_offset, SEEK_SET);
|
||||||
if (file.ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header))
|
if (file->ReadBytes(&exefs_header, sizeof(ExeFs_Header)) != sizeof(ExeFs_Header))
|
||||||
return ResultStatus::Error;
|
return ResultStatus::Error;
|
||||||
|
|
||||||
if (is_encrypted) {
|
if (is_encrypted) {
|
||||||
@@ -259,7 +260,7 @@ ResultStatus NCCHContainer::Load() {
|
|||||||
.ProcessData(data, data, sizeof(exefs_header));
|
.ProcessData(data, data, sizeof(exefs_header));
|
||||||
}
|
}
|
||||||
|
|
||||||
exefs_file = SDMCFile(root_folder, filepath, "rb");
|
exefs_file = std::make_shared<SDMCFile>(root_folder, filepath, "rb");
|
||||||
has_exefs = true;
|
has_exefs = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,7 +277,7 @@ ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vector<u8>&
|
|||||||
if (result != ResultStatus::Success)
|
if (result != ResultStatus::Success)
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
if (!exefs_file.IsOpen())
|
if (!exefs_file->IsOpen())
|
||||||
return ResultStatus::Error;
|
return ResultStatus::Error;
|
||||||
|
|
||||||
LOG_DEBUG(Service_FS, "{} sections:", kMaxSections);
|
LOG_DEBUG(Service_FS, "{} sections:", kMaxSections);
|
||||||
@@ -289,14 +290,14 @@ ResultStatus NCCHContainer::LoadSectionExeFS(const char* name, std::vector<u8>&
|
|||||||
section.offset, section.size, section.name);
|
section.offset, section.size, section.name);
|
||||||
|
|
||||||
s64 section_offset = (section.offset + exefs_offset + sizeof(ExeFs_Header));
|
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<CryptoPP::AES>::Decryption dec(primary_key.data(),
|
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption dec(primary_key.data(),
|
||||||
primary_key.size(), exefs_ctr.data());
|
primary_key.size(), exefs_ctr.data());
|
||||||
dec.Seek(section.offset + sizeof(ExeFs_Header));
|
dec.Seek(section.offset + sizeof(ExeFs_Header));
|
||||||
|
|
||||||
buffer.resize(section.size);
|
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;
|
return ResultStatus::Error;
|
||||||
if (is_encrypted) {
|
if (is_encrypted) {
|
||||||
dec.ProcessData(&buffer[0], &buffer[0], section.size);
|
dec.ProcessData(&buffer[0], &buffer[0], section.size);
|
||||||
@@ -418,6 +419,123 @@ ResultStatus NCCHContainer::ReadSeedCrypto(bool& used) {
|
|||||||
return ResultStatus::Success;
|
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<FileUtil::IOFile>(destination, "wb");
|
||||||
|
if (!*dest_file) {
|
||||||
|
LOG_ERROR(Core, "Could not open file {}", destination);
|
||||||
|
return ResultStatus::Error;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_encrypted) {
|
||||||
|
// Simply copy everything
|
||||||
|
QuickDecryptor<SDMCFile, FileUtil::IOFile> 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<SDMCFile, FileUtil::IOFile> 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)
|
#pragma pack(push, 1)
|
||||||
struct RomFSIVFCHeader {
|
struct RomFSIVFCHeader {
|
||||||
u32_le magic;
|
u32_le magic;
|
||||||
|
|||||||
@@ -265,6 +265,13 @@ public:
|
|||||||
*/
|
*/
|
||||||
ResultStatus ReadSeedCrypto(bool& used);
|
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;
|
NCCH_Header ncch_header;
|
||||||
ExHeader_Header exheader_header;
|
ExHeader_Header exheader_header;
|
||||||
ExeFs_Header exefs_header;
|
ExeFs_Header exefs_header;
|
||||||
@@ -289,8 +296,8 @@ private:
|
|||||||
|
|
||||||
std::string root_folder;
|
std::string root_folder;
|
||||||
std::string filepath;
|
std::string filepath;
|
||||||
SDMCFile file;
|
std::shared_ptr<SDMCFile> file;
|
||||||
SDMCFile exefs_file;
|
std::shared_ptr<SDMCFile> exefs_file;
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -23,15 +23,20 @@ template <typename In, typename Out>
|
|||||||
QuickDecryptor<In, Out>::~QuickDecryptor() = default;
|
QuickDecryptor<In, Out>::~QuickDecryptor() = default;
|
||||||
|
|
||||||
template <typename In, typename Out>
|
template <typename In, typename Out>
|
||||||
bool QuickDecryptor<In, Out>::DecryptAndWriteFile(std::unique_ptr<In> source_, std::size_t size,
|
bool QuickDecryptor<In, Out>::DecryptAndWriteFile(std::shared_ptr<In> source_, std::size_t size,
|
||||||
std::unique_ptr<Out> destination_,
|
std::shared_ptr<Out> destination_,
|
||||||
const ProgressCallback& callback_, bool decrypt_,
|
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) {
|
if (is_running) {
|
||||||
LOG_ERROR(Core, "Decryptor is running");
|
LOG_ERROR(Core, "Decryptor is running");
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (size == 0) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
for (auto& event : data_read_event) {
|
for (auto& event : data_read_event) {
|
||||||
event.Reset();
|
event.Reset();
|
||||||
}
|
}
|
||||||
@@ -48,6 +53,7 @@ bool QuickDecryptor<In, Out>::DecryptAndWriteFile(std::unique_ptr<In> source_, s
|
|||||||
decrypt = decrypt_;
|
decrypt = decrypt_;
|
||||||
key = std::move(key_);
|
key = std::move(key_);
|
||||||
ctr = std::move(ctr_);
|
ctr = std::move(ctr_);
|
||||||
|
aes_seek_pos = aes_seek_pos_;
|
||||||
callback = callback_;
|
callback = callback_;
|
||||||
|
|
||||||
current_total_size = size;
|
current_total_size = size;
|
||||||
@@ -117,6 +123,7 @@ template <typename In, typename Out>
|
|||||||
void QuickDecryptor<In, Out>::DataDecryptLoop() {
|
void QuickDecryptor<In, Out>::DataDecryptLoop() {
|
||||||
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption aes;
|
CryptoPP::CTR_Mode<CryptoPP::AES>::Decryption aes;
|
||||||
aes.SetKeyWithIV(key.data(), key.size(), ctr.data());
|
aes.SetKeyWithIV(key.data(), key.size(), ctr.data());
|
||||||
|
aes.Seek(aes_seek_pos);
|
||||||
|
|
||||||
std::size_t current_buffer = 0;
|
std::size_t current_buffer = 0;
|
||||||
std::size_t file_size = current_total_size;
|
std::size_t file_size = current_total_size;
|
||||||
|
|||||||
@@ -33,11 +33,23 @@ public:
|
|||||||
explicit QuickDecryptor();
|
explicit QuickDecryptor();
|
||||||
~QuickDecryptor();
|
~QuickDecryptor();
|
||||||
|
|
||||||
bool DecryptAndWriteFile(std::unique_ptr<In> source, std::size_t size,
|
/**
|
||||||
std::unique_ptr<Out> 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<In> source, std::size_t size,
|
||||||
|
std::shared_ptr<Out> destination,
|
||||||
const ProgressCallback& callback = [](std::size_t, std::size_t) {},
|
const ProgressCallback& callback = [](std::size_t, std::size_t) {},
|
||||||
bool decrypt = false, Core::Key::AESKey key = {},
|
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 DataReadLoop();
|
||||||
void DataDecryptLoop();
|
void DataDecryptLoop();
|
||||||
@@ -51,11 +63,12 @@ public:
|
|||||||
private:
|
private:
|
||||||
static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB
|
static constexpr std::size_t BufferSize = 16 * 1024; // 16 KB
|
||||||
|
|
||||||
std::unique_ptr<In> source;
|
std::shared_ptr<In> source;
|
||||||
std::unique_ptr<Out> destination;
|
std::shared_ptr<Out> destination;
|
||||||
bool decrypt{};
|
bool decrypt{};
|
||||||
Core::Key::AESKey key;
|
Core::Key::AESKey key;
|
||||||
Core::Key::AESKey ctr;
|
Core::Key::AESKey ctr;
|
||||||
|
std::size_t aes_seek_pos;
|
||||||
|
|
||||||
// Total size of this content, may consist of multiple files
|
// Total size of this content, may consist of multiple files
|
||||||
std::size_t total_size{};
|
std::size_t total_size{};
|
||||||
|
|||||||
Reference in New Issue
Block a user