diff --git a/programs/nstool/source/AesCtrWrappedIFile.cpp b/programs/nstool/source/AesCtrWrappedIFile.cpp new file mode 100644 index 0000000..29c4344 --- /dev/null +++ b/programs/nstool/source/AesCtrWrappedIFile.cpp @@ -0,0 +1,71 @@ +#include "AesCtrWrappedIFile.h" + +AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) : + mFile(file), + mKey(key), + mBaseCtr(ctr) +{ + mScratch.alloc(kAesCtrScratchAllocSize); +} + +size_t AesCtrWrappedIFile::size() +{ + return mFile.size(); +} + +void AesCtrWrappedIFile::seek(size_t offset) +{ + mFile.seek(offset); + crypto::aes::AesIncrementCounter(mBaseCtr.iv, offset>>4, mCurrentCtr.iv); + mBlockOffset = offset & 0xf; +} + +void AesCtrWrappedIFile::read(byte_t* out, size_t len) +{ + for (size_t i = 0; i < (len / kAesCtrScratchSize); i++) + { + mFile.read(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize); + crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes()); + memcpy(out + (i * kAesCtrScratchSize), mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize); + } + + if (len % kAesCtrScratchSize) + { + size_t read_len = len % kAesCtrScratchSize; + size_t read_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize); + mFile.read(mScratch.getBytes() + mBlockOffset, read_len); + crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes()); + memcpy(out + read_pos, mScratch.getBytes() + mBlockOffset, read_len); + } +} + +void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len) +{ + seek(offset); + read(out, len); +} + +void AesCtrWrappedIFile::write(const byte_t* out, size_t len) +{ + for (size_t i = 0; i < (len / kAesCtrScratchSize); i++) + { + memcpy(mScratch.getBytes() + mBlockOffset, out + (i * kAesCtrScratchSize), kAesCtrScratchSize); + crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes()); + mFile.write(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize); + } + + if (len % kAesCtrScratchSize) + { + size_t write_len = len % kAesCtrScratchSize; + size_t write_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize); + memcpy(mScratch.getBytes() + mBlockOffset, out + write_pos, write_len); + crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes()); + mFile.write(mScratch.getBytes() + mBlockOffset, write_len); + } +} + +void AesCtrWrappedIFile::write(const byte_t* out, size_t offset, size_t len) +{ + seek(offset); + write(out, len); +} \ No newline at end of file diff --git a/programs/nstool/source/AesCtrWrappedIFile.h b/programs/nstool/source/AesCtrWrappedIFile.h new file mode 100644 index 0000000..5f88844 --- /dev/null +++ b/programs/nstool/source/AesCtrWrappedIFile.h @@ -0,0 +1,27 @@ +#include +#include +#include + +class AesCtrWrappedIFile : public fnd::IFile +{ +public: + AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr); + + size_t size(); + void seek(size_t offset); + void read(byte_t* out, size_t len); + void read(byte_t* out, size_t offset, size_t len); + void write(const byte_t* out, size_t len); + void write(const byte_t* out, size_t offset, size_t len); +private: + const std::string kModuleName = "AesCtrWrappedIFile"; + static const size_t kAesCtrScratchSize = 0x1000000; + static const size_t kAesCtrScratchAllocSize = kAesCtrScratchSize + crypto::aes::kAesBlockSize; + + fnd::IFile& mFile; + crypto::aes::sAes128Key mKey; + crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr; + size_t mBlockOffset; + + fnd::MemoryBlob mScratch; +}; \ No newline at end of file diff --git a/programs/nstool/source/NcaPartitionProcess.cpp b/programs/nstool/source/NcaPartitionProcess.cpp new file mode 100644 index 0000000..e69de29 diff --git a/programs/nstool/source/NcaPartitionProcess.h b/programs/nstool/source/NcaPartitionProcess.h new file mode 100644 index 0000000..7424e34 --- /dev/null +++ b/programs/nstool/source/NcaPartitionProcess.h @@ -0,0 +1,16 @@ +#pragma once +#include +#include +#include +#include + +#include "nstool.h" + +class NcaPartitionProcess +{ +public: + NcaPartitionProcess(); +private: + const std::string kModuleName = "NcaPartitionProcess"; + +}; \ No newline at end of file diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index f624a1a..a954617 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -1,6 +1,11 @@ -#include "NcaProcess.h" #include #include +#include +#include "NcaProcess.h" +#include "PfsProcess.h" +#include "RomfsProcess.h" +#include "OffsetAdjustedIFile.h" +#include "AesCtrWrappedIFile.h" std::string kFormatVersionStr[] { @@ -20,7 +25,8 @@ std::string kContentTypeStr[] "Meta", "Control", "Manual", - "Data" + "Data", + "PublicData" }; std::string kEncryptionTypeStr[] @@ -35,7 +41,7 @@ std::string kEncryptionTypeStr[] std::string kHashTypeStr[] { "Auto", - "UNKNOWN_1", + "None", "HierarchicalSha256", "HierarchicalIntegrity" }; @@ -81,6 +87,7 @@ void NcaProcess::displayHeader() } } + /* if (mBodyKeyList.getSize() > 0) { printf(" Key Area Keys:\n"); @@ -90,23 +97,221 @@ void NcaProcess::displayHeader() fnd::SimpleTextOutput::hexDump(mBodyKeyList[i].key, crypto::aes::kAes128KeySize); } } + */ + + printf(" Partitions:\n"); + for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++) + { + const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i]; + nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index]; + + printf(" %lu:\n", i); + printf(" Index: %d\n", partition.index); + printf(" Offset: 0x%" PRIx64 "\n", partition.offset); + printf(" Size: 0x%" PRIx64 "\n", partition.size); + + + crypto::sha::sSha256Hash ncaFsHeaderHash; + crypto::sha::Sha256((byte_t*)&fs_header, sizeof(nx::sNcaFsHeader), ncaFsHeaderHash.bytes); + if (partition.hash.compare(ncaFsHeaderHash) == false) + { + throw fnd::Exception(kModuleName, "NcaFsHeader has bad sha256 hash"); + } + + //fnd::SimpleTextOutput::hxdStyleDump((byte_t*)&fs_header, sizeof(nx::sNcaFsHeader)); + + + printf(" FsHeader:\n"); + printf(" Version: 0x%d\n", fs_header.version.get()); + printf(" Format Type: %s\n", kFormatTypeStr[fs_header.format_type].c_str()); + printf(" Hash Type: %s\n", kHashTypeStr[fs_header.hash_type].c_str()); + printf(" Enc. Type: %s\n", kEncryptionTypeStr[fs_header.encryption_type].c_str()); + if (fs_header.encryption_type == nx::nca::CRYPT_AESCTR) + { + printf(" CTR: "); + crypto::aes::sAesIvCtr ctr; + nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv); + crypto::aes::AesIncrementCounter(ctr.iv, partition.offset>>4, ctr.iv); + fnd::SimpleTextOutput::hexDump(ctr.iv, sizeof(crypto::aes::sAesIvCtr)); + } + if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) + { + printf(" HierarchicalIntegrity Header:\n"); + printf(" Id: 0x%x\n", fs_header.ivfc_header.id.get()); + printf(" MasterHashSize: 0x%x\n", fs_header.ivfc_header.master_hash_size.get()); + printf(" LevelNum: %d\n", fs_header.ivfc_header.level_num.get()); + for (size_t i = 0; i < fs_header.ivfc_header.level_num.get(); i++) + { + printf(" Level %d:\n", i); + printf(" LogicalOffset: 0x%" PRIx64 "\n", fs_header.ivfc_header.level_header[i].logical_offset.get()); + printf(" HashDataSize: 0x%" PRIx64 "\n", fs_header.ivfc_header.level_header[i].hash_data_size.get()); + printf(" BlockSize: 0x%" PRIx32 "\n", fs_header.ivfc_header.level_header[i].block_size.get()); + } + printf(" Master Hash: "); + fnd::SimpleTextOutput::hexDump(fs_header.ivfc_header.master_hash.bytes, 0x20); + + + } + else if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) + { + nx::sHierarchicalSha256Header& hash_hdr = fs_header.hierarchicalsha256_header; + printf(" HierarchicalSha256 Header:\n"); + printf(" Master Hash: "); + fnd::SimpleTextOutput::hexDump(hash_hdr.master_hash.bytes, 0x20); + printf(" HashBlockSize: 0x%x\n", hash_hdr.hash_block_size.get()); + printf(" HashLevelNum: 0x%x\n", hash_hdr.hash_level_num.get()); + printf(" HashDataOffset: 0x%" PRIx64 "\n", hash_hdr.hash_data.offset.get()); + printf(" HashDataSize: 0x%" PRIx64 "\n", hash_hdr.hash_data.size.get()); + printf(" HashTargetOffset: 0x%" PRIx64 "\n", hash_hdr.hash_target.offset.get()); + printf(" HashTargetSize: 0x%" PRIx64 "\n", hash_hdr.hash_target.size.get()); + + } + else + { + printf(" Hash Superblock:\n"); + fnd::SimpleTextOutput::hxdStyleDump(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); + } + } } -void NcaProcess::decryptBodyKeyList() +void NcaProcess::generateNcaBodyEncryptionKeys() { - crypto::aes::sAes128Key zero_key; - memset(zero_key.key, 0, sizeof(zero_key)); - + // create zeros key + crypto::aes::sAes128Key zero_aesctr_key; + memset(zero_aesctr_key.key, 0, sizeof(zero_aesctr_key)); + crypto::aes::sAesXts128Key zero_aesxts_key; + memset(zero_aesxts_key.key, 0, sizeof(zero_aesxts_key)); + + // get key data from header byte_t masterkey_rev = nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(mHdr.getKeyGeneration()); byte_t keak_index = mHdr.getKaekIndex(); - crypto::aes::sAes128Key tmp_key; - if (mKeyset->nca.key_area_key[keak_index][masterkey_rev] != zero_key) + // set flag to indicate that the keys are not available + mBodyKeys.aes_ctr.isSet = false; + mBodyKeys.aes_xts.isSet = false; + + // if this has a rights id, the key needs to be sourced from a ticket + if (mHdr.hasRightsId() == true) { - for (size_t i = 0; i < 4; i++) + // if the titlekey_kek is available + if (mKeyset->ticket.titlekey_kek[masterkey_rev] != zero_aesctr_key) { - crypto::aes::AesEcbDecrypt(mHdr.getEncAesKeys()[i].key, 0x10, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key, tmp_key.key); - mBodyKeyList.addElement(tmp_key); + // the title key is provided (sourced from ticket) + if (mKeyset->nca.manual_title_key_aesctr != zero_aesctr_key) + { + nx::AesKeygen::generateKey(mBodyKeys.aes_ctr.var.key, mKeyset->nca.manual_title_key_aesctr.key, mKeyset->ticket.titlekey_kek[masterkey_rev].key); + mBodyKeys.aes_ctr.isSet = true; + } + if (mKeyset->nca.manual_title_key_aesxts != zero_aesxts_key) + { + nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[0], mKeyset->nca.manual_title_key_aesxts.key[0], mKeyset->ticket.titlekey_kek[masterkey_rev].key); + nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[1], mKeyset->nca.manual_title_key_aesxts.key[1], mKeyset->ticket.titlekey_kek[masterkey_rev].key); + mBodyKeys.aes_xts.isSet = true; + } + } + } + // otherwise decrypt key area + else + { + // if the titlekey_kek is available + if (mKeyset->nca.key_area_key[keak_index][masterkey_rev] != zero_aesctr_key) + { + nx::AesKeygen::generateKey(mBodyKeys.aes_ctr.var.key, mHdr.getEncAesKeys()[nx::nca::KEY_AESCTR].key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key); + mBodyKeys.aes_ctr.isSet = true; + + nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[0], mHdr.getEncAesKeys()[nx::nca::KEY_AESXTS_0].key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key); + nx::AesKeygen::generateKey(mBodyKeys.aes_xts.var.key[1], mHdr.getEncAesKeys()[nx::nca::KEY_AESXTS_1].key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key); + mBodyKeys.aes_xts.isSet = true; + } + } + + // if the keys weren't generated, check if the keys were supplied by the user + if (mBodyKeys.aes_ctr.isSet == false && mKeyset->nca.manual_body_key_aesctr != zero_aesctr_key) + { + mBodyKeys.aes_ctr = mKeyset->nca.manual_body_key_aesctr; + } + if (mBodyKeys.aes_xts.isSet == false && mKeyset->nca.manual_body_key_aesxts != zero_aesxts_key) + { + mBodyKeys.aes_xts = mKeyset->nca.manual_body_key_aesxts; + } +} + +void NcaProcess::processPartitions() +{ + for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++) + { + const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i]; + nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index]; + + crypto::aes::sAesIvCtr ctr; + nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv); + + // create reader + fnd::IFile* partitionReader = nullptr; + + AesCtrWrappedIFile aesCtrFile = AesCtrWrappedIFile(*mReader, mBodyKeys.aes_ctr.var, ctr); + switch(fs_header.encryption_type) + { + case (nx::nca::CRYPT_AESXTS): + case (nx::nca::CRYPT_AESCTREX): + partitionReader = nullptr; + break; + case (nx::nca::CRYPT_AESCTR): + partitionReader = mBodyKeys.aes_ctr.isSet? &aesCtrFile : nullptr; + break; + case (nx::nca::CRYPT_NONE): + partitionReader = mReader; + break; + } + + // if the reader is null, skip + if (partitionReader == nullptr) + { + printf("[WARNING] NCA Partition %d not readable\n", partition.index); + continue; + } + + size_t data_offset = 0; + switch (fs_header.hash_type) + { + case (nx::nca::HASH_HIERARCHICAL_SHA256): + data_offset = fs_header.hierarchicalsha256_header.hash_target.offset.get(); + break; + case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): + data_offset = fs_header.ivfc_header.level_header[5].logical_offset.get(); + break; + case (nx::nca::HASH_NONE): + data_offset = 0; + break; + default: + throw fnd::Exception(kModuleName, "Unknown hash type"); + } + + if (fs_header.format_type == nx::nca::FORMAT_PFS0) + { + PfsProcess pfs; + pfs.setInputFile(*partitionReader); + pfs.setInputFileOffset(partition.offset + data_offset); + pfs.setCliOutputMode(mCliOutputType); + pfs.setListFs(mListFs); + if (mPartitionPath[partition.index].doExtract) + pfs.setExtractPath(mPartitionPath[partition.index].path); + pfs.process(); + } + else if (fs_header.format_type == nx::nca::FORMAT_ROMFS) + { + RomfsProcess romfs; + romfs.setInputFile(*partitionReader); + romfs.setInputFileOffset(partition.offset + data_offset); + romfs.setCliOutputMode(mCliOutputType); + romfs.setListFs(mListFs); + if (mPartitionPath[partition.index].doExtract) + romfs.setExtractPath(mPartitionPath[partition.index].path); + romfs.process(); + } + else + { + throw fnd::Exception(kModuleName, "Unknown format type"); } } } @@ -116,7 +321,16 @@ NcaProcess::NcaProcess() : mOffset(0), mKeyset(nullptr), mCliOutputType(OUTPUT_NORMAL), - mVerify(false) + mVerify(false), + mListFs(false) +{ + mPartitionPath[0].doExtract = false; + mPartitionPath[1].doExtract = false; + mPartitionPath[2].doExtract = false; + mPartitionPath[3].doExtract = false; +} + +NcaProcess::~NcaProcess() { } @@ -136,19 +350,80 @@ void NcaProcess::process() // decrypt header block nx::NcaUtils::decryptNcaHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyset->nca.header_key); + // generate header hash + crypto::sha::Sha256((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader), mHdrHash.bytes); + + // validate signature[0] + if (mVerify) + { + if (crypto::rsa::pss::rsaVerify(mKeyset->nca.header_sign_key, crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_main) != 0) + { + // this is minimal even though it's a warning because it's a validation method + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] NCA Header Main Signature: FAIL \n"); + } + } + // proccess main header mHdr.importBinary((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader)); + // validate fs headers + if (mVerify) + { + crypto::sha::sSha256Hash calc_hash; + + for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++) + { + const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i]; + + crypto::sha::Sha256((const byte_t*)&mHdrBlock.fs_header[partition.index], sizeof(nx::sNcaFsHeader), calc_hash.bytes); + + if (calc_hash.compare(partition.hash) == false) + { + // this is minimal even though it's a warning because it's a validation method + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] NCA FsHeader[%d] Hash: FAIL \n", partition.index); + } + } + } + + // determine keys + + + /* + NCA is a file container + A hashed and signed file container + + To verify a NCA: (R=regular step) + 1 - decrypt header (R) + 2 - verify signature[0] + 3 - validate hashes of fs_headers + 4 - determine how to read/decrypt the partitions (R) + 5 - validate the partitions depending on their hash method + 6 - if this NCA is a Program or Patch, open main.npdm from partition0 + 7 - validate ACID + 8 - use public key in ACID to verify NCA signature[1] + + Things to consider + * because of the manditory steps between verifcation steps + the NCA should be ready to be pulled to pieces before any printing is done + so the verification text can be presented without interuption + + */ + + // decrypt key area - decryptBodyKeyList(); + generateNcaBodyEncryptionKeys(); if (mCliOutputType >= OUTPUT_NORMAL) displayHeader(); + + processPartitions(); } -void NcaProcess::setInputFile(fnd::IFile& reader) +void NcaProcess::setInputFile(fnd::IFile* reader) { - mReader = &reader; + mReader = reader; } void NcaProcess::setInputFileOffset(size_t offset) @@ -169,4 +444,33 @@ void NcaProcess::setCliOutputMode(CliOutputType type) void NcaProcess::setVerifyMode(bool verify) { mVerify = verify; -} \ No newline at end of file +} + +void NcaProcess::setPartition0ExtractPath(const std::string& path) +{ + mPartitionPath[0].path = path; + mPartitionPath[0].doExtract = true; +} + +void NcaProcess::setPartition1ExtractPath(const std::string& path) +{ + mPartitionPath[1].path = path; + mPartitionPath[1].doExtract = true; +} + +void NcaProcess::setPartition2ExtractPath(const std::string& path) +{ + mPartitionPath[2].path = path; + mPartitionPath[2].doExtract = true; +} + +void NcaProcess::setPartition3ExtractPath(const std::string& path) +{ + mPartitionPath[3].path = path; + mPartitionPath[3].doExtract = true; +} + +void NcaProcess::setListFs(bool list_fs) +{ + mListFs = list_fs; +} diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index 18b17df..0d9890b 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -10,34 +10,59 @@ class NcaProcess { public: NcaProcess(); + ~NcaProcess(); void process(); // generic - void setInputFile(fnd::IFile& reader); + void setInputFile(fnd::IFile* reader); void setInputFileOffset(size_t offset); void setKeyset(const sKeyset* keyset); void setCliOutputMode(CliOutputType type); void setVerifyMode(bool verify); // nca specfic + void setPartition0ExtractPath(const std::string& path); + void setPartition1ExtractPath(const std::string& path); + void setPartition2ExtractPath(const std::string& path); + void setPartition3ExtractPath(const std::string& path); + void setListFs(bool list_fs); private: const std::string kModuleName = "NcaProcess"; + // user options fnd::IFile* mReader; size_t mOffset; const sKeyset* mKeyset; CliOutputType mCliOutputType; bool mVerify; + struct sExtract + { + std::string path; + bool doExtract; + } mPartitionPath[nx::nca::kPartitionNum]; + + bool mListFs; + + // data nx::sNcaHeaderBlock mHdrBlock; + crypto::sha::sSha256Hash mHdrHash; nx::NcaHeader mHdr; - fnd::List mBodyKeyList; + // crypto + struct sKeys + { + sOptional aes_ctr; + sOptional aes_xts; + } mBodyKeys; + void displayHeader(); - void decryptBodyKeyList(); + void generateNcaBodyEncryptionKeys(); + + void processPartitions(); }; \ No newline at end of file diff --git a/programs/nstool/source/OffsetAdjustedIFile.cpp b/programs/nstool/source/OffsetAdjustedIFile.cpp new file mode 100644 index 0000000..fdf7cdb --- /dev/null +++ b/programs/nstool/source/OffsetAdjustedIFile.cpp @@ -0,0 +1,45 @@ +#include "OffsetAdjustedIFile.h" + +OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size) : + mFile(file), + mBaseOffset(offset), + mCurrentOffset(0), + mSize(size) +{ + +} + +size_t OffsetAdjustedIFile::size() +{ + return mSize; +} + +void OffsetAdjustedIFile::seek(size_t offset) +{ + mCurrentOffset = offset; + mFile.seek(offset + mBaseOffset); +} + +void OffsetAdjustedIFile::read(byte_t* out, size_t len) +{ + seek(mCurrentOffset); + mFile.read(out, len); +} + +void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len) +{ + seek(offset); + read(out, len); +} + +void OffsetAdjustedIFile::write(const byte_t* out, size_t len) +{ + seek(mCurrentOffset); + mFile.write(out, len); +} + +void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len) +{ + seek(offset); + write(out, len); +} \ No newline at end of file diff --git a/programs/nstool/source/OffsetAdjustedIFile.h b/programs/nstool/source/OffsetAdjustedIFile.h new file mode 100644 index 0000000..18b3b9c --- /dev/null +++ b/programs/nstool/source/OffsetAdjustedIFile.h @@ -0,0 +1,18 @@ +#include + +class OffsetAdjustedIFile : public fnd::IFile +{ +public: + OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size); + + size_t size(); + void seek(size_t offset); + void read(byte_t* out, size_t len); + void read(byte_t* out, size_t offset, size_t len); + void write(const byte_t* out, size_t len); + void write(const byte_t* out, size_t offset, size_t len); +private: + fnd::IFile& mFile; + size_t mBaseOffset, mCurrentOffset; + size_t mSize; +}; \ No newline at end of file diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index e447efc..d50db04 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -429,7 +429,14 @@ void UserSettings::populateKeyset(sCmdArgs& args) if (args.nca_titlekey.isSet) { - decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key.key, sizeof(crypto::aes::sAes128Key)); + if (args.nca_bodykey.var.length() == (sizeof(crypto::aes::sAes128Key)*2)) + { + decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesctr.key, sizeof(crypto::aes::sAes128Key)); + } + else + { + decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesxts.key[0], sizeof(crypto::aes::sAesXts128Key)); + } } #undef _SAVE_KEYDATA @@ -511,6 +518,10 @@ void UserSettings::populateUserSettings(sCmdArgs& args) mNormalPath = args.normal_path; mSecurePath = args.secure_path; mFsPath = args.fs_path; + mPart0Path = args.part0_path; + mPart1Path = args.part1_path; + mPart2Path = args.part2_path; + mPart3Path = args.part3_path; // determine output path if (args.verbose_output.isSet) diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp index b415329..8296f9a 100644 --- a/programs/nstool/source/main.cpp +++ b/programs/nstool/source/main.cpp @@ -71,11 +71,22 @@ int main(int argc, char** argv) { NcaProcess nca; - nca.setInputFile(inputFile); + nca.setInputFile(&inputFile); nca.setKeyset(&user_set.getKeyset()); nca.setCliOutputMode(user_set.getCliOutputType()); nca.setVerifyMode(user_set.isVerifyFile()); + + if (user_set.getPart0Path().isSet) + nca.setPartition0ExtractPath(user_set.getPart0Path().var); + if (user_set.getPart1Path().isSet) + nca.setPartition1ExtractPath(user_set.getPart1Path().var); + if (user_set.getPart2Path().isSet) + nca.setPartition2ExtractPath(user_set.getPart2Path().var); + if (user_set.getPart3Path().isSet) + nca.setPartition3ExtractPath(user_set.getPart3Path().var); + nca.setListFs(user_set.isListFs()); + nca.process(); } else if (user_set.getFileType() == FILE_NPDM) diff --git a/programs/nstool/source/nstool.h b/programs/nstool/source/nstool.h index 54d12ce..049cec9 100644 --- a/programs/nstool/source/nstool.h +++ b/programs/nstool/source/nstool.h @@ -58,7 +58,8 @@ struct sKeyset crypto::aes::sAesXts128Key header_key; crypto::aes::sAes128Key key_area_key[kNcaKeakNum][kMasterKeyNum]; - crypto::aes::sAes128Key manual_title_key; + crypto::aes::sAes128Key manual_title_key_aesctr; + crypto::aes::sAesXts128Key manual_title_key_aesxts; crypto::aes::sAes128Key manual_body_key_aesctr; crypto::aes::sAesXts128Key manual_body_key_aesxts; } nca;