From 1a34e5acd3356a1967df4391f293e7ccfba9125f Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 11 May 2018 20:17:55 +0800 Subject: [PATCH 01/59] Fix typo in KEYS.md --- KEYS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/KEYS.md b/KEYS.md index db6e6ce..bd1b706 100644 --- a/KEYS.md +++ b/KEYS.md @@ -46,7 +46,7 @@ acid_sign_key_private : RSA2048 Private Exponent (0x100 bytes) # Compatibility with hactool keyset files NXTools keyset files share the same keyset file format as [hactool](https://github.com/SciresM/hactool/blob/master/KEYS.md), but names of keys may differ. For compatibility, hactool names for equivalent keys are accepted. ``` -titlekey_source : hactool alias for ticket_commonkey_source +titlekek_source : hactool alias for ticket_commonkey_source header_key_source : hactool alias for nca_header_key_source header_kek_source : hactool alias for nca_header_kek_source key_area_key_application_source : hactool alias for nca_body_keak_application_source From d5da5afaae28d0998e930da3c7ffbc854df09108 Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 11 May 2018 21:10:04 +0800 Subject: [PATCH 02/59] [fnd] Add pure virtual destructor to IFile to prevent pointers of it being deleted. --- lib/libfnd/include/fnd/IFile.h | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/libfnd/include/fnd/IFile.h b/lib/libfnd/include/fnd/IFile.h index 3c12bba..c3db714 100644 --- a/lib/libfnd/include/fnd/IFile.h +++ b/lib/libfnd/include/fnd/IFile.h @@ -6,6 +6,8 @@ namespace fnd class IFile { public: + virtual ~IFile() = 0; + virtual size_t size() = 0; virtual void seek(size_t offset) = 0; virtual void read(byte_t* out, size_t len) = 0; From 90bbbede608cbf6ed6ec5d95126c5a1b9411b11d Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 11 May 2018 21:10:39 +0800 Subject: [PATCH 03/59] [nstool] Misc rename. --- programs/nstool/source/UserSettings.cpp | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index d50db04..773709a 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -587,14 +587,14 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path) static const size_t kMaxReadSize = 0x1000; FileType file_type = FILE_INVALID; fnd::SimpleFile file; - fnd::MemoryBlob blob; + fnd::MemoryBlob scratch; // open file file.open(path, file.Read); // read file - blob.alloc(MIN(kMaxReadSize, file.size())); - file.read(blob.getBytes(), 0, blob.getSize()); + scratch.alloc(MIN(kMaxReadSize, file.size())); + file.read(scratch.getBytes(), 0, scratch.getSize()); // close file file.close(); @@ -602,14 +602,14 @@ FileType UserSettings::determineFileTypeFromFile(const std::string& path) byte_t nca_raw[nx::nca::kHeaderSize]; nx::sNcaHeader* nca_header = (nx::sNcaHeader*)(nca_raw + nx::NcaUtils::sectorToOffset(1)); - if (blob.getSize() >= nx::nca::kHeaderSize) + if (scratch.getSize() >= nx::nca::kHeaderSize) { - nx::NcaUtils::decryptNcaHeader(blob.getBytes(), nca_raw, mKeyset.nca.header_key); + nx::NcaUtils::decryptNcaHeader(scratch.getBytes(), nca_raw, mKeyset.nca.header_key); } - // _QUICK_CAST resolves to a pointer of type 'st' located at blob.getBytes() + 'oft' -#define _QUICK_CAST(st, oft) ((st*)(blob.getBytes() + (oft))) -#define _ASSERT_SIZE(size) (blob.getSize() >= (size)) + // _QUICK_CAST resolves to a pointer of type 'st' located at scratch.getBytes() + 'oft' +#define _QUICK_CAST(st, oft) ((st*)(scratch.getBytes() + (oft))) +#define _ASSERT_SIZE(size) (scratch.getSize() >= (size)) // test npdm if (_ASSERT_SIZE(sizeof(nx::sXciHeaderPage)) && std::string(_QUICK_CAST(nx::sXciHeaderPage, 0)->header.signature, 4) == nx::xci::kXciSig) From 8e360332e5a37cce5fc9e9ca135c8c0ffb63c036 Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 11 May 2018 21:20:28 +0800 Subject: [PATCH 04/59] [nstool] Changed handling of shared IFile derrivatives. Always a pointer --- programs/nstool/source/AesCtrWrappedIFile.cpp | 14 +++++++------- programs/nstool/source/AesCtrWrappedIFile.h | 4 ++-- programs/nstool/source/NcaProcess.cpp | 6 +++--- programs/nstool/source/NcaProcess.h | 3 +-- programs/nstool/source/NpdmProcess.cpp | 4 ++-- programs/nstool/source/NpdmProcess.h | 2 +- programs/nstool/source/OffsetAdjustedIFile.cpp | 8 ++++---- programs/nstool/source/OffsetAdjustedIFile.h | 4 ++-- programs/nstool/source/PfsProcess.cpp | 4 ++-- programs/nstool/source/PfsProcess.h | 2 +- programs/nstool/source/RomfsProcess.cpp | 4 ++-- programs/nstool/source/RomfsProcess.h | 2 +- programs/nstool/source/XciProcess.cpp | 8 ++++---- programs/nstool/source/XciProcess.h | 2 +- programs/nstool/source/main.cpp | 8 ++++---- 15 files changed, 37 insertions(+), 38 deletions(-) diff --git a/programs/nstool/source/AesCtrWrappedIFile.cpp b/programs/nstool/source/AesCtrWrappedIFile.cpp index 29c4344..79d966f 100644 --- a/programs/nstool/source/AesCtrWrappedIFile.cpp +++ b/programs/nstool/source/AesCtrWrappedIFile.cpp @@ -1,6 +1,6 @@ #include "AesCtrWrappedIFile.h" -AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) : +AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile* file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) : mFile(file), mKey(key), mBaseCtr(ctr) @@ -10,12 +10,12 @@ AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes size_t AesCtrWrappedIFile::size() { - return mFile.size(); + return mFile->size(); } void AesCtrWrappedIFile::seek(size_t offset) { - mFile.seek(offset); + mFile->seek(offset); crypto::aes::AesIncrementCounter(mBaseCtr.iv, offset>>4, mCurrentCtr.iv); mBlockOffset = offset & 0xf; } @@ -24,7 +24,7 @@ void AesCtrWrappedIFile::read(byte_t* out, size_t len) { for (size_t i = 0; i < (len / kAesCtrScratchSize); i++) { - mFile.read(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize); + 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); } @@ -33,7 +33,7 @@ void AesCtrWrappedIFile::read(byte_t* out, size_t len) { size_t read_len = len % kAesCtrScratchSize; size_t read_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize); - mFile.read(mScratch.getBytes() + mBlockOffset, read_len); + 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); } @@ -51,7 +51,7 @@ void AesCtrWrappedIFile::write(const byte_t* out, size_t len) { 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); + mFile->write(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize); } if (len % kAesCtrScratchSize) @@ -60,7 +60,7 @@ void AesCtrWrappedIFile::write(const byte_t* out, size_t len) 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); + mFile->write(mScratch.getBytes() + mBlockOffset, write_len); } } diff --git a/programs/nstool/source/AesCtrWrappedIFile.h b/programs/nstool/source/AesCtrWrappedIFile.h index 5f88844..f2a001c 100644 --- a/programs/nstool/source/AesCtrWrappedIFile.h +++ b/programs/nstool/source/AesCtrWrappedIFile.h @@ -5,7 +5,7 @@ class AesCtrWrappedIFile : public fnd::IFile { public: - AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr); + AesCtrWrappedIFile(fnd::IFile* file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr); size_t size(); void seek(size_t offset); @@ -18,7 +18,7 @@ private: static const size_t kAesCtrScratchSize = 0x1000000; static const size_t kAesCtrScratchAllocSize = kAesCtrScratchSize + crypto::aes::kAesBlockSize; - fnd::IFile& mFile; + fnd::IFile* mFile; crypto::aes::sAes128Key mKey; crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr; size_t mBlockOffset; diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index a954617..b6f830b 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -249,7 +249,7 @@ void NcaProcess::processPartitions() // create reader fnd::IFile* partitionReader = nullptr; - AesCtrWrappedIFile aesCtrFile = AesCtrWrappedIFile(*mReader, mBodyKeys.aes_ctr.var, ctr); + AesCtrWrappedIFile aesCtrFile = AesCtrWrappedIFile(mReader, mBodyKeys.aes_ctr.var, ctr); switch(fs_header.encryption_type) { case (nx::nca::CRYPT_AESXTS): @@ -290,7 +290,7 @@ void NcaProcess::processPartitions() if (fs_header.format_type == nx::nca::FORMAT_PFS0) { PfsProcess pfs; - pfs.setInputFile(*partitionReader); + pfs.setInputFile(partitionReader); pfs.setInputFileOffset(partition.offset + data_offset); pfs.setCliOutputMode(mCliOutputType); pfs.setListFs(mListFs); @@ -301,7 +301,7 @@ void NcaProcess::processPartitions() else if (fs_header.format_type == nx::nca::FORMAT_ROMFS) { RomfsProcess romfs; - romfs.setInputFile(*partitionReader); + romfs.setInputFile(partitionReader); romfs.setInputFileOffset(partition.offset + data_offset); romfs.setCliOutputMode(mCliOutputType); romfs.setListFs(mListFs); diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index 0d9890b..e7fc0fa 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -57,8 +57,7 @@ private: sOptional aes_ctr; sOptional aes_xts; } mBodyKeys; - - + void displayHeader(); diff --git a/programs/nstool/source/NpdmProcess.cpp b/programs/nstool/source/NpdmProcess.cpp index 26cc318..aeac024 100644 --- a/programs/nstool/source/NpdmProcess.cpp +++ b/programs/nstool/source/NpdmProcess.cpp @@ -665,9 +665,9 @@ void NpdmProcess::process() } } -void NpdmProcess::setInputFile(fnd::IFile& reader) +void NpdmProcess::setInputFile(fnd::IFile* reader) { - mReader = &reader; + mReader = reader; } void NpdmProcess::setInputFileOffset(size_t offset) diff --git a/programs/nstool/source/NpdmProcess.h b/programs/nstool/source/NpdmProcess.h index 36bef32..ece7623 100644 --- a/programs/nstool/source/NpdmProcess.h +++ b/programs/nstool/source/NpdmProcess.h @@ -13,7 +13,7 @@ public: void process(); - void setInputFile(fnd::IFile& reader); + void setInputFile(fnd::IFile* reader); void setInputFileOffset(size_t offset); void setKeyset(const sKeyset* keyset); void setCliOutputMode(CliOutputType type); diff --git a/programs/nstool/source/OffsetAdjustedIFile.cpp b/programs/nstool/source/OffsetAdjustedIFile.cpp index fdf7cdb..bda80d6 100644 --- a/programs/nstool/source/OffsetAdjustedIFile.cpp +++ b/programs/nstool/source/OffsetAdjustedIFile.cpp @@ -1,6 +1,6 @@ #include "OffsetAdjustedIFile.h" -OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size) : +OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile* file, size_t offset, size_t size) : mFile(file), mBaseOffset(offset), mCurrentOffset(0), @@ -17,13 +17,13 @@ size_t OffsetAdjustedIFile::size() void OffsetAdjustedIFile::seek(size_t offset) { mCurrentOffset = offset; - mFile.seek(offset + mBaseOffset); + mFile->seek(offset + mBaseOffset); } void OffsetAdjustedIFile::read(byte_t* out, size_t len) { seek(mCurrentOffset); - mFile.read(out, len); + mFile->read(out, len); } void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len) @@ -35,7 +35,7 @@ void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len) void OffsetAdjustedIFile::write(const byte_t* out, size_t len) { seek(mCurrentOffset); - mFile.write(out, len); + mFile->write(out, len); } void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len) diff --git a/programs/nstool/source/OffsetAdjustedIFile.h b/programs/nstool/source/OffsetAdjustedIFile.h index 18b3b9c..5b60dcf 100644 --- a/programs/nstool/source/OffsetAdjustedIFile.h +++ b/programs/nstool/source/OffsetAdjustedIFile.h @@ -3,7 +3,7 @@ class OffsetAdjustedIFile : public fnd::IFile { public: - OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size); + OffsetAdjustedIFile(fnd::IFile* file, size_t offset, size_t size); size_t size(); void seek(size_t offset); @@ -12,7 +12,7 @@ public: 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; + fnd::IFile* mFile; size_t mBaseOffset, mCurrentOffset; size_t mSize; }; \ No newline at end of file diff --git a/programs/nstool/source/PfsProcess.cpp b/programs/nstool/source/PfsProcess.cpp index 8577e8f..fa04bd3 100644 --- a/programs/nstool/source/PfsProcess.cpp +++ b/programs/nstool/source/PfsProcess.cpp @@ -139,9 +139,9 @@ void PfsProcess::process() extractFs(); } -void PfsProcess::setInputFile(fnd::IFile& reader) +void PfsProcess::setInputFile(fnd::IFile* reader) { - mReader = &reader; + mReader = reader; } void PfsProcess::setInputFileOffset(size_t offset) diff --git a/programs/nstool/source/PfsProcess.h b/programs/nstool/source/PfsProcess.h index 7a07a80..bf062c0 100644 --- a/programs/nstool/source/PfsProcess.h +++ b/programs/nstool/source/PfsProcess.h @@ -14,7 +14,7 @@ public: 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); diff --git a/programs/nstool/source/RomfsProcess.cpp b/programs/nstool/source/RomfsProcess.cpp index 0c3d8a1..95aa7c5 100644 --- a/programs/nstool/source/RomfsProcess.cpp +++ b/programs/nstool/source/RomfsProcess.cpp @@ -256,9 +256,9 @@ void RomfsProcess::process() extractFs(); } -void RomfsProcess::setInputFile(fnd::IFile& reader) +void RomfsProcess::setInputFile(fnd::IFile* reader) { - mReader = &reader; + mReader = reader; } void RomfsProcess::setInputFileOffset(size_t offset) diff --git a/programs/nstool/source/RomfsProcess.h b/programs/nstool/source/RomfsProcess.h index 42cb54f..ee3ecc1 100644 --- a/programs/nstool/source/RomfsProcess.h +++ b/programs/nstool/source/RomfsProcess.h @@ -93,7 +93,7 @@ public: 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); diff --git a/programs/nstool/source/XciProcess.cpp b/programs/nstool/source/XciProcess.cpp index 3e0ae68..54ffdbf 100644 --- a/programs/nstool/source/XciProcess.cpp +++ b/programs/nstool/source/XciProcess.cpp @@ -141,7 +141,7 @@ void XciProcess::processRootPfs() printf("[WARNING] XCI Root HFS0: FAIL (bad hash)\n"); } } - mRootPfs.setInputFile(*mReader); + mRootPfs.setInputFile(mReader); mRootPfs.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress()); mRootPfs.setListFs(mListFs); mRootPfs.setVerifyMode(mVerify); @@ -156,7 +156,7 @@ void XciProcess::processPartitionPfs() for (size_t i = 0; i < rootPartitions.getSize(); i++) { PfsProcess tmp; - tmp.setInputFile(*mReader); + tmp.setInputFile(mReader); tmp.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress() + rootPartitions[i].offset); tmp.setListFs(mListFs); tmp.setVerifyMode(mVerify); @@ -227,9 +227,9 @@ void XciProcess::process() processPartitionPfs(); } -void XciProcess::setInputFile(fnd::IFile& reader) +void XciProcess::setInputFile(fnd::IFile* reader) { - mReader = &reader; + mReader = reader; } void XciProcess::setInputFileOffset(size_t offset) diff --git a/programs/nstool/source/XciProcess.h b/programs/nstool/source/XciProcess.h index 5141515..d0b7061 100644 --- a/programs/nstool/source/XciProcess.h +++ b/programs/nstool/source/XciProcess.h @@ -17,7 +17,7 @@ public: 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); diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp index 8296f9a..8907c39 100644 --- a/programs/nstool/source/main.cpp +++ b/programs/nstool/source/main.cpp @@ -21,7 +21,7 @@ int main(int argc, char** argv) { XciProcess xci; - xci.setInputFile(inputFile); + xci.setInputFile(&inputFile); xci.setKeyset(&user_set.getKeyset()); xci.setCliOutputMode(user_set.getCliOutputType()); @@ -41,7 +41,7 @@ int main(int argc, char** argv) { PfsProcess pfs; - pfs.setInputFile(inputFile); + pfs.setInputFile(&inputFile); pfs.setKeyset(&user_set.getKeyset()); pfs.setCliOutputMode(user_set.getCliOutputType()); pfs.setVerifyMode(user_set.isVerifyFile()); @@ -56,7 +56,7 @@ int main(int argc, char** argv) { RomfsProcess romfs; - romfs.setInputFile(inputFile); + romfs.setInputFile(&inputFile); romfs.setKeyset(&user_set.getKeyset()); romfs.setCliOutputMode(user_set.getCliOutputType()); romfs.setVerifyMode(user_set.isVerifyFile()); @@ -93,7 +93,7 @@ int main(int argc, char** argv) { NpdmProcess npdm; - npdm.setInputFile(inputFile); + npdm.setInputFile(&inputFile); npdm.setKeyset(&user_set.getKeyset()); npdm.setCliOutputMode(user_set.getCliOutputType()); npdm.setVerifyMode(user_set.isVerifyFile()); From 8904b6e46c7c67d5d84b83b46b3d14572c11068b Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 11 May 2018 21:47:23 +0800 Subject: [PATCH 05/59] [fnd] Changed IFile so that a deleted IFile will trigger any layered destructors. --- lib/libfnd/include/fnd/IFile.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libfnd/include/fnd/IFile.h b/lib/libfnd/include/fnd/IFile.h index c3db714..d992962 100644 --- a/lib/libfnd/include/fnd/IFile.h +++ b/lib/libfnd/include/fnd/IFile.h @@ -6,7 +6,7 @@ namespace fnd class IFile { public: - virtual ~IFile() = 0; + inline virtual ~IFile() {} virtual size_t size() = 0; virtual void seek(size_t offset) = 0; From 7237a15098aa7985bae8fa2e0fb29ffec85114ba Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 11 May 2018 23:47:31 +0800 Subject: [PATCH 06/59] [nstool] Added PfsProcess struct check before using struct. --- programs/nstool/source/PfsProcess.cpp | 9 +++++++++ programs/nstool/source/PfsProcess.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/programs/nstool/source/PfsProcess.cpp b/programs/nstool/source/PfsProcess.cpp index fa04bd3..452f38a 100644 --- a/programs/nstool/source/PfsProcess.cpp +++ b/programs/nstool/source/PfsProcess.cpp @@ -42,6 +42,11 @@ size_t PfsProcess::determineHeaderSize(const nx::sPfsHeader* hdr) return sizeof(nx::sPfsHeader) + hdr->file_num.get() * fileEntrySize + hdr->name_table_size.get(); } +bool PfsProcess::validateHeaderMagic(const nx::sPfsHeader* hdr) +{ + return std::string(hdr->signature, 4) == nx::pfs::kPfsSig || std::string(hdr->signature, 4) == nx::pfs::kHashedPfsSig; +} + void PfsProcess::validateHfs() { fnd::MemoryBlob scratch; @@ -122,6 +127,10 @@ void PfsProcess::process() // open minimum header to get full header size scratch.alloc(sizeof(nx::sPfsHeader)); mReader->read(scratch.getBytes(), mOffset, scratch.getSize()); + if (validateHeaderMagic(((nx::sPfsHeader*)scratch.getBytes())) == false) + { + throw fnd::Exception(kModuleName, "Corrupt Header"); + } size_t pfsHeaderSize = determineHeaderSize(((nx::sPfsHeader*)scratch.getBytes())); // open minimum header to get full header size diff --git a/programs/nstool/source/PfsProcess.h b/programs/nstool/source/PfsProcess.h index bf062c0..d55324a 100644 --- a/programs/nstool/source/PfsProcess.h +++ b/programs/nstool/source/PfsProcess.h @@ -48,6 +48,8 @@ private: void displayHeader(); void displayFs(); size_t determineHeaderSize(const nx::sPfsHeader* hdr); + bool validateHeaderMagic(const nx::sPfsHeader* hdr); void validateHfs(); void extractFs(); + }; \ No newline at end of file From 13f595f3f45988c3e7d1de4fe93b4aa2d5841096 Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 11 May 2018 23:51:39 +0800 Subject: [PATCH 07/59] misc --- programs/nstool/source/PfsProcess.h | 1 - 1 file changed, 1 deletion(-) diff --git a/programs/nstool/source/PfsProcess.h b/programs/nstool/source/PfsProcess.h index d55324a..7137775 100644 --- a/programs/nstool/source/PfsProcess.h +++ b/programs/nstool/source/PfsProcess.h @@ -51,5 +51,4 @@ private: bool validateHeaderMagic(const nx::sPfsHeader* hdr); void validateHfs(); void extractFs(); - }; \ No newline at end of file From 3d57420c70819dcdaee0d2434a1a50788bb64db8 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 12 May 2018 00:20:39 +0800 Subject: [PATCH 08/59] [nstool] Fix typo importing --titlekey --- programs/nstool/source/UserSettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index 773709a..4f2f963 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -429,7 +429,7 @@ void UserSettings::populateKeyset(sCmdArgs& args) if (args.nca_titlekey.isSet) { - if (args.nca_bodykey.var.length() == (sizeof(crypto::aes::sAes128Key)*2)) + if (args.nca_titlekey.var.length() == (sizeof(crypto::aes::sAes128Key)*2)) { decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesctr.key, sizeof(crypto::aes::sAes128Key)); } From a6c30a8f0fcb12f51f9e3f94922f1b5fbaf0abba Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 12 May 2018 00:26:19 +0800 Subject: [PATCH 09/59] [nstool] Add NCA sig[1] validation, better code structure. --- programs/nstool/source/CopiedIFile.h | 17 + programs/nstool/source/NcaProcess.cpp | 442 +++++++++++++++++--------- programs/nstool/source/NcaProcess.h | 23 +- 3 files changed, 321 insertions(+), 161 deletions(-) create mode 100644 programs/nstool/source/CopiedIFile.h diff --git a/programs/nstool/source/CopiedIFile.h b/programs/nstool/source/CopiedIFile.h new file mode 100644 index 0000000..918b28e --- /dev/null +++ b/programs/nstool/source/CopiedIFile.h @@ -0,0 +1,17 @@ +#pragma once +#include + +class CopiedIFile : public fnd::IFile +{ +public: + inline CopiedIFile(fnd::IFile* file) : mFile(file) {} + + inline size_t size() { return mFile->size(); } + inline void seek(size_t offset) { mFile->seek(offset); } + inline void read(byte_t* out, size_t len) { mFile->read(out, len); } + inline void read(byte_t* out, size_t offset, size_t len) { mFile->read(out, offset, len); } + inline void write(const byte_t* out, size_t len) { mFile->write(out, len); } + inline void write(const byte_t* out, size_t offset, size_t len) { mFile->write(out, offset, len); } +private: + fnd::IFile* mFile; +}; \ No newline at end of file diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index b6f830b..97d0d4d 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -1,11 +1,14 @@ +#include #include #include #include +#include #include "NcaProcess.h" #include "PfsProcess.h" #include "RomfsProcess.h" #include "OffsetAdjustedIFile.h" #include "AesCtrWrappedIFile.h" +#include "CopiedIFile.h" std::string kFormatVersionStr[] { @@ -59,6 +62,240 @@ std::string kKaekIndexStr[] "System" }; +void NcaProcess::generateNcaBodyEncryptionKeys() +{ + // 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(); + + // 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) + { + // if the titlekey_kek is available + if (mKeyset->ticket.titlekey_kek[masterkey_rev] != zero_aesctr_key) + { + crypto::aes::sAesIvCtr iv; + iv.set(mHdr.getRightsId()); + // the title key is provided (sourced from ticket) + if (mKeyset->nca.manual_title_key_aesctr != zero_aesctr_key) + { + crypto::aes::AesCbcDecrypt(mKeyset->nca.manual_title_key_aesctr.key, 16, mKeyset->ticket.titlekey_kek[masterkey_rev].key, iv.iv, mBodyKeys.aes_ctr.var.key); + mBodyKeys.aes_ctr.isSet = true; + } + if (mKeyset->nca.manual_title_key_aesxts != zero_aesxts_key) + { + crypto::aes::AesCbcDecrypt(mKeyset->nca.manual_title_key_aesxts.key[0], 16, mKeyset->ticket.titlekey_kek[masterkey_rev].key, iv.iv, mBodyKeys.aes_xts.var.key[0]); + crypto::aes::AesCbcDecrypt(mKeyset->nca.manual_title_key_aesxts.key[1], 16, mKeyset->ticket.titlekey_kek[masterkey_rev].key, iv.iv, mBodyKeys.aes_xts.var.key[1]); + 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::generatePartitionConfiguration() +{ + std::stringstream error; + + for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++) + { + // get reference to relevant structures + const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i]; + nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index]; + + // validate header hash + crypto::sha::sSha256Hash calc_hash; + crypto::sha::Sha256((const byte_t*)&mHdrBlock.fs_header[partition.index], sizeof(nx::sNcaFsHeader), calc_hash.bytes); + if (calc_hash.compare(partition.hash) == false) + { + error.clear(); + error << "NCA FS Header [" << partition.index << "] Hash: FAIL \n"; + throw fnd::Exception(kModuleName, error.str()); + } + + + // setup AES-CTR + crypto::aes::sAesIvCtr ctr; + nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv); + + // save partition config + mPartitions[partition.index].reader = nullptr; + mPartitions[partition.index].offset = partition.offset; + mPartitions[partition.index].size = partition.size; + mPartitions[partition.index].format_type = (nx::nca::FormatType)fs_header.format_type; + mPartitions[partition.index].hash_type = (nx::nca::HashType)fs_header.hash_type; + memcpy(mPartitions[partition.index].hash_superblock, fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); + + // filter out unrecognised format types + switch (mPartitions[partition.index].format_type) + { + case (nx::nca::FORMAT_PFS0): + case (nx::nca::FORMAT_ROMFS): + break; + default: + continue; + } + + // filter out unrecognised hash types + switch (mPartitions[partition.index].hash_type) + { + case (nx::nca::HASH_NONE): + case (nx::nca::HASH_HIERARCHICAL_SHA256): + case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): + break; + default: + continue; + } + + // create reader + switch(fs_header.encryption_type) + { + case (nx::nca::CRYPT_AESXTS): + case (nx::nca::CRYPT_AESCTREX): + mPartitions[partition.index].reader = nullptr; + break; + case (nx::nca::CRYPT_AESCTR): + mPartitions[partition.index].reader = mBodyKeys.aes_ctr.isSet? new AesCtrWrappedIFile(mReader, mBodyKeys.aes_ctr.var, ctr) : nullptr; + break; + case (nx::nca::CRYPT_NONE): + mPartitions[partition.index].reader = new CopiedIFile(mReader); + break; + default: + error.clear(); + error << "NCA FS Header [" << partition.index << "] EncryptionType(" << fs_header.encryption_type << "): UNKNOWN \n"; + throw fnd::Exception(kModuleName, error.str()); + } + + // determine the data offset + if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) + { + mPartitions[partition.index].data_offset = mPartitions[partition.index].hierarchicalsha256_header.hash_target.offset.get(); + } + else if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) + { + for (size_t j = 0; j < nx::ivfc::kMaxIvfcLevel; j++) + { + if (mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get() != 0) + { + mPartitions[partition.index].data_offset = mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get(); + break; + } + } + } + else if (mPartitions[partition.index].hash_type == nx::nca::HASH_NONE) + { + mPartitions[partition.index].data_offset = 0; + } + } +} + +void NcaProcess::validatePartitionHash() +{ + +} + +void NcaProcess::validateNcaSignatures() +{ + // validate signature[0] + 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"); + } + + // validate signature[1] + if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) + { + if (mPartitions[0].format_type == nx::nca::FORMAT_PFS0) + { + if (mPartitions[0].reader != nullptr) + { + PfsProcess exefs; + exefs.setInputFile(mPartitions[0].reader); + exefs.setInputFileOffset(mPartitions[0].offset + mPartitions[0].data_offset); + exefs.setCliOutputMode(OUTPUT_MINIMAL); + exefs.process(); + + // open main.npdm + if (exefs.getPfsHeader().getFileList().hasElement(kNpdmExefsPath) == true) + { + const nx::PfsHeader::sFile& npdmFile = exefs.getPfsHeader().getFileList()[exefs.getPfsHeader().getFileList().getIndexOf(kNpdmExefsPath)]; + + fnd::MemoryBlob scratch; + scratch.alloc(npdmFile.size); + mPartitions[0].reader->read(scratch.getBytes(), mPartitions[0].offset + mPartitions[0].data_offset + npdmFile.offset, npdmFile.size); + + nx::NpdmBinary npdmBinary; + + npdmBinary.importBinary(scratch.getBytes(), scratch.getSize()); + + if (crypto::rsa::pss::rsaVerify(npdmBinary.getAcid().getNcaHeader2RsaKey(), crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_acid) != 0) + { + // this is minimal even though it's a warning because it's a validation method + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] NCA Header ACID Signature: FAIL \n"); + } + + } + else + { + // this is minimal even though it's a warning because it's a validation method + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] NCA Header ACID Signature: FAIL (\"%s\" not present in ExeFs)\n", kNpdmExefsPath.c_str()); + } + + + } + else + { + // this is minimal even though it's a warning because it's a validation method + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] NCA Header ACID Signature: FAIL (ExeFs unreadable)\n"); + } + } + else + { + // this is minimal even though it's a warning because it's a validation method + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] NCA Header ACID Signature: FAIL (No ExeFs partition)\n"); + } + } +} + void NcaProcess::displayHeader() { crypto::aes::sAes128Key zero_key; @@ -174,144 +411,46 @@ void NcaProcess::displayHeader() } } -void NcaProcess::generateNcaBodyEncryptionKeys() -{ - // 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(); - - // 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) - { - // if the titlekey_kek is available - if (mKeyset->ticket.titlekey_kek[masterkey_rev] != zero_aesctr_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; - } + size_t index = mHdr.getPartitions()[i].index; + struct sPartitionInfo& partition = mPartitions[index]; // if the reader is null, skip - if (partitionReader == nullptr) + if (partition.reader == nullptr) { - printf("[WARNING] NCA Partition %d not readable\n", partition.index); + printf("[WARNING] NCA Partition %d not readable\n", 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) + if (partition.format_type == nx::nca::FORMAT_PFS0) { PfsProcess pfs; - pfs.setInputFile(partitionReader); - pfs.setInputFileOffset(partition.offset + data_offset); + pfs.setInputFile(partition.reader); + pfs.setInputFileOffset(partition.offset + partition.data_offset); pfs.setCliOutputMode(mCliOutputType); pfs.setListFs(mListFs); - if (mPartitionPath[partition.index].doExtract) - pfs.setExtractPath(mPartitionPath[partition.index].path); + if (mPartitionPath[index].doExtract) + pfs.setExtractPath(mPartitionPath[index].path); + //printf("pfs.process(%lx)\n",partition.data_offset); pfs.process(); + //printf("pfs.process() end\n"); } - else if (fs_header.format_type == nx::nca::FORMAT_ROMFS) + else if (partition.format_type == nx::nca::FORMAT_ROMFS) { RomfsProcess romfs; - romfs.setInputFile(partitionReader); - romfs.setInputFileOffset(partition.offset + data_offset); + romfs.setInputFile(partition.reader); + romfs.setInputFileOffset(partition.offset + partition.data_offset); romfs.setCliOutputMode(mCliOutputType); romfs.setListFs(mListFs); - if (mPartitionPath[partition.index].doExtract) - romfs.setExtractPath(mPartitionPath[partition.index].path); + if (mPartitionPath[index].doExtract) + romfs.setExtractPath(mPartitionPath[index].path); + //printf("romfs.process(%lx)\n", partition.data_offset); romfs.process(); - } - else - { - throw fnd::Exception(kModuleName, "Unknown format type"); + //printf("romfs.process() end\n"); } } } @@ -324,15 +463,22 @@ NcaProcess::NcaProcess() : mVerify(false), mListFs(false) { - mPartitionPath[0].doExtract = false; - mPartitionPath[1].doExtract = false; - mPartitionPath[2].doExtract = false; - mPartitionPath[3].doExtract = false; + for (size_t i = 0; i < nx::nca::kPartitionNum; i++) + { + mPartitionPath[i].doExtract = false; + mPartitions[i].reader = nullptr; + } } NcaProcess::~NcaProcess() { - + for (size_t i = 0; i < nx::nca::kPartitionNum; i++) + { + if (mPartitions[i].reader != nullptr) + { + delete mPartitions[i].reader; + } + } } void NcaProcess::process() @@ -353,42 +499,25 @@ void NcaProcess::process() // 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 - + generateNcaBodyEncryptionKeys(); + + // import/generate fs header data + generatePartitionConfiguration(); + + // validate signatures + if (mVerify) + validateNcaSignatures(); + + // display header + if (mCliOutputType >= OUTPUT_NORMAL) + displayHeader(); + + // process partition + processPartitions(); /* NCA is a file container @@ -413,12 +542,9 @@ void NcaProcess::process() // decrypt key area - generateNcaBodyEncryptionKeys(); + - if (mCliOutputType >= OUTPUT_NORMAL) - displayHeader(); - - processPartitions(); + } void NcaProcess::setInputFile(fnd::IFile* reader) diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index e7fc0fa..c5dced0 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -30,6 +30,7 @@ public: private: const std::string kModuleName = "NcaProcess"; + const std::string kNpdmExefsPath = "main.npdm"; // user options fnd::IFile* mReader; @@ -57,11 +58,27 @@ private: sOptional aes_ctr; sOptional aes_xts; } mBodyKeys; - + + struct sPartitionInfo + { + fnd::IFile* reader; + size_t offset; + size_t data_offset; + size_t size; + nx::nca::FormatType format_type; + nx::nca::HashType hash_type; - void displayHeader(); + union { + byte_t hash_superblock[nx::nca::kFsHeaderHashSuperblockLen]; + nx::sHierarchicalSha256Header hierarchicalsha256_header; + nx::sIvfcHeader ivfc_header; + }; + } mPartitions[nx::nca::kPartitionNum]; void generateNcaBodyEncryptionKeys(); - + void generatePartitionConfiguration(); + void validatePartitionHash(); + void validateNcaSignatures(); + void displayHeader(); void processPartitions(); }; \ No newline at end of file From acc84942247bb1448b85e2f2ecf798a841e1886e Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 12 May 2018 00:28:27 +0800 Subject: [PATCH 10/59] [nstool] remove unused files. --- programs/nstool/source/NcaPartitionProcess.cpp | 0 programs/nstool/source/NcaPartitionProcess.h | 16 ---------------- 2 files changed, 16 deletions(-) delete mode 100644 programs/nstool/source/NcaPartitionProcess.cpp delete mode 100644 programs/nstool/source/NcaPartitionProcess.h diff --git a/programs/nstool/source/NcaPartitionProcess.cpp b/programs/nstool/source/NcaPartitionProcess.cpp deleted file mode 100644 index e69de29..0000000 diff --git a/programs/nstool/source/NcaPartitionProcess.h b/programs/nstool/source/NcaPartitionProcess.h deleted file mode 100644 index 7424e34..0000000 --- a/programs/nstool/source/NcaPartitionProcess.h +++ /dev/null @@ -1,16 +0,0 @@ -#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 From a4d52b349510b44972ac300f7191806ca5622995 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 12 May 2018 11:51:13 +0800 Subject: [PATCH 11/59] [crypto] Removed AesCtrStream --- lib/libcrypto/crypto.vcxproj | 2 - lib/libcrypto/crypto.vcxproj.filters | 6 - lib/libcrypto/include/crypto/AesCtrStream.h | 165 -------------------- lib/libcrypto/source/AesCtrStream.cpp | 138 ---------------- 4 files changed, 311 deletions(-) delete mode 100644 lib/libcrypto/include/crypto/AesCtrStream.h delete mode 100644 lib/libcrypto/source/AesCtrStream.cpp diff --git a/lib/libcrypto/crypto.vcxproj b/lib/libcrypto/crypto.vcxproj index e14120d..97b2a0e 100644 --- a/lib/libcrypto/crypto.vcxproj +++ b/lib/libcrypto/crypto.vcxproj @@ -121,7 +121,6 @@ - @@ -137,7 +136,6 @@ - diff --git a/lib/libcrypto/crypto.vcxproj.filters b/lib/libcrypto/crypto.vcxproj.filters index 1447c9e..c8b57c0 100644 --- a/lib/libcrypto/crypto.vcxproj.filters +++ b/lib/libcrypto/crypto.vcxproj.filters @@ -27,9 +27,6 @@ Header Files\crypto - - Header Files\crypto - Header Files\crypto @@ -71,9 +68,6 @@ Source Files - - Source Files - Source Files diff --git a/lib/libcrypto/include/crypto/AesCtrStream.h b/lib/libcrypto/include/crypto/AesCtrStream.h deleted file mode 100644 index b4eea4a..0000000 --- a/lib/libcrypto/include/crypto/AesCtrStream.h +++ /dev/null @@ -1,165 +0,0 @@ -#pragma once -#include -#include -#include -#include - -namespace crypto -{ - namespace aes - { - class AesCtrStream - { - public: - AesCtrStream(); - ~AesCtrStream(); - - void seek(size_t offset); - void read(size_t size, uint8_t* out); - void read(size_t offset, size_t size, uint8_t* out); - void write(size_t size, const uint8_t* in); - void write(size_t offset, size_t size, const uint8_t* in); - - void AddRegion(size_t start, size_t end, const uint8_t aes_key[kAes128KeySize], const uint8_t aes_ctr[kAesBlockSize]); - - protected: - // Virtual methods for implementation of seek/read/write - virtual void seek_internal(size_t offset) = 0; - virtual void read_internal(size_t size, size_t& read_len, uint8_t* out) = 0; - virtual void write_internal(size_t size, size_t& write_len, const uint8_t* in) = 0; - - private: - const std::string kModuleName = "AES_CTR_STREAM"; - static const size_t kIoBufferLen = 0x10000; - - // private implementation of crypto region - class CryptRegion - { - public: - // stubbed constructor - CryptRegion() : - start_(0), - end_(0), - is_plaintext_(true) - { - CleanUp(); - } - - // plaintext constructor - CryptRegion(size_t start, size_t end) : - start_(start), - end_(end), - is_plaintext_(true) - { - CleanUp(); - } - - // encrypted constructor - CryptRegion(size_t start, size_t end, const uint8_t aes_key[kAes128KeySize], const uint8_t aes_ctr[kAesBlockSize]) : - start_(start), - end_(end), - is_plaintext_(false) - { - CleanUp(); - memcpy(aes_key_, aes_key, kAes128KeySize); - memcpy(ctr_init_, aes_ctr, kAesBlockSize); - memcpy(ctr_, ctr_init_, kAesBlockSize); - } - - // destructor - ~CryptRegion() - { - CleanUp(); - } - - size_t start() const { return start_; } - size_t end() const { return end_; } - size_t size() const { return end_ - start_; } - size_t remaining_size(size_t start) const { return end_ - start; } - const uint8_t* aes_key() const { return aes_key_; } - uint8_t* aes_ctr() { return ctr_; } - - bool is_in_region(size_t start) const { return start >= start_ && start < end_; } - bool is_in_region(size_t start, size_t end) const { return is_in_region(start) && end > start_ && end <= end_; } - - void UpdateAesCtr(size_t start) - { - if (is_in_region(start)) - AesIncrementCounter(ctr_init_, ((start - start_) >> 4), ctr_); - } - - void GenerateXorpad(size_t start, size_t size, uint8_t* out) - { - // don't operate if requested size exceeds region size - if (is_in_region(start, start + size) == false) - { - return; - } - - if (is_plaintext_ == true) - { - memset(out, 0, size); - return; - } - - // parameters - size_t block_offset = (start - start_) & 0xf; - size_t block_num = size >> 4; - for (size_t pos = 0; pos < block_num; pos += (kPadBufferLen >> 4)) - { - // clear pad buffer - memset(pad_buffer_, 0, kPadBufferCapacity); - - // encrypt pad buffer to create xorpad - UpdateAesCtr(start + (pos << 4)); - AesCtr(pad_buffer_, kPadBufferCapacity, aes_key(), aes_ctr(), pad_buffer_); - - // determine the number of blocks to copy to xorpad - size_t copy_size = kPadBufferLen < ((block_num - pos) << 4) ? kPadBufferLen : ((block_num - pos) << 4); - - // copy - memcpy(out + (pos << 4), pad_buffer_ + block_offset, copy_size); - } - } - private: - static const size_t kPadBufferLen = 0x10000; - static const size_t kPadBufferCapacity = kPadBufferLen + kAesBlockSize; // has an extra block to accomodate non block aligned starts - - size_t start_; - size_t end_; - bool is_plaintext_; - uint8_t aes_key_[kAes128KeySize]; - uint8_t ctr_init_[kAesBlockSize]; - uint8_t ctr_[kAesBlockSize]; - uint8_t pad_buffer_[kPadBufferCapacity]; - - void CleanUp() - { - memset(aes_key_, 0, kAes128KeySize); - memset(ctr_init_, 0, kAesBlockSize); - memset(ctr_, 0, kAesBlockSize); - } - }; - - - - inline void xor_data(size_t size, const uint8_t* data1, const uint8_t* data2, uint8_t* out) - { - for (size_t idx = 0; idx < size; idx++) - { - out[idx] = data1[idx] ^ data2[idx]; - } - } - - // Crypto Regions - size_t offset_; - std::vector regions_; - - // IO Buffer - uint8_t io_buffer_[kIoBufferLen]; - uint8_t pad_buffer_[kIoBufferLen]; - - void GenerateXorPad(size_t start); - }; - } -} \ No newline at end of file diff --git a/lib/libcrypto/source/AesCtrStream.cpp b/lib/libcrypto/source/AesCtrStream.cpp deleted file mode 100644 index 9ab8c99..0000000 --- a/lib/libcrypto/source/AesCtrStream.cpp +++ /dev/null @@ -1,138 +0,0 @@ -#include - -using namespace crypto::aes; - -AesCtrStream::AesCtrStream() -{ -} - - -AesCtrStream::~AesCtrStream() -{ -} - -void AesCtrStream::seek(size_t offset) -{ - offset_ = offset; - seek_internal(offset_); -} - -void AesCtrStream::read(size_t size, uint8_t * out) -{ - size_t read_len = 0; - size_t read_size = 0; - for (size_t pos = 0; pos < size; pos += read_size, offset_ += read_size) - { - // calculate read size - read_size = (size - pos) < kIoBufferLen ? (size - pos) : kIoBufferLen; - - // read data - read_internal(read_size, read_len, io_buffer_); - if (read_size != read_len) - { - throw fnd::Exception(kModuleName, "Stream read length unexpected"); - } - - // crypt data - GenerateXorPad(offset_); - xor_data(read_size, pad_buffer_, io_buffer_, out + pos); - } -} - -void AesCtrStream::read(size_t offset, size_t size, uint8_t * out) -{ - seek(offset); - read(size, out); -} - -void AesCtrStream::write(size_t size, const uint8_t * in) -{ - size_t write_len = 0; - size_t write_size = 0; - for (size_t pos = 0; pos < size; pos += write_size, offset_ += write_size) - { - // calculate write size - write_size = (size - pos) < kIoBufferLen ? (size - pos) : kIoBufferLen; - - // crypt data - GenerateXorPad(offset_); - xor_data(write_size, pad_buffer_, in + pos, io_buffer_); - - // write data - write_internal(write_size, write_len, io_buffer_); - if (write_size != write_len) - { - throw fnd::Exception(kModuleName, "Stream write length unexpected"); - } - } -} - -void AesCtrStream::write(size_t offset, size_t size, const uint8_t * in) -{ - seek(offset); - write(size, in); -} - -void AesCtrStream::AddRegion(size_t start, size_t end, const uint8_t aes_key[kAes128KeySize], const uint8_t aes_ctr[kAesBlockSize]) -{ - if (start >= end) - { - throw fnd::Exception(kModuleName, "Illegal start/end position"); - } - if (aes_key == nullptr || aes_ctr == nullptr) - { - throw fnd::Exception(kModuleName, "Illegal aes configuration (nullptr)"); - } - - regions_.push_back(CryptRegion(start, end, aes_key, aes_ctr)); -} - -void AesCtrStream::GenerateXorPad(size_t start) -{ - size_t pad_size = 0; - for (size_t pos = 0; pos < kIoBufferLen; pos += pad_size) - { - CryptRegion* cur_region = nullptr; - CryptRegion* next_region = nullptr; - for (size_t idx = 0; idx < regions_.size(); idx++) - { - if (regions_[idx].is_in_region(start + pos)) - { - cur_region = ®ions_[idx]; - } - else if (regions_[idx].start() > (start + pos) && (next_region == nullptr || next_region->start() > regions_[idx].start())) - { - next_region = ®ions_[idx]; - } - } - - // if this exists in the a crypto region - if (cur_region != nullptr) - { - pad_size = cur_region->remaining_size(start + pos); - if (pad_size > kIoBufferLen - pos) - { - pad_size = kIoBufferLen - pos; - } - cur_region->GenerateXorpad(start + pos, pad_size, pad_buffer_ + pos); - } - - // there is a crypto region ahead, bridge the gap - else if (next_region != nullptr) - { - pad_size = next_region->start() - (start + pos); - if (pad_size > kIoBufferLen - pos) - { - pad_size = kIoBufferLen - pos; - } - memset(pad_buffer_ + pos, 0, pad_size); - } - // there are no more crypto regions - else - { - pad_size = kIoBufferLen - pos; - memset(pad_buffer_ + pos, 0, pad_size); - } - } - -} From 3fe758281115c85a70c09c7524391e1f74b22071 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 12 May 2018 11:51:38 +0800 Subject: [PATCH 12/59] [nstool] Updated VS project files. --- programs/nstool/nstool.vcxproj | 6 ++++++ programs/nstool/nstool.vcxproj.filters | 18 ++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/programs/nstool/nstool.vcxproj b/programs/nstool/nstool.vcxproj index 5b877cc..d6492f2 100644 --- a/programs/nstool/nstool.vcxproj +++ b/programs/nstool/nstool.vcxproj @@ -162,9 +162,12 @@ + + + @@ -172,8 +175,11 @@ + + + diff --git a/programs/nstool/nstool.vcxproj.filters b/programs/nstool/nstool.vcxproj.filters index eebaaf8..930a7f0 100644 --- a/programs/nstool/nstool.vcxproj.filters +++ b/programs/nstool/nstool.vcxproj.filters @@ -39,6 +39,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -59,6 +68,15 @@ Source Files + + Source Files + + + Source Files + + + Source Files + From 480a6225cc21c7874b502e2b22de36c8ce250557 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 12 May 2018 11:53:42 +0800 Subject: [PATCH 13/59] [nx] Updated VS project files. --- lib/libnx/nx.vcxproj | 1 + lib/libnx/nx.vcxproj.filters | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lib/libnx/nx.vcxproj b/lib/libnx/nx.vcxproj index 4f5ca30..2f184e6 100644 --- a/lib/libnx/nx.vcxproj +++ b/lib/libnx/nx.vcxproj @@ -28,6 +28,7 @@ + diff --git a/lib/libnx/nx.vcxproj.filters b/lib/libnx/nx.vcxproj.filters index 0c5cd52..0d0c91d 100644 --- a/lib/libnx/nx.vcxproj.filters +++ b/lib/libnx/nx.vcxproj.filters @@ -141,6 +141,9 @@ Header Files + + Header Files + From a02ba432920712c097b19d7f94ce80949a723650 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 12 May 2018 17:36:50 +0800 Subject: [PATCH 14/59] [nx] Add ContentMetaBinary --- lib/libnx/include/nx/ContentMetaBinary.h | 149 +++++++++++ lib/libnx/include/nx/cmnt.h | 139 +++++++++++ lib/libnx/nx.vcxproj | 3 + lib/libnx/nx.vcxproj.filters | 9 + lib/libnx/source/ContentMetaBinary.cpp | 300 +++++++++++++++++++++++ 5 files changed, 600 insertions(+) create mode 100644 lib/libnx/include/nx/ContentMetaBinary.h create mode 100644 lib/libnx/include/nx/cmnt.h create mode 100644 lib/libnx/source/ContentMetaBinary.cpp diff --git a/lib/libnx/include/nx/ContentMetaBinary.h b/lib/libnx/include/nx/ContentMetaBinary.h new file mode 100644 index 0000000..c9f08cb --- /dev/null +++ b/lib/libnx/include/nx/ContentMetaBinary.h @@ -0,0 +1,149 @@ +#pragma once +#include +#include +#include +#include + + +namespace nx +{ + class ContentMetaBinary : + public fnd::ISerialiseableBinary + { + public: + struct ContentInfo + { + crypto::sha::sSha256Hash hash; + byte_t nca_id[cmnt::kContentIdLen]; + size_t size; + cmnt::ContentType type; + + ContentInfo& operator=(const ContentInfo& other) + { + hash = other.hash; + memcpy(nca_id, other.nca_id, cmnt::kContentIdLen); + size = other.size; + type = other.type; + return *this; + } + + bool operator==(const ContentInfo& other) const + { + return (hash == other.hash) \ + && (memcmp(nca_id, other.nca_id, cmnt::kContentIdLen) == 0) \ + && (size == other.size) \ + && (type == other.type); + } + + bool operator!=(const ContentInfo& other) const + { + return !operator==(other); + } + }; + + struct ContentMetaInfo + { + uint64_t id; + uint32_t version; + cmnt::ContentMetaType type; + byte_t attributes; + + ContentMetaInfo& operator=(const ContentMetaInfo& other) + { + id = other.id; + version = other.version; + type = other.type; + attributes = other.attributes; + return *this; + } + + bool operator==(const ContentMetaInfo& other) const + { + return (id == other.id) \ + && (version == other.version) \ + && (type == other.type) \ + && (attributes == other.attributes); + } + + bool operator!=(const ContentMetaInfo& other) const + { + return !operator==(other); + } + }; + + ContentMetaBinary(); + ContentMetaBinary(const ContentMetaBinary& other); + ContentMetaBinary(const byte_t* bytes, size_t len); + + // to be used after export + const byte_t* getBytes() const; + size_t getSize() const; + + // export/import binary + void exportBinary(); + void importBinary(const byte_t* bytes, size_t len); + + // variables + void clear(); + + uint64_t getTitleId() const; + void setTitleId(uint64_t title_id); + + uint32_t getTitleVersion() const; + void setTitleVersion(uint32_t version); + + cmnt::ContentMetaType getType() const; + void setType(cmnt::ContentMetaType type); + + byte_t getAttributes() const; + void setAttributes(byte_t attributes); + + uint32_t getRequiredSystemVersion() const; + void setRequiredSystemVersion(uint32_t version); + + const fnd::List& getContentInfo() const; + void setContentInfo(const fnd::List& info); + + const fnd::List& getContentMetaInfo() const; + void setContentMetaInfo(const fnd::List& info); + + const fnd::MemoryBlob& getExtendedData() const; + void setExtendedData(const fnd::MemoryBlob& data); + + const nx::sDigest& getDigest() const; + void setDigest(const nx::sDigest& digest); + + + private: + const std::string kModuleName = "CONTENT_META_BINARY"; + + // binary blob + fnd::MemoryBlob mBinaryBlob; + + // variables + uint64_t mTitleId; + uint32_t mTitleVersion; + cmnt::ContentMetaType mType; + byte_t mAttributes; + uint32_t mRequiredSystemVersion; + fnd::MemoryBlob mExtendedHeader; + fnd::List mContentInfo; + fnd::List mContentMetaInfo; + fnd::MemoryBlob mExtendedData; + nx::sDigest mDigest; + + inline size_t getExtendedHeaderOffset() const { return sizeof(sContentMetaHeader); } + inline size_t getContentInfoOffset(size_t exhdrSize) const { return getExtendedHeaderOffset() + exhdrSize; } + inline size_t getContentMetaInfoOffset(size_t exhdrSize, size_t contentInfoNum) const { return getContentInfoOffset(exhdrSize) + contentInfoNum * sizeof(sContentInfo); } + inline size_t getExtendedDataOffset(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum) const { return getContentMetaInfoOffset(exhdrSize, contentInfoNum) + contentMetaNum * sizeof(sContentMetaInfo); } + inline size_t getDigestOffset(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getExtendedDataOffset(exhdrSize, contentInfoNum, contentMetaNum) + exdataSize; } + inline size_t getTotalSize(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getDigestOffset(exhdrSize, contentInfoNum, contentMetaNum, exdataSize) + cmnt::kDigestLen; } + + bool validateExtendedHeaderSize(cmnt::ContentMetaType type, size_t exhdrSize); + size_t getExtendedDataSize(cmnt::ContentMetaType type, const byte_t* data); + void validateBinary(const byte_t* bytes, size_t len); + + bool isEqual(const ContentMetaBinary& other) const; + void copyFrom(const ContentMetaBinary& other); + }; +} \ No newline at end of file diff --git a/lib/libnx/include/nx/cmnt.h b/lib/libnx/include/nx/cmnt.h new file mode 100644 index 0000000..3b37bf0 --- /dev/null +++ b/lib/libnx/include/nx/cmnt.h @@ -0,0 +1,139 @@ +#pragma once +#include +#include +#include +#include +#include + +namespace nx +{ + namespace cmnt + { + enum ContentType + { + TYPE_META = 0, + TYPE_PROGRAM, + TYPE_DATA, + TYPE_CONTROL, + TYPE_HTML_DOCUMENT, + TYPE_LEGAL_INFORMATION, + TYPE_DELTA_FRAGMENT + }; + + enum ContentMetaType + { + METATYPE_SYSTEM_PROGRAM = 1, + METATYPE_SYSTEM_DATA, + METATYPE_SYSTEM_UPDATE, + METATYPE_BOOT_IMAGE_PACKAGE, + METATYPE_BOOT_IMAGE_PACKAGE_SAFE, + + METATYPE_APPLICATION = 0x80, + METATYPE_PATCH, // can have extended data + METATYPE_ADD_ON_CONTENT, + METATYPE_DELTA // can have extended data + }; + + enum UpdateType + { + UPDATETYPE_APPLY_AS_DELTA, + UPDATETYPE_OVERWRITE, + UPDATETYPE_CREATE + }; + + enum ContentMetaAttribute + { + ATTRIBUTE_NONE, + ATTRIBUTE_INCLUDES_EX_FAT_DRIVER, + ATTRIBUTE_REBOOTLESS + }; + + static const uint32_t kRequiredSystemVersion = 335544320; + static const uint32_t kDefaultVersion = 335545344; + static const size_t kContentIdLen = 0x10; + static const size_t kDigestLen = 0x20; + } + + +#pragma pack(push,1) + /* + struct sContentMeta + { + sContentMetaHeader hdr; + byte_t exhdr[]; // optional + sContentInfo info[]; + sContentMetaInfo meta[]; + byte_t extdata[]; + byte_t digest[32] + }; + */ + + struct sContentMetaHeader + { + le_uint64_t id; + le_uint32_t version; + byte_t type; + byte_t reserved_0; + le_uint16_t exhdr_size; + le_uint16_t content_count; + le_uint16_t content_meta_count; + byte_t attributes; + byte_t reserved_1[3]; + le_uint32_t required_system_version; + byte_t reserved_2[4]; + }; + + struct sContentInfo + { + crypto::sha::sSha256Hash content_hash; + byte_t content_id[cmnt::kContentIdLen]; + le_uint32_t size_lower; + le_uint16_t size_higher; + byte_t content_type; + byte_t id_offset; + }; + + struct sContentMetaInfo + { + le_uint64_t id; + le_uint32_t version; + byte_t type; + byte_t attributes; + byte_t reserved[2]; + }; + + struct sApplicationMetaExtendedHeader + { + le_uint64_t id; + le_uint32_t required_system_version; + byte_t reserved[4]; + }; + + struct sPatchMetaExtendedHeader + { + le_uint64_t id; + le_uint32_t required_system_version; + le_uint32_t extended_data_size; + byte_t reserved[8]; + }; + + struct sAddOnContentMetaExtendedHeader + { + le_uint64_t id; + le_uint32_t required_system_version; + byte_t reserved[4]; + }; + + struct sDeltaMetaExtendedHeader + { + le_uint64_t id; + le_uint32_t extended_data_size; + byte_t reserved[4]; + }; + + struct sDigest + { + byte_t data[cmnt::kDigestLen]; + }; +#pragma pack(pop) +} \ No newline at end of file diff --git a/lib/libnx/nx.vcxproj b/lib/libnx/nx.vcxproj index 2f184e6..5d097a6 100644 --- a/lib/libnx/nx.vcxproj +++ b/lib/libnx/nx.vcxproj @@ -24,6 +24,8 @@ + + @@ -68,6 +70,7 @@ + diff --git a/lib/libnx/nx.vcxproj.filters b/lib/libnx/nx.vcxproj.filters index 0d0c91d..6710ddb 100644 --- a/lib/libnx/nx.vcxproj.filters +++ b/lib/libnx/nx.vcxproj.filters @@ -144,6 +144,12 @@ Header Files + + Header Files + + + Header Files + @@ -245,6 +251,9 @@ Source Files + + Source Files + diff --git a/lib/libnx/source/ContentMetaBinary.cpp b/lib/libnx/source/ContentMetaBinary.cpp new file mode 100644 index 0000000..003da2f --- /dev/null +++ b/lib/libnx/source/ContentMetaBinary.cpp @@ -0,0 +1,300 @@ +#include + +nx::ContentMetaBinary::ContentMetaBinary() +{ + clear(); +} + +nx::ContentMetaBinary::ContentMetaBinary(const ContentMetaBinary & other) +{ + copyFrom(other); +} + +nx::ContentMetaBinary::ContentMetaBinary(const byte_t * bytes, size_t len) +{ + importBinary(bytes, len); +} + +const byte_t * nx::ContentMetaBinary::getBytes() const +{ + return mBinaryBlob.getBytes(); +} + +size_t nx::ContentMetaBinary::getSize() const +{ + return mBinaryBlob.getSize(); +} + +void nx::ContentMetaBinary::exportBinary() +{ + throw fnd::Exception(kModuleName, "exportBinary() not implemented"); +} + +void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len) +{ + // clear member variables + clear(); + + // validate layout + validateBinary(bytes, len); + + // get pointer to header structure + const sContentMetaHeader* hdr = (const sContentMetaHeader*)bytes; + + mTitleId = hdr->id.get(); + mTitleVersion = hdr->version.get(); + mType = (cmnt::ContentMetaType)hdr->type; + mAttributes = hdr->attributes; + mRequiredSystemVersion = hdr->required_system_version.get(); + size_t exdata_size = 0; + + // save exheader + if (hdr->exhdr_size.get() > 0) + { + mExtendedHeader.alloc(hdr->exhdr_size.get()); + memcpy(mExtendedHeader.getBytes(), bytes + getExtendedHeaderOffset(), hdr->exhdr_size.get()); + + exdata_size = getExtendedDataSize(mType, mExtendedHeader.getBytes()); + } + + // save content info + if (hdr->content_count.get() > 0) + { + const sContentInfo* info = (const sContentInfo*)(bytes + getContentInfoOffset(hdr->exhdr_size.get())); + for (size_t i = 0; i < hdr->content_count.get(); i++) + { + mContentInfo[i].hash = info[i].content_hash; + memcpy(mContentInfo[i].nca_id, info[i].content_id, cmnt::kContentIdLen); + mContentInfo[i].size = (uint64_t)(info[i].size_lower.get()) | (uint64_t)(info[i].size_higher.get()) << 32; + mContentInfo[i].type = (cmnt::ContentType)info[i].content_type; + } + } + + // save content meta info + if (hdr->content_meta_count.get() > 0) + { + const sContentMetaInfo* info = (const sContentMetaInfo*)(bytes + getContentMetaInfoOffset(hdr->exhdr_size.get(), hdr->content_count.get())); + for (size_t i = 0; i < hdr->content_meta_count.get(); i++) + { + mContentMetaInfo[i].id = info[i].id.get(); + mContentMetaInfo[i].version = info[i].version.get(); + mContentMetaInfo[i].type = (cmnt::ContentMetaType)info[i].type; + mContentMetaInfo[i].attributes = info[i].attributes; + } + } + + // save exdata + if (exdata_size > 0) + { + mExtendedData.alloc(exdata_size); + memcpy(mExtendedData.getBytes(), bytes + getExtendedDataOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get()), exdata_size); + } + + // save digest + memcpy(mDigest.data, bytes + getDigestOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), exdata_size), cmnt::kDigestLen); + +} + +void nx::ContentMetaBinary::clear() +{ + mBinaryBlob.clear(); + mTitleId = 0; + mTitleVersion = 0; + mType = cmnt::METATYPE_SYSTEM_PROGRAM; + mAttributes = cmnt::ATTRIBUTE_NONE; + mRequiredSystemVersion = 0; + mExtendedHeader.clear(); + mContentInfo.clear(); + mContentMetaInfo.clear(); + mExtendedData.clear(); + memset(mDigest.data, 0, cmnt::kDigestLen); +} + +uint64_t nx::ContentMetaBinary::getTitleId() const +{ + return mTitleId; +} + +void nx::ContentMetaBinary::setTitleId(uint64_t title_id) +{ + mTitleId = title_id; +} + +uint32_t nx::ContentMetaBinary::getTitleVersion() const +{ + return mTitleVersion; +} + +void nx::ContentMetaBinary::setTitleVersion(uint32_t version) +{ + mTitleVersion = version; +} + +nx::cmnt::ContentMetaType nx::ContentMetaBinary::getType() const +{ + return mType; +} + +void nx::ContentMetaBinary::setType(cmnt::ContentMetaType type) +{ + mType = type; +} + +byte_t nx::ContentMetaBinary::getAttributes() const +{ + return mAttributes; +} + +void nx::ContentMetaBinary::setAttributes(byte_t attributes) +{ + mAttributes = attributes; +} + +uint32_t nx::ContentMetaBinary::getRequiredSystemVersion() const +{ + return mRequiredSystemVersion; +} + +void nx::ContentMetaBinary::setRequiredSystemVersion(uint32_t version) +{ + mRequiredSystemVersion = version; +} + +const fnd::List& nx::ContentMetaBinary::getContentInfo() const +{ + return mContentInfo; +} + +void nx::ContentMetaBinary::setContentInfo(const fnd::List& info) +{ + mContentInfo = info; +} + +const fnd::List& nx::ContentMetaBinary::getContentMetaInfo() const +{ + return mContentMetaInfo; +} + +void nx::ContentMetaBinary::setContentMetaInfo(const fnd::List& info) +{ + mContentMetaInfo = info; +} + +const fnd::MemoryBlob & nx::ContentMetaBinary::getExtendedData() const +{ + return mExtendedData; +} + +void nx::ContentMetaBinary::setExtendedData(const fnd::MemoryBlob & data) +{ + mExtendedData = data; +} + +const nx::sDigest & nx::ContentMetaBinary::getDigest() const +{ + return mDigest; +} + +void nx::ContentMetaBinary::setDigest(const nx::sDigest & digest) +{ + + memcpy(mDigest.data, digest.data, cmnt::kDigestLen); +} + +bool nx::ContentMetaBinary::validateExtendedHeaderSize(cmnt::ContentMetaType type, size_t exhdrSize) +{ + bool validSize = false; + + if (type == cmnt::METATYPE_APPLICATION && exhdrSize == sizeof(sApplicationMetaExtendedHeader)) + validSize = true; + else if (type == cmnt::METATYPE_PATCH && exhdrSize == sizeof(sPatchMetaExtendedHeader)) + validSize = true; + else if (type == cmnt::METATYPE_ADD_ON_CONTENT && exhdrSize == sizeof(sAddOnContentMetaExtendedHeader)) + validSize = true; + else if (type == cmnt::METATYPE_DELTA && exhdrSize == sizeof(sDeltaMetaExtendedHeader)) + validSize = true; + + return validSize; +} + +size_t nx::ContentMetaBinary::getExtendedDataSize(cmnt::ContentMetaType type, const byte_t * data) +{ + size_t exdata_len = 0; + if (type == cmnt::METATYPE_PATCH) + { + const sPatchMetaExtendedHeader* exhdr = (const sPatchMetaExtendedHeader*)(data); + exdata_len = exhdr->extended_data_size.get(); + } + else if (type == cmnt::METATYPE_DELTA) + { + const sDeltaMetaExtendedHeader* exhdr = (const sDeltaMetaExtendedHeader*)(data); + exdata_len = exhdr->extended_data_size.get(); + } + return exdata_len; +} + +void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len) +{ + // check if it is large enough to read the header + if (len < sizeof(sContentMetaHeader)) + { + throw fnd::Exception(kModuleName, "Binary too small"); + } + + // get pointer to header structure + const sContentMetaHeader* hdr = (const sContentMetaHeader*)bytes; + + // validate extended header size + if (validateExtendedHeaderSize((cmnt::ContentMetaType)hdr->type, hdr->exhdr_size.get()) == false) + { + throw fnd::Exception(kModuleName, "Invalid extended header size"); + } + + // check binary size again for new minimum size + if (len < getTotalSize(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), 0)) + { + throw fnd::Exception(kModuleName, "Binary too small"); + } + + // check binary size again with extended data size + if (len < getTotalSize(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), getExtendedDataSize((cmnt::ContentMetaType)hdr->type, bytes + getExtendedHeaderOffset()))) + { + throw fnd::Exception(kModuleName, "Binary too small"); + } +} + +bool nx::ContentMetaBinary::isEqual(const ContentMetaBinary & other) const +{ + return (mTitleId == other.mTitleId) \ + && (mTitleVersion == other.mTitleVersion) \ + && (mType == other.mType) \ + && (mAttributes == other.mAttributes) \ + && (mRequiredSystemVersion == other.mRequiredSystemVersion) \ + && (mExtendedHeader == other.mExtendedHeader) \ + && (mContentInfo == other.mContentInfo) \ + && (mContentMetaInfo == other.mContentMetaInfo) \ + && (mExtendedData == other.mExtendedData) \ + && (memcmp(mDigest.data, other.mDigest.data, cmnt::kDigestLen) == 0); +} + +void nx::ContentMetaBinary::copyFrom(const ContentMetaBinary & other) +{ + if (other.getSize() > 0) + { + importBinary(other.getBytes(), other.getSize()); + } + else + { + clear(); + mTitleId = other.mTitleId; + mTitleVersion = other.mTitleVersion; + mType = other.mType; + mAttributes = other.mAttributes; + mRequiredSystemVersion = other.mRequiredSystemVersion; + mExtendedHeader = other.mExtendedHeader; + mContentInfo = other.mContentInfo; + mContentMetaInfo = other.mContentMetaInfo; + mExtendedData = other.mExtendedData; + memcpy(mDigest.data, other.mDigest.data, cmnt::kDigestLen); + } +} From b36875661e64328b0c57f2b3adc8c19930b65e00 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 12 May 2018 17:37:15 +0800 Subject: [PATCH 15/59] [nstool] Start CmntProcess --- programs/nstool/nstool.vcxproj | 2 ++ programs/nstool/nstool.vcxproj.filters | 6 ++++++ programs/nstool/source/CmntProcess.cpp | 1 + programs/nstool/source/CmntProcess.h | 1 + programs/nstool/source/UserSettings.cpp | 18 ++++++++++-------- programs/nstool/source/nstool.h | 1 + 6 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 programs/nstool/source/CmntProcess.cpp create mode 100644 programs/nstool/source/CmntProcess.h diff --git a/programs/nstool/nstool.vcxproj b/programs/nstool/nstool.vcxproj index d6492f2..150401f 100644 --- a/programs/nstool/nstool.vcxproj +++ b/programs/nstool/nstool.vcxproj @@ -163,6 +163,7 @@ + @@ -176,6 +177,7 @@ + diff --git a/programs/nstool/nstool.vcxproj.filters b/programs/nstool/nstool.vcxproj.filters index 930a7f0..61f7449 100644 --- a/programs/nstool/nstool.vcxproj.filters +++ b/programs/nstool/nstool.vcxproj.filters @@ -48,6 +48,9 @@ Header Files + + Header Files + @@ -77,6 +80,9 @@ Source Files + + Source Files + diff --git a/programs/nstool/source/CmntProcess.cpp b/programs/nstool/source/CmntProcess.cpp new file mode 100644 index 0000000..d321c22 --- /dev/null +++ b/programs/nstool/source/CmntProcess.cpp @@ -0,0 +1 @@ +#include "CmntProcess.h" \ No newline at end of file diff --git a/programs/nstool/source/CmntProcess.h b/programs/nstool/source/CmntProcess.h new file mode 100644 index 0000000..6f70f09 --- /dev/null +++ b/programs/nstool/source/CmntProcess.h @@ -0,0 +1 @@ +#pragma once diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index 4f2f963..b71f551 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -38,7 +38,7 @@ void UserSettings::showHelp() printf("\n General Options:\n"); printf(" -d, --dev Use devkit keyset\n"); printf(" -k, --keyset Specify keyset file\n"); - printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm]\n"); + printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cmnt]\n"); printf(" -y, --verify Verify file\n"); printf(" -v, --verbose Verbose output\n"); printf(" -q, --quiet Minimal output\n"); @@ -565,17 +565,19 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str) FileType type; if (str == "xci") type = FILE_XCI; - else if ( str == "partitionfs" \ - || str == "pfs" || str == "pfs0" \ - || str == "hfs" || str == "hfs0" \ - || str == "nsp") + else if (str == "partitionfs" \ + || str == "pfs" || str == "pfs0" \ + || str == "hfs" || str == "hfs0" \ + || str == "nsp") type = FILE_PARTITIONFS; - else if (str == "romfs") + else if (str == "romfs") type = FILE_ROMFS; - else if (str == "nca") + else if (str == "nca") type = FILE_NCA; - else if (str == "npdm") + else if (str == "npdm") type = FILE_NPDM; + else if (str == "cmnt") + type = FILE_CMNT; else type = FILE_INVALID; diff --git a/programs/nstool/source/nstool.h b/programs/nstool/source/nstool.h index 049cec9..99827fb 100644 --- a/programs/nstool/source/nstool.h +++ b/programs/nstool/source/nstool.h @@ -17,6 +17,7 @@ enum FileType FILE_ROMFS, FILE_NCA, FILE_NPDM, + FILE_CMNT, FILE_INVALID = -1, }; From 79c24153bbe90fb367cbf9de19bf6bdedc60efea Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 12 May 2018 23:02:53 +0800 Subject: [PATCH 16/59] [nx|nstool] Added support for reading ContentMeta (.cnmt). --- lib/libnx/include/nx/ContentMetaBinary.h | 143 ++++++++++++-- lib/libnx/include/nx/{cmnt.h => cnmt.h} | 17 +- lib/libnx/source/ContentMetaBinary.cpp | 154 +++++++++++---- programs/nstool/source/CmntProcess.cpp | 1 - programs/nstool/source/CmntProcess.h | 1 - programs/nstool/source/CnmtProcess.cpp | 185 ++++++++++++++++++ programs/nstool/source/CnmtProcess.h | 33 ++++ programs/nstool/source/NcaProcess.cpp | 53 ++--- programs/nstool/source/NcaProcess.h | 7 +- programs/nstool/source/NpdmProcess.cpp | 26 ++- programs/nstool/source/NpdmProcess.h | 7 +- .../nstool/source/OffsetAdjustedIFile.cpp | 2 +- programs/nstool/source/PfsProcess.cpp | 34 ++-- programs/nstool/source/PfsProcess.h | 7 +- programs/nstool/source/RomfsProcess.cpp | 35 ++-- programs/nstool/source/RomfsProcess.h | 7 +- programs/nstool/source/UserSettings.cpp | 6 +- programs/nstool/source/XciProcess.cpp | 31 +-- programs/nstool/source/XciProcess.h | 5 +- programs/nstool/source/main.cpp | 23 ++- programs/nstool/source/nstool.h | 2 +- 21 files changed, 589 insertions(+), 190 deletions(-) rename lib/libnx/include/nx/{cmnt.h => cnmt.h} (90%) delete mode 100644 programs/nstool/source/CmntProcess.cpp delete mode 100644 programs/nstool/source/CmntProcess.h create mode 100644 programs/nstool/source/CnmtProcess.cpp create mode 100644 programs/nstool/source/CnmtProcess.h diff --git a/lib/libnx/include/nx/ContentMetaBinary.h b/lib/libnx/include/nx/ContentMetaBinary.h index c9f08cb..6bf2ddb 100644 --- a/lib/libnx/include/nx/ContentMetaBinary.h +++ b/lib/libnx/include/nx/ContentMetaBinary.h @@ -2,7 +2,7 @@ #include #include #include -#include +#include namespace nx @@ -14,14 +14,14 @@ namespace nx struct ContentInfo { crypto::sha::sSha256Hash hash; - byte_t nca_id[cmnt::kContentIdLen]; + byte_t nca_id[cnmt::kContentIdLen]; size_t size; - cmnt::ContentType type; + cnmt::ContentType type; ContentInfo& operator=(const ContentInfo& other) { hash = other.hash; - memcpy(nca_id, other.nca_id, cmnt::kContentIdLen); + memcpy(nca_id, other.nca_id, cnmt::kContentIdLen); size = other.size; type = other.type; return *this; @@ -30,7 +30,7 @@ namespace nx bool operator==(const ContentInfo& other) const { return (hash == other.hash) \ - && (memcmp(nca_id, other.nca_id, cmnt::kContentIdLen) == 0) \ + && (memcmp(nca_id, other.nca_id, cnmt::kContentIdLen) == 0) \ && (size == other.size) \ && (type == other.type); } @@ -45,7 +45,7 @@ namespace nx { uint64_t id; uint32_t version; - cmnt::ContentMetaType type; + cnmt::ContentMetaType type; byte_t attributes; ContentMetaInfo& operator=(const ContentMetaInfo& other) @@ -71,6 +71,99 @@ namespace nx } }; + struct ApplicationMetaExtendedHeader + { + uint64_t patch_id; + uint32_t required_system_version; + + ApplicationMetaExtendedHeader& operator=(const ApplicationMetaExtendedHeader& other) + { + patch_id = other.patch_id; + required_system_version = other.required_system_version; + return *this; + } + + bool operator==(const ApplicationMetaExtendedHeader& other) const + { + return (patch_id == other.patch_id) \ + && (required_system_version == other.required_system_version); + } + + bool operator!=(const ApplicationMetaExtendedHeader& other) const + { + return !operator==(other); + } + }; + + struct PatchMetaExtendedHeader + { + uint64_t application_id; + uint32_t required_system_version; + + PatchMetaExtendedHeader& operator=(const PatchMetaExtendedHeader& other) + { + application_id = other.application_id; + required_system_version = other.required_system_version; + return *this; + } + + bool operator==(const PatchMetaExtendedHeader& other) const + { + return (application_id == other.application_id) \ + && (required_system_version == other.required_system_version); + } + + bool operator!=(const PatchMetaExtendedHeader& other) const + { + return !operator==(other); + } + }; + + struct AddOnContentMetaExtendedHeader + { + uint64_t application_id; + uint32_t required_system_version; + + AddOnContentMetaExtendedHeader& operator=(const AddOnContentMetaExtendedHeader& other) + { + application_id = other.application_id; + required_system_version = other.required_system_version; + return *this; + } + + bool operator==(const AddOnContentMetaExtendedHeader& other) const + { + return (application_id == other.application_id) \ + && (required_system_version == other.required_system_version); + } + + bool operator!=(const AddOnContentMetaExtendedHeader& other) const + { + return !operator==(other); + } + }; + + struct DeltaMetaExtendedHeader + { + uint64_t application_id; + + DeltaMetaExtendedHeader& operator=(const DeltaMetaExtendedHeader& other) + { + application_id = other.application_id; + return *this; + } + + bool operator==(const DeltaMetaExtendedHeader& other) const + { + return (application_id == other.application_id); + } + + bool operator!=(const DeltaMetaExtendedHeader& other) const + { + return !operator==(other); + } + }; + ContentMetaBinary(); ContentMetaBinary(const ContentMetaBinary& other); ContentMetaBinary(const byte_t* bytes, size_t len); @@ -92,14 +185,26 @@ namespace nx uint32_t getTitleVersion() const; void setTitleVersion(uint32_t version); - cmnt::ContentMetaType getType() const; - void setType(cmnt::ContentMetaType type); + cnmt::ContentMetaType getType() const; + void setType(cnmt::ContentMetaType type); byte_t getAttributes() const; void setAttributes(byte_t attributes); - uint32_t getRequiredSystemVersion() const; - void setRequiredSystemVersion(uint32_t version); + uint32_t getRequiredDownloadSystemVersion() const; + void setRequiredDownloadSystemVersion(uint32_t version); + + const ApplicationMetaExtendedHeader& getApplicationMetaExtendedHeader() const; + void setApplicationMetaExtendedHeader(const ApplicationMetaExtendedHeader& exhdr); + + const PatchMetaExtendedHeader& getPatchMetaExtendedHeader() const; + void setPatchMetaExtendedHeader(const PatchMetaExtendedHeader& exhdr); + + const AddOnContentMetaExtendedHeader& getAddOnContentMetaExtendedHeader() const; + void setAddOnContentMetaExtendedHeader(const AddOnContentMetaExtendedHeader& exhdr); + + const DeltaMetaExtendedHeader& getDeltaMetaExtendedHeader() const; + void setDeltaMetaExtendedHeader(const DeltaMetaExtendedHeader& exhdr); const fnd::List& getContentInfo() const; void setContentInfo(const fnd::List& info); @@ -123,10 +228,16 @@ namespace nx // variables uint64_t mTitleId; uint32_t mTitleVersion; - cmnt::ContentMetaType mType; + cnmt::ContentMetaType mType; byte_t mAttributes; - uint32_t mRequiredSystemVersion; + uint32_t mRequiredDownloadSystemVersion; fnd::MemoryBlob mExtendedHeader; + + ApplicationMetaExtendedHeader mApplicationMetaExtendedHeader; + PatchMetaExtendedHeader mPatchMetaExtendedHeader; + AddOnContentMetaExtendedHeader mAddOnContentMetaExtendedHeader; + DeltaMetaExtendedHeader mDeltaMetaExtendedHeader; + fnd::List mContentInfo; fnd::List mContentMetaInfo; fnd::MemoryBlob mExtendedData; @@ -137,11 +248,11 @@ namespace nx inline size_t getContentMetaInfoOffset(size_t exhdrSize, size_t contentInfoNum) const { return getContentInfoOffset(exhdrSize) + contentInfoNum * sizeof(sContentInfo); } inline size_t getExtendedDataOffset(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum) const { return getContentMetaInfoOffset(exhdrSize, contentInfoNum) + contentMetaNum * sizeof(sContentMetaInfo); } inline size_t getDigestOffset(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getExtendedDataOffset(exhdrSize, contentInfoNum, contentMetaNum) + exdataSize; } - inline size_t getTotalSize(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getDigestOffset(exhdrSize, contentInfoNum, contentMetaNum, exdataSize) + cmnt::kDigestLen; } + inline size_t getTotalSize(size_t exhdrSize, size_t contentInfoNum, size_t contentMetaNum, size_t exdataSize) const { return getDigestOffset(exhdrSize, contentInfoNum, contentMetaNum, exdataSize) + cnmt::kDigestLen; } - bool validateExtendedHeaderSize(cmnt::ContentMetaType type, size_t exhdrSize); - size_t getExtendedDataSize(cmnt::ContentMetaType type, const byte_t* data); - void validateBinary(const byte_t* bytes, size_t len); + bool validateExtendedHeaderSize(cnmt::ContentMetaType type, size_t exhdrSize) const; + size_t getExtendedDataSize(cnmt::ContentMetaType type, const byte_t* data) const; + void validateBinary(const byte_t* bytes, size_t len) const; bool isEqual(const ContentMetaBinary& other) const; void copyFrom(const ContentMetaBinary& other); diff --git a/lib/libnx/include/nx/cmnt.h b/lib/libnx/include/nx/cnmt.h similarity index 90% rename from lib/libnx/include/nx/cmnt.h rename to lib/libnx/include/nx/cnmt.h index 3b37bf0..8912a89 100644 --- a/lib/libnx/include/nx/cmnt.h +++ b/lib/libnx/include/nx/cnmt.h @@ -7,7 +7,7 @@ namespace nx { - namespace cmnt + namespace cnmt { enum ContentType { @@ -43,7 +43,6 @@ namespace nx enum ContentMetaAttribute { - ATTRIBUTE_NONE, ATTRIBUTE_INCLUDES_EX_FAT_DRIVER, ATTRIBUTE_REBOOTLESS }; @@ -79,14 +78,14 @@ namespace nx le_uint16_t content_meta_count; byte_t attributes; byte_t reserved_1[3]; - le_uint32_t required_system_version; + le_uint32_t required_download_system_version; byte_t reserved_2[4]; }; struct sContentInfo { crypto::sha::sSha256Hash content_hash; - byte_t content_id[cmnt::kContentIdLen]; + byte_t content_id[cnmt::kContentIdLen]; le_uint32_t size_lower; le_uint16_t size_higher; byte_t content_type; @@ -104,14 +103,14 @@ namespace nx struct sApplicationMetaExtendedHeader { - le_uint64_t id; + le_uint64_t patch_id; le_uint32_t required_system_version; byte_t reserved[4]; }; struct sPatchMetaExtendedHeader { - le_uint64_t id; + le_uint64_t application_id; le_uint32_t required_system_version; le_uint32_t extended_data_size; byte_t reserved[8]; @@ -119,21 +118,21 @@ namespace nx struct sAddOnContentMetaExtendedHeader { - le_uint64_t id; + le_uint64_t application_id; le_uint32_t required_system_version; byte_t reserved[4]; }; struct sDeltaMetaExtendedHeader { - le_uint64_t id; + le_uint64_t application_id; le_uint32_t extended_data_size; byte_t reserved[4]; }; struct sDigest { - byte_t data[cmnt::kDigestLen]; + byte_t data[cnmt::kDigestLen]; }; #pragma pack(pop) } \ No newline at end of file diff --git a/lib/libnx/source/ContentMetaBinary.cpp b/lib/libnx/source/ContentMetaBinary.cpp index 003da2f..450eb61 100644 --- a/lib/libnx/source/ContentMetaBinary.cpp +++ b/lib/libnx/source/ContentMetaBinary.cpp @@ -43,9 +43,9 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len) mTitleId = hdr->id.get(); mTitleVersion = hdr->version.get(); - mType = (cmnt::ContentMetaType)hdr->type; + mType = (cnmt::ContentMetaType)hdr->type; mAttributes = hdr->attributes; - mRequiredSystemVersion = hdr->required_system_version.get(); + mRequiredDownloadSystemVersion = hdr->required_download_system_version.get(); size_t exdata_size = 0; // save exheader @@ -54,6 +54,27 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len) mExtendedHeader.alloc(hdr->exhdr_size.get()); memcpy(mExtendedHeader.getBytes(), bytes + getExtendedHeaderOffset(), hdr->exhdr_size.get()); + switch (mType) + { + case (cnmt::METATYPE_APPLICATION): + mApplicationMetaExtendedHeader.patch_id = ((sApplicationMetaExtendedHeader*)mExtendedHeader.getBytes())->patch_id.get(); + mApplicationMetaExtendedHeader.required_system_version = ((sApplicationMetaExtendedHeader*)mExtendedHeader.getBytes())->required_system_version.get(); + break; + case (cnmt::METATYPE_PATCH): + mPatchMetaExtendedHeader.application_id = ((sPatchMetaExtendedHeader*)mExtendedHeader.getBytes())->application_id.get(); + mPatchMetaExtendedHeader.required_system_version = ((sPatchMetaExtendedHeader*)mExtendedHeader.getBytes())->required_system_version.get(); + break; + case (cnmt::METATYPE_ADD_ON_CONTENT): + mAddOnContentMetaExtendedHeader.application_id = ((sAddOnContentMetaExtendedHeader*)mExtendedHeader.getBytes())->application_id.get(); + mAddOnContentMetaExtendedHeader.required_system_version = ((sAddOnContentMetaExtendedHeader*)mExtendedHeader.getBytes())->required_system_version.get(); + break; + case (cnmt::METATYPE_DELTA): + mDeltaMetaExtendedHeader.application_id = ((sDeltaMetaExtendedHeader*)mExtendedHeader.getBytes())->application_id.get(); + break; + default: + break; + } + exdata_size = getExtendedDataSize(mType, mExtendedHeader.getBytes()); } @@ -64,9 +85,9 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len) for (size_t i = 0; i < hdr->content_count.get(); i++) { mContentInfo[i].hash = info[i].content_hash; - memcpy(mContentInfo[i].nca_id, info[i].content_id, cmnt::kContentIdLen); + memcpy(mContentInfo[i].nca_id, info[i].content_id, cnmt::kContentIdLen); mContentInfo[i].size = (uint64_t)(info[i].size_lower.get()) | (uint64_t)(info[i].size_higher.get()) << 32; - mContentInfo[i].type = (cmnt::ContentType)info[i].content_type; + mContentInfo[i].type = (cnmt::ContentType)info[i].content_type; } } @@ -78,7 +99,7 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len) { mContentMetaInfo[i].id = info[i].id.get(); mContentMetaInfo[i].version = info[i].version.get(); - mContentMetaInfo[i].type = (cmnt::ContentMetaType)info[i].type; + mContentMetaInfo[i].type = (cnmt::ContentMetaType)info[i].type; mContentMetaInfo[i].attributes = info[i].attributes; } } @@ -91,7 +112,7 @@ void nx::ContentMetaBinary::importBinary(const byte_t * bytes, size_t len) } // save digest - memcpy(mDigest.data, bytes + getDigestOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), exdata_size), cmnt::kDigestLen); + memcpy(mDigest.data, bytes + getDigestOffset(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), exdata_size), cnmt::kDigestLen); } @@ -100,14 +121,18 @@ void nx::ContentMetaBinary::clear() mBinaryBlob.clear(); mTitleId = 0; mTitleVersion = 0; - mType = cmnt::METATYPE_SYSTEM_PROGRAM; - mAttributes = cmnt::ATTRIBUTE_NONE; - mRequiredSystemVersion = 0; + mType = cnmt::METATYPE_SYSTEM_PROGRAM; + mAttributes = 0; + mRequiredDownloadSystemVersion = 0; mExtendedHeader.clear(); + memset(&mApplicationMetaExtendedHeader, 0, sizeof(mApplicationMetaExtendedHeader)); + memset(&mPatchMetaExtendedHeader, 0, sizeof(mPatchMetaExtendedHeader)); + memset(&mAddOnContentMetaExtendedHeader, 0, sizeof(mAddOnContentMetaExtendedHeader)); + memset(&mDeltaMetaExtendedHeader, 0, sizeof(mDeltaMetaExtendedHeader)); mContentInfo.clear(); mContentMetaInfo.clear(); mExtendedData.clear(); - memset(mDigest.data, 0, cmnt::kDigestLen); + memset(mDigest.data, 0, cnmt::kDigestLen); } uint64_t nx::ContentMetaBinary::getTitleId() const @@ -130,12 +155,12 @@ void nx::ContentMetaBinary::setTitleVersion(uint32_t version) mTitleVersion = version; } -nx::cmnt::ContentMetaType nx::ContentMetaBinary::getType() const +nx::cnmt::ContentMetaType nx::ContentMetaBinary::getType() const { return mType; } -void nx::ContentMetaBinary::setType(cmnt::ContentMetaType type) +void nx::ContentMetaBinary::setType(cnmt::ContentMetaType type) { mType = type; } @@ -150,14 +175,54 @@ void nx::ContentMetaBinary::setAttributes(byte_t attributes) mAttributes = attributes; } -uint32_t nx::ContentMetaBinary::getRequiredSystemVersion() const +uint32_t nx::ContentMetaBinary::getRequiredDownloadSystemVersion() const { - return mRequiredSystemVersion; + return mRequiredDownloadSystemVersion; } -void nx::ContentMetaBinary::setRequiredSystemVersion(uint32_t version) +void nx::ContentMetaBinary::setRequiredDownloadSystemVersion(uint32_t version) { - mRequiredSystemVersion = version; + mRequiredDownloadSystemVersion = version; +} + +const nx::ContentMetaBinary::ApplicationMetaExtendedHeader& nx::ContentMetaBinary::getApplicationMetaExtendedHeader() const +{ + return mApplicationMetaExtendedHeader; +} + +void nx::ContentMetaBinary::setApplicationMetaExtendedHeader(const ApplicationMetaExtendedHeader& exhdr) +{ + mApplicationMetaExtendedHeader = exhdr; +} + +const nx::ContentMetaBinary::PatchMetaExtendedHeader& nx::ContentMetaBinary::getPatchMetaExtendedHeader() const +{ + return mPatchMetaExtendedHeader; +} + +void nx::ContentMetaBinary::setPatchMetaExtendedHeader(const PatchMetaExtendedHeader& exhdr) +{ + mPatchMetaExtendedHeader = exhdr; +} + +const nx::ContentMetaBinary::AddOnContentMetaExtendedHeader& nx::ContentMetaBinary::getAddOnContentMetaExtendedHeader() const +{ + return mAddOnContentMetaExtendedHeader; +} + +void nx::ContentMetaBinary::setAddOnContentMetaExtendedHeader(const AddOnContentMetaExtendedHeader& exhdr) +{ + mAddOnContentMetaExtendedHeader = exhdr; +} + +const nx::ContentMetaBinary::DeltaMetaExtendedHeader& nx::ContentMetaBinary::getDeltaMetaExtendedHeader() const +{ + return mDeltaMetaExtendedHeader; +} + +void nx::ContentMetaBinary::setDeltaMetaExtendedHeader(const DeltaMetaExtendedHeader& exhdr) +{ + mDeltaMetaExtendedHeader = exhdr; } const fnd::List& nx::ContentMetaBinary::getContentInfo() const @@ -198,34 +263,43 @@ const nx::sDigest & nx::ContentMetaBinary::getDigest() const void nx::ContentMetaBinary::setDigest(const nx::sDigest & digest) { - memcpy(mDigest.data, digest.data, cmnt::kDigestLen); + memcpy(mDigest.data, digest.data, cnmt::kDigestLen); } -bool nx::ContentMetaBinary::validateExtendedHeaderSize(cmnt::ContentMetaType type, size_t exhdrSize) +bool nx::ContentMetaBinary::validateExtendedHeaderSize(cnmt::ContentMetaType type, size_t exhdrSize) const { bool validSize = false; - if (type == cmnt::METATYPE_APPLICATION && exhdrSize == sizeof(sApplicationMetaExtendedHeader)) - validSize = true; - else if (type == cmnt::METATYPE_PATCH && exhdrSize == sizeof(sPatchMetaExtendedHeader)) - validSize = true; - else if (type == cmnt::METATYPE_ADD_ON_CONTENT && exhdrSize == sizeof(sAddOnContentMetaExtendedHeader)) - validSize = true; - else if (type == cmnt::METATYPE_DELTA && exhdrSize == sizeof(sDeltaMetaExtendedHeader)) - validSize = true; + switch (type) + { + case (cnmt::METATYPE_APPLICATION): + validSize = (exhdrSize == sizeof(sApplicationMetaExtendedHeader)); + break; + case (cnmt::METATYPE_PATCH): + validSize = (exhdrSize == sizeof(sPatchMetaExtendedHeader)); + break; + case (cnmt::METATYPE_ADD_ON_CONTENT): + validSize = (exhdrSize == sizeof(sAddOnContentMetaExtendedHeader)); + break; + case (cnmt::METATYPE_DELTA): + validSize = (exhdrSize == sizeof(sDeltaMetaExtendedHeader)); + break; + default: + validSize = (exhdrSize == 0); + } return validSize; } -size_t nx::ContentMetaBinary::getExtendedDataSize(cmnt::ContentMetaType type, const byte_t * data) +size_t nx::ContentMetaBinary::getExtendedDataSize(cnmt::ContentMetaType type, const byte_t * data) const { size_t exdata_len = 0; - if (type == cmnt::METATYPE_PATCH) + if (type == cnmt::METATYPE_PATCH) { const sPatchMetaExtendedHeader* exhdr = (const sPatchMetaExtendedHeader*)(data); exdata_len = exhdr->extended_data_size.get(); } - else if (type == cmnt::METATYPE_DELTA) + else if (type == cnmt::METATYPE_DELTA) { const sDeltaMetaExtendedHeader* exhdr = (const sDeltaMetaExtendedHeader*)(data); exdata_len = exhdr->extended_data_size.get(); @@ -233,7 +307,7 @@ size_t nx::ContentMetaBinary::getExtendedDataSize(cmnt::ContentMetaType type, co return exdata_len; } -void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len) +void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len) const { // check if it is large enough to read the header if (len < sizeof(sContentMetaHeader)) @@ -245,7 +319,7 @@ void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len) const sContentMetaHeader* hdr = (const sContentMetaHeader*)bytes; // validate extended header size - if (validateExtendedHeaderSize((cmnt::ContentMetaType)hdr->type, hdr->exhdr_size.get()) == false) + if (validateExtendedHeaderSize((cnmt::ContentMetaType)hdr->type, hdr->exhdr_size.get()) == false) { throw fnd::Exception(kModuleName, "Invalid extended header size"); } @@ -257,7 +331,7 @@ void nx::ContentMetaBinary::validateBinary(const byte_t * bytes, size_t len) } // check binary size again with extended data size - if (len < getTotalSize(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), getExtendedDataSize((cmnt::ContentMetaType)hdr->type, bytes + getExtendedHeaderOffset()))) + if (len < getTotalSize(hdr->exhdr_size.get(), hdr->content_count.get(), hdr->content_meta_count.get(), getExtendedDataSize((cnmt::ContentMetaType)hdr->type, bytes + getExtendedHeaderOffset()))) { throw fnd::Exception(kModuleName, "Binary too small"); } @@ -269,12 +343,16 @@ bool nx::ContentMetaBinary::isEqual(const ContentMetaBinary & other) const && (mTitleVersion == other.mTitleVersion) \ && (mType == other.mType) \ && (mAttributes == other.mAttributes) \ - && (mRequiredSystemVersion == other.mRequiredSystemVersion) \ + && (mRequiredDownloadSystemVersion == other.mRequiredDownloadSystemVersion) \ && (mExtendedHeader == other.mExtendedHeader) \ + && (mApplicationMetaExtendedHeader == other.mApplicationMetaExtendedHeader) \ + && (mPatchMetaExtendedHeader == other.mPatchMetaExtendedHeader) \ + && (mAddOnContentMetaExtendedHeader == other.mAddOnContentMetaExtendedHeader) \ + && (mDeltaMetaExtendedHeader == other.mDeltaMetaExtendedHeader) \ && (mContentInfo == other.mContentInfo) \ && (mContentMetaInfo == other.mContentMetaInfo) \ && (mExtendedData == other.mExtendedData) \ - && (memcmp(mDigest.data, other.mDigest.data, cmnt::kDigestLen) == 0); + && (memcmp(mDigest.data, other.mDigest.data, cnmt::kDigestLen) == 0); } void nx::ContentMetaBinary::copyFrom(const ContentMetaBinary & other) @@ -290,11 +368,15 @@ void nx::ContentMetaBinary::copyFrom(const ContentMetaBinary & other) mTitleVersion = other.mTitleVersion; mType = other.mType; mAttributes = other.mAttributes; - mRequiredSystemVersion = other.mRequiredSystemVersion; + mRequiredDownloadSystemVersion = other.mRequiredDownloadSystemVersion; mExtendedHeader = other.mExtendedHeader; + mApplicationMetaExtendedHeader = other.mApplicationMetaExtendedHeader; + mPatchMetaExtendedHeader = other.mPatchMetaExtendedHeader; + mAddOnContentMetaExtendedHeader = other.mAddOnContentMetaExtendedHeader; + mDeltaMetaExtendedHeader = other.mDeltaMetaExtendedHeader; mContentInfo = other.mContentInfo; mContentMetaInfo = other.mContentMetaInfo; mExtendedData = other.mExtendedData; - memcpy(mDigest.data, other.mDigest.data, cmnt::kDigestLen); + memcpy(mDigest.data, other.mDigest.data, cnmt::kDigestLen); } } diff --git a/programs/nstool/source/CmntProcess.cpp b/programs/nstool/source/CmntProcess.cpp deleted file mode 100644 index d321c22..0000000 --- a/programs/nstool/source/CmntProcess.cpp +++ /dev/null @@ -1 +0,0 @@ -#include "CmntProcess.h" \ No newline at end of file diff --git a/programs/nstool/source/CmntProcess.h b/programs/nstool/source/CmntProcess.h deleted file mode 100644 index 6f70f09..0000000 --- a/programs/nstool/source/CmntProcess.h +++ /dev/null @@ -1 +0,0 @@ -#pragma once diff --git a/programs/nstool/source/CnmtProcess.cpp b/programs/nstool/source/CnmtProcess.cpp new file mode 100644 index 0000000..5aade5c --- /dev/null +++ b/programs/nstool/source/CnmtProcess.cpp @@ -0,0 +1,185 @@ +#include +#include "OffsetAdjustedIFile.h" +#include "CnmtProcess.h" + +const std::string kContentTypeStr[7] = +{ + "Meta", + "Program", + "Data", + "Control", + "HtmlDocument", + "LegalInformation", + "DeltaFragment" +}; + +const std::string kContentMetaTypeStr[2][0x80] = +{ + { + "" + "SystemProgram", + "SystemData", + "SystemUpdate", + "BootImagePackage", + "BootImagePackageSafe" + }, + { + "Application", + "Patch", + "AddOnContent", + "Delta" + } +}; + +inline const std::string& getContentMetaTypeStr(byte_t index) +{ + return (index < 0x80) ? kContentMetaTypeStr[0][index] : kContentMetaTypeStr[1][index-0x80]; +} + +const std::string kUpdateTypeStr[3] = +{ + "ApplyAsDelta", + "Overwrite", + "Create" +}; + +const std::string kContentMetaAttrStr[3] = +{ + "IncludesExFatDriver", + "Rebootless" +}; + +inline const char* getBoolStr(bool isTrue) +{ + return isTrue? "TRUE" : "FALSE"; +} + +void CnmtProcess::displayCmnt() +{ +#define _SPLIT_VER(ver) ( (ver>>24) & 0xff), ( (ver>>16) & 0xff), ( (ver>>8) & 0xff), (ver & 0xff) + + printf("[ContentMeta]\n"); + printf(" TitleId: 0x%" PRIx64 "\n", mCnmt.getTitleId()); + uint32_t ver = mCnmt.getTitleVersion(); + printf(" Version: v%d.%d.%d-%d (v%" PRId32 ")\n", _SPLIT_VER(ver), ver); + printf(" Type: %s\n", getContentMetaTypeStr(mCnmt.getType()).c_str()); + printf(" Attributes: %x\n", mCnmt.getAttributes()); + printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER))); + printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS))); + ver = mCnmt.getRequiredDownloadSystemVersion(); + printf(" RequiredDownloadSystemVersion: v%d.%d.%d-%d (v%" PRId32 ")\n", _SPLIT_VER(ver), ver); + switch(mCnmt.getType()) + { + case (nx::cnmt::METATYPE_APPLICATION): + printf(" ApplicationExtendedHeader:\n"); + printf(" RequiredSystemVersion: %" PRId32 "\n", mCnmt.getApplicationMetaExtendedHeader().required_system_version); + printf(" PatchId: 0x%016" PRIx64 "\n", mCnmt.getApplicationMetaExtendedHeader().patch_id); + break; + case (nx::cnmt::METATYPE_PATCH): + printf(" PatchMetaExtendedHeader:\n"); + printf(" RequiredSystemVersion: %" PRId32 "\n", mCnmt.getPatchMetaExtendedHeader().required_system_version); + printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getPatchMetaExtendedHeader().application_id); + break; + case (nx::cnmt::METATYPE_ADD_ON_CONTENT): + printf(" AddOnContentMetaExtendedHeader:\n"); + printf(" RequiredSystemVersion: %" PRId32 "\n", mCnmt.getAddOnContentMetaExtendedHeader().required_system_version); + printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getAddOnContentMetaExtendedHeader().application_id); + break; + case (nx::cnmt::METATYPE_DELTA): + printf(" DeltaMetaExtendedHeader:\n"); + printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getDeltaMetaExtendedHeader().application_id); + break; + default: + break; + } + if (mCnmt.getContentInfo().getSize() > 0) + { + printf(" ContentInfo:\n"); + for (size_t i = 0; i < mCnmt.getContentInfo().getSize(); i++) + { + const nx::ContentMetaBinary::ContentInfo& info = mCnmt.getContentInfo()[i]; + printf(" %d\n", i); + printf(" Type: %s\n", kContentTypeStr[info.type].c_str()); + printf(" Id: "); + fnd::SimpleTextOutput::hexDump(info.nca_id, nx::cnmt::kContentIdLen); + printf(" Size: 0x%" PRIx64 "\n", info.size); + printf(" Hash: "); + fnd::SimpleTextOutput::hexDump(info.hash.bytes, sizeof(info.hash)); + } + } + if (mCnmt.getContentMetaInfo().getSize() > 0) + { + printf(" ContentMetaInfo:\n"); + for (size_t i = 0; i < mCnmt.getContentMetaInfo().getSize(); i++) + { + const nx::ContentMetaBinary::ContentMetaInfo& info = mCnmt.getContentMetaInfo()[i]; + printf(" %d\n", i); + printf(" Id: 0x%" PRIx64 "\n", info.id); + ver = info.version; + printf(" Version: v%d.%d.%d-%d (v%" PRId32 ")\n", _SPLIT_VER(ver), ver); + printf(" Type: %s\n", getContentMetaTypeStr(info.type).c_str()); + printf(" Attributes: %x\n", mCnmt.getAttributes()); + printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER))); + printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS))); + } + } + printf(" Digest: "); + fnd::SimpleTextOutput::hexDump(mCnmt.getDigest().data, nx::cnmt::kDigestLen); + +#undef _SPLIT_VER +} + +CnmtProcess::CnmtProcess() : + mReader(nullptr), + mCliOutputType(OUTPUT_NORMAL), + mVerify(false) +{ +} + +CnmtProcess::~CnmtProcess() +{ + if (mReader != nullptr) + { + delete mReader; + } +} + +void CnmtProcess::process() +{ + fnd::MemoryBlob scratch; + + if (mReader == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + scratch.alloc(mReader->size()); + mReader->read(scratch.getBytes(), 0, scratch.getSize()); + + mCnmt.importBinary(scratch.getBytes(), scratch.getSize()); + + if (mCliOutputType >= OUTPUT_NORMAL) + { + displayCmnt(); + } +} + +void CnmtProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) +{ + mReader = new OffsetAdjustedIFile(file, offset, size); +} + +void CnmtProcess::setCliOutputMode(CliOutputType type) +{ + mCliOutputType = type; +} + +void CnmtProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +const nx::ContentMetaBinary& CnmtProcess::getContentMetaBinary() const +{ + return mCnmt; +} diff --git a/programs/nstool/source/CnmtProcess.h b/programs/nstool/source/CnmtProcess.h new file mode 100644 index 0000000..4e57947 --- /dev/null +++ b/programs/nstool/source/CnmtProcess.h @@ -0,0 +1,33 @@ +#pragma once +#include +#include +#include +#include + +#include "nstool.h" + +class CnmtProcess +{ +public: + CnmtProcess(); + ~CnmtProcess(); + + void process(); + + void setInputFile(fnd::IFile* file, size_t offset, size_t size); + void setCliOutputMode(CliOutputType type); + void setVerifyMode(bool verify); + + const nx::ContentMetaBinary& getContentMetaBinary() const; + +private: + const std::string kModuleName = "CnmtProcess"; + + fnd::IFile* mReader; + CliOutputType mCliOutputType; + bool mVerify; + + nx::ContentMetaBinary mCnmt; + + void displayCmnt(); +}; \ No newline at end of file diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 97d0d4d..eb1a8a3 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -2,10 +2,10 @@ #include #include #include -#include #include "NcaProcess.h" #include "PfsProcess.h" #include "RomfsProcess.h" +#include "NpdmProcess.h" #include "OffsetAdjustedIFile.h" #include "AesCtrWrappedIFile.h" #include "CopiedIFile.h" @@ -199,10 +199,11 @@ void NcaProcess::generatePartitionConfiguration() throw fnd::Exception(kModuleName, error.str()); } - // determine the data offset + // determine the data offset & size if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) { mPartitions[partition.index].data_offset = mPartitions[partition.index].hierarchicalsha256_header.hash_target.offset.get(); + mPartitions[partition.index].data_size = mPartitions[partition.index].hierarchicalsha256_header.hash_target.size.get(); } else if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) { @@ -211,6 +212,7 @@ void NcaProcess::generatePartitionConfiguration() if (mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get() != 0) { mPartitions[partition.index].data_offset = mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get(); + mPartitions[partition.index].data_size = mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].hash_data_size.get(); break; } } @@ -245,25 +247,21 @@ void NcaProcess::validateNcaSignatures() if (mPartitions[0].reader != nullptr) { PfsProcess exefs; - exefs.setInputFile(mPartitions[0].reader); - exefs.setInputFileOffset(mPartitions[0].offset + mPartitions[0].data_offset); + exefs.setInputFile(mPartitions[0].reader, mPartitions[0].offset + mPartitions[0].data_offset, mPartitions[0].data_size); exefs.setCliOutputMode(OUTPUT_MINIMAL); exefs.process(); // open main.npdm if (exefs.getPfsHeader().getFileList().hasElement(kNpdmExefsPath) == true) { - const nx::PfsHeader::sFile& npdmFile = exefs.getPfsHeader().getFileList()[exefs.getPfsHeader().getFileList().getIndexOf(kNpdmExefsPath)]; + const nx::PfsHeader::sFile& file = exefs.getPfsHeader().getFileList()[exefs.getPfsHeader().getFileList().getIndexOf(kNpdmExefsPath)]; - fnd::MemoryBlob scratch; - scratch.alloc(npdmFile.size); - mPartitions[0].reader->read(scratch.getBytes(), mPartitions[0].offset + mPartitions[0].data_offset + npdmFile.offset, npdmFile.size); + NpdmProcess npdm; + npdm.setInputFile(mPartitions[0].reader, mPartitions[0].offset + mPartitions[0].data_offset + file.offset, file.size); + npdm.setCliOutputMode(OUTPUT_MINIMAL); + npdm.process(); - nx::NpdmBinary npdmBinary; - - npdmBinary.importBinary(scratch.getBytes(), scratch.getSize()); - - if (crypto::rsa::pss::rsaVerify(npdmBinary.getAcid().getNcaHeader2RsaKey(), crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_acid) != 0) + if (crypto::rsa::pss::rsaVerify(npdm.getNpdmBinary().getAcid().getNcaHeader2RsaKey(), crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_acid) != 0) { // this is minimal even though it's a warning because it's a validation method if (mCliOutputType >= OUTPUT_MINIMAL) @@ -429,8 +427,7 @@ void NcaProcess::processPartitions() if (partition.format_type == nx::nca::FORMAT_PFS0) { PfsProcess pfs; - pfs.setInputFile(partition.reader); - pfs.setInputFileOffset(partition.offset + partition.data_offset); + pfs.setInputFile(partition.reader, partition.offset + partition.data_offset, partition.data_size); pfs.setCliOutputMode(mCliOutputType); pfs.setListFs(mListFs); if (mPartitionPath[index].doExtract) @@ -442,8 +439,7 @@ void NcaProcess::processPartitions() else if (partition.format_type == nx::nca::FORMAT_ROMFS) { RomfsProcess romfs; - romfs.setInputFile(partition.reader); - romfs.setInputFileOffset(partition.offset + partition.data_offset); + romfs.setInputFile(partition.reader, partition.offset + partition.data_offset, partition.data_size); romfs.setCliOutputMode(mCliOutputType); romfs.setListFs(mListFs); if (mPartitionPath[index].doExtract) @@ -457,7 +453,6 @@ void NcaProcess::processPartitions() NcaProcess::NcaProcess() : mReader(nullptr), - mOffset(0), mKeyset(nullptr), mCliOutputType(OUTPUT_NORMAL), mVerify(false), @@ -472,6 +467,11 @@ NcaProcess::NcaProcess() : NcaProcess::~NcaProcess() { + if (mReader != nullptr) + { + delete mReader; + } + for (size_t i = 0; i < nx::nca::kPartitionNum; i++) { if (mPartitions[i].reader != nullptr) @@ -491,7 +491,7 @@ void NcaProcess::process() } // read header block - mReader->read((byte_t*)&mHdrBlock, mOffset, sizeof(nx::sNcaHeaderBlock)); + mReader->read((byte_t*)&mHdrBlock, 0, sizeof(nx::sNcaHeaderBlock)); // decrypt header block nx::NcaUtils::decryptNcaHeader((byte_t*)&mHdrBlock, (byte_t*)&mHdrBlock, mKeyset->nca.header_key); @@ -539,22 +539,11 @@ void NcaProcess::process() so the verification text can be presented without interuption */ - - - // decrypt key area - - - } -void NcaProcess::setInputFile(fnd::IFile* reader) +void NcaProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) { - mReader = reader; -} - -void NcaProcess::setInputFileOffset(size_t offset) -{ - mOffset = offset; + mReader = new OffsetAdjustedIFile(file, offset, size); } void NcaProcess::setKeyset(const sKeyset* keyset) diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index c5dced0..1505d77 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -15,8 +15,7 @@ public: void process(); // generic - void setInputFile(fnd::IFile* reader); - void setInputFileOffset(size_t offset); + void setInputFile(fnd::IFile* file, size_t offset, size_t size); void setKeyset(const sKeyset* keyset); void setCliOutputMode(CliOutputType type); void setVerifyMode(bool verify); @@ -34,7 +33,6 @@ private: // user options fnd::IFile* mReader; - size_t mOffset; const sKeyset* mKeyset; CliOutputType mCliOutputType; bool mVerify; @@ -63,8 +61,9 @@ private: { fnd::IFile* reader; size_t offset; - size_t data_offset; size_t size; + size_t data_offset; + size_t data_size; nx::nca::FormatType format_type; nx::nca::HashType hash_type; diff --git a/programs/nstool/source/NpdmProcess.cpp b/programs/nstool/source/NpdmProcess.cpp index aeac024..5c3c5d7 100644 --- a/programs/nstool/source/NpdmProcess.cpp +++ b/programs/nstool/source/NpdmProcess.cpp @@ -1,6 +1,5 @@ +#include "OffsetAdjustedIFile.h" #include "NpdmProcess.h" -#include -#include const std::string kInstructionType[2] = { "32Bit", "64Bit" }; const std::string kProcAddrSpace[4] = { "Unknown", "64Bit", "32Bit", "32Bit no reserved" }; @@ -619,13 +618,20 @@ void NpdmProcess::displayKernelCap(const nx::KcBinary& kern) NpdmProcess::NpdmProcess() : mReader(nullptr), - mOffset(0), mKeyset(nullptr), mCliOutputType(OUTPUT_NORMAL), mVerify(false) { } +NpdmProcess::~NpdmProcess() +{ + if (mReader != nullptr) + { + delete mReader; + } +} + void NpdmProcess::process() { fnd::MemoryBlob scratch; @@ -665,14 +671,9 @@ void NpdmProcess::process() } } -void NpdmProcess::setInputFile(fnd::IFile* reader) +void NpdmProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) { - mReader = reader; -} - -void NpdmProcess::setInputFileOffset(size_t offset) -{ - mOffset = offset; + mReader = new OffsetAdjustedIFile(file, offset, size); } void NpdmProcess::setKeyset(const sKeyset* keyset) @@ -688,4 +689,9 @@ void NpdmProcess::setCliOutputMode(CliOutputType type) void NpdmProcess::setVerifyMode(bool verify) { mVerify = verify; +} + +const nx::NpdmBinary& NpdmProcess::getNpdmBinary() const +{ + return mNpdm; } \ No newline at end of file diff --git a/programs/nstool/source/NpdmProcess.h b/programs/nstool/source/NpdmProcess.h index ece7623..e14cbd0 100644 --- a/programs/nstool/source/NpdmProcess.h +++ b/programs/nstool/source/NpdmProcess.h @@ -10,20 +10,21 @@ class NpdmProcess { public: NpdmProcess(); + ~NpdmProcess(); void process(); - void setInputFile(fnd::IFile* reader); - void setInputFileOffset(size_t offset); + void setInputFile(fnd::IFile* file, size_t offset, size_t size); void setKeyset(const sKeyset* keyset); void setCliOutputMode(CliOutputType type); void setVerifyMode(bool verify); + const nx::NpdmBinary& getNpdmBinary() const; + private: const std::string kModuleName = "NpdmProcess"; fnd::IFile* mReader; - size_t mOffset; const sKeyset* mKeyset; CliOutputType mCliOutputType; bool mVerify; diff --git a/programs/nstool/source/OffsetAdjustedIFile.cpp b/programs/nstool/source/OffsetAdjustedIFile.cpp index bda80d6..e9f7231 100644 --- a/programs/nstool/source/OffsetAdjustedIFile.cpp +++ b/programs/nstool/source/OffsetAdjustedIFile.cpp @@ -16,7 +16,7 @@ size_t OffsetAdjustedIFile::size() void OffsetAdjustedIFile::seek(size_t offset) { - mCurrentOffset = offset; + mCurrentOffset = MIN(offset, mSize); mFile->seek(offset + mBaseOffset); } diff --git a/programs/nstool/source/PfsProcess.cpp b/programs/nstool/source/PfsProcess.cpp index 452f38a..b108c2c 100644 --- a/programs/nstool/source/PfsProcess.cpp +++ b/programs/nstool/source/PfsProcess.cpp @@ -1,6 +1,7 @@ -#include "PfsProcess.h" #include #include +#include "OffsetAdjustedIFile.h" +#include "PfsProcess.h" void PfsProcess::displayHeader() { @@ -55,7 +56,7 @@ void PfsProcess::validateHfs() for (size_t i = 0; i < file.getSize(); i++) { scratch.alloc(file[i].hash_protected_size); - mReader->read(scratch.getBytes(), mOffset + file[i].offset, file[i].hash_protected_size); + mReader->read(scratch.getBytes(), file[i].offset, file[i].hash_protected_size); crypto::sha::Sha256(scratch.getBytes(), scratch.getSize(), hash.bytes); if (hash != file[i].hash) { @@ -85,7 +86,7 @@ void PfsProcess::extractFs() fnd::io::appendToPath(file_path, mExtractPath); fnd::io::appendToPath(file_path, file[i].name); outFile.open(file_path, outFile.Create); - mReader->seek(mOffset + file[i].offset); + mReader->seek(file[i].offset); for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++) { mReader->read(scratch.getBytes(), kFileExportBlockSize); @@ -102,8 +103,6 @@ void PfsProcess::extractFs() PfsProcess::PfsProcess() : mReader(nullptr), - mOffset(0), - mKeyset(nullptr), mCliOutputType(OUTPUT_NORMAL), mVerify(false), mExtractPath(), @@ -112,7 +111,14 @@ PfsProcess::PfsProcess() : mListFs(false), mPfs() { +} +PfsProcess::~PfsProcess() +{ + if (mReader != nullptr) + { + delete mReader; + } } void PfsProcess::process() @@ -126,7 +132,7 @@ void PfsProcess::process() // open minimum header to get full header size scratch.alloc(sizeof(nx::sPfsHeader)); - mReader->read(scratch.getBytes(), mOffset, scratch.getSize()); + mReader->read(scratch.getBytes(), 0, scratch.getSize()); if (validateHeaderMagic(((nx::sPfsHeader*)scratch.getBytes())) == false) { throw fnd::Exception(kModuleName, "Corrupt Header"); @@ -135,7 +141,7 @@ void PfsProcess::process() // open minimum header to get full header size scratch.alloc(pfsHeaderSize); - mReader->read(scratch.getBytes(), mOffset, scratch.getSize()); + mReader->read(scratch.getBytes(), 0, scratch.getSize()); mPfs.importBinary(scratch.getBytes(), scratch.getSize()); if (mCliOutputType >= OUTPUT_NORMAL) @@ -148,19 +154,9 @@ void PfsProcess::process() extractFs(); } -void PfsProcess::setInputFile(fnd::IFile* reader) +void PfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) { - mReader = reader; -} - -void PfsProcess::setInputFileOffset(size_t offset) -{ - mOffset = offset; -} - -void PfsProcess::setKeyset(const sKeyset* keyset) -{ - mKeyset = keyset; + mReader = new OffsetAdjustedIFile(file, offset, size); } void PfsProcess::setCliOutputMode(CliOutputType type) diff --git a/programs/nstool/source/PfsProcess.h b/programs/nstool/source/PfsProcess.h index 7137775..e099ef4 100644 --- a/programs/nstool/source/PfsProcess.h +++ b/programs/nstool/source/PfsProcess.h @@ -10,13 +10,12 @@ class PfsProcess { public: PfsProcess(); + ~PfsProcess(); void process(); // generic - void setInputFile(fnd::IFile* reader); - void setInputFileOffset(size_t offset); - void setKeyset(const sKeyset* keyset); + void setInputFile(fnd::IFile* file, size_t offset, size_t size); void setCliOutputMode(CliOutputType type); void setVerifyMode(bool verify); @@ -32,8 +31,6 @@ private: static const size_t kFileExportBlockSize = 0x1000000; fnd::IFile* mReader; - size_t mOffset; - const sKeyset* mKeyset; CliOutputType mCliOutputType; bool mVerify; diff --git a/programs/nstool/source/RomfsProcess.cpp b/programs/nstool/source/RomfsProcess.cpp index 95aa7c5..1a0f8a3 100644 --- a/programs/nstool/source/RomfsProcess.cpp +++ b/programs/nstool/source/RomfsProcess.cpp @@ -1,7 +1,8 @@ -#include "RomfsProcess.h" #include #include #include +#include "OffsetAdjustedIFile.h" +#include "RomfsProcess.h" void RomfsProcess::printTab(size_t tab) const { @@ -87,7 +88,7 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir) outFile.open(file_path, outFile.Create); - mReader->seek(mOffset + dir.file_list[i].offset); + mReader->seek(dir.file_list[i].offset); for (size_t j = 0; j < (dir.file_list[i].size / kFileExportBlockSize); j++) { mReader->read(scratch.getBytes(), kFileExportBlockSize); @@ -186,7 +187,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir) void RomfsProcess::resolveRomfs() { // read header - mReader->read((byte_t*)&mHdr, mOffset, sizeof(nx::sRomfsHeader)); + mReader->read((byte_t*)&mHdr, 0, sizeof(nx::sRomfsHeader)); // logic check on the header layout if (validateHeaderLayout(&mHdr) == false) @@ -196,13 +197,13 @@ void RomfsProcess::resolveRomfs() // read directory nodes mDirNodes.alloc(mHdr.sections[nx::romfs::DIR_NODE_TABLE].size.get()); - mReader->read(mDirNodes.getBytes(), mOffset + mHdr.sections[nx::romfs::DIR_NODE_TABLE].offset.get(), mDirNodes.getSize()); + mReader->read(mDirNodes.getBytes(), mHdr.sections[nx::romfs::DIR_NODE_TABLE].offset.get(), mDirNodes.getSize()); //printf("[RAW DIR NODES]\n"); //fnd::SimpleTextOutput::hxdStyleDump(mDirNodes.getBytes(), mDirNodes.getSize()); // read file nodes mFileNodes.alloc(mHdr.sections[nx::romfs::FILE_NODE_TABLE].size.get()); - mReader->read(mFileNodes.getBytes(), mOffset + mHdr.sections[nx::romfs::FILE_NODE_TABLE].offset.get(), mFileNodes.getSize()); + mReader->read(mFileNodes.getBytes(), mHdr.sections[nx::romfs::FILE_NODE_TABLE].offset.get(), mFileNodes.getSize()); //printf("[RAW FILE NODES]\n"); //fnd::SimpleTextOutput::hxdStyleDump(mFileNodes.getBytes(), mFileNodes.getSize()); @@ -223,8 +224,6 @@ void RomfsProcess::resolveRomfs() RomfsProcess::RomfsProcess() : mReader(nullptr), - mOffset(0), - mKeyset(nullptr), mCliOutputType(OUTPUT_NORMAL), mVerify(false), mExtractPath(), @@ -239,6 +238,14 @@ RomfsProcess::RomfsProcess() : mRootDir.file_list.clear(); } +RomfsProcess::~RomfsProcess() +{ + if (mReader != nullptr) + { + delete mReader; + } +} + void RomfsProcess::process() { if (mReader == nullptr) @@ -256,19 +263,9 @@ void RomfsProcess::process() extractFs(); } -void RomfsProcess::setInputFile(fnd::IFile* reader) +void RomfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) { - mReader = reader; -} - -void RomfsProcess::setInputFileOffset(size_t offset) -{ - mOffset = offset; -} - -void RomfsProcess::setKeyset(const sKeyset* keyset) -{ - mKeyset = keyset; + mReader = new OffsetAdjustedIFile(file, offset, size); } void RomfsProcess::setCliOutputMode(CliOutputType type) diff --git a/programs/nstool/source/RomfsProcess.h b/programs/nstool/source/RomfsProcess.h index ee3ecc1..89d9702 100644 --- a/programs/nstool/source/RomfsProcess.h +++ b/programs/nstool/source/RomfsProcess.h @@ -89,13 +89,12 @@ public: }; RomfsProcess(); + ~RomfsProcess(); void process(); // generic - void setInputFile(fnd::IFile* reader); - void setInputFileOffset(size_t offset); - void setKeyset(const sKeyset* keyset); + void setInputFile(fnd::IFile* file, size_t offset, size_t size); void setCliOutputMode(CliOutputType type); void setVerifyMode(bool verify); @@ -110,8 +109,6 @@ private: static const size_t kFileExportBlockSize = 0x1000000; fnd::IFile* mReader; - size_t mOffset; - const sKeyset* mKeyset; CliOutputType mCliOutputType; bool mVerify; diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index b71f551..86d57ee 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -38,7 +38,7 @@ void UserSettings::showHelp() printf("\n General Options:\n"); printf(" -d, --dev Use devkit keyset\n"); printf(" -k, --keyset Specify keyset file\n"); - printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cmnt]\n"); + printf(" -t, --type Specify input file type [xci, pfs, romfs, nca, npdm, cnmt]\n"); printf(" -y, --verify Verify file\n"); printf(" -v, --verbose Verbose output\n"); printf(" -q, --quiet Minimal output\n"); @@ -576,8 +576,8 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str) type = FILE_NCA; else if (str == "npdm") type = FILE_NPDM; - else if (str == "cmnt") - type = FILE_CMNT; + else if (str == "cnmt") + type = FILE_CNMT; else type = FILE_INVALID; diff --git a/programs/nstool/source/XciProcess.cpp b/programs/nstool/source/XciProcess.cpp index 54ffdbf..c9417a2 100644 --- a/programs/nstool/source/XciProcess.cpp +++ b/programs/nstool/source/XciProcess.cpp @@ -1,6 +1,7 @@ -#include "XciProcess.h" #include #include +#include "OffsetAdjustedIFile.h" +#include "XciProcess.h" inline const char* getBoolStr(bool isTrue) { @@ -136,13 +137,12 @@ void XciProcess::processRootPfs() { if (mVerify) { - if (validateRegionOfFile(mOffset + mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().bytes) == false) + if (validateRegionOfFile(mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize(), mHdr.getPartitionFsHash().bytes) == false) { printf("[WARNING] XCI Root HFS0: FAIL (bad hash)\n"); } } - mRootPfs.setInputFile(mReader); - mRootPfs.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress()); + mRootPfs.setInputFile(mReader, mHdr.getPartitionFsAddress(), mHdr.getPartitionFsSize()); mRootPfs.setListFs(mListFs); mRootPfs.setVerifyMode(mVerify); mRootPfs.setCliOutputMode(mCliOutputType); @@ -156,8 +156,7 @@ void XciProcess::processPartitionPfs() for (size_t i = 0; i < rootPartitions.getSize(); i++) { PfsProcess tmp; - tmp.setInputFile(mReader); - tmp.setInputFileOffset(mOffset + mHdr.getPartitionFsAddress() + rootPartitions[i].offset); + tmp.setInputFile(mReader, mHdr.getPartitionFsAddress() + rootPartitions[i].offset, rootPartitions[i].size); tmp.setListFs(mListFs); tmp.setVerifyMode(mVerify); tmp.setCliOutputMode(mCliOutputType); @@ -174,7 +173,6 @@ void XciProcess::processPartitionPfs() XciProcess::XciProcess() : mReader(nullptr), - mOffset(0), mKeyset(nullptr), mCliOutputType(OUTPUT_NORMAL), mVerify(false), @@ -189,6 +187,14 @@ XciProcess::XciProcess() : mSecurePath.doExtract = false; } +XciProcess::~XciProcess() +{ + if (mReader != nullptr) + { + delete mReader; + } +} + void XciProcess::process() { fnd::MemoryBlob scratch; @@ -199,7 +205,7 @@ void XciProcess::process() } // read header page - mReader->read((byte_t*)&mHdrPage, mOffset, sizeof(nx::sXciHeaderPage)); + mReader->read((byte_t*)&mHdrPage, 0, sizeof(nx::sXciHeaderPage)); // allocate memory for and decrypt sXciHeader scratch.alloc(sizeof(nx::sXciHeader)); @@ -227,14 +233,9 @@ void XciProcess::process() processPartitionPfs(); } -void XciProcess::setInputFile(fnd::IFile* reader) +void XciProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) { - mReader = reader; -} - -void XciProcess::setInputFileOffset(size_t offset) -{ - mOffset = offset; + mReader = new OffsetAdjustedIFile(file, offset, size); } void XciProcess::setKeyset(const sKeyset* keyset) diff --git a/programs/nstool/source/XciProcess.h b/programs/nstool/source/XciProcess.h index d0b7061..3f6acd3 100644 --- a/programs/nstool/source/XciProcess.h +++ b/programs/nstool/source/XciProcess.h @@ -13,12 +13,12 @@ class XciProcess { public: XciProcess(); + ~XciProcess(); void process(); // generic - void setInputFile(fnd::IFile* reader); - void setInputFileOffset(size_t offset); + void setInputFile(fnd::IFile* file, size_t offset, size_t size); void setKeyset(const sKeyset* keyset); void setCliOutputMode(CliOutputType type); void setVerifyMode(bool verify); @@ -35,7 +35,6 @@ private: static const size_t kFileExportBlockSize = 0x1000000; fnd::IFile* mReader; - size_t mOffset; const sKeyset* mKeyset; CliOutputType mCliOutputType; bool mVerify; diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp index 8907c39..89afa70 100644 --- a/programs/nstool/source/main.cpp +++ b/programs/nstool/source/main.cpp @@ -6,6 +6,7 @@ #include "RomfsProcess.h" #include "NcaProcess.h" #include "NpdmProcess.h" +#include "CnmtProcess.h" int main(int argc, char** argv) @@ -21,7 +22,7 @@ int main(int argc, char** argv) { XciProcess xci; - xci.setInputFile(&inputFile); + xci.setInputFile(&inputFile, 0, inputFile.size()); xci.setKeyset(&user_set.getKeyset()); xci.setCliOutputMode(user_set.getCliOutputType()); @@ -41,8 +42,7 @@ int main(int argc, char** argv) { PfsProcess pfs; - pfs.setInputFile(&inputFile); - pfs.setKeyset(&user_set.getKeyset()); + pfs.setInputFile(&inputFile, 0, inputFile.size()); pfs.setCliOutputMode(user_set.getCliOutputType()); pfs.setVerifyMode(user_set.isVerifyFile()); @@ -56,8 +56,7 @@ int main(int argc, char** argv) { RomfsProcess romfs; - romfs.setInputFile(&inputFile); - romfs.setKeyset(&user_set.getKeyset()); + romfs.setInputFile(&inputFile, 0, inputFile.size()); romfs.setCliOutputMode(user_set.getCliOutputType()); romfs.setVerifyMode(user_set.isVerifyFile()); @@ -71,7 +70,7 @@ int main(int argc, char** argv) { NcaProcess nca; - nca.setInputFile(&inputFile); + nca.setInputFile(&inputFile, 0, inputFile.size()); nca.setKeyset(&user_set.getKeyset()); nca.setCliOutputMode(user_set.getCliOutputType()); nca.setVerifyMode(user_set.isVerifyFile()); @@ -93,13 +92,23 @@ int main(int argc, char** argv) { NpdmProcess npdm; - npdm.setInputFile(&inputFile); + npdm.setInputFile(&inputFile, 0, inputFile.size()); npdm.setKeyset(&user_set.getKeyset()); npdm.setCliOutputMode(user_set.getCliOutputType()); npdm.setVerifyMode(user_set.isVerifyFile()); npdm.process(); } + else if (user_set.getFileType() == FILE_CNMT) + { + CnmtProcess cnmt; + + cnmt.setInputFile(&inputFile, 0, inputFile.size()); + cnmt.setCliOutputMode(user_set.getCliOutputType()); + cnmt.setVerifyMode(user_set.isVerifyFile()); + + cnmt.process(); + } } catch (const fnd::Exception& e) { printf("\n\n%s\n", e.what()); diff --git a/programs/nstool/source/nstool.h b/programs/nstool/source/nstool.h index 99827fb..54686d2 100644 --- a/programs/nstool/source/nstool.h +++ b/programs/nstool/source/nstool.h @@ -17,7 +17,7 @@ enum FileType FILE_ROMFS, FILE_NCA, FILE_NPDM, - FILE_CMNT, + FILE_CNMT, FILE_INVALID = -1, }; From bd25c8c364e14115ba4508280f8b98bc7addf4c1 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sun, 13 May 2018 00:04:07 +0800 Subject: [PATCH 17/59] [nstool] Corrected formatting of title version strings. --- programs/nstool/source/CnmtProcess.cpp | 25 +++++++++++-------------- programs/nstool/source/NcaProcess.cpp | 5 +++-- programs/nstool/source/XciProcess.cpp | 4 +++- 3 files changed, 17 insertions(+), 17 deletions(-) diff --git a/programs/nstool/source/CnmtProcess.cpp b/programs/nstool/source/CnmtProcess.cpp index 5aade5c..8d05018 100644 --- a/programs/nstool/source/CnmtProcess.cpp +++ b/programs/nstool/source/CnmtProcess.cpp @@ -16,7 +16,7 @@ const std::string kContentTypeStr[7] = const std::string kContentMetaTypeStr[2][0x80] = { { - "" + "", "SystemProgram", "SystemData", "SystemUpdate", @@ -56,33 +56,31 @@ inline const char* getBoolStr(bool isTrue) void CnmtProcess::displayCmnt() { -#define _SPLIT_VER(ver) ( (ver>>24) & 0xff), ( (ver>>16) & 0xff), ( (ver>>8) & 0xff), (ver & 0xff) +#define _SPLIT_VER(ver) ( (ver>>26) & 0x3f), ( (ver>>20) & 0x3f), ( (ver>>16) & 0xf), (ver & 0xffff) printf("[ContentMeta]\n"); - printf(" TitleId: 0x%" PRIx64 "\n", mCnmt.getTitleId()); - uint32_t ver = mCnmt.getTitleVersion(); - printf(" Version: v%d.%d.%d-%d (v%" PRId32 ")\n", _SPLIT_VER(ver), ver); - printf(" Type: %s\n", getContentMetaTypeStr(mCnmt.getType()).c_str()); + printf(" TitleId: 0x%016" PRIx64 "\n", mCnmt.getTitleId()); + printf(" Version: v%" PRId32 " (%d.%d.%d.%d)\n", mCnmt.getTitleVersion(), _SPLIT_VER(mCnmt.getTitleVersion())); + printf(" Type: %s (%d)\n", getContentMetaTypeStr(mCnmt.getType()).c_str(), mCnmt.getType()); printf(" Attributes: %x\n", mCnmt.getAttributes()); printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER))); printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS))); - ver = mCnmt.getRequiredDownloadSystemVersion(); - printf(" RequiredDownloadSystemVersion: v%d.%d.%d-%d (v%" PRId32 ")\n", _SPLIT_VER(ver), ver); + printf(" RequiredDownloadSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", mCnmt.getRequiredDownloadSystemVersion(), _SPLIT_VER(mCnmt.getRequiredDownloadSystemVersion())); switch(mCnmt.getType()) { case (nx::cnmt::METATYPE_APPLICATION): printf(" ApplicationExtendedHeader:\n"); - printf(" RequiredSystemVersion: %" PRId32 "\n", mCnmt.getApplicationMetaExtendedHeader().required_system_version); + printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", mCnmt.getApplicationMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getApplicationMetaExtendedHeader().required_system_version)); printf(" PatchId: 0x%016" PRIx64 "\n", mCnmt.getApplicationMetaExtendedHeader().patch_id); break; case (nx::cnmt::METATYPE_PATCH): printf(" PatchMetaExtendedHeader:\n"); - printf(" RequiredSystemVersion: %" PRId32 "\n", mCnmt.getPatchMetaExtendedHeader().required_system_version); + printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d))\n", mCnmt.getPatchMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getPatchMetaExtendedHeader().required_system_version)); printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getPatchMetaExtendedHeader().application_id); break; case (nx::cnmt::METATYPE_ADD_ON_CONTENT): printf(" AddOnContentMetaExtendedHeader:\n"); - printf(" RequiredSystemVersion: %" PRId32 "\n", mCnmt.getAddOnContentMetaExtendedHeader().required_system_version); + printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", mCnmt.getAddOnContentMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getAddOnContentMetaExtendedHeader().required_system_version)); printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getAddOnContentMetaExtendedHeader().application_id); break; case (nx::cnmt::METATYPE_DELTA): @@ -114,9 +112,8 @@ void CnmtProcess::displayCmnt() { const nx::ContentMetaBinary::ContentMetaInfo& info = mCnmt.getContentMetaInfo()[i]; printf(" %d\n", i); - printf(" Id: 0x%" PRIx64 "\n", info.id); - ver = info.version; - printf(" Version: v%d.%d.%d-%d (v%" PRId32 ")\n", _SPLIT_VER(ver), ver); + printf(" Id: 0x%016" PRIx64 "\n", info.id); + printf(" Version: v%" PRId32 " (%d.%d.%d.%d)\n", info.version, _SPLIT_VER(info.version)); printf(" Type: %s\n", getContentMetaTypeStr(info.type).c_str()); printf(" Attributes: %x\n", mCnmt.getAttributes()); printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER))); diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index eb1a8a3..559d2d0 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -308,8 +308,9 @@ void NcaProcess::displayHeader() printf(" Size: 0x%" PRIx64 "\n", mHdr.getContentSize()); printf(" ProgID: 0x%016" PRIx64 "\n", mHdr.getProgramId()); printf(" Content Index: %" PRIu32 "\n", mHdr.getContentIndex()); - uint32_t ver = mHdr.getSdkAddonVersion(); - printf(" SdkAddon Ver.: v%d.%d.%d (v%" PRIu32 ")\n", (ver>>24 & 0xff),(ver>>16 & 0xff),(ver>>8 & 0xff), ver); +#define _SPLIT_VER(ver) ( (ver>>24) & 0xff), ( (ver>>16) & 0xff), ( (ver>>8) & 0xff) + printf(" SdkAddon Ver.: v%" PRIu32 " (%d.%d.%d)\n", mHdr.getSdkAddonVersion(), _SPLIT_VER(mHdr.getSdkAddonVersion())); +#undef _SPLIT_VER printf(" RightsId: "); fnd::SimpleTextOutput::hexDump(mHdr.getRightsId(), nx::nca::kRightsIdLen); printf(" Key Area Keys: (Encrypted)\n"); diff --git a/programs/nstool/source/XciProcess.cpp b/programs/nstool/source/XciProcess.cpp index c9417a2..e7a2788 100644 --- a/programs/nstool/source/XciProcess.cpp +++ b/programs/nstool/source/XciProcess.cpp @@ -104,7 +104,9 @@ void XciProcess::displayHeader() printf(" Wait1TimeWrite: 0x%x\n", mHdr.getWait1TimeWrite()); printf(" Wait2TimeWrite: 0x%x\n", mHdr.getWait2TimeWrite()); printf(" FwMode: 0x%x\n", mHdr.getFwMode()); - printf(" UppVersion: %d\n", mHdr.getUppVersion()); +#define _SPLIT_VER(ver) ( (ver>>26) & 0x3f), ( (ver>>20) & 0x3f), ( (ver>>16) & 0xf), (ver & 0xffff) + printf(" UppVersion: v%" PRId32 " (%d.%d.%d.%d)\n", mHdr.getUppVersion(), _SPLIT_VER(mHdr.getUppVersion())); +#undef _SPLIT_VER printf(" UppHash: "); fnd::SimpleTextOutput::hexDump(mHdr.getUppHash(), 8); printf(" UppId: %016" PRIx64 "\n", mHdr.getUppId()); From 7da6aee09db60189224a5c88d33b4123d7d0a464 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 15 May 2018 18:13:10 +0800 Subject: [PATCH 18/59] [nx|nstool] Update VS project files. --- lib/libnx/nx.vcxproj | 2 +- lib/libnx/nx.vcxproj.filters | 2 +- programs/nstool/nstool.vcxproj | 4 ++-- programs/nstool/nstool.vcxproj.filters | 4 ++-- 4 files changed, 6 insertions(+), 6 deletions(-) diff --git a/lib/libnx/nx.vcxproj b/lib/libnx/nx.vcxproj index 5d097a6..4d0658a 100644 --- a/lib/libnx/nx.vcxproj +++ b/lib/libnx/nx.vcxproj @@ -24,7 +24,7 @@ - + diff --git a/lib/libnx/nx.vcxproj.filters b/lib/libnx/nx.vcxproj.filters index 6710ddb..f465981 100644 --- a/lib/libnx/nx.vcxproj.filters +++ b/lib/libnx/nx.vcxproj.filters @@ -147,7 +147,7 @@ Header Files - + Header Files diff --git a/programs/nstool/nstool.vcxproj b/programs/nstool/nstool.vcxproj index 150401f..8acf80d 100644 --- a/programs/nstool/nstool.vcxproj +++ b/programs/nstool/nstool.vcxproj @@ -163,7 +163,7 @@ - + @@ -177,7 +177,7 @@ - + diff --git a/programs/nstool/nstool.vcxproj.filters b/programs/nstool/nstool.vcxproj.filters index 61f7449..f012b76 100644 --- a/programs/nstool/nstool.vcxproj.filters +++ b/programs/nstool/nstool.vcxproj.filters @@ -48,7 +48,7 @@ Header Files - + Header Files @@ -80,7 +80,7 @@ Source Files - + Source Files From af35e0410dc249037e93d872a1fca3498df001eb Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 15 May 2018 18:23:56 +0800 Subject: [PATCH 19/59] [nstool] Add type raw value to CnmtProcess --- programs/nstool/source/CnmtProcess.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/nstool/source/CnmtProcess.cpp b/programs/nstool/source/CnmtProcess.cpp index 8d05018..91d5bd6 100644 --- a/programs/nstool/source/CnmtProcess.cpp +++ b/programs/nstool/source/CnmtProcess.cpp @@ -97,7 +97,7 @@ void CnmtProcess::displayCmnt() { const nx::ContentMetaBinary::ContentInfo& info = mCnmt.getContentInfo()[i]; printf(" %d\n", i); - printf(" Type: %s\n", kContentTypeStr[info.type].c_str()); + printf(" Type: %s (%d)\n", kContentTypeStr[info.type].c_str(), info.type); printf(" Id: "); fnd::SimpleTextOutput::hexDump(info.nca_id, nx::cnmt::kContentIdLen); printf(" Size: 0x%" PRIx64 "\n", info.size); @@ -114,7 +114,7 @@ void CnmtProcess::displayCmnt() printf(" %d\n", i); printf(" Id: 0x%016" PRIx64 "\n", info.id); printf(" Version: v%" PRId32 " (%d.%d.%d.%d)\n", info.version, _SPLIT_VER(info.version)); - printf(" Type: %s\n", getContentMetaTypeStr(info.type).c_str()); + printf(" Type: %s (%d)\n", getContentMetaTypeStr(info.type).c_str(), info.type); printf(" Attributes: %x\n", mCnmt.getAttributes()); printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER))); printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS))); From 4d537956f0e07c9ee152d4d78f9404283a36ca00 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 15 May 2018 21:26:40 +0800 Subject: [PATCH 20/59] [nx] Add proper padding to nx::sKeyDataArea (xci) --- lib/libnx/include/nx/xci.h | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/libnx/include/nx/xci.h b/lib/libnx/include/nx/xci.h index 849a033..751e71d 100644 --- a/lib/libnx/include/nx/xci.h +++ b/lib/libnx/include/nx/xci.h @@ -115,9 +115,11 @@ namespace nx struct sKeyDataArea { sInitialData initial_data; // AES128-CCM encrypted {titlekey[16]} - byte_t encrypted_00[0x200*6]; // AES128-CTR encrypted {titlekey[16]} - byte_t encrypted_00_aesctr_data[0x100]; // RSA2048-OAEP-SHA256 encrypted AES-CTR data used for encrypted_00 {key[16],iv[16]} - byte_t reserved_01[0x100]; + byte_t reserved_00[xci::kPageSize - sizeof(sInitialData)]; + byte_t encrypted_00[xci::kPageSize * 6]; // AES128-CTR encrypted {titlekey[16]} + byte_t encrypted_00_aesctr_data[crypto::rsa::kRsa2048Size]; // RSA2048-OAEP-SHA256 encrypted AES-CTR data used for encrypted_00 {key[16],iv[16]} + byte_t reserved_01[xci::kPageSize - crypto::rsa::kRsa2048Size]; }; // sizeof() = 512*8 (8 pages) + #pragma pack(pop) } From 6069cbb1cb5570c77ea99cd197529d3f6934dacf Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 15 May 2018 21:36:04 +0800 Subject: [PATCH 21/59] [nstool] Remove magic values. --- programs/nstool/source/NcaProcess.cpp | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 559d2d0..f7bb715 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -242,12 +242,12 @@ void NcaProcess::validateNcaSignatures() // validate signature[1] if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) { - if (mPartitions[0].format_type == nx::nca::FORMAT_PFS0) + if (mPartitions[nx::nca::PARTITION_CODE].format_type == nx::nca::FORMAT_PFS0) { - if (mPartitions[0].reader != nullptr) + if (mPartitions[nx::nca::PARTITION_CODE].reader != nullptr) { PfsProcess exefs; - exefs.setInputFile(mPartitions[0].reader, mPartitions[0].offset + mPartitions[0].data_offset, mPartitions[0].data_size); + exefs.setInputFile(mPartitions[nx::nca::PARTITION_CODE].reader, mPartitions[nx::nca::PARTITION_CODE].offset + mPartitions[nx::nca::PARTITION_CODE].data_offset, mPartitions[nx::nca::PARTITION_CODE].data_size); exefs.setCliOutputMode(OUTPUT_MINIMAL); exefs.process(); @@ -257,7 +257,7 @@ void NcaProcess::validateNcaSignatures() const nx::PfsHeader::sFile& file = exefs.getPfsHeader().getFileList()[exefs.getPfsHeader().getFileList().getIndexOf(kNpdmExefsPath)]; NpdmProcess npdm; - npdm.setInputFile(mPartitions[0].reader, mPartitions[0].offset + mPartitions[0].data_offset + file.offset, file.size); + npdm.setInputFile(mPartitions[nx::nca::PARTITION_CODE].reader, mPartitions[nx::nca::PARTITION_CODE].offset + mPartitions[nx::nca::PARTITION_CODE].data_offset + file.offset, file.size); npdm.setCliOutputMode(OUTPUT_MINIMAL); npdm.process(); From 7c123ca97bb7b98de92b3d12c99c4cdec805fc83 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 15 May 2018 23:00:07 +0800 Subject: [PATCH 22/59] [nstool] Add descriptive names to NCA mount points. --- programs/nstool/source/NcaProcess.cpp | 35 +++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 559d2d0..8559172 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -62,6 +62,23 @@ std::string kKaekIndexStr[] "System" }; +std::string kContentTypeForMountStr[] +{ + "program", + "meta", + "control", + "manual", + "data", + "publicdata" +}; + +std::string kProgramPartitionNameStr[] +{ + "code", + "data", + "logo" +}; + void NcaProcess::generateNcaBodyEncryptionKeys() { // create zeros key @@ -431,6 +448,15 @@ void NcaProcess::processPartitions() pfs.setInputFile(partition.reader, partition.offset + partition.data_offset, partition.data_size); pfs.setCliOutputMode(mCliOutputType); pfs.setListFs(mListFs); + if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) + { + pfs.setMountPointName(kContentTypeForMountStr[mHdr.getContentType()] + ":/" + kProgramPartitionNameStr[i]); + } + else + { + pfs.setMountPointName(kContentTypeForMountStr[mHdr.getContentType()] + ":/"); + } + if (mPartitionPath[index].doExtract) pfs.setExtractPath(mPartitionPath[index].path); //printf("pfs.process(%lx)\n",partition.data_offset); @@ -443,6 +469,15 @@ void NcaProcess::processPartitions() romfs.setInputFile(partition.reader, partition.offset + partition.data_offset, partition.data_size); romfs.setCliOutputMode(mCliOutputType); romfs.setListFs(mListFs); + if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) + { + romfs.setMountPointName(kContentTypeForMountStr[mHdr.getContentType()] + ":/" + kProgramPartitionNameStr[i]); + } + else + { + romfs.setMountPointName(kContentTypeForMountStr[mHdr.getContentType()] + ":/"); + } + if (mPartitionPath[index].doExtract) romfs.setExtractPath(mPartitionPath[index].path); //printf("romfs.process(%lx)\n", partition.data_offset); From d3a6de737a788f9e80fc57e11149a4cbf04d3917 Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 18 May 2018 16:47:45 +0800 Subject: [PATCH 23/59] Include License in VS project files. --- LICENSE | 2 +- NXTools.sln | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/LICENSE b/LICENSE index 4454c6a..ea69356 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2017 Jack +Copyright (c) 2017-2018 Jack Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/NXTools.sln b/NXTools.sln index 93850f1..4ae6953 100644 --- a/NXTools.sln +++ b/NXTools.sln @@ -27,6 +27,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution ProjectSection(SolutionItems) = preProject .gitignore = .gitignore KEYS.md = KEYS.md + LICENSE = LICENSE makefile = makefile README.md = README.md EndProjectSection From c1fc536c668be2b95454fd01af1862a169f5a7c2 Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 18 May 2018 16:47:55 +0800 Subject: [PATCH 24/59] Update readme --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 7440a24..acd06e2 100644 --- a/README.md +++ b/README.md @@ -6,14 +6,14 @@ Tools & Libraries for NX (Nintendo Switch). # Tools -* __nstool__ - read *.npdm, read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp), read *.xci +* __nstool__ - read *.npdm, read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp), read/extract *.xci, read/extract *.nca # Libraries * __libfnd__ - Foundation library. * __libcrypto__ - Cryptographic functions (AES,SHA,RSA). Wrapper for [mbedTLS](https://github.com/ARMmbed/mbedtls) -* __libes__ - Handling of (NS relevant) eShop file type processing. (eTickets, etc) -* __libnx__ - Handling of NS file types +* __libes__ - Handling of (NX relevant) eShop file type processing. (eTickets, etc) +* __libnx__ - Handling of NX file types # Building From 1a5f045e0a4c384089b994a9f70d5086991aa7cb Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 18 May 2018 17:08:48 +0800 Subject: [PATCH 25/59] [nstool] Show pfs and romfs file offsets from the base offsets. --- programs/nstool/source/PfsProcess.cpp | 6 ++++-- programs/nstool/source/PfsProcess.h | 1 + programs/nstool/source/RomfsProcess.cpp | 4 +++- programs/nstool/source/RomfsProcess.h | 2 ++ 4 files changed, 10 insertions(+), 3 deletions(-) diff --git a/programs/nstool/source/PfsProcess.cpp b/programs/nstool/source/PfsProcess.cpp index b108c2c..ae2d3e4 100644 --- a/programs/nstool/source/PfsProcess.cpp +++ b/programs/nstool/source/PfsProcess.cpp @@ -20,9 +20,9 @@ void PfsProcess::displayFs() if (mCliOutputType >= OUTPUT_VERBOSE) { if (mPfs.getFsType() == mPfs.TYPE_PFS0) - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size); + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", mBaseOffset + mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size); else - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size, mPfs.getFileList()[i].hash_protected_size); + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", mBaseOffset + mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size, mPfs.getFileList()[i].hash_protected_size); } else { @@ -105,6 +105,7 @@ PfsProcess::PfsProcess() : mReader(nullptr), mCliOutputType(OUTPUT_NORMAL), mVerify(false), + mBaseOffset(0), mExtractPath(), mExtract(false), mMountName(), @@ -157,6 +158,7 @@ void PfsProcess::process() void PfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) { mReader = new OffsetAdjustedIFile(file, offset, size); + mBaseOffset = offset; } void PfsProcess::setCliOutputMode(CliOutputType type) diff --git a/programs/nstool/source/PfsProcess.h b/programs/nstool/source/PfsProcess.h index e099ef4..9439eae 100644 --- a/programs/nstool/source/PfsProcess.h +++ b/programs/nstool/source/PfsProcess.h @@ -34,6 +34,7 @@ private: CliOutputType mCliOutputType; bool mVerify; + size_t mBaseOffset; std::string mExtractPath; bool mExtract; diff --git a/programs/nstool/source/RomfsProcess.cpp b/programs/nstool/source/RomfsProcess.cpp index 1a0f8a3..c11e46c 100644 --- a/programs/nstool/source/RomfsProcess.cpp +++ b/programs/nstool/source/RomfsProcess.cpp @@ -18,7 +18,7 @@ void RomfsProcess::displayFile(const sFile& file, size_t tab) const printf("%s", file.name.c_str()); if (mCliOutputType >= OUTPUT_VERBOSE) { - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")", file.offset, file.size); + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")", mBaseOffset + file.offset, file.size); } putchar('\n'); } @@ -226,6 +226,7 @@ RomfsProcess::RomfsProcess() : mReader(nullptr), mCliOutputType(OUTPUT_NORMAL), mVerify(false), + mBaseOffset(0), mExtractPath(), mExtract(false), mMountName(), @@ -266,6 +267,7 @@ void RomfsProcess::process() void RomfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) { mReader = new OffsetAdjustedIFile(file, offset, size); + mBaseOffset = offset; } void RomfsProcess::setCliOutputMode(CliOutputType type) diff --git a/programs/nstool/source/RomfsProcess.h b/programs/nstool/source/RomfsProcess.h index 89d9702..5a839ae 100644 --- a/programs/nstool/source/RomfsProcess.h +++ b/programs/nstool/source/RomfsProcess.h @@ -112,6 +112,8 @@ private: CliOutputType mCliOutputType; bool mVerify; + size_t mBaseOffset; + std::string mExtractPath; bool mExtract; std::string mMountName; From f2021d851aa0555b33f948e2076289a2639a700f Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 18 May 2018 17:43:51 +0800 Subject: [PATCH 26/59] [nstool] Remove unused private method. --- programs/nstool/source/RomfsProcess.h | 1 - 1 file changed, 1 deletion(-) diff --git a/programs/nstool/source/RomfsProcess.h b/programs/nstool/source/RomfsProcess.h index 5a839ae..2719308 100644 --- a/programs/nstool/source/RomfsProcess.h +++ b/programs/nstool/source/RomfsProcess.h @@ -137,7 +137,6 @@ private: void displayHeader(); void displayFs(); - void extractFile(const std::string& path, const sFile& file); void extractDir(const std::string& path, const sDirectory& dir); void extractFs(); From aa1ed007eb19bb3781d961d1b9d1599e0a17ab13 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 19 May 2018 11:57:40 +0800 Subject: [PATCH 27/59] Start moving towards a more generatlised hash tree system. --- .../include/nx/HierarchicalIntergrityHeader.h | 49 +++++++++++++ .../include/nx/HierarchicalSha256Header.h | 49 +++++++++++++ lib/libnx/include/nx/hierarchicalintegrity.h | 36 ++++++++++ lib/libnx/include/nx/hierarchicalsha256.h | 6 +- lib/libnx/include/nx/ivfc.h | 36 ---------- lib/libnx/include/nx/nca.h | 4 +- lib/libnx/nx.vcxproj | 6 +- lib/libnx/nx.vcxproj.filters | 18 ++++- .../source/HierarchicalIntegrityHeader.cpp | 0 lib/libnx/source/HierarchicalSha256Header.cpp | 0 programs/nstool/source/NcaProcess.cpp | 71 ++++++++++++------- programs/nstool/source/NcaProcess.h | 2 +- 12 files changed, 206 insertions(+), 71 deletions(-) create mode 100644 lib/libnx/include/nx/HierarchicalIntergrityHeader.h create mode 100644 lib/libnx/include/nx/HierarchicalSha256Header.h create mode 100644 lib/libnx/include/nx/hierarchicalintegrity.h delete mode 100644 lib/libnx/include/nx/ivfc.h create mode 100644 lib/libnx/source/HierarchicalIntegrityHeader.cpp create mode 100644 lib/libnx/source/HierarchicalSha256Header.cpp diff --git a/lib/libnx/include/nx/HierarchicalIntergrityHeader.h b/lib/libnx/include/nx/HierarchicalIntergrityHeader.h new file mode 100644 index 0000000..9f2e4db --- /dev/null +++ b/lib/libnx/include/nx/HierarchicalIntergrityHeader.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include +#include + +namespace nx +{ + class HierarchicalIntergrityHeader : + public fnd::ISerialiseableBinary + { + public: + + HierarchicalIntergrityHeader(); + HierarchicalIntergrityHeader(const HierarchicalIntergrityHeader& other); + HierarchicalIntergrityHeader(const byte_t* bytes, size_t len); + + bool operator==(const HierarchicalIntergrityHeader& other) const; + bool operator!=(const HierarchicalIntergrityHeader& other) const; + void operator=(const HierarchicalIntergrityHeader& other); + + // to be used after export + const byte_t* getBytes() const; + size_t getSize() const; + + // export/import binary + void exportBinary(); + void importBinary(const byte_t* bytes, size_t len); + + // variables + void clear(); + + + private: + const std::string kModuleName = "HIERARCHICAL_INTERGRITY_HEADER"; + + // binary + fnd::MemoryBlob mBinaryBlob; + + // data + + + uint64_t blockNumToSize(uint32_t block_num) const; + uint32_t sizeToBlockNum(uint64_t real_size) const; + bool isEqual(const HierarchicalIntergrityHeader& other) const; + void copyFrom(const HierarchicalIntergrityHeader& other); + }; + +} \ No newline at end of file diff --git a/lib/libnx/include/nx/HierarchicalSha256Header.h b/lib/libnx/include/nx/HierarchicalSha256Header.h new file mode 100644 index 0000000..3101096 --- /dev/null +++ b/lib/libnx/include/nx/HierarchicalSha256Header.h @@ -0,0 +1,49 @@ +#pragma once +#include +#include +#include +#include + +namespace nx +{ + class HierarchicalSha256Header : + public fnd::ISerialiseableBinary + { + public: + + HierarchicalSha256Header(); + HierarchicalSha256Header(const HierarchicalSha256Header& other); + HierarchicalSha256Header(const byte_t* bytes, size_t len); + + bool operator==(const HierarchicalSha256Header& other) const; + bool operator!=(const HierarchicalSha256Header& other) const; + void operator=(const HierarchicalSha256Header& other); + + // to be used after export + const byte_t* getBytes() const; + size_t getSize() const; + + // export/import binary + void exportBinary(); + void importBinary(const byte_t* bytes, size_t len); + + // variables + void clear(); + + + private: + const std::string kModuleName = "HIERARCHICAL_SHA256_HEADER"; + + // binary + fnd::MemoryBlob mBinaryBlob; + + // data + + + uint64_t blockNumToSize(uint32_t block_num) const; + uint32_t sizeToBlockNum(uint64_t real_size) const; + bool isEqual(const HierarchicalSha256Header& other) const; + void copyFrom(const HierarchicalSha256Header& other); + }; + +} \ No newline at end of file diff --git a/lib/libnx/include/nx/hierarchicalintegrity.h b/lib/libnx/include/nx/hierarchicalintegrity.h new file mode 100644 index 0000000..50677dd --- /dev/null +++ b/lib/libnx/include/nx/hierarchicalintegrity.h @@ -0,0 +1,36 @@ +#pragma once +#include +#include +#include +#include + +namespace nx +{ + // Also known as HierarchicalIntegrity + namespace hierarchicalintegrity + { + const std::string kStructSig = "IVFC"; + static const size_t kMaxLayerNum = 7; + static const uint32_t kTypeId = 0x20000; + static const size_t kMaxMasterHashNum = 3; + } + +#pragma pack(push,1) + struct sHierarchicalIntegrityHeader + { + char signature[4]; + le_uint32_t type_id; + le_uint32_t master_hash_size; + le_uint32_t layer_num; + struct sLayer + { + le_uint64_t offset; + le_uint64_t size; + le_uint32_t block_size; + byte_t reserved[4]; + } layer[hierarchicalintegrity::kMaxLayerNum]; + byte_t reserved_00[0x8]; + crypto::sha::sSha256Hash master_hash[hierarchicalintegrity::kMaxMasterHashNum]; + }; +#pragma pack(pop) +} diff --git a/lib/libnx/include/nx/hierarchicalsha256.h b/lib/libnx/include/nx/hierarchicalsha256.h index 7039dcc..8db3b26 100644 --- a/lib/libnx/include/nx/hierarchicalsha256.h +++ b/lib/libnx/include/nx/hierarchicalsha256.h @@ -9,6 +9,8 @@ namespace nx namespace hierarchicalsha256 { static const size_t kDefaultLevelNum = 2; + + static const size_t kMaxLayoutNum = 2; } #pragma pack(push,1) @@ -16,12 +18,12 @@ namespace nx { crypto::sha::sSha256Hash master_hash; le_uint32_t hash_block_size; - le_uint32_t hash_level_num; + le_uint32_t layer_num; struct sLayout { le_uint64_t offset; le_uint64_t size; - } hash_data, hash_target; + } layer[hierarchicalsha256::kMaxLayoutNum]; }; #pragma pack(pop) } diff --git a/lib/libnx/include/nx/ivfc.h b/lib/libnx/include/nx/ivfc.h deleted file mode 100644 index 0d74426..0000000 --- a/lib/libnx/include/nx/ivfc.h +++ /dev/null @@ -1,36 +0,0 @@ -#pragma once -#include -#include -#include -#include -#include - -namespace nx -{ - // Also known as HierarchicalIntegrity - namespace ivfc - { - const std::string kIvfcSig = "IVFC"; - static const size_t kMaxIvfcLevel = 7; - static const uint32_t kIvfcId = 0x20000; - } - -#pragma pack(push,1) - struct sIvfcHeader - { - char signature[4]; - le_uint32_t id; - le_uint32_t master_hash_size; - le_uint32_t level_num; - struct sIvfcLevelHeader - { - le_uint64_t logical_offset; - le_uint64_t hash_data_size; - le_uint32_t block_size; - byte_t reserved[4]; - } level_header[ivfc::kMaxIvfcLevel]; - byte_t reserved_00[0x8]; - crypto::sha::sSha256Hash master_hash; - }; -#pragma pack(pop) -} diff --git a/lib/libnx/include/nx/nca.h b/lib/libnx/include/nx/nca.h index e301ae7..c05ed43 100644 --- a/lib/libnx/include/nx/nca.h +++ b/lib/libnx/include/nx/nca.h @@ -5,7 +5,7 @@ #include #include #include -#include +#include #include namespace nx @@ -123,7 +123,7 @@ namespace nx union { byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen]; nx::sHierarchicalSha256Header hierarchicalsha256_header; - nx::sIvfcHeader ivfc_header; + nx::sHierarchicalIntegrityHeader hierarchicalintergrity_header; }; crypto::aes::sAesIvCtr base_ctr; byte_t reserved_1[0xB8]; diff --git a/lib/libnx/nx.vcxproj b/lib/libnx/nx.vcxproj index 4d0658a..5fde829 100644 --- a/lib/libnx/nx.vcxproj +++ b/lib/libnx/nx.vcxproj @@ -30,11 +30,13 @@ + + - + @@ -75,6 +77,8 @@ + + diff --git a/lib/libnx/nx.vcxproj.filters b/lib/libnx/nx.vcxproj.filters index f465981..b1e1f7d 100644 --- a/lib/libnx/nx.vcxproj.filters +++ b/lib/libnx/nx.vcxproj.filters @@ -123,9 +123,6 @@ Header Files - - Header Files - Header Files @@ -150,6 +147,15 @@ Header Files + + Header Files + + + Header Files + + + Header Files + @@ -254,6 +260,12 @@ Source Files + + Source Files + + + Source Files + diff --git a/lib/libnx/source/HierarchicalIntegrityHeader.cpp b/lib/libnx/source/HierarchicalIntegrityHeader.cpp new file mode 100644 index 0000000..e69de29 diff --git a/lib/libnx/source/HierarchicalSha256Header.cpp b/lib/libnx/source/HierarchicalSha256Header.cpp new file mode 100644 index 0000000..e69de29 diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index ae5f678..3ad8048 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -219,20 +219,33 @@ void NcaProcess::generatePartitionConfiguration() // determine the data offset & size if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) { - mPartitions[partition.index].data_offset = mPartitions[partition.index].hierarchicalsha256_header.hash_target.offset.get(); - mPartitions[partition.index].data_size = mPartitions[partition.index].hierarchicalsha256_header.hash_target.size.get(); + mPartitions[partition.index].data_offset = mPartitions[partition.index].hierarchicalsha256_header.layer[1].offset.get(); + mPartitions[partition.index].data_size = mPartitions[partition.index].hierarchicalsha256_header.layer[1].size.get(); } else if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) { - for (size_t j = 0; j < nx::ivfc::kMaxIvfcLevel; j++) + mPartitions[partition.index].data_offset = mPartitions[partition.index].hierarchicalintergrity_header.layer[5].offset.get(); + mPartitions[partition.index].data_size = mPartitions[partition.index].hierarchicalintergrity_header.layer[5].size.get(); + /* + if (mPartitions[partition.index].hierarchicalintergrity_header.layer_num.get() > nx::hierarchicalintegrity::kMaxLayerNum) { - if (mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get() != 0) - { - mPartitions[partition.index].data_offset = mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].logical_offset.get(); - mPartitions[partition.index].data_size = mPartitions[partition.index].ivfc_header.level_header[nx::ivfc::kMaxIvfcLevel-1-j].hash_data_size.get(); - break; - } + error.clear(); + error << "NCA FS Header [" << partition.index << "] HierarchicalIntergrity header has an unsupported layer num (" << mPartitions[partition.index].hierarchicalintergrity_header.layer_num.get() << ")\n"; + throw fnd::Exception(kModuleName, error.str()); } + const nx::sHierarchicalIntegrityHeader& hdr = mPartitions[partition.index].hierarchicalintergrity_header; + + for (size_t j = 0; j < nx::hierarchicalintegrity::kMaxLayerNum; j++) + { + size_t index = nx::hierarchicalintegrity::kMaxLayerNum - 1 - j; + if (hdr.layer[index].offset.get() != 0) + { + mPartitions[partition.index].data_offset = hdr.layer[index].offset.get(); + mPartitions[partition.index].data_size = hdr.layer[index].size.get(); + break; + } + } + */ } else if (mPartitions[partition.index].hash_type == nx::nca::HASH_NONE) { @@ -389,19 +402,24 @@ void NcaProcess::displayHeader() } if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) { + nx::sHierarchicalIntegrityHeader& hash_hdr = fs_header.hierarchicalintergrity_header; 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(" TypeId: 0x%x\n", hash_hdr.type_id.get()); + printf(" MasterHashSize: 0x%x\n", hash_hdr.master_hash_size.get()); + printf(" LayerNum: %d\n", hash_hdr.layer_num.get()); + for (size_t i = 0; i < hash_hdr.layer_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(" Layer %d:\n", i); + printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.layer[i].offset.get()); + printf(" Size: 0x%" PRIx64 "\n", hash_hdr.layer[i].size.get()); + printf(" BlockSize: 0x%" PRIx32 "\n", hash_hdr.layer[i].block_size.get()); } - printf(" Master Hash: "); - fnd::SimpleTextOutput::hexDump(fs_header.ivfc_header.master_hash.bytes, 0x20); + for (size_t j = 0; j < hash_hdr.master_hash_size.get() / sizeof(crypto::sha::sSha256Hash); j++) + { + printf(" Master Hash %d: ", j); + fnd::SimpleTextOutput::hexDump(hash_hdr.master_hash[j].bytes, sizeof(crypto::sha::sSha256Hash)); + } + } @@ -410,14 +428,15 @@ void NcaProcess::displayHeader() nx::sHierarchicalSha256Header& hash_hdr = fs_header.hierarchicalsha256_header; printf(" HierarchicalSha256 Header:\n"); printf(" Master Hash: "); - fnd::SimpleTextOutput::hexDump(hash_hdr.master_hash.bytes, 0x20); + fnd::SimpleTextOutput::hexDump(hash_hdr.master_hash.bytes, sizeof(crypto::sha::sSha256Hash)); 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()); - + printf(" LayerNum: %d\n", hash_hdr.layer_num.get()); + for (size_t i = 0; i < hash_hdr.layer_num.get(); i++) + { + printf(" Layer %d:\n", i); + printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.layer[i].offset.get()); + printf(" Size: 0x%" PRIx64 "\n", hash_hdr.layer[i].size.get()); + } } else { diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index 1505d77..eb05b47 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -70,7 +70,7 @@ private: union { byte_t hash_superblock[nx::nca::kFsHeaderHashSuperblockLen]; nx::sHierarchicalSha256Header hierarchicalsha256_header; - nx::sIvfcHeader ivfc_header; + nx::sHierarchicalIntegrityHeader hierarchicalintergrity_header; }; } mPartitions[nx::nca::kPartitionNum]; From e64f309c31e8ccf2d4621740d2d5207c88ccb24a Mon Sep 17 00:00:00 2001 From: jakcron Date: Sun, 20 May 2018 09:18:38 +0800 Subject: [PATCH 28/59] [nx] Rename because of typo --- .../include/nx/HierarchicalIntegrityHeader.h | 75 +++++++++++++++++++ .../include/nx/HierarchicalIntergrityHeader.h | 49 ------------ 2 files changed, 75 insertions(+), 49 deletions(-) create mode 100644 lib/libnx/include/nx/HierarchicalIntegrityHeader.h delete mode 100644 lib/libnx/include/nx/HierarchicalIntergrityHeader.h diff --git a/lib/libnx/include/nx/HierarchicalIntegrityHeader.h b/lib/libnx/include/nx/HierarchicalIntegrityHeader.h new file mode 100644 index 0000000..f40f117 --- /dev/null +++ b/lib/libnx/include/nx/HierarchicalIntegrityHeader.h @@ -0,0 +1,75 @@ +#pragma once +#include +#include +#include +#include + +namespace nx +{ + class HierarchicalIntegrityHeader : + public fnd::ISerialiseableBinary + { + public: + struct sLayer + { + size_t offset; + size_t size; + size_t hash_block_size; + + void operator=(const sLayer& other) + { + offset = other.offset; + size = other.size; + hash_block_size = other.hash_block_size; + } + + bool operator==(const sLayer& other) + { + return (offset == other.offset && size == other.size && hash_block_size == other.hash_block_size); + } + + bool operator!=(const sLayer& other) + { + return !(*this == other); + } + }; + + HierarchicalIntegrityHeader(); + HierarchicalIntegrityHeader(const HierarchicalIntegrityHeader& other); + HierarchicalIntegrityHeader(const byte_t* bytes, size_t len); + + bool operator==(const HierarchicalIntegrityHeader& other) const; + bool operator!=(const HierarchicalIntegrityHeader& other) const; + void operator=(const HierarchicalIntegrityHeader& other); + + // to be used after export + const byte_t* getBytes() const; + size_t getSize() const; + + // export/import binary + void exportBinary(); + void importBinary(const byte_t* bytes, size_t len); + + // variables + void clear(); + + const fnd::List& getLayerInfo() const; + void setLayerInfo(const fnd::List& layer_info); + + const fnd::List& getMasterHashList() const; + void setMasterHashList(const fnd::List& master_hash_list); + private: + const std::string kModuleName = "HIERARCHICAL_INTEGRITY_HEADER"; + + // binary + fnd::MemoryBlob mBinaryBlob; + + // data + fnd::List mLayerInfo; + fnd::List mMasterHashList; + + bool isEqual(const HierarchicalIntegrityHeader& other) const; + void copyFrom(const HierarchicalIntegrityHeader& other); + }; + +} \ No newline at end of file diff --git a/lib/libnx/include/nx/HierarchicalIntergrityHeader.h b/lib/libnx/include/nx/HierarchicalIntergrityHeader.h deleted file mode 100644 index 9f2e4db..0000000 --- a/lib/libnx/include/nx/HierarchicalIntergrityHeader.h +++ /dev/null @@ -1,49 +0,0 @@ -#pragma once -#include -#include -#include -#include - -namespace nx -{ - class HierarchicalIntergrityHeader : - public fnd::ISerialiseableBinary - { - public: - - HierarchicalIntergrityHeader(); - HierarchicalIntergrityHeader(const HierarchicalIntergrityHeader& other); - HierarchicalIntergrityHeader(const byte_t* bytes, size_t len); - - bool operator==(const HierarchicalIntergrityHeader& other) const; - bool operator!=(const HierarchicalIntergrityHeader& other) const; - void operator=(const HierarchicalIntergrityHeader& other); - - // to be used after export - const byte_t* getBytes() const; - size_t getSize() const; - - // export/import binary - void exportBinary(); - void importBinary(const byte_t* bytes, size_t len); - - // variables - void clear(); - - - private: - const std::string kModuleName = "HIERARCHICAL_INTERGRITY_HEADER"; - - // binary - fnd::MemoryBlob mBinaryBlob; - - // data - - - uint64_t blockNumToSize(uint32_t block_num) const; - uint32_t sizeToBlockNum(uint64_t real_size) const; - bool isEqual(const HierarchicalIntergrityHeader& other) const; - void copyFrom(const HierarchicalIntergrityHeader& other); - }; - -} \ No newline at end of file From d93116863e39eb8a107ee305e64f5e44f434ba0b Mon Sep 17 00:00:00 2001 From: jakcron Date: Sun, 20 May 2018 09:19:41 +0800 Subject: [PATCH 29/59] [nx] Add implement importBinary for HierarchicalSha256Header and stubbed HierarchicalIntegrityHeader. --- .../include/nx/HierarchicalSha256Header.h | 34 ++++- lib/libnx/include/nx/hierarchicalintegrity.h | 4 +- lib/libnx/include/nx/hierarchicalsha256.h | 2 +- lib/libnx/nx.vcxproj | 2 +- lib/libnx/nx.vcxproj.filters | 2 +- .../source/HierarchicalIntegrityHeader.cpp | 96 +++++++++++++ lib/libnx/source/HierarchicalSha256Header.cpp | 132 ++++++++++++++++++ 7 files changed, 264 insertions(+), 8 deletions(-) diff --git a/lib/libnx/include/nx/HierarchicalSha256Header.h b/lib/libnx/include/nx/HierarchicalSha256Header.h index 3101096..a165b5b 100644 --- a/lib/libnx/include/nx/HierarchicalSha256Header.h +++ b/lib/libnx/include/nx/HierarchicalSha256Header.h @@ -10,6 +10,27 @@ namespace nx public fnd::ISerialiseableBinary { public: + struct sLayer + { + size_t offset; + size_t size; + + void operator=(const sLayer& other) + { + offset = other.offset; + size = other.size; + } + + bool operator==(const sLayer& other) + { + return (offset == other.offset && size == other.size); + } + + bool operator!=(const sLayer& other) + { + return !(*this == other); + } + }; HierarchicalSha256Header(); HierarchicalSha256Header(const HierarchicalSha256Header& other); @@ -30,7 +51,14 @@ namespace nx // variables void clear(); + const crypto::sha::sSha256Hash& getMasterHash() const; + void setMasterHash(const crypto::sha::sSha256Hash& master_hash); + size_t getHashBlockSize() const; + void setHashBlockSize(size_t hash_block_size); + + const fnd::List& getLayerInfo() const; + void setLayerInfo(const fnd::List& layer_info); private: const std::string kModuleName = "HIERARCHICAL_SHA256_HEADER"; @@ -38,10 +66,10 @@ namespace nx fnd::MemoryBlob mBinaryBlob; // data + crypto::sha::sSha256Hash mMasterHash; + size_t mHashBlockSize; + fnd::List mLayerInfo; - - uint64_t blockNumToSize(uint32_t block_num) const; - uint32_t sizeToBlockNum(uint64_t real_size) const; bool isEqual(const HierarchicalSha256Header& other) const; void copyFrom(const HierarchicalSha256Header& other); }; diff --git a/lib/libnx/include/nx/hierarchicalintegrity.h b/lib/libnx/include/nx/hierarchicalintegrity.h index 50677dd..02a9297 100644 --- a/lib/libnx/include/nx/hierarchicalintegrity.h +++ b/lib/libnx/include/nx/hierarchicalintegrity.h @@ -6,12 +6,12 @@ namespace nx { - // Also known as HierarchicalIntegrity + // Also known to the public as IVFC namespace hierarchicalintegrity { const std::string kStructSig = "IVFC"; - static const size_t kMaxLayerNum = 7; static const uint32_t kTypeId = 0x20000; + static const size_t kMaxLayerNum = 7; static const size_t kMaxMasterHashNum = 3; } diff --git a/lib/libnx/include/nx/hierarchicalsha256.h b/lib/libnx/include/nx/hierarchicalsha256.h index 8db3b26..a4da019 100644 --- a/lib/libnx/include/nx/hierarchicalsha256.h +++ b/lib/libnx/include/nx/hierarchicalsha256.h @@ -19,7 +19,7 @@ namespace nx crypto::sha::sSha256Hash master_hash; le_uint32_t hash_block_size; le_uint32_t layer_num; - struct sLayout + struct sLayer { le_uint64_t offset; le_uint64_t size; diff --git a/lib/libnx/nx.vcxproj b/lib/libnx/nx.vcxproj index 5fde829..1dd00bc 100644 --- a/lib/libnx/nx.vcxproj +++ b/lib/libnx/nx.vcxproj @@ -30,7 +30,7 @@ - + diff --git a/lib/libnx/nx.vcxproj.filters b/lib/libnx/nx.vcxproj.filters index b1e1f7d..c817750 100644 --- a/lib/libnx/nx.vcxproj.filters +++ b/lib/libnx/nx.vcxproj.filters @@ -153,7 +153,7 @@ Header Files - + Header Files diff --git a/lib/libnx/source/HierarchicalIntegrityHeader.cpp b/lib/libnx/source/HierarchicalIntegrityHeader.cpp index e69de29..0830653 100644 --- a/lib/libnx/source/HierarchicalIntegrityHeader.cpp +++ b/lib/libnx/source/HierarchicalIntegrityHeader.cpp @@ -0,0 +1,96 @@ +#include + +nx::HierarchicalIntegrityHeader::HierarchicalIntegrityHeader() +{ + clear(); +} + +nx::HierarchicalIntegrityHeader::HierarchicalIntegrityHeader(const HierarchicalIntegrityHeader & other) +{ + copyFrom(other); +} + +nx::HierarchicalIntegrityHeader::HierarchicalIntegrityHeader(const byte_t * bytes, size_t len) +{ + importBinary(bytes, len); +} + +bool nx::HierarchicalIntegrityHeader::operator==(const HierarchicalIntegrityHeader & other) const +{ + return isEqual(other); +} + +bool nx::HierarchicalIntegrityHeader::operator!=(const HierarchicalIntegrityHeader & other) const +{ + return !isEqual(other); +} + +void nx::HierarchicalIntegrityHeader::operator=(const HierarchicalIntegrityHeader & other) +{ + copyFrom(other); +} + +const byte_t * nx::HierarchicalIntegrityHeader::getBytes() const +{ + return mBinaryBlob.getBytes(); +} + +size_t nx::HierarchicalIntegrityHeader::getSize() const +{ + return mBinaryBlob.getSize(); +} + +void nx::HierarchicalIntegrityHeader::exportBinary() +{ + throw fnd::Exception(kModuleName, "exportBinary() not implemented"); +} + +void nx::HierarchicalIntegrityHeader::importBinary(const byte_t * bytes, size_t len) +{ + throw fnd::Exception(kModuleName, "importBinary() not implemented"); +} + +void nx::HierarchicalIntegrityHeader::clear() +{ + mLayerInfo.clear(); + mMasterHashList.clear(); +} + +const fnd::List& nx::HierarchicalIntegrityHeader::getLayerInfo() const +{ + return mLayerInfo; +} + +void nx::HierarchicalIntegrityHeader::setLayerInfo(const fnd::List& layer_info) +{ + mLayerInfo = layer_info; +} + +const fnd::List& nx::HierarchicalIntegrityHeader::getMasterHashList() const +{ + return mMasterHashList; +} + +void nx::HierarchicalIntegrityHeader::setMasterHashList(const fnd::List& master_hash_list) +{ + mMasterHashList = master_hash_list; +} + +bool nx::HierarchicalIntegrityHeader::isEqual(const HierarchicalIntegrityHeader & other) const +{ + return (mLayerInfo == other.mLayerInfo) \ + && (mMasterHashList == other.mMasterHashList); +} + +void nx::HierarchicalIntegrityHeader::copyFrom(const HierarchicalIntegrityHeader & other) +{ + if (other.getSize() != 0) + { + importBinary(other.getBytes(), other.getSize()); + } + else + { + mLayerInfo = other.mLayerInfo; + mMasterHashList = other.mMasterHashList; + } +} diff --git a/lib/libnx/source/HierarchicalSha256Header.cpp b/lib/libnx/source/HierarchicalSha256Header.cpp index e69de29..de9ca79 100644 --- a/lib/libnx/source/HierarchicalSha256Header.cpp +++ b/lib/libnx/source/HierarchicalSha256Header.cpp @@ -0,0 +1,132 @@ +#include +#include + + +nx::HierarchicalSha256Header::HierarchicalSha256Header() +{ + clear(); +} + +nx::HierarchicalSha256Header::HierarchicalSha256Header(const HierarchicalSha256Header & other) +{ + copyFrom(other); +} + +nx::HierarchicalSha256Header::HierarchicalSha256Header(const byte_t * bytes, size_t len) +{ + importBinary(bytes, len); +} + +bool nx::HierarchicalSha256Header::operator==(const HierarchicalSha256Header & other) const +{ + return isEqual(other); +} + +bool nx::HierarchicalSha256Header::operator!=(const HierarchicalSha256Header & other) const +{ + return !isEqual(other); +} + +void nx::HierarchicalSha256Header::operator=(const HierarchicalSha256Header & other) +{ + copyFrom(other); +} + +const byte_t * nx::HierarchicalSha256Header::getBytes() const +{ + return mBinaryBlob.getBytes(); +} + +size_t nx::HierarchicalSha256Header::getSize() const +{ + return mBinaryBlob.getSize(); +} + +void nx::HierarchicalSha256Header::exportBinary() +{ + throw fnd::Exception(kModuleName, "exportBinary() not implemented"); +} + +void nx::HierarchicalSha256Header::importBinary(const byte_t * bytes, size_t len) +{ + if (len < sizeof(nx::sHierarchicalSha256Header)) + { + throw fnd::Exception(kModuleName, "Header too small"); + } + + const nx::sHierarchicalSha256Header* hdr = (const nx::sHierarchicalSha256Header*)bytes; + + if (hdr->layer_num.get() != nx::hierarchicalsha256::kDefaultLevelNum) + { + std::stringstream ss; + ss.clear(); + ss << "Invalid layer count. "; + ss << "(actual=" << hdr->layer_num.get() << ", expected=" << nx::hierarchicalsha256::kDefaultLevelNum << ")"; + throw fnd::Exception(kModuleName, ss.str()); + } + + mMasterHash = hdr->master_hash; + mHashBlockSize = hdr->hash_block_size.get(); + for (size_t i = 0; i < hdr->layer_num.get(); i++) + { + mLayerInfo.addElement({hdr->layer[i].offset.get(), hdr->layer[i].size.get()}); + } +} + +void nx::HierarchicalSha256Header::clear() +{ + memset(mMasterHash.bytes, 0, sizeof(crypto::sha::sSha256Hash)); + mHashBlockSize = 0; + mLayerInfo.clear(); +} + +const crypto::sha::sSha256Hash & nx::HierarchicalSha256Header::getMasterHash() const +{ + return mMasterHash; +} + +void nx::HierarchicalSha256Header::setMasterHash(const crypto::sha::sSha256Hash & master_hash) +{ + mMasterHash = master_hash; +} + +size_t nx::HierarchicalSha256Header::getHashBlockSize() const +{ + return mHashBlockSize; +} + +void nx::HierarchicalSha256Header::setHashBlockSize(size_t hash_block_size) +{ + mHashBlockSize = hash_block_size; +} + +const fnd::List& nx::HierarchicalSha256Header::getLayerInfo() const +{ + return mLayerInfo; +} + +void nx::HierarchicalSha256Header::setLayerInfo(const fnd::List& layer_info) +{ + mLayerInfo = layer_info; +} + +bool nx::HierarchicalSha256Header::isEqual(const HierarchicalSha256Header & other) const +{ + return (mMasterHash == other.mMasterHash) \ + && (mHashBlockSize == other.mHashBlockSize) \ + && (mLayerInfo == other.mLayerInfo); +} + +void nx::HierarchicalSha256Header::copyFrom(const HierarchicalSha256Header & other) +{ + if (other.getSize() != 0) + { + importBinary(other.getBytes(), other.getSize()); + } + else + { + mMasterHash = other.mMasterHash; + mHashBlockSize = other.mHashBlockSize; + mLayerInfo = other.mLayerInfo; + } +} From 446927b53ebbe9d836e74f5f554dc4d014d9f721 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sun, 20 May 2018 21:57:38 +0800 Subject: [PATCH 30/59] [libnx|nstool] Added HierarchicalSha256Header and HierarchicalIntegrityHeader --- .../include/nx/HierarchicalIntegrityHeader.h | 12 +- .../include/nx/HierarchicalSha256Header.h | 4 +- lib/libnx/include/nx/hierarchicalintegrity.h | 23 ++- lib/libnx/include/nx/hierarchicalsha256.h | 7 +- lib/libnx/include/nx/nca.h | 12 +- .../source/HierarchicalIntegrityHeader.cpp | 62 ++++++- lib/libnx/source/HierarchicalSha256Header.cpp | 13 +- lib/libnx/source/NcaUtils.cpp | 5 +- programs/nstool/source/NcaProcess.cpp | 168 +++++++----------- programs/nstool/source/NcaProcess.h | 16 +- 10 files changed, 170 insertions(+), 152 deletions(-) diff --git a/lib/libnx/include/nx/HierarchicalIntegrityHeader.h b/lib/libnx/include/nx/HierarchicalIntegrityHeader.h index f40f117..ddcb5f9 100644 --- a/lib/libnx/include/nx/HierarchicalIntegrityHeader.h +++ b/lib/libnx/include/nx/HierarchicalIntegrityHeader.h @@ -1,5 +1,5 @@ #pragma once -#include +#include #include #include #include @@ -14,21 +14,21 @@ namespace nx { size_t offset; size_t size; - size_t hash_block_size; + size_t block_size; void operator=(const sLayer& other) { offset = other.offset; size = other.size; - hash_block_size = other.hash_block_size; + block_size = other.block_size; } - bool operator==(const sLayer& other) + bool operator==(const sLayer& other) const { - return (offset == other.offset && size == other.size && hash_block_size == other.hash_block_size); + return (offset == other.offset && size == other.size && block_size == other.block_size); } - bool operator!=(const sLayer& other) + bool operator!=(const sLayer& other) const { return !(*this == other); } diff --git a/lib/libnx/include/nx/HierarchicalSha256Header.h b/lib/libnx/include/nx/HierarchicalSha256Header.h index a165b5b..f5568e8 100644 --- a/lib/libnx/include/nx/HierarchicalSha256Header.h +++ b/lib/libnx/include/nx/HierarchicalSha256Header.h @@ -21,12 +21,12 @@ namespace nx size = other.size; } - bool operator==(const sLayer& other) + bool operator==(const sLayer& other) const { return (offset == other.offset && size == other.size); } - bool operator!=(const sLayer& other) + bool operator!=(const sLayer& other) const { return !(*this == other); } diff --git a/lib/libnx/include/nx/hierarchicalintegrity.h b/lib/libnx/include/nx/hierarchicalintegrity.h index 02a9297..e60350e 100644 --- a/lib/libnx/include/nx/hierarchicalintegrity.h +++ b/lib/libnx/include/nx/hierarchicalintegrity.h @@ -10,9 +10,9 @@ namespace nx namespace hierarchicalintegrity { const std::string kStructSig = "IVFC"; - static const uint32_t kTypeId = 0x20000; - static const size_t kMaxLayerNum = 7; - static const size_t kMaxMasterHashNum = 3; + static const uint32_t kRomfsTypeId = 0x20000; + static const size_t kDefaultLayerNum = 6; + static const size_t kHeaderAlignLen = 0x20; } #pragma pack(push,1) @@ -22,15 +22,14 @@ namespace nx le_uint32_t type_id; le_uint32_t master_hash_size; le_uint32_t layer_num; - struct sLayer - { - le_uint64_t offset; - le_uint64_t size; - le_uint32_t block_size; - byte_t reserved[4]; - } layer[hierarchicalintegrity::kMaxLayerNum]; - byte_t reserved_00[0x8]; - crypto::sha::sSha256Hash master_hash[hierarchicalintegrity::kMaxMasterHashNum]; + }; + + struct sHierarchicalIntegrityLayerInfo // sizeof(0x18) + { + le_uint64_t offset; + le_uint64_t size; + le_uint32_t block_size; + byte_t reserved[4]; }; #pragma pack(pop) } diff --git a/lib/libnx/include/nx/hierarchicalsha256.h b/lib/libnx/include/nx/hierarchicalsha256.h index a4da019..a620826 100644 --- a/lib/libnx/include/nx/hierarchicalsha256.h +++ b/lib/libnx/include/nx/hierarchicalsha256.h @@ -8,9 +8,8 @@ namespace nx { namespace hierarchicalsha256 { - static const size_t kDefaultLevelNum = 2; - - static const size_t kMaxLayoutNum = 2; + static const size_t kDefaultLayerNum = 2; + static const size_t kMaxLayerNum = 2; } #pragma pack(push,1) @@ -23,7 +22,7 @@ namespace nx { le_uint64_t offset; le_uint64_t size; - } layer[hierarchicalsha256::kMaxLayoutNum]; + } layer[hierarchicalsha256::kMaxLayerNum]; }; #pragma pack(pop) } diff --git a/lib/libnx/include/nx/nca.h b/lib/libnx/include/nx/nca.h index c05ed43..7158813 100644 --- a/lib/libnx/include/nx/nca.h +++ b/lib/libnx/include/nx/nca.h @@ -5,8 +5,6 @@ #include #include #include -#include -#include namespace nx { @@ -21,7 +19,7 @@ namespace nx static const size_t kAesKeyNum = 16; static const size_t kRightsIdLen = 0x10; static const size_t kKeyAreaEncryptionKeyNum = 3; - static const size_t kFsHeaderHashSuperblockLen = 0x130; + static const size_t kFsHeaderHashSuperblockLen = 0x138; static const uint16_t kDefaultFsHeaderVersion = 2; enum ProgramPartitionId @@ -120,12 +118,8 @@ namespace nx byte_t hash_type; byte_t encryption_type; byte_t reserved_0[3]; - union { - byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen]; - nx::sHierarchicalSha256Header hierarchicalsha256_header; - nx::sHierarchicalIntegrityHeader hierarchicalintergrity_header; - }; - crypto::aes::sAesIvCtr base_ctr; + byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen]; + byte_t aes_ctr_upper[8]; byte_t reserved_1[0xB8]; }; diff --git a/lib/libnx/source/HierarchicalIntegrityHeader.cpp b/lib/libnx/source/HierarchicalIntegrityHeader.cpp index 0830653..5c37e16 100644 --- a/lib/libnx/source/HierarchicalIntegrityHeader.cpp +++ b/lib/libnx/source/HierarchicalIntegrityHeader.cpp @@ -1,3 +1,4 @@ +#include #include nx::HierarchicalIntegrityHeader::HierarchicalIntegrityHeader() @@ -47,7 +48,66 @@ void nx::HierarchicalIntegrityHeader::exportBinary() void nx::HierarchicalIntegrityHeader::importBinary(const byte_t * bytes, size_t len) { - throw fnd::Exception(kModuleName, "importBinary() not implemented"); + std::stringstream error_str; + + // validate size for at least header + if (len < sizeof(nx::sHierarchicalIntegrityHeader)) + { + throw fnd::Exception(kModuleName, "Header too small"); + } + + const nx::sHierarchicalIntegrityHeader* hdr = (const nx::sHierarchicalIntegrityHeader*)bytes; + + // Validate Header Sig "IVFC" + if (std::string(hdr->signature, 4) != hierarchicalintegrity::kStructSig) + { + throw fnd::Exception(kModuleName, "Invalid struct magic"); + } + + // Validate TypeId + if (hdr->type_id.get() != nx::hierarchicalintegrity::kRomfsTypeId) + { + error_str.clear(); + error_str << "Unsupported type id (" << std::hex << hdr->type_id.get() << ")"; + throw fnd::Exception(kModuleName, error_str.str()); + } + + // Validate Layer Num + if (hdr->layer_num.get() != hierarchicalintegrity::kDefaultLayerNum+1) + { + error_str.clear(); + error_str << "Invalid layer count. "; + error_str << "(actual=" << std::dec << hdr->layer_num.get() << ", expected=" << nx::hierarchicalintegrity::kDefaultLayerNum+1 << ")"; + throw fnd::Exception(kModuleName, error_str.str()); + } + + // Get Sizes/Offsets + size_t master_hash_offset = align((sizeof(nx::sHierarchicalIntegrityHeader) + sizeof(nx::sHierarchicalIntegrityLayerInfo) * hdr->layer_num.get()), nx::hierarchicalintegrity::kHeaderAlignLen); + size_t total_size = master_hash_offset + hdr->master_hash_size.get(); + + // Validate total size + if (len < total_size) + { + throw fnd::Exception(kModuleName, "Header too small"); + } + + // copy to internal storage + mBinaryBlob.alloc(total_size); + memcpy(mBinaryBlob.getBytes(), bytes, mBinaryBlob.getSize()); + + // save layer info + const nx::sHierarchicalIntegrityLayerInfo* layer_info = (const nx::sHierarchicalIntegrityLayerInfo*)(mBinaryBlob.getBytes() + sizeof(nx::sHierarchicalIntegrityHeader)); + for (size_t i = 0; i < hierarchicalintegrity::kDefaultLayerNum; i++) + { + mLayerInfo.addElement({layer_info[i].offset.get(), layer_info[i].size.get(), layer_info[i].block_size.get()}); + } + + // save hash list + const crypto::sha::sSha256Hash* hash_list = (const crypto::sha::sSha256Hash*)(mBinaryBlob.getBytes() + master_hash_offset); + for (size_t i = 0; i < hdr->master_hash_size.get()/sizeof(crypto::sha::sSha256Hash); i++) + { + mMasterHashList.addElement(hash_list[i]); + } } void nx::HierarchicalIntegrityHeader::clear() diff --git a/lib/libnx/source/HierarchicalSha256Header.cpp b/lib/libnx/source/HierarchicalSha256Header.cpp index de9ca79..a7d288d 100644 --- a/lib/libnx/source/HierarchicalSha256Header.cpp +++ b/lib/libnx/source/HierarchicalSha256Header.cpp @@ -49,6 +49,8 @@ void nx::HierarchicalSha256Header::exportBinary() void nx::HierarchicalSha256Header::importBinary(const byte_t * bytes, size_t len) { + std::stringstream error_str; + if (len < sizeof(nx::sHierarchicalSha256Header)) { throw fnd::Exception(kModuleName, "Header too small"); @@ -56,13 +58,12 @@ void nx::HierarchicalSha256Header::importBinary(const byte_t * bytes, size_t len const nx::sHierarchicalSha256Header* hdr = (const nx::sHierarchicalSha256Header*)bytes; - if (hdr->layer_num.get() != nx::hierarchicalsha256::kDefaultLevelNum) + if (hdr->layer_num.get() != nx::hierarchicalsha256::kDefaultLayerNum) { - std::stringstream ss; - ss.clear(); - ss << "Invalid layer count. "; - ss << "(actual=" << hdr->layer_num.get() << ", expected=" << nx::hierarchicalsha256::kDefaultLevelNum << ")"; - throw fnd::Exception(kModuleName, ss.str()); + error_str.clear(); + error_str << "Invalid layer count. "; + error_str << "(actual=" << std::dec << hdr->layer_num.get() << ", expected=" << nx::hierarchicalsha256::kDefaultLayerNum << ")"; + throw fnd::Exception(kModuleName, error_str.str()); } mMasterHash = hdr->master_hash; diff --git a/lib/libnx/source/NcaUtils.cpp b/lib/libnx/source/NcaUtils.cpp index 0ffa1ad..3ee87a6 100644 --- a/lib/libnx/source/NcaUtils.cpp +++ b/lib/libnx/source/NcaUtils.cpp @@ -50,8 +50,9 @@ byte_t nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(byte_t key_generation void nx::NcaUtils::getNcaPartitionAesCtr(const nx::sNcaFsHeader* hdr, byte_t* ctr) { - for (size_t i = 0; i < 16; i++) + for (size_t i = 0; i < 8; i++) { - ctr[15-i] = hdr->base_ctr.iv[i]; + ctr[7-i] = hdr->aes_ctr_upper[i]; + ctr[15-i] = 0; } } \ No newline at end of file diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 3ad8048..c302172 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -120,7 +120,7 @@ void NcaProcess::generateNcaBodyEncryptionKeys() // otherwise decrypt key area else { - // if the titlekey_kek is available + // if the key_area_key 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); @@ -153,6 +153,9 @@ void NcaProcess::generatePartitionConfiguration() const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i]; nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index]; + // output structure + sPartitionInfo& info = mPartitions[partition.index]; + // validate header hash crypto::sha::sSha256Hash calc_hash; crypto::sha::Sha256((const byte_t*)&mHdrBlock.fs_header[partition.index], sizeof(nx::sNcaFsHeader), calc_hash.bytes); @@ -165,19 +168,19 @@ void NcaProcess::generatePartitionConfiguration() // setup AES-CTR - crypto::aes::sAesIvCtr ctr; - nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv); + nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, info.aes_ctr.iv); // save partition config - mPartitions[partition.index].reader = nullptr; - mPartitions[partition.index].offset = partition.offset; - mPartitions[partition.index].size = partition.size; - mPartitions[partition.index].format_type = (nx::nca::FormatType)fs_header.format_type; - mPartitions[partition.index].hash_type = (nx::nca::HashType)fs_header.hash_type; - memcpy(mPartitions[partition.index].hash_superblock, fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); + info.reader = nullptr; + info.offset = partition.offset; + info.size = partition.size; + info.version = fs_header.version.get(); + info.format_type = (nx::nca::FormatType)fs_header.format_type; + info.hash_type = (nx::nca::HashType)fs_header.hash_type; + info.enc_type = (nx::nca::EncryptionType)fs_header.encryption_type; // filter out unrecognised format types - switch (mPartitions[partition.index].format_type) + switch (info.format_type) { case (nx::nca::FORMAT_PFS0): case (nx::nca::FORMAT_ROMFS): @@ -186,71 +189,45 @@ void NcaProcess::generatePartitionConfiguration() continue; } - // filter out unrecognised hash types - switch (mPartitions[partition.index].hash_type) + // filter out unrecognised hash types, and get data offsets + switch (info.hash_type) { case (nx::nca::HASH_NONE): + info.data_offset = info.offset; + info.data_size = info.size; + break; case (nx::nca::HASH_HIERARCHICAL_SHA256): + info.hierarchicalsha256_header.importBinary(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); + info.data_offset = info.hierarchicalsha256_header.getLayerInfo().atBack().offset; + info.data_size = info.hierarchicalsha256_header.getLayerInfo().atBack().size; + break; case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): + info.hierarchicalintergrity_header.importBinary(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); + info.data_offset = info.hierarchicalintergrity_header.getLayerInfo().atBack().offset; + info.data_size = info.hierarchicalintergrity_header.getLayerInfo().atBack().size; break; default: continue; } - // create reader + // create reader based on encryption type0 switch(fs_header.encryption_type) { case (nx::nca::CRYPT_AESXTS): case (nx::nca::CRYPT_AESCTREX): - mPartitions[partition.index].reader = nullptr; + info.reader = nullptr; break; case (nx::nca::CRYPT_AESCTR): - mPartitions[partition.index].reader = mBodyKeys.aes_ctr.isSet? new AesCtrWrappedIFile(mReader, mBodyKeys.aes_ctr.var, ctr) : nullptr; + info.reader = mBodyKeys.aes_ctr.isSet? new AesCtrWrappedIFile(mReader, mBodyKeys.aes_ctr.var, info.aes_ctr) : nullptr; break; case (nx::nca::CRYPT_NONE): - mPartitions[partition.index].reader = new CopiedIFile(mReader); + info.reader = new CopiedIFile(mReader); break; default: error.clear(); error << "NCA FS Header [" << partition.index << "] EncryptionType(" << fs_header.encryption_type << "): UNKNOWN \n"; throw fnd::Exception(kModuleName, error.str()); } - - // determine the data offset & size - if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) - { - mPartitions[partition.index].data_offset = mPartitions[partition.index].hierarchicalsha256_header.layer[1].offset.get(); - mPartitions[partition.index].data_size = mPartitions[partition.index].hierarchicalsha256_header.layer[1].size.get(); - } - else if (mPartitions[partition.index].hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) - { - mPartitions[partition.index].data_offset = mPartitions[partition.index].hierarchicalintergrity_header.layer[5].offset.get(); - mPartitions[partition.index].data_size = mPartitions[partition.index].hierarchicalintergrity_header.layer[5].size.get(); - /* - if (mPartitions[partition.index].hierarchicalintergrity_header.layer_num.get() > nx::hierarchicalintegrity::kMaxLayerNum) - { - error.clear(); - error << "NCA FS Header [" << partition.index << "] HierarchicalIntergrity header has an unsupported layer num (" << mPartitions[partition.index].hierarchicalintergrity_header.layer_num.get() << ")\n"; - throw fnd::Exception(kModuleName, error.str()); - } - const nx::sHierarchicalIntegrityHeader& hdr = mPartitions[partition.index].hierarchicalintergrity_header; - - for (size_t j = 0; j < nx::hierarchicalintegrity::kMaxLayerNum; j++) - { - size_t index = nx::hierarchicalintegrity::kMaxLayerNum - 1 - j; - if (hdr.layer[index].offset.get() != 0) - { - mPartitions[partition.index].data_offset = hdr.layer[index].offset.get(); - mPartitions[partition.index].data_size = hdr.layer[index].size.get(); - break; - } - } - */ - } - else if (mPartitions[partition.index].hash_type == nx::nca::HASH_NONE) - { - mPartitions[partition.index].data_offset = 0; - } } } @@ -368,81 +345,64 @@ void NcaProcess::displayHeader() 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]; + sPartitionInfo& info = mPartitions[i]; printf(" %lu:\n", i); - printf(" Index: %d\n", partition.index); - printf(" Offset: 0x%" PRIx64 "\n", partition.offset); - printf(" Size: 0x%" PRIx64 "\n", partition.size); + printf(" Offset: 0x%" PRIx64 "\n", info.offset); + printf(" Size: 0x%" PRIx64 "\n", info.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(" Version: 0x%d\n", info.version); + printf(" Format Type: %s\n", kFormatTypeStr[info.format_type].c_str()); + printf(" Hash Type: %s\n", kHashTypeStr[info.hash_type].c_str()); + printf(" Enc. Type: %s\n", kEncryptionTypeStr[info.enc_type].c_str()); + if (info.enc_type == nx::nca::CRYPT_AESCTR) { - printf(" CTR: "); + printf(" AES-CTR: "); crypto::aes::sAesIvCtr ctr; - nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv); - crypto::aes::AesIncrementCounter(ctr.iv, partition.offset>>4, ctr.iv); + crypto::aes::AesIncrementCounter(info.aes_ctr.iv, info.offset>>4, ctr.iv); fnd::SimpleTextOutput::hexDump(ctr.iv, sizeof(crypto::aes::sAesIvCtr)); } - if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) + if (info.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) { - nx::sHierarchicalIntegrityHeader& hash_hdr = fs_header.hierarchicalintergrity_header; + nx::HierarchicalIntegrityHeader& hash_hdr = info.hierarchicalintergrity_header; printf(" HierarchicalIntegrity Header:\n"); - printf(" TypeId: 0x%x\n", hash_hdr.type_id.get()); - printf(" MasterHashSize: 0x%x\n", hash_hdr.master_hash_size.get()); - printf(" LayerNum: %d\n", hash_hdr.layer_num.get()); - for (size_t i = 0; i < hash_hdr.layer_num.get(); i++) + //printf(" TypeId: 0x%x\n", hash_hdr.type_id.get()); + //printf(" MasterHashSize: 0x%x\n", hash_hdr.master_hash_size.get()); + //printf(" LayerNum: %d\n", hash_hdr.getLayerInfo().getSize()); + for (size_t j = 0; j < hash_hdr.getLayerInfo().getSize(); j++) { - printf(" Layer %d:\n", i); - printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.layer[i].offset.get()); - printf(" Size: 0x%" PRIx64 "\n", hash_hdr.layer[i].size.get()); - printf(" BlockSize: 0x%" PRIx32 "\n", hash_hdr.layer[i].block_size.get()); + printf(" Layer %d:\n", j); + printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getLayerInfo()[j].offset); + printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getLayerInfo()[j].size); + printf(" BlockSize: 0x%" PRIx32 "\n", hash_hdr.getLayerInfo()[j].block_size); } - for (size_t j = 0; j < hash_hdr.master_hash_size.get() / sizeof(crypto::sha::sSha256Hash); j++) + for (size_t j = 0; j < hash_hdr.getMasterHashList().getSize(); j++) { printf(" Master Hash %d: ", j); - fnd::SimpleTextOutput::hexDump(hash_hdr.master_hash[j].bytes, sizeof(crypto::sha::sSha256Hash)); + fnd::SimpleTextOutput::hexDump(hash_hdr.getMasterHashList()[j].bytes, sizeof(crypto::sha::sSha256Hash)); } - - - } - else if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) + else if (info.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) { - nx::sHierarchicalSha256Header& hash_hdr = fs_header.hierarchicalsha256_header; + nx::HierarchicalSha256Header& hash_hdr = info.hierarchicalsha256_header; printf(" HierarchicalSha256 Header:\n"); printf(" Master Hash: "); - fnd::SimpleTextOutput::hexDump(hash_hdr.master_hash.bytes, sizeof(crypto::sha::sSha256Hash)); - printf(" HashBlockSize: 0x%x\n", hash_hdr.hash_block_size.get()); - printf(" LayerNum: %d\n", hash_hdr.layer_num.get()); - for (size_t i = 0; i < hash_hdr.layer_num.get(); i++) + fnd::SimpleTextOutput::hexDump(hash_hdr.getMasterHash().bytes, sizeof(crypto::sha::sSha256Hash)); + printf(" HashBlockSize: 0x%x\n", hash_hdr.getHashBlockSize()); + //printf(" LayerNum: %d\n", hash_hdr.getLayerInfo().getSize()); + for (size_t i = 0; i < hash_hdr.getLayerInfo().getSize(); i++) { printf(" Layer %d:\n", i); - printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.layer[i].offset.get()); - printf(" Size: 0x%" PRIx64 "\n", hash_hdr.layer[i].size.get()); + printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getLayerInfo()[i].offset); + printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getLayerInfo()[i].size); } } - else - { - printf(" Hash Superblock:\n"); - fnd::SimpleTextOutput::hxdStyleDump(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); - } + //else + //{ + // printf(" Hash Superblock:\n"); + // fnd::SimpleTextOutput::hxdStyleDump(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); + //} } } diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index eb05b47..d6438dc 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -3,6 +3,9 @@ #include #include #include +#include +#include + #include "nstool.h" @@ -64,14 +67,15 @@ private: size_t size; size_t data_offset; size_t data_size; + + // meta data + uint16_t version; nx::nca::FormatType format_type; nx::nca::HashType hash_type; - - union { - byte_t hash_superblock[nx::nca::kFsHeaderHashSuperblockLen]; - nx::sHierarchicalSha256Header hierarchicalsha256_header; - nx::sHierarchicalIntegrityHeader hierarchicalintergrity_header; - }; + nx::nca::EncryptionType enc_type; + nx::HierarchicalSha256Header hierarchicalsha256_header; + nx::HierarchicalIntegrityHeader hierarchicalintergrity_header; + crypto::aes::sAesIvCtr aes_ctr; } mPartitions[nx::nca::kPartitionNum]; void generateNcaBodyEncryptionKeys(); From 1e75262f3feb9498974c7adff800ecd161956022 Mon Sep 17 00:00:00 2001 From: jakcron Date: Mon, 21 May 2018 20:48:59 +0800 Subject: [PATCH 31/59] [nstool] Wrapped IFile classes now can optionally specify when constructed if they own their IFile pointer. By default they do not. --- programs/nstool/source/AesCtrWrappedIFile.cpp | 18 ++++++++++++++++++ programs/nstool/source/AesCtrWrappedIFile.h | 3 +++ .../nstool/source/OffsetAdjustedIFile.cpp | 19 +++++++++++++++++++ programs/nstool/source/OffsetAdjustedIFile.h | 3 +++ 4 files changed, 43 insertions(+) diff --git a/programs/nstool/source/AesCtrWrappedIFile.cpp b/programs/nstool/source/AesCtrWrappedIFile.cpp index 79d966f..134ded9 100644 --- a/programs/nstool/source/AesCtrWrappedIFile.cpp +++ b/programs/nstool/source/AesCtrWrappedIFile.cpp @@ -1,6 +1,7 @@ #include "AesCtrWrappedIFile.h" AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile* file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) : + mOwnIFile(false), mFile(file), mKey(key), mBaseCtr(ctr) @@ -8,6 +9,23 @@ AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile* file, const crypto::aes::sAes mScratch.alloc(kAesCtrScratchAllocSize); } +AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile* file, bool ownIfile, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) : + mOwnIFile(ownIfile), + mFile(file), + mKey(key), + mBaseCtr(ctr) +{ + mScratch.alloc(kAesCtrScratchAllocSize); +} + +AesCtrWrappedIFile::~AesCtrWrappedIFile() +{ + if (mOwnIFile) + { + delete mFile; + } +} + size_t AesCtrWrappedIFile::size() { return mFile->size(); diff --git a/programs/nstool/source/AesCtrWrappedIFile.h b/programs/nstool/source/AesCtrWrappedIFile.h index f2a001c..3eaaaad 100644 --- a/programs/nstool/source/AesCtrWrappedIFile.h +++ b/programs/nstool/source/AesCtrWrappedIFile.h @@ -6,6 +6,8 @@ class AesCtrWrappedIFile : public fnd::IFile { public: AesCtrWrappedIFile(fnd::IFile* file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr); + AesCtrWrappedIFile(fnd::IFile* file, bool ownIfile, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr); + ~AesCtrWrappedIFile(); size_t size(); void seek(size_t offset); @@ -18,6 +20,7 @@ private: static const size_t kAesCtrScratchSize = 0x1000000; static const size_t kAesCtrScratchAllocSize = kAesCtrScratchSize + crypto::aes::kAesBlockSize; + bool mOwnIFile; fnd::IFile* mFile; crypto::aes::sAes128Key mKey; crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr; diff --git a/programs/nstool/source/OffsetAdjustedIFile.cpp b/programs/nstool/source/OffsetAdjustedIFile.cpp index e9f7231..d2fb0cf 100644 --- a/programs/nstool/source/OffsetAdjustedIFile.cpp +++ b/programs/nstool/source/OffsetAdjustedIFile.cpp @@ -1,6 +1,7 @@ #include "OffsetAdjustedIFile.h" OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile* file, size_t offset, size_t size) : + mOwnIFile(false), mFile(file), mBaseOffset(offset), mCurrentOffset(0), @@ -9,6 +10,24 @@ OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile* file, size_t offset, size_t } +OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile* file, bool ownIFile, size_t offset, size_t size) : + mOwnIFile(ownIFile), + mFile(file), + mBaseOffset(offset), + mCurrentOffset(0), + mSize(size) +{ + +} + +OffsetAdjustedIFile::~OffsetAdjustedIFile() +{ + if (mOwnIFile) + { + delete mFile; + } +} + size_t OffsetAdjustedIFile::size() { return mSize; diff --git a/programs/nstool/source/OffsetAdjustedIFile.h b/programs/nstool/source/OffsetAdjustedIFile.h index 5b60dcf..b1c1e09 100644 --- a/programs/nstool/source/OffsetAdjustedIFile.h +++ b/programs/nstool/source/OffsetAdjustedIFile.h @@ -4,6 +4,8 @@ class OffsetAdjustedIFile : public fnd::IFile { public: OffsetAdjustedIFile(fnd::IFile* file, size_t offset, size_t size); + OffsetAdjustedIFile(fnd::IFile* file, bool ownIFile, size_t offset, size_t size); + ~OffsetAdjustedIFile(); size_t size(); void seek(size_t offset); @@ -12,6 +14,7 @@ public: void write(const byte_t* out, size_t len); void write(const byte_t* out, size_t offset, size_t len); private: + bool mOwnIFile; fnd::IFile* mFile; size_t mBaseOffset, mCurrentOffset; size_t mSize; From e4b86ab5663ab3f9c77faa445ccefb0ff544bd81 Mon Sep 17 00:00:00 2001 From: jakcron Date: Mon, 21 May 2018 20:49:28 +0800 Subject: [PATCH 32/59] [nstool] Formatting changes. --- programs/nstool/source/NcaProcess.cpp | 50 ++++++++++++++------------- programs/nstool/source/NcaProcess.h | 6 ++-- 2 files changed, 28 insertions(+), 28 deletions(-) diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index c302172..e84f444 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -167,6 +167,13 @@ void NcaProcess::generatePartitionConfiguration() } + if (fs_header.version.get() != nx::nca::kDefaultFsHeaderVersion) + { + error.clear(); + error << "NCA FS Header [" << partition.index << "] Version(" << fs_header.version.get() << "): UNSUPPORTED\n"; + throw fnd::Exception(kModuleName, error.str()); + } + // setup AES-CTR nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, info.aes_ctr.iv); @@ -174,7 +181,6 @@ void NcaProcess::generatePartitionConfiguration() info.reader = nullptr; info.offset = partition.offset; info.size = partition.size; - info.version = fs_header.version.get(); info.format_type = (nx::nca::FormatType)fs_header.format_type; info.hash_type = (nx::nca::HashType)fs_header.hash_type; info.enc_type = (nx::nca::EncryptionType)fs_header.encryption_type; @@ -186,7 +192,9 @@ void NcaProcess::generatePartitionConfiguration() case (nx::nca::FORMAT_ROMFS): break; default: - continue; + error.clear(); + error << "NCA FS Header [" << partition.index << "] FormatType(" << info.format_type << "): UNKNOWN \n"; + throw fnd::Exception(kModuleName, error.str()); } // filter out unrecognised hash types, and get data offsets @@ -197,21 +205,23 @@ void NcaProcess::generatePartitionConfiguration() info.data_size = info.size; break; case (nx::nca::HASH_HIERARCHICAL_SHA256): - info.hierarchicalsha256_header.importBinary(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); - info.data_offset = info.hierarchicalsha256_header.getLayerInfo().atBack().offset; - info.data_size = info.hierarchicalsha256_header.getLayerInfo().atBack().size; + info.hash_sha256_header.importBinary(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); + info.data_offset = info.hash_sha256_header.getLayerInfo().atBack().offset; + info.data_size = info.hash_sha256_header.getLayerInfo().atBack().size; break; case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): - info.hierarchicalintergrity_header.importBinary(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); - info.data_offset = info.hierarchicalintergrity_header.getLayerInfo().atBack().offset; - info.data_size = info.hierarchicalintergrity_header.getLayerInfo().atBack().size; + info.hash_integ_header.importBinary(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); + info.data_offset = info.hash_integ_header.getLayerInfo().atBack().offset; + info.data_size = info.hash_integ_header.getLayerInfo().atBack().size; break; default: - continue; + error.clear(); + error << "NCA FS Header [" << partition.index << "] HashType(" << info.hash_type << "): UNKNOWN \n"; + throw fnd::Exception(kModuleName, error.str()); } // create reader based on encryption type0 - switch(fs_header.encryption_type) + switch(info.enc_type) { case (nx::nca::CRYPT_AESXTS): case (nx::nca::CRYPT_AESCTREX): @@ -225,17 +235,12 @@ void NcaProcess::generatePartitionConfiguration() break; default: error.clear(); - error << "NCA FS Header [" << partition.index << "] EncryptionType(" << fs_header.encryption_type << "): UNKNOWN \n"; + error << "NCA FS Header [" << partition.index << "] EncryptionType(" << info.enc_type << "): UNKNOWN \n"; throw fnd::Exception(kModuleName, error.str()); } } } -void NcaProcess::validatePartitionHash() -{ - -} - void NcaProcess::validateNcaSignatures() { // validate signature[0] @@ -350,12 +355,9 @@ void NcaProcess::displayHeader() printf(" %lu:\n", i); printf(" Offset: 0x%" PRIx64 "\n", info.offset); printf(" Size: 0x%" PRIx64 "\n", info.size); - - printf(" FsHeader:\n"); - printf(" Version: 0x%d\n", info.version); - printf(" Format Type: %s\n", kFormatTypeStr[info.format_type].c_str()); - printf(" Hash Type: %s\n", kHashTypeStr[info.hash_type].c_str()); - printf(" Enc. Type: %s\n", kEncryptionTypeStr[info.enc_type].c_str()); + printf(" Format Type: %s\n", kFormatTypeStr[info.format_type].c_str()); + printf(" Hash Type: %s\n", kHashTypeStr[info.hash_type].c_str()); + printf(" Enc. Type: %s\n", kEncryptionTypeStr[info.enc_type].c_str()); if (info.enc_type == nx::nca::CRYPT_AESCTR) { printf(" AES-CTR: "); @@ -365,7 +367,7 @@ void NcaProcess::displayHeader() } if (info.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) { - nx::HierarchicalIntegrityHeader& hash_hdr = info.hierarchicalintergrity_header; + nx::HierarchicalIntegrityHeader& hash_hdr = info.hash_integ_header; printf(" HierarchicalIntegrity Header:\n"); //printf(" TypeId: 0x%x\n", hash_hdr.type_id.get()); //printf(" MasterHashSize: 0x%x\n", hash_hdr.master_hash_size.get()); @@ -385,7 +387,7 @@ void NcaProcess::displayHeader() } else if (info.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) { - nx::HierarchicalSha256Header& hash_hdr = info.hierarchicalsha256_header; + nx::HierarchicalSha256Header& hash_hdr = info.hash_sha256_header; printf(" HierarchicalSha256 Header:\n"); printf(" Master Hash: "); fnd::SimpleTextOutput::hexDump(hash_hdr.getMasterHash().bytes, sizeof(crypto::sha::sSha256Hash)); diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index d6438dc..55e09ac 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -69,18 +69,16 @@ private: size_t data_size; // meta data - uint16_t version; nx::nca::FormatType format_type; nx::nca::HashType hash_type; nx::nca::EncryptionType enc_type; - nx::HierarchicalSha256Header hierarchicalsha256_header; - nx::HierarchicalIntegrityHeader hierarchicalintergrity_header; + nx::HierarchicalSha256Header hash_sha256_header; + nx::HierarchicalIntegrityHeader hash_integ_header; crypto::aes::sAesIvCtr aes_ctr; } mPartitions[nx::nca::kPartitionNum]; void generateNcaBodyEncryptionKeys(); void generatePartitionConfiguration(); - void validatePartitionHash(); void validateNcaSignatures(); void displayHeader(); void processPartitions(); From 1f3865c9795c6b7f706fca18a174078f33ba4402 Mon Sep 17 00:00:00 2001 From: jakcron Date: Mon, 21 May 2018 20:49:46 +0800 Subject: [PATCH 33/59] [nstool] Add HashTreeMeta. --- programs/nstool/source/HashTreeMeta.cpp | 124 ++++++++++++++++++++++++ programs/nstool/source/HashTreeMeta.h | 60 ++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 programs/nstool/source/HashTreeMeta.cpp create mode 100644 programs/nstool/source/HashTreeMeta.h diff --git a/programs/nstool/source/HashTreeMeta.cpp b/programs/nstool/source/HashTreeMeta.cpp new file mode 100644 index 0000000..049bd3c --- /dev/null +++ b/programs/nstool/source/HashTreeMeta.cpp @@ -0,0 +1,124 @@ +#include "HashTreeMeta.h" + +HashTreeMeta::HashTreeMeta() : + mLayerInfo(), + mDataLayer(), + mMasterHashList() +{ + +} + +HashTreeMeta::HashTreeMeta(const nx::HierarchicalIntegrityHeader& hdr) : + mLayerInfo(), + mDataLayer(), + mMasterHashList() +{ + importHierarchicalIntergityHeader(hdr); +} + +HashTreeMeta::HashTreeMeta(const nx::HierarchicalSha256Header& hdr) : + mLayerInfo(), + mDataLayer(), + mMasterHashList() +{ + importHierarchicalSha256Header(hdr); +} + +bool HashTreeMeta::operator==(const HashTreeMeta& other) const +{ + return isEqual(other); +} + +bool HashTreeMeta::operator!=(const HashTreeMeta& other) const +{ + return !isEqual(other); +} + +void HashTreeMeta::operator=(const HashTreeMeta& other) +{ + copyFrom(other); +} + +void HashTreeMeta::importHierarchicalIntergityHeader(const nx::HierarchicalIntegrityHeader& hdr) +{ + for (size_t i = 0; i < hdr.getLayerInfo().getSize(); i++) + { + sLayer layer; + layer.offset = hdr.getLayerInfo()[i].offset; + layer.size = hdr.getLayerInfo()[i].size; + layer.block_size = _BIT(hdr.getLayerInfo()[i].block_size); + if (i+1 == hdr.getLayerInfo().getSize()) + { + mDataLayer = layer; + } + else + { + mLayerInfo.addElement(layer); + } + } + mMasterHashList = hdr.getMasterHashList(); +} + +void HashTreeMeta::importHierarchicalSha256Header(const nx::HierarchicalSha256Header& hdr) +{ + for (size_t i = 0; i < hdr.getLayerInfo().getSize(); i++) + { + sLayer layer; + layer.offset = hdr.getLayerInfo()[i].offset; + layer.size = hdr.getLayerInfo()[i].size; + layer.block_size = hdr.getHashBlockSize(); + if (i+1 == hdr.getLayerInfo().getSize()) + { + mDataLayer = layer; + } + else + { + mLayerInfo.addElement(layer); + } + } + mMasterHashList.addElement(hdr.getMasterHash()); +} + +const fnd::List& HashTreeMeta::getHashLayerInfo() const +{ + return mLayerInfo; +} + +void HashTreeMeta::setHashLayerInfo(const fnd::List& layer_info) +{ + mLayerInfo = layer_info; +} + +const HashTreeMeta::sLayer& HashTreeMeta::getDataLayer() const +{ + return mDataLayer; +} + +void HashTreeMeta::setDataLayer(const sLayer& data_info) +{ + mDataLayer = data_info; +} + +const fnd::List& HashTreeMeta::getMasterHashList() const +{ + return mMasterHashList; +} + +void HashTreeMeta::setMasterHashList(const fnd::List& master_hash_list) +{ + mMasterHashList = master_hash_list; +} + +bool HashTreeMeta::isEqual(const HashTreeMeta& other) const +{ + return (mLayerInfo == other.mLayerInfo) \ + && (mDataLayer == other.mDataLayer) \ + && (mMasterHashList == other.mMasterHashList); +} + +void HashTreeMeta::copyFrom(const HashTreeMeta& other) +{ + mLayerInfo = other.mLayerInfo; + mDataLayer = other.mDataLayer; + mMasterHashList = other.mMasterHashList; +} diff --git a/programs/nstool/source/HashTreeMeta.h b/programs/nstool/source/HashTreeMeta.h new file mode 100644 index 0000000..12d9e54 --- /dev/null +++ b/programs/nstool/source/HashTreeMeta.h @@ -0,0 +1,60 @@ +#pragma once +#include +#include + +class HashTreeMeta +{ +public: + struct sLayer + { + size_t offset; + size_t size; + size_t block_size; + + void operator=(const sLayer& other) + { + offset = other.offset; + size = other.size; + block_size = other.block_size; + } + + bool operator==(const sLayer& other) const + { + return (offset == other.offset && size == other.size && block_size == other.block_size); + } + + bool operator!=(const sLayer& other) const + { + return !(*this == other); + } + }; + + HashTreeMeta(); + HashTreeMeta(const nx::HierarchicalIntegrityHeader& hdr); + HashTreeMeta(const nx::HierarchicalSha256Header& hdr); + + bool operator==(const HashTreeMeta& other) const; + bool operator!=(const HashTreeMeta& other) const; + void operator=(const HashTreeMeta& other); + + void importHierarchicalIntergityHeader(const nx::HierarchicalIntegrityHeader& hdr); + void importHierarchicalSha256Header(const nx::HierarchicalSha256Header& hdr); + + const fnd::List& getHashLayerInfo() const; + void setHashLayerInfo(const fnd::List& layer_info); + + const sLayer& getDataLayer() const; + void setDataLayer(const sLayer& data_info); + + const fnd::List& getMasterHashList() const; + void setMasterHashList(const fnd::List& master_hash_list); +private: + + // data + fnd::List mLayerInfo; + sLayer mDataLayer; + fnd::List mMasterHashList; + + bool isEqual(const HashTreeMeta& other) const; + void copyFrom(const HashTreeMeta& other); +}; \ No newline at end of file From bd991a25e1e836f3866143b6018302891aa99fdc Mon Sep 17 00:00:00 2001 From: jakcron Date: Mon, 21 May 2018 21:08:37 +0800 Subject: [PATCH 34/59] [nstool] Integrate HashTreeMeta into NcaProcess --- programs/nstool/source/NcaProcess.cpp | 81 ++++++++++++++------------- programs/nstool/source/NcaProcess.h | 6 +- 2 files changed, 45 insertions(+), 42 deletions(-) diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index e84f444..a78bb44 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -197,29 +197,6 @@ void NcaProcess::generatePartitionConfiguration() throw fnd::Exception(kModuleName, error.str()); } - // filter out unrecognised hash types, and get data offsets - switch (info.hash_type) - { - case (nx::nca::HASH_NONE): - info.data_offset = info.offset; - info.data_size = info.size; - break; - case (nx::nca::HASH_HIERARCHICAL_SHA256): - info.hash_sha256_header.importBinary(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); - info.data_offset = info.hash_sha256_header.getLayerInfo().atBack().offset; - info.data_size = info.hash_sha256_header.getLayerInfo().atBack().size; - break; - case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): - info.hash_integ_header.importBinary(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); - info.data_offset = info.hash_integ_header.getLayerInfo().atBack().offset; - info.data_size = info.hash_integ_header.getLayerInfo().atBack().size; - break; - default: - error.clear(); - error << "NCA FS Header [" << partition.index << "] HashType(" << info.hash_type << "): UNKNOWN \n"; - throw fnd::Exception(kModuleName, error.str()); - } - // create reader based on encryption type0 switch(info.enc_type) { @@ -238,6 +215,29 @@ void NcaProcess::generatePartitionConfiguration() error << "NCA FS Header [" << partition.index << "] EncryptionType(" << info.enc_type << "): UNKNOWN \n"; throw fnd::Exception(kModuleName, error.str()); } + + // filter out unrecognised hash types, and get data offsets + switch (info.hash_type) + { + case (nx::nca::HASH_NONE): + info.data_offset = info.offset; + info.data_size = info.size; + break; + case (nx::nca::HASH_HIERARCHICAL_SHA256): + info.hash_tree_meta.importHierarchicalSha256Header(nx::HierarchicalSha256Header(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); + info.data_offset = info.hash_tree_meta.getDataLayer().offset; + info.data_size = info.hash_tree_meta.getDataLayer().size; + break; + case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): + info.hash_tree_meta.importHierarchicalIntergityHeader(nx::HierarchicalIntegrityHeader(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); + info.data_offset = info.hash_tree_meta.getDataLayer().offset; + info.data_size = info.hash_tree_meta.getDataLayer().size; + break; + default: + error.clear(); + error << "NCA FS Header [" << partition.index << "] HashType(" << info.hash_type << "): UNKNOWN \n"; + throw fnd::Exception(kModuleName, error.str()); + } } } @@ -367,18 +367,23 @@ void NcaProcess::displayHeader() } if (info.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) { - nx::HierarchicalIntegrityHeader& hash_hdr = info.hash_integ_header; + HashTreeMeta& hash_hdr = info.hash_tree_meta; printf(" HierarchicalIntegrity Header:\n"); //printf(" TypeId: 0x%x\n", hash_hdr.type_id.get()); //printf(" MasterHashSize: 0x%x\n", hash_hdr.master_hash_size.get()); //printf(" LayerNum: %d\n", hash_hdr.getLayerInfo().getSize()); - for (size_t j = 0; j < hash_hdr.getLayerInfo().getSize(); j++) + for (size_t j = 0; j < hash_hdr.getHashLayerInfo().getSize(); j++) { - printf(" Layer %d:\n", j); - printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getLayerInfo()[j].offset); - printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getLayerInfo()[j].size); - printf(" BlockSize: 0x%" PRIx32 "\n", hash_hdr.getLayerInfo()[j].block_size); + printf(" Hash Layer %d:\n", j); + printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getHashLayerInfo()[j].offset); + printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getHashLayerInfo()[j].size); + printf(" BlockSize: 0x%" PRIx32 "\n", hash_hdr.getHashLayerInfo()[j].block_size); } + + printf(" Data Layer:\n"); + printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getDataLayer().offset); + printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getDataLayer().size); + printf(" BlockSize: 0x%" PRIx32 "\n", hash_hdr.getDataLayer().block_size); for (size_t j = 0; j < hash_hdr.getMasterHashList().getSize(); j++) { printf(" Master Hash %d: ", j); @@ -387,18 +392,18 @@ void NcaProcess::displayHeader() } else if (info.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) { - nx::HierarchicalSha256Header& hash_hdr = info.hash_sha256_header; + HashTreeMeta& hash_hdr = info.hash_tree_meta; printf(" HierarchicalSha256 Header:\n"); printf(" Master Hash: "); - fnd::SimpleTextOutput::hexDump(hash_hdr.getMasterHash().bytes, sizeof(crypto::sha::sSha256Hash)); - printf(" HashBlockSize: 0x%x\n", hash_hdr.getHashBlockSize()); + fnd::SimpleTextOutput::hexDump(hash_hdr.getMasterHashList()[0].bytes, sizeof(crypto::sha::sSha256Hash)); + printf(" HashBlockSize: 0x%x\n", hash_hdr.getDataLayer().block_size); //printf(" LayerNum: %d\n", hash_hdr.getLayerInfo().getSize()); - for (size_t i = 0; i < hash_hdr.getLayerInfo().getSize(); i++) - { - printf(" Layer %d:\n", i); - printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getLayerInfo()[i].offset); - printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getLayerInfo()[i].size); - } + printf(" Hash Layer:\n"); + printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getHashLayerInfo()[0].offset); + printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getHashLayerInfo()[0].size); + printf(" Data Layer:\n"); + printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getDataLayer().offset); + printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getDataLayer().size); } //else //{ diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index 55e09ac..591d2f2 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -3,8 +3,7 @@ #include #include #include -#include -#include +#include "HashTreeMeta.h" #include "nstool.h" @@ -72,8 +71,7 @@ private: nx::nca::FormatType format_type; nx::nca::HashType hash_type; nx::nca::EncryptionType enc_type; - nx::HierarchicalSha256Header hash_sha256_header; - nx::HierarchicalIntegrityHeader hash_integ_header; + HashTreeMeta hash_tree_meta; crypto::aes::sAesIvCtr aes_ctr; } mPartitions[nx::nca::kPartitionNum]; From c1f343d052a54feb522457ac4a11e7aa112cad95 Mon Sep 17 00:00:00 2001 From: jakcron Date: Mon, 21 May 2018 23:03:28 +0800 Subject: [PATCH 35/59] [nstool] Add flag to signal hash calc aligning. --- programs/nstool/source/HashTreeMeta.cpp | 25 +++++++++++++++++++++---- programs/nstool/source/HashTreeMeta.h | 6 +++++- 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/programs/nstool/source/HashTreeMeta.cpp b/programs/nstool/source/HashTreeMeta.cpp index 049bd3c..d1e9abd 100644 --- a/programs/nstool/source/HashTreeMeta.cpp +++ b/programs/nstool/source/HashTreeMeta.cpp @@ -3,7 +3,8 @@ HashTreeMeta::HashTreeMeta() : mLayerInfo(), mDataLayer(), - mMasterHashList() + mMasterHashList(), + mDoAlignHashToBlock(false) { } @@ -11,7 +12,8 @@ HashTreeMeta::HashTreeMeta() : HashTreeMeta::HashTreeMeta(const nx::HierarchicalIntegrityHeader& hdr) : mLayerInfo(), mDataLayer(), - mMasterHashList() + mMasterHashList(), + mDoAlignHashToBlock(false) { importHierarchicalIntergityHeader(hdr); } @@ -19,7 +21,8 @@ HashTreeMeta::HashTreeMeta(const nx::HierarchicalIntegrityHeader& hdr) : HashTreeMeta::HashTreeMeta(const nx::HierarchicalSha256Header& hdr) : mLayerInfo(), mDataLayer(), - mMasterHashList() + mMasterHashList(), + mDoAlignHashToBlock(false) { importHierarchicalSha256Header(hdr); } @@ -41,6 +44,7 @@ void HashTreeMeta::operator=(const HashTreeMeta& other) void HashTreeMeta::importHierarchicalIntergityHeader(const nx::HierarchicalIntegrityHeader& hdr) { + mDoAlignHashToBlock = true; for (size_t i = 0; i < hdr.getLayerInfo().getSize(); i++) { sLayer layer; @@ -61,6 +65,7 @@ void HashTreeMeta::importHierarchicalIntergityHeader(const nx::HierarchicalInteg void HashTreeMeta::importHierarchicalSha256Header(const nx::HierarchicalSha256Header& hdr) { + mDoAlignHashToBlock = false; for (size_t i = 0; i < hdr.getLayerInfo().getSize(); i++) { sLayer layer; @@ -109,11 +114,22 @@ void HashTreeMeta::setMasterHashList(const fnd::List& mMasterHashList = master_hash_list; } +bool HashTreeMeta::getAlignHashToBlock() const +{ + return mDoAlignHashToBlock; +} + +void HashTreeMeta::setAlignHashToBlock(bool doAlign) +{ + mDoAlignHashToBlock = doAlign; +} + bool HashTreeMeta::isEqual(const HashTreeMeta& other) const { return (mLayerInfo == other.mLayerInfo) \ && (mDataLayer == other.mDataLayer) \ - && (mMasterHashList == other.mMasterHashList); + && (mMasterHashList == other.mMasterHashList) \ + && (mDoAlignHashToBlock == other.mDoAlignHashToBlock); } void HashTreeMeta::copyFrom(const HashTreeMeta& other) @@ -121,4 +137,5 @@ void HashTreeMeta::copyFrom(const HashTreeMeta& other) mLayerInfo = other.mLayerInfo; mDataLayer = other.mDataLayer; mMasterHashList = other.mMasterHashList; + mDoAlignHashToBlock = other.mDoAlignHashToBlock; } diff --git a/programs/nstool/source/HashTreeMeta.h b/programs/nstool/source/HashTreeMeta.h index 12d9e54..474be89 100644 --- a/programs/nstool/source/HashTreeMeta.h +++ b/programs/nstool/source/HashTreeMeta.h @@ -48,12 +48,16 @@ public: const fnd::List& getMasterHashList() const; void setMasterHashList(const fnd::List& master_hash_list); + + bool getAlignHashToBlock() const; + void setAlignHashToBlock(bool doAlign); private: // data fnd::List mLayerInfo; sLayer mDataLayer; - fnd::List mMasterHashList; + fnd::List mMasterHashList; + bool mDoAlignHashToBlock; bool isEqual(const HashTreeMeta& other) const; void copyFrom(const HashTreeMeta& other); From 18e4c175df9c05e68f698cac5747604d68f320c5 Mon Sep 17 00:00:00 2001 From: jakcron Date: Mon, 21 May 2018 23:03:57 +0800 Subject: [PATCH 36/59] [nstool] Add HashTreeWrappedIFile --- .../nstool/source/HashTreeWrappedIFile.cpp | 210 ++++++++++++++++++ programs/nstool/source/HashTreeWrappedIFile.h | 47 ++++ 2 files changed, 257 insertions(+) create mode 100644 programs/nstool/source/HashTreeWrappedIFile.cpp create mode 100644 programs/nstool/source/HashTreeWrappedIFile.h diff --git a/programs/nstool/source/HashTreeWrappedIFile.cpp b/programs/nstool/source/HashTreeWrappedIFile.cpp new file mode 100644 index 0000000..7f4f644 --- /dev/null +++ b/programs/nstool/source/HashTreeWrappedIFile.cpp @@ -0,0 +1,210 @@ + + +#include "HashTreeWrappedIFile.h" +#include "CopiedIFile.h" +#include "OffsetAdjustedIFile.h" + + +HashTreeWrappedIFile::HashTreeWrappedIFile(fnd::IFile* file, const HashTreeMeta& hdr) : + mOwnIFile(true), + mFile(file), + mData(nullptr), + mDataHashLayer(), + mAlignHashCalcToBlock(false) +{ + initialiseDataLayer(hdr); +} + +HashTreeWrappedIFile::HashTreeWrappedIFile(fnd::IFile* file, bool ownIFile, const HashTreeMeta& hdr) : + mOwnIFile(ownIFile), + mFile(file), + mData(nullptr), + mDataHashLayer(), + mAlignHashCalcToBlock(false) +{ + initialiseDataLayer(hdr); +} + +HashTreeWrappedIFile::~HashTreeWrappedIFile() +{ + if (mOwnIFile) + { + delete mFile; + } + delete mData; +} + +size_t HashTreeWrappedIFile::size() +{ + return mData->size(); +} + +void HashTreeWrappedIFile::seek(size_t offset) +{ + mDataOffset = offset; +} + +void HashTreeWrappedIFile::read(byte_t* out, size_t len) +{ + size_t offset_in_start_block = getOffsetInBlock(mDataOffset); + size_t offset_in_end_block = getOffsetInBlock(offset_in_start_block + len); + + size_t start_block = getOffsetBlock(mDataOffset); + size_t block_num = align(offset_in_start_block + len, mDataBlockSize) / mDataBlockSize; + + size_t scratch_block_capacity = mScratch.getSize() / mDataBlockSize; + + size_t partial_last_block_num = block_num % scratch_block_capacity; + bool has_partial_block_num = partial_last_block_num > 0; + size_t read_iterations = (block_num / scratch_block_capacity) + has_partial_block_num; + + + size_t block_read_len; + size_t block_export_offset; + size_t block_export_size; + size_t block_export_pos = 0; + for (size_t i = 0; i < read_iterations; i++) + { + // how many blocks to read from source file + block_read_len = (i+1 == read_iterations && has_partial_block_num) ? partial_last_block_num : scratch_block_capacity; + + // offset in this current read to copy from + block_export_offset = (i == 0) ? offset_in_start_block : 0; + + // size of current read to copy + block_export_size = (block_read_len * mDataBlockSize) - block_export_offset; + + // if last read, reduce the export size by one block less offset_in_end_block + if (i+1 == read_iterations) + { + block_export_size -= (mDataBlockSize - offset_in_end_block); + } + + // read the blocks + readData(start_block + (i * scratch_block_capacity), block_read_len); + + // export the section of data that is relevant + memcpy(out + block_export_pos, mScratch.getBytes() + block_export_offset, block_export_size); + + // update export position + block_export_pos += block_export_size; + } + + // update offset + mDataOffset += len; +} + +void HashTreeWrappedIFile::read(byte_t* out, size_t offset, size_t len) +{ + seek(offset); + read(out, len); +} + +void HashTreeWrappedIFile::write(const byte_t* out, size_t len) +{ + throw fnd::Exception(kModuleName, "write() not supported"); +} + +void HashTreeWrappedIFile::write(const byte_t* out, size_t offset, size_t len) +{ + throw fnd::Exception(kModuleName, "write() not supported"); +} + +void HashTreeWrappedIFile::initialiseDataLayer(const HashTreeMeta& hdr) +{ + crypto::sha::sSha256Hash hash; + fnd::MemoryBlob cur, prev; + + mAlignHashCalcToBlock = hdr.getAlignHashToBlock(); + + // copy master hash into prev + prev.alloc(sizeof(crypto::sha::sSha256Hash) * hdr.getMasterHashList().getSize()); + for (size_t i = 0; i < hdr.getMasterHashList().getSize(); i++) + { + ((crypto::sha::sSha256Hash*)prev.getBytes())[i] = hdr.getMasterHashList()[i]; + } + + // check each hash layer + for (size_t i = 0; i < hdr.getHashLayerInfo().getSize(); i++) + { + // get block size + const HashTreeMeta::sLayer& layer = hdr.getHashLayerInfo()[i]; + + // allocate layer + cur.alloc(align(layer.size, layer.block_size)); + + // read layer + mFile->read(cur.getBytes(), layer.offset, layer.size); + + // validate blocks + size_t validate_size; + for (size_t j = 0; j < cur.getSize() / layer.block_size; j++) + { + validate_size = mAlignHashCalcToBlock? layer.block_size : MIN(layer.size - (j * layer.block_size), layer.block_size); + crypto::sha::Sha256(cur.getBytes() + (j * layer.block_size), validate_size, hash.bytes); + if (hash.compare(prev.getBytes() + j * sizeof(crypto::sha::sSha256Hash)) == false) + { + mErrorSs << "Hash tree layer verification failed (layer: " << i << ", block: " << j << ")"; + throw fnd::Exception(kModuleName, mErrorSs.str()); + } + } + + // set prev to cur + prev = cur; + } + + // save last layer as hash table for data layer + crypto::sha::sSha256Hash* hash_list = (crypto::sha::sSha256Hash*)prev.getBytes(); + for (size_t i = 0; i < prev.getSize() / sizeof(crypto::sha::sSha256Hash); i++) + { + mDataHashLayer.addElement(hash_list[i]); + } + + // generate reader for data layer + mData = new OffsetAdjustedIFile(mFile, false, hdr.getDataLayer().offset, hdr.getDataLayer().size); + mDataOffset = 0; + mDataBlockSize = hdr.getDataLayer().block_size; + + // allocate scratchpad + mScratch.alloc(mDataBlockSize * 0x10); +} + +void HashTreeWrappedIFile::readData(size_t block_offset, size_t block_num) +{ + seek(block_offset * mDataBlockSize); + crypto::sha::sSha256Hash hash; + + // determine read size + size_t read_len = 0; + if ((block_offset + block_num) == getBlockNum(mData->size())) + { + read_len = (block_num-1) * mDataBlockSize + getRemanderBlockReadSize(mData->size()); + memset(mScratch.getBytes(), 0, block_num * mDataBlockSize); + } + else if ((block_offset + block_num) < getBlockNum(mData->size())) + { + read_len = block_num * mDataBlockSize; + } + else + { + throw fnd::Exception(kModuleName, "Out of bounds file read"); + } + + // read + mData->read(mScratch.getBytes(), block_offset * mDataBlockSize, read_len); + + + + // validate blocks + size_t validate_size; + for (size_t i = 0; i < block_num; i++) + { + validate_size = mAlignHashCalcToBlock? mDataBlockSize : MIN(read_len - (i * mDataBlockSize), mDataBlockSize); + crypto::sha::Sha256(mScratch.getBytes() + (i * mDataBlockSize), validate_size, hash.bytes); + if (hash != mDataHashLayer[block_offset + i]) + { + mErrorSs << "Hash tree layer verification failed (layer: data, block: " << (block_offset + i) << ", offset: 0x" << std::hex << ((block_offset + i) * mDataBlockSize) << ", size: 0x" << std::hex << validate_size <<")"; + throw fnd::Exception(kModuleName, mErrorSs.str()); + } + } +} \ No newline at end of file diff --git a/programs/nstool/source/HashTreeWrappedIFile.h b/programs/nstool/source/HashTreeWrappedIFile.h new file mode 100644 index 0000000..952a834 --- /dev/null +++ b/programs/nstool/source/HashTreeWrappedIFile.h @@ -0,0 +1,47 @@ +#pragma once +#include +#include +#include +#include +#include "HashTreeMeta.h" + + +class HashTreeWrappedIFile : public fnd::IFile +{ +public: + HashTreeWrappedIFile(fnd::IFile* file, const HashTreeMeta& hdr); + HashTreeWrappedIFile(fnd::IFile* file, bool ownIFile, const HashTreeMeta& hdr); + ~HashTreeWrappedIFile(); + + 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 = "HashTreeWrappedIFile"; + std::stringstream mErrorSs; + + bool mOwnIFile; + fnd::IFile* mFile; + + + + // data file + fnd::IFile* mData; + size_t mDataOffset; + size_t mDataBlockSize; + fnd::List mDataHashLayer; + bool mAlignHashCalcToBlock; + + fnd::MemoryBlob mScratch; + + inline size_t getOffsetBlock(size_t offset) const { return offset / mDataBlockSize; } + inline size_t getOffsetInBlock(size_t offset) const { return offset % mDataBlockSize; } + inline size_t getRemanderBlockReadSize(size_t total_size) const { return total_size % mDataBlockSize; } + inline size_t getBlockNum(size_t total_size) const { return (total_size / mDataBlockSize) + (getRemanderBlockReadSize(total_size) > 0); } + + void initialiseDataLayer(const HashTreeMeta& hdr); + void readData(size_t block_offset, size_t block_num); +}; \ No newline at end of file From cd322434c6eb1dbfdedd7f704f742e7706c26638 Mon Sep 17 00:00:00 2001 From: jakcron Date: Mon, 21 May 2018 23:04:35 +0800 Subject: [PATCH 37/59] [nstool] Encorporate HashWrappedIFile to NcaProcess. --- programs/nstool/source/NcaProcess.cpp | 37 ++++++++++++++++++--------- programs/nstool/source/NcaProcess.h | 2 -- 2 files changed, 25 insertions(+), 14 deletions(-) diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index a78bb44..466f469 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -9,6 +9,7 @@ #include "OffsetAdjustedIFile.h" #include "AesCtrWrappedIFile.h" #include "CopiedIFile.h" +#include "HashTreeWrappedIFile.h" std::string kFormatVersionStr[] { @@ -205,10 +206,10 @@ void NcaProcess::generatePartitionConfiguration() info.reader = nullptr; break; case (nx::nca::CRYPT_AESCTR): - info.reader = mBodyKeys.aes_ctr.isSet? new AesCtrWrappedIFile(mReader, mBodyKeys.aes_ctr.var, info.aes_ctr) : nullptr; + info.reader = mBodyKeys.aes_ctr.isSet? new OffsetAdjustedIFile(new AesCtrWrappedIFile(mReader, mBodyKeys.aes_ctr.var, info.aes_ctr), true, info.offset, info.size) : nullptr; break; case (nx::nca::CRYPT_NONE): - info.reader = new CopiedIFile(mReader); + info.reader = new OffsetAdjustedIFile(mReader, info.offset, info.size); break; default: error.clear(); @@ -220,24 +221,36 @@ void NcaProcess::generatePartitionConfiguration() switch (info.hash_type) { case (nx::nca::HASH_NONE): - info.data_offset = info.offset; - info.data_size = info.size; break; case (nx::nca::HASH_HIERARCHICAL_SHA256): info.hash_tree_meta.importHierarchicalSha256Header(nx::HierarchicalSha256Header(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); - info.data_offset = info.hash_tree_meta.getDataLayer().offset; - info.data_size = info.hash_tree_meta.getDataLayer().size; + //info.reader = (info.reader == nullptr) ? nullptr : new OffsetAdjustedIFile(info.reader, true, info.hash_tree_meta.getDataLayer().offset, info.hash_tree_meta.getDataLayer().size); + info.reader = (info.reader == nullptr) ? nullptr : new HashTreeWrappedIFile(info.reader, true, info.hash_tree_meta); break; case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): info.hash_tree_meta.importHierarchicalIntergityHeader(nx::HierarchicalIntegrityHeader(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); - info.data_offset = info.hash_tree_meta.getDataLayer().offset; - info.data_size = info.hash_tree_meta.getDataLayer().size; + //info.reader = (info.reader == nullptr) ? nullptr : new OffsetAdjustedIFile(info.reader, true, info.hash_tree_meta.getDataLayer().offset, info.hash_tree_meta.getDataLayer().size); + info.reader = (info.reader == nullptr) ? nullptr : new HashTreeWrappedIFile(info.reader, true, info.hash_tree_meta); break; default: error.clear(); error << "NCA FS Header [" << partition.index << "] HashType(" << info.hash_type << "): UNKNOWN \n"; throw fnd::Exception(kModuleName, error.str()); } + + + /* + if (info.reader != nullptr) + { + fnd::MemoryBlob sss; + sss.alloc(info.reader->size()); + info.reader->read(sss.getBytes(), sss.getSize()); + printf("[%d] START\n", i); + fnd::SimpleTextOutput::hxdStyleDump(sss.getBytes(), 0x100); + printf("[%d] END\n", i); + } + */ + } } @@ -259,7 +272,7 @@ void NcaProcess::validateNcaSignatures() if (mPartitions[nx::nca::PARTITION_CODE].reader != nullptr) { PfsProcess exefs; - exefs.setInputFile(mPartitions[nx::nca::PARTITION_CODE].reader, mPartitions[nx::nca::PARTITION_CODE].offset + mPartitions[nx::nca::PARTITION_CODE].data_offset, mPartitions[nx::nca::PARTITION_CODE].data_size); + exefs.setInputFile(mPartitions[nx::nca::PARTITION_CODE].reader, 0, mPartitions[nx::nca::PARTITION_CODE].reader->size()); exefs.setCliOutputMode(OUTPUT_MINIMAL); exefs.process(); @@ -269,7 +282,7 @@ void NcaProcess::validateNcaSignatures() const nx::PfsHeader::sFile& file = exefs.getPfsHeader().getFileList()[exefs.getPfsHeader().getFileList().getIndexOf(kNpdmExefsPath)]; NpdmProcess npdm; - npdm.setInputFile(mPartitions[nx::nca::PARTITION_CODE].reader, mPartitions[nx::nca::PARTITION_CODE].offset + mPartitions[nx::nca::PARTITION_CODE].data_offset + file.offset, file.size); + npdm.setInputFile(mPartitions[nx::nca::PARTITION_CODE].reader, file.offset, file.size); npdm.setCliOutputMode(OUTPUT_MINIMAL); npdm.process(); @@ -431,7 +444,7 @@ void NcaProcess::processPartitions() if (partition.format_type == nx::nca::FORMAT_PFS0) { PfsProcess pfs; - pfs.setInputFile(partition.reader, partition.offset + partition.data_offset, partition.data_size); + pfs.setInputFile(partition.reader, 0, partition.reader->size()); pfs.setCliOutputMode(mCliOutputType); pfs.setListFs(mListFs); if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) @@ -452,7 +465,7 @@ void NcaProcess::processPartitions() else if (partition.format_type == nx::nca::FORMAT_ROMFS) { RomfsProcess romfs; - romfs.setInputFile(partition.reader, partition.offset + partition.data_offset, partition.data_size); + romfs.setInputFile(partition.reader, 0, partition.reader->size()); romfs.setCliOutputMode(mCliOutputType); romfs.setListFs(mListFs); if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index 591d2f2..b2ca893 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -64,8 +64,6 @@ private: fnd::IFile* reader; size_t offset; size_t size; - size_t data_offset; - size_t data_size; // meta data nx::nca::FormatType format_type; From 9ee1033e319eeccaf15597b981468862040f0d80 Mon Sep 17 00:00:00 2001 From: jakcron Date: Mon, 21 May 2018 23:06:53 +0800 Subject: [PATCH 38/59] [nstool] Remove unneeded empty lines. --- programs/nstool/source/HashTreeWrappedIFile.cpp | 2 -- 1 file changed, 2 deletions(-) diff --git a/programs/nstool/source/HashTreeWrappedIFile.cpp b/programs/nstool/source/HashTreeWrappedIFile.cpp index 7f4f644..8015761 100644 --- a/programs/nstool/source/HashTreeWrappedIFile.cpp +++ b/programs/nstool/source/HashTreeWrappedIFile.cpp @@ -1,5 +1,3 @@ - - #include "HashTreeWrappedIFile.h" #include "CopiedIFile.h" #include "OffsetAdjustedIFile.h" From 24437935e708ab866d8a9c032cf4b9a1b175125d Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 10:30:31 +0800 Subject: [PATCH 39/59] [nstool] Made a disctinction between NSP and PFS, for future use. --- programs/nstool/source/UserSettings.cpp | 7 ++++--- programs/nstool/source/main.cpp | 2 +- programs/nstool/source/nstool.h | 1 + 3 files changed, 6 insertions(+), 4 deletions(-) diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index 86d57ee..494810f 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -565,10 +565,11 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str) FileType type; if (str == "xci") type = FILE_XCI; - else if (str == "partitionfs" \ + else if (str == "nsp") // may become its own + type = FILE_NSP; + else if (str == "partitionfs" || str == "hashedpartitionfs" \ || str == "pfs" || str == "pfs0" \ - || str == "hfs" || str == "hfs0" \ - || str == "nsp") + || str == "hfs" || str == "hfs0") type = FILE_PARTITIONFS; else if (str == "romfs") type = FILE_ROMFS; diff --git a/programs/nstool/source/main.cpp b/programs/nstool/source/main.cpp index 89afa70..3dea72a 100644 --- a/programs/nstool/source/main.cpp +++ b/programs/nstool/source/main.cpp @@ -38,7 +38,7 @@ int main(int argc, char** argv) xci.process(); } - else if (user_set.getFileType() == FILE_PARTITIONFS) + else if (user_set.getFileType() == FILE_PARTITIONFS || user_set.getFileType() == FILE_NSP) { PfsProcess pfs; diff --git a/programs/nstool/source/nstool.h b/programs/nstool/source/nstool.h index 54686d2..b38e085 100644 --- a/programs/nstool/source/nstool.h +++ b/programs/nstool/source/nstool.h @@ -13,6 +13,7 @@ static const size_t kNcaKeakNum = nx::nca::kKeyAreaEncryptionKeyNum; enum FileType { FILE_XCI, + FILE_NSP, FILE_PARTITIONFS, FILE_ROMFS, FILE_NCA, From d4a5baccebf819448adc89600ec5ad190ae70f40 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 10:30:52 +0800 Subject: [PATCH 40/59] [nstool] Misc --- programs/nstool/source/UserSettings.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index 494810f..988ab58 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -565,7 +565,7 @@ FileType UserSettings::getFileTypeFromString(const std::string& type_str) FileType type; if (str == "xci") type = FILE_XCI; - else if (str == "nsp") // may become its own + else if (str == "nsp") type = FILE_NSP; else if (str == "partitionfs" || str == "hashedpartitionfs" \ || str == "pfs" || str == "pfs0" \ From a2aefc282713522a93890584dd52239aea6cca01 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 13:01:58 +0800 Subject: [PATCH 41/59] [nstool] Fix bug where titlekek_source was not read. --- programs/nstool/source/UserSettings.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/nstool/source/UserSettings.cpp b/programs/nstool/source/UserSettings.cpp index 988ab58..0bb1194 100644 --- a/programs/nstool/source/UserSettings.cpp +++ b/programs/nstool/source/UserSettings.cpp @@ -362,8 +362,8 @@ void UserSettings::populateKeyset(sCmdArgs& args) } _SAVE_KEYDATA(_CONCAT_3_STRINGS(kPackage2Base, kKeyStr, kSourceStr), package2_key_source.key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kTicketCommonKeyBase[0], kKeyStr, kSourceStr), ticket_titlekek_source.key, 0x10); - _SAVE_KEYDATA(_CONCAT_3_STRINGS(kTicketCommonKeyBase[1], kKeyStr, kSourceStr), ticket_titlekek_source.key, 0x10); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[0], kSourceStr), ticket_titlekek_source.key, 0x10); + _SAVE_KEYDATA(_CONCAT_2_STRINGS(kTicketCommonKeyBase[1], kSourceStr), ticket_titlekek_source.key, 0x10); _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[0], kSourceStr), key_area_key_source[0].key, 0x10); _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[1], kSourceStr), key_area_key_source[1].key, 0x10); _SAVE_KEYDATA(_CONCAT_3_STRINGS(kNcaBodyBase[0], kNcaBodyKeakIndexName[2], kSourceStr), key_area_key_source[2].key, 0x10); From c7e79b1d55ace16e7fbed27c7a3514bd89a6efe4 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 13:47:07 +0800 Subject: [PATCH 42/59] [nstool] Fix titlekey decryption. --- programs/nstool/source/NcaProcess.cpp | 21 ++++++++++++++++----- 1 file changed, 16 insertions(+), 5 deletions(-) diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 466f469..2723f9f 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -102,18 +102,16 @@ void NcaProcess::generateNcaBodyEncryptionKeys() // if the titlekey_kek is available if (mKeyset->ticket.titlekey_kek[masterkey_rev] != zero_aesctr_key) { - crypto::aes::sAesIvCtr iv; - iv.set(mHdr.getRightsId()); // the title key is provided (sourced from ticket) if (mKeyset->nca.manual_title_key_aesctr != zero_aesctr_key) { - crypto::aes::AesCbcDecrypt(mKeyset->nca.manual_title_key_aesctr.key, 16, mKeyset->ticket.titlekey_kek[masterkey_rev].key, iv.iv, mBodyKeys.aes_ctr.var.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) { - crypto::aes::AesCbcDecrypt(mKeyset->nca.manual_title_key_aesxts.key[0], 16, mKeyset->ticket.titlekey_kek[masterkey_rev].key, iv.iv, mBodyKeys.aes_xts.var.key[0]); - crypto::aes::AesCbcDecrypt(mKeyset->nca.manual_title_key_aesxts.key[1], 16, mKeyset->ticket.titlekey_kek[masterkey_rev].key, iv.iv, mBodyKeys.aes_xts.var.key[1]); + 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; } } @@ -142,6 +140,19 @@ void NcaProcess::generateNcaBodyEncryptionKeys() { mBodyKeys.aes_xts = mKeyset->nca.manual_body_key_aesxts; } + + if (mBodyKeys.aes_ctr.isSet) + { + printf("AES-CTR Key: "); + fnd::SimpleTextOutput::hexDump(mBodyKeys.aes_ctr.var.key, sizeof(mBodyKeys.aes_ctr.var)); + } + if (mBodyKeys.aes_xts.isSet) + { + printf("AES-XTS Key0: "); + fnd::SimpleTextOutput::hexDump(mBodyKeys.aes_xts.var.key[0], sizeof(mBodyKeys.aes_ctr.var)); + printf("AES-XTS Key1: "); + fnd::SimpleTextOutput::hexDump(mBodyKeys.aes_xts.var.key[1], sizeof(mBodyKeys.aes_ctr.var)); + } } void NcaProcess::generatePartitionConfiguration() From ab1f092d759af39c4066be979e4fa6dfbb9ec7f3 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 14:37:38 +0800 Subject: [PATCH 43/59] [fnd] unstub Exception::error() --- lib/libfnd/source/Exception.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libfnd/source/Exception.cpp b/lib/libfnd/source/Exception.cpp index c50f256..e123cc4 100644 --- a/lib/libfnd/source/Exception.cpp +++ b/lib/libfnd/source/Exception.cpp @@ -44,5 +44,5 @@ const char* Exception::module() const noexcept const char * fnd::Exception::error() const noexcept { - return nullptr; + return error_.c_str(); } From 60eda9e42e59c27bc05e17ce7ece43ce3dca310e Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 14:38:14 +0800 Subject: [PATCH 44/59] [nstool] Give reasons for why a NCA partition is unreadable. --- programs/nstool/source/NcaProcess.cpp | 124 +++++++++++++++----------- programs/nstool/source/NcaProcess.h | 1 + 2 files changed, 75 insertions(+), 50 deletions(-) diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 2723f9f..94c1ee4 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -140,7 +141,7 @@ void NcaProcess::generateNcaBodyEncryptionKeys() { mBodyKeys.aes_xts = mKeyset->nca.manual_body_key_aesxts; } - + /* if (mBodyKeys.aes_ctr.isSet) { printf("AES-CTR Key: "); @@ -153,6 +154,7 @@ void NcaProcess::generateNcaBodyEncryptionKeys() printf("AES-XTS Key1: "); fnd::SimpleTextOutput::hexDump(mBodyKeys.aes_xts.var.key[1], sizeof(mBodyKeys.aes_ctr.var)); } + */ } void NcaProcess::generatePartitionConfiguration() @@ -182,7 +184,7 @@ void NcaProcess::generatePartitionConfiguration() if (fs_header.version.get() != nx::nca::kDefaultFsHeaderVersion) { error.clear(); - error << "NCA FS Header [" << partition.index << "] Version(" << fs_header.version.get() << "): UNSUPPORTED\n"; + error << "NCA FS Header [" << partition.index << "] Version(" << fs_header.version.get() << "): UNSUPPORTED"; throw fnd::Exception(kModuleName, error.str()); } @@ -197,65 +199,82 @@ void NcaProcess::generatePartitionConfiguration() info.hash_type = (nx::nca::HashType)fs_header.hash_type; info.enc_type = (nx::nca::EncryptionType)fs_header.encryption_type; - // filter out unrecognised format types - switch (info.format_type) + try { - case (nx::nca::FORMAT_PFS0): - case (nx::nca::FORMAT_ROMFS): - break; - default: - error.clear(); - error << "NCA FS Header [" << partition.index << "] FormatType(" << info.format_type << "): UNKNOWN \n"; - throw fnd::Exception(kModuleName, error.str()); - } + // filter out unrecognised format types + switch (info.format_type) + { + case (nx::nca::FORMAT_PFS0): + case (nx::nca::FORMAT_ROMFS): + break; + default: + error.clear(); + error << "FormatType(" << info.format_type << "): UNKNOWN"; + throw fnd::Exception(kModuleName, error.str()); + } - // create reader based on encryption type0 - switch(info.enc_type) - { - case (nx::nca::CRYPT_AESXTS): - case (nx::nca::CRYPT_AESCTREX): - info.reader = nullptr; - break; - case (nx::nca::CRYPT_AESCTR): - info.reader = mBodyKeys.aes_ctr.isSet? new OffsetAdjustedIFile(new AesCtrWrappedIFile(mReader, mBodyKeys.aes_ctr.var, info.aes_ctr), true, info.offset, info.size) : nullptr; - break; - case (nx::nca::CRYPT_NONE): + // create reader based on encryption type0 + if (info.enc_type == nx::nca::CRYPT_NONE) + { info.reader = new OffsetAdjustedIFile(mReader, info.offset, info.size); - break; - default: + } + else if (info.enc_type == nx::nca::CRYPT_AESCTR) + { + if (mBodyKeys.aes_ctr.isSet == false) + throw fnd::Exception(kModuleName, "AES-CTR Key was not determined"); + info.reader = new OffsetAdjustedIFile(new AesCtrWrappedIFile(mReader, mBodyKeys.aes_ctr.var, info.aes_ctr), true, info.offset, info.size); + } + else if (info.enc_type == nx::nca::CRYPT_AESXTS || info.enc_type == nx::nca::CRYPT_AESCTREX) + { error.clear(); - error << "NCA FS Header [" << partition.index << "] EncryptionType(" << info.enc_type << "): UNKNOWN \n"; + error << "EncryptionType(" << kEncryptionTypeStr[info.enc_type] << "): UNSUPPORTED"; throw fnd::Exception(kModuleName, error.str()); - } + } + else + { + error.clear(); + error << "EncryptionType(" << info.enc_type << "): UNKNOWN"; + throw fnd::Exception(kModuleName, error.str()); + } - // filter out unrecognised hash types, and get data offsets - switch (info.hash_type) + // filter out unrecognised hash types, and hash based readers + if (info.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256 || info.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) + { + switch(info.hash_type) + { + case (nx::nca::HASH_HIERARCHICAL_SHA256): + info.hash_tree_meta.importHierarchicalSha256Header(nx::HierarchicalSha256Header(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); + break; + case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): + info.hash_tree_meta.importHierarchicalIntergityHeader(nx::HierarchicalIntegrityHeader(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); + break; + } + + fnd::IFile* tmp = info.reader; + info.reader = nullptr; + info.reader = new HashTreeWrappedIFile(tmp, true, info.hash_tree_meta); + } + else if (info.hash_type != nx::nca::HASH_NONE) + { + error.clear(); + error << "HashType(" << info.hash_type << "): UNKNOWN"; + throw fnd::Exception(kModuleName, error.str()); + } + } + catch (const fnd::Exception& e) { - case (nx::nca::HASH_NONE): - break; - case (nx::nca::HASH_HIERARCHICAL_SHA256): - info.hash_tree_meta.importHierarchicalSha256Header(nx::HierarchicalSha256Header(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); - //info.reader = (info.reader == nullptr) ? nullptr : new OffsetAdjustedIFile(info.reader, true, info.hash_tree_meta.getDataLayer().offset, info.hash_tree_meta.getDataLayer().size); - info.reader = (info.reader == nullptr) ? nullptr : new HashTreeWrappedIFile(info.reader, true, info.hash_tree_meta); - break; - case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): - info.hash_tree_meta.importHierarchicalIntergityHeader(nx::HierarchicalIntegrityHeader(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); - //info.reader = (info.reader == nullptr) ? nullptr : new OffsetAdjustedIFile(info.reader, true, info.hash_tree_meta.getDataLayer().offset, info.hash_tree_meta.getDataLayer().size); - info.reader = (info.reader == nullptr) ? nullptr : new HashTreeWrappedIFile(info.reader, true, info.hash_tree_meta); - break; - default: - error.clear(); - error << "NCA FS Header [" << partition.index << "] HashType(" << info.hash_type << "): UNKNOWN \n"; - throw fnd::Exception(kModuleName, error.str()); + info.fail_reason = std::string(e.error()); + if (info.reader != nullptr) + delete info.reader; + info.reader = nullptr; + info.hash_tree_meta = HashTreeMeta(); } - - /* if (info.reader != nullptr) { fnd::MemoryBlob sss; - sss.alloc(info.reader->size()); - info.reader->read(sss.getBytes(), sss.getSize()); + sss.alloc(0x100); + info.reader->read(sss.getBytes(), 0x100); printf("[%d] START\n", i); fnd::SimpleTextOutput::hxdStyleDump(sss.getBytes(), 0x100); printf("[%d] END\n", i); @@ -448,7 +467,12 @@ void NcaProcess::processPartitions() // if the reader is null, skip if (partition.reader == nullptr) { - printf("[WARNING] NCA Partition %d not readable\n", index); + printf("[WARNING] NCA Partition %d not readable.", index); + if (partition.fail_reason.empty() == false) + { + printf(" (%s)", partition.fail_reason.c_str()); + } + printf("\n"); continue; } diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index b2ca893..7b1bdb8 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -62,6 +62,7 @@ private: struct sPartitionInfo { fnd::IFile* reader; + std::string fail_reason; size_t offset; size_t size; From 3437031128a3980bfbc3491e881f0abdcba8c4cc Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 19:03:42 +0800 Subject: [PATCH 45/59] [nstool] NCA processing more robust. --- programs/nstool/source/NcaProcess.cpp | 312 ++++++++++++-------------- 1 file changed, 149 insertions(+), 163 deletions(-) diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 94c1ee4..951aadb 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -81,6 +81,146 @@ std::string kProgramPartitionNameStr[] "logo" }; + +NcaProcess::NcaProcess() : + mReader(nullptr), + mKeyset(nullptr), + mCliOutputType(OUTPUT_NORMAL), + mVerify(false), + mListFs(false) +{ + for (size_t i = 0; i < nx::nca::kPartitionNum; i++) + { + mPartitionPath[i].doExtract = false; + mPartitions[i].reader = nullptr; + } +} + +NcaProcess::~NcaProcess() +{ + if (mReader != nullptr) + { + delete mReader; + } + + for (size_t i = 0; i < nx::nca::kPartitionNum; i++) + { + if (mPartitions[i].reader != nullptr) + { + delete mPartitions[i].reader; + } + } +} + +void NcaProcess::process() +{ + fnd::MemoryBlob scratch; + + if (mReader == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + // read header block + mReader->read((byte_t*)&mHdrBlock, 0, sizeof(nx::sNcaHeaderBlock)); + + // 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); + + // proccess main header + mHdr.importBinary((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader)); + + // determine keys + generateNcaBodyEncryptionKeys(); + + // import/generate fs header data + generatePartitionConfiguration(); + + // validate signatures + if (mVerify) + validateNcaSignatures(); + + // display header + if (mCliOutputType >= OUTPUT_NORMAL) + displayHeader(); + + // process partition + processPartitions(); + + /* + 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 + + */ +} + +void NcaProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) +{ + mReader = new OffsetAdjustedIFile(file, offset, size); +} + +void NcaProcess::setKeyset(const sKeyset* keyset) +{ + mKeyset = keyset; +} + +void NcaProcess::setCliOutputMode(CliOutputType type) +{ + mCliOutputType = type; +} + +void NcaProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +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; +} + void NcaProcess::generateNcaBodyEncryptionKeys() { // create zeros key @@ -147,6 +287,7 @@ void NcaProcess::generateNcaBodyEncryptionKeys() printf("AES-CTR Key: "); fnd::SimpleTextOutput::hexDump(mBodyKeys.aes_ctr.var.key, sizeof(mBodyKeys.aes_ctr.var)); } + if (mBodyKeys.aes_xts.isSet) { printf("AES-XTS Key0: "); @@ -198,7 +339,13 @@ void NcaProcess::generatePartitionConfiguration() info.format_type = (nx::nca::FormatType)fs_header.format_type; info.hash_type = (nx::nca::HashType)fs_header.hash_type; info.enc_type = (nx::nca::EncryptionType)fs_header.encryption_type; + if (info.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256) + info.hash_tree_meta.importHierarchicalSha256Header(nx::HierarchicalSha256Header(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); + else if (info.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) + info.hash_tree_meta.importHierarchicalIntergityHeader(nx::HierarchicalIntegrityHeader(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); + + // create reader try { // filter out unrecognised format types @@ -239,17 +386,7 @@ void NcaProcess::generatePartitionConfiguration() // filter out unrecognised hash types, and hash based readers if (info.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256 || info.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY) - { - switch(info.hash_type) - { - case (nx::nca::HASH_HIERARCHICAL_SHA256): - info.hash_tree_meta.importHierarchicalSha256Header(nx::HierarchicalSha256Header(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); - break; - case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): - info.hash_tree_meta.importHierarchicalIntergityHeader(nx::HierarchicalIntegrityHeader(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen)); - break; - } - + { fnd::IFile* tmp = info.reader; info.reader = nullptr; info.reader = new HashTreeWrappedIFile(tmp, true, info.hash_tree_meta); @@ -263,24 +400,12 @@ void NcaProcess::generatePartitionConfiguration() } catch (const fnd::Exception& e) { + printf("ugh\n"); info.fail_reason = std::string(e.error()); if (info.reader != nullptr) delete info.reader; info.reader = nullptr; - info.hash_tree_meta = HashTreeMeta(); } - /* - if (info.reader != nullptr) - { - fnd::MemoryBlob sss; - sss.alloc(0x100); - info.reader->read(sss.getBytes(), 0x100); - printf("[%d] START\n", i); - fnd::SimpleTextOutput::hxdStyleDump(sss.getBytes(), 0x100); - printf("[%d] END\n", i); - } - */ - } } @@ -520,142 +645,3 @@ void NcaProcess::processPartitions() } } } - -NcaProcess::NcaProcess() : - mReader(nullptr), - mKeyset(nullptr), - mCliOutputType(OUTPUT_NORMAL), - mVerify(false), - mListFs(false) -{ - for (size_t i = 0; i < nx::nca::kPartitionNum; i++) - { - mPartitionPath[i].doExtract = false; - mPartitions[i].reader = nullptr; - } -} - -NcaProcess::~NcaProcess() -{ - if (mReader != nullptr) - { - delete mReader; - } - - for (size_t i = 0; i < nx::nca::kPartitionNum; i++) - { - if (mPartitions[i].reader != nullptr) - { - delete mPartitions[i].reader; - } - } -} - -void NcaProcess::process() -{ - fnd::MemoryBlob scratch; - - if (mReader == nullptr) - { - throw fnd::Exception(kModuleName, "No file reader set."); - } - - // read header block - mReader->read((byte_t*)&mHdrBlock, 0, sizeof(nx::sNcaHeaderBlock)); - - // 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); - - // proccess main header - mHdr.importBinary((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader)); - - // determine keys - generateNcaBodyEncryptionKeys(); - - // import/generate fs header data - generatePartitionConfiguration(); - - // validate signatures - if (mVerify) - validateNcaSignatures(); - - // display header - if (mCliOutputType >= OUTPUT_NORMAL) - displayHeader(); - - // process partition - processPartitions(); - - /* - 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 - - */ -} - -void NcaProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) -{ - mReader = new OffsetAdjustedIFile(file, offset, size); -} - -void NcaProcess::setKeyset(const sKeyset* keyset) -{ - mKeyset = keyset; -} - -void NcaProcess::setCliOutputMode(CliOutputType type) -{ - mCliOutputType = type; -} - -void NcaProcess::setVerifyMode(bool verify) -{ - mVerify = verify; -} - -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; -} From 7c024c290b7d9b7fc7a3d42a605c517e6273823a Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 19:36:39 +0800 Subject: [PATCH 46/59] [nstool] Removed relative offsets in Pfs/Romfs Process because it isn't guarenteed to be reliable. --- programs/nstool/source/PfsProcess.cpp | 6 ++---- programs/nstool/source/PfsProcess.h | 2 -- programs/nstool/source/RomfsProcess.cpp | 4 +--- programs/nstool/source/RomfsProcess.h | 2 -- 4 files changed, 3 insertions(+), 11 deletions(-) diff --git a/programs/nstool/source/PfsProcess.cpp b/programs/nstool/source/PfsProcess.cpp index ae2d3e4..b108c2c 100644 --- a/programs/nstool/source/PfsProcess.cpp +++ b/programs/nstool/source/PfsProcess.cpp @@ -20,9 +20,9 @@ void PfsProcess::displayFs() if (mCliOutputType >= OUTPUT_VERBOSE) { if (mPfs.getFsType() == mPfs.TYPE_PFS0) - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", mBaseOffset + mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size); + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size); else - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", mBaseOffset + mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size, mPfs.getFileList()[i].hash_protected_size); + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size, mPfs.getFileList()[i].hash_protected_size); } else { @@ -105,7 +105,6 @@ PfsProcess::PfsProcess() : mReader(nullptr), mCliOutputType(OUTPUT_NORMAL), mVerify(false), - mBaseOffset(0), mExtractPath(), mExtract(false), mMountName(), @@ -158,7 +157,6 @@ void PfsProcess::process() void PfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) { mReader = new OffsetAdjustedIFile(file, offset, size); - mBaseOffset = offset; } void PfsProcess::setCliOutputMode(CliOutputType type) diff --git a/programs/nstool/source/PfsProcess.h b/programs/nstool/source/PfsProcess.h index 9439eae..18ad507 100644 --- a/programs/nstool/source/PfsProcess.h +++ b/programs/nstool/source/PfsProcess.h @@ -34,8 +34,6 @@ private: CliOutputType mCliOutputType; bool mVerify; - size_t mBaseOffset; - std::string mExtractPath; bool mExtract; std::string mMountName; diff --git a/programs/nstool/source/RomfsProcess.cpp b/programs/nstool/source/RomfsProcess.cpp index c11e46c..1a0f8a3 100644 --- a/programs/nstool/source/RomfsProcess.cpp +++ b/programs/nstool/source/RomfsProcess.cpp @@ -18,7 +18,7 @@ void RomfsProcess::displayFile(const sFile& file, size_t tab) const printf("%s", file.name.c_str()); if (mCliOutputType >= OUTPUT_VERBOSE) { - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")", mBaseOffset + file.offset, file.size); + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")", file.offset, file.size); } putchar('\n'); } @@ -226,7 +226,6 @@ RomfsProcess::RomfsProcess() : mReader(nullptr), mCliOutputType(OUTPUT_NORMAL), mVerify(false), - mBaseOffset(0), mExtractPath(), mExtract(false), mMountName(), @@ -267,7 +266,6 @@ void RomfsProcess::process() void RomfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) { mReader = new OffsetAdjustedIFile(file, offset, size); - mBaseOffset = offset; } void RomfsProcess::setCliOutputMode(CliOutputType type) diff --git a/programs/nstool/source/RomfsProcess.h b/programs/nstool/source/RomfsProcess.h index 2719308..5c2a2e0 100644 --- a/programs/nstool/source/RomfsProcess.h +++ b/programs/nstool/source/RomfsProcess.h @@ -112,8 +112,6 @@ private: CliOutputType mCliOutputType; bool mVerify; - size_t mBaseOffset; - std::string mExtractPath; bool mExtract; std::string mMountName; From e1ccf81e54fa8d93c15943f0078d48c042faea1e Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 19:38:05 +0800 Subject: [PATCH 47/59] [nstool] Fixed a bug in OffsetAdjustedIFile that failed to track file position. --- programs/nstool/source/OffsetAdjustedIFile.cpp | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/programs/nstool/source/OffsetAdjustedIFile.cpp b/programs/nstool/source/OffsetAdjustedIFile.cpp index d2fb0cf..4690217 100644 --- a/programs/nstool/source/OffsetAdjustedIFile.cpp +++ b/programs/nstool/source/OffsetAdjustedIFile.cpp @@ -36,13 +36,14 @@ size_t OffsetAdjustedIFile::size() void OffsetAdjustedIFile::seek(size_t offset) { mCurrentOffset = MIN(offset, mSize); - mFile->seek(offset + mBaseOffset); } void OffsetAdjustedIFile::read(byte_t* out, size_t len) { - seek(mCurrentOffset); + // assert proper position in file + mFile->seek(mCurrentOffset + mBaseOffset); mFile->read(out, len); + mCurrentOffset += len; } void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len) @@ -53,8 +54,10 @@ void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len) void OffsetAdjustedIFile::write(const byte_t* out, size_t len) { - seek(mCurrentOffset); + // assert proper position in file + mFile->seek(mCurrentOffset + mBaseOffset); mFile->write(out, len); + mCurrentOffset += len; } void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len) From 33313f208db77c663a9d5a91e06a47a88709d900 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 19:45:40 +0800 Subject: [PATCH 48/59] [nstool] Where not guarenteed to control file position, IFile wrappers assert their position. --- programs/nstool/source/AesCtrWrappedIFile.cpp | 28 +++++++++++++------ programs/nstool/source/AesCtrWrappedIFile.h | 3 ++ .../nstool/source/OffsetAdjustedIFile.cpp | 4 +-- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/programs/nstool/source/AesCtrWrappedIFile.cpp b/programs/nstool/source/AesCtrWrappedIFile.cpp index 134ded9..8ce436e 100644 --- a/programs/nstool/source/AesCtrWrappedIFile.cpp +++ b/programs/nstool/source/AesCtrWrappedIFile.cpp @@ -1,19 +1,16 @@ #include "AesCtrWrappedIFile.h" AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile* file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) : - mOwnIFile(false), - mFile(file), - mKey(key), - mBaseCtr(ctr) + AesCtrWrappedIFile(file, false, key, ctr) { - mScratch.alloc(kAesCtrScratchAllocSize); } AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile* file, bool ownIfile, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) : mOwnIFile(ownIfile), mFile(file), mKey(key), - mBaseCtr(ctr) + mBaseCtr(ctr), + mFileOffset(0) { mScratch.alloc(kAesCtrScratchAllocSize); } @@ -33,13 +30,13 @@ size_t AesCtrWrappedIFile::size() void AesCtrWrappedIFile::seek(size_t offset) { - mFile->seek(offset); - crypto::aes::AesIncrementCounter(mBaseCtr.iv, offset>>4, mCurrentCtr.iv); - mBlockOffset = offset & 0xf; + mFileOffset = offset; } void AesCtrWrappedIFile::read(byte_t* out, size_t len) { + internalSeek(); + for (size_t i = 0; i < (len / kAesCtrScratchSize); i++) { mFile->read(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize); @@ -55,6 +52,8 @@ void AesCtrWrappedIFile::read(byte_t* out, size_t len) crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes()); memcpy(out + read_pos, mScratch.getBytes() + mBlockOffset, read_len); } + + seek(mFileOffset + len); } void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len) @@ -65,6 +64,8 @@ void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len) void AesCtrWrappedIFile::write(const byte_t* out, size_t len) { + internalSeek(); + for (size_t i = 0; i < (len / kAesCtrScratchSize); i++) { memcpy(mScratch.getBytes() + mBlockOffset, out + (i * kAesCtrScratchSize), kAesCtrScratchSize); @@ -80,10 +81,19 @@ void AesCtrWrappedIFile::write(const byte_t* out, size_t len) crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes()); mFile->write(mScratch.getBytes() + mBlockOffset, write_len); } + + seek(mFileOffset + len); } void AesCtrWrappedIFile::write(const byte_t* out, size_t offset, size_t len) { seek(offset); write(out, len); +} + +void AesCtrWrappedIFile::internalSeek() +{ + mFile->seek(mFileOffset); + crypto::aes::AesIncrementCounter(mBaseCtr.iv, mFileOffset>>4, mCurrentCtr.iv); + mBlockOffset = mFileOffset & 0xf; } \ No newline at end of file diff --git a/programs/nstool/source/AesCtrWrappedIFile.h b/programs/nstool/source/AesCtrWrappedIFile.h index 3eaaaad..0d30754 100644 --- a/programs/nstool/source/AesCtrWrappedIFile.h +++ b/programs/nstool/source/AesCtrWrappedIFile.h @@ -25,6 +25,9 @@ private: crypto::aes::sAes128Key mKey; crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr; size_t mBlockOffset; + size_t mFileOffset; fnd::MemoryBlob mScratch; + + void internalSeek(); }; \ No newline at end of file diff --git a/programs/nstool/source/OffsetAdjustedIFile.cpp b/programs/nstool/source/OffsetAdjustedIFile.cpp index 4690217..e4dcd49 100644 --- a/programs/nstool/source/OffsetAdjustedIFile.cpp +++ b/programs/nstool/source/OffsetAdjustedIFile.cpp @@ -43,7 +43,7 @@ void OffsetAdjustedIFile::read(byte_t* out, size_t len) // assert proper position in file mFile->seek(mCurrentOffset + mBaseOffset); mFile->read(out, len); - mCurrentOffset += len; + seek(mCurrentOffset + len); } void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len) @@ -57,7 +57,7 @@ void OffsetAdjustedIFile::write(const byte_t* out, size_t len) // assert proper position in file mFile->seek(mCurrentOffset + mBaseOffset); mFile->write(out, len); - mCurrentOffset += len; + seek(mCurrentOffset + len); } void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len) From 7428640df91a75432f5b5c20ad0c9ae1d8f14c41 Mon Sep 17 00:00:00 2001 From: jakcron Date: Tue, 22 May 2018 19:55:56 +0800 Subject: [PATCH 49/59] [nstool] Whitespace. --- programs/nstool/source/HashTreeWrappedIFile.h | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/programs/nstool/source/HashTreeWrappedIFile.h b/programs/nstool/source/HashTreeWrappedIFile.h index 952a834..5e92014 100644 --- a/programs/nstool/source/HashTreeWrappedIFile.h +++ b/programs/nstool/source/HashTreeWrappedIFile.h @@ -24,9 +24,7 @@ private: std::stringstream mErrorSs; bool mOwnIFile; - fnd::IFile* mFile; - - + fnd::IFile* mFile; // data file fnd::IFile* mData; From cd00be22211c9ec853a2c120cde48a216591753c Mon Sep 17 00:00:00 2001 From: jakcron Date: Wed, 23 May 2018 15:07:31 +0800 Subject: [PATCH 50/59] [nstool] Add source to VS Project files. --- programs/nstool/nstool.vcxproj | 4 ++++ programs/nstool/nstool.vcxproj.filters | 12 ++++++++++++ 2 files changed, 16 insertions(+) diff --git a/programs/nstool/nstool.vcxproj b/programs/nstool/nstool.vcxproj index 8acf80d..04304c1 100644 --- a/programs/nstool/nstool.vcxproj +++ b/programs/nstool/nstool.vcxproj @@ -165,6 +165,8 @@ + + @@ -178,6 +180,8 @@ + + diff --git a/programs/nstool/nstool.vcxproj.filters b/programs/nstool/nstool.vcxproj.filters index f012b76..f4b0f20 100644 --- a/programs/nstool/nstool.vcxproj.filters +++ b/programs/nstool/nstool.vcxproj.filters @@ -51,6 +51,12 @@ Header Files + + Header Files + + + Header Files + @@ -83,6 +89,12 @@ Source Files + + Source Files + + + Source Files + From 0b0d5466056d9902c9423e584934ccaaad02507d Mon Sep 17 00:00:00 2001 From: jakcron Date: Wed, 23 May 2018 15:19:54 +0800 Subject: [PATCH 51/59] [nstool] Change processing of NCA key area. --- programs/nstool/source/NcaProcess.cpp | 83 +++++++++++++++++++++------ programs/nstool/source/NcaProcess.h | 30 ++++++++++ 2 files changed, 96 insertions(+), 17 deletions(-) diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index 951aadb..d860938 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -233,6 +233,27 @@ void NcaProcess::generateNcaBodyEncryptionKeys() byte_t masterkey_rev = nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(mHdr.getKeyGeneration()); byte_t keak_index = mHdr.getKaekIndex(); + // process key area + sKeys::sKeyAreaKey keak; + for (size_t i = 0; i < nx::nca::kAesKeyNum; i++) + { + if (mHdr.getEncAesKeys()[i] != zero_aesctr_key) + { + keak.index = (byte_t)i; + keak.enc = mHdr.getEncAesKeys()[i]; + if (i < 4 && mKeyset->nca.key_area_key[keak_index][masterkey_rev] != zero_aesctr_key) + { + keak.decrypted = true; + nx::AesKeygen::generateKey(keak.dec.key, keak.enc.key, mKeyset->nca.key_area_key[keak_index][masterkey_rev].key); + } + else + { + keak.decrypted = false; + } + mBodyKeys.keak_list.addElement(keak); + } + } + // set flag to indicate that the keys are not available mBodyKeys.aes_ctr.isSet = false; mBodyKeys.aes_xts.isSet = false; @@ -260,15 +281,31 @@ void NcaProcess::generateNcaBodyEncryptionKeys() // otherwise decrypt key area else { - // if the key_area_key is available - if (mKeyset->nca.key_area_key[keak_index][masterkey_rev] != zero_aesctr_key) + crypto::aes::sAes128Key keak_aesctr_key = zero_aesctr_key; + crypto::aes::sAesXts128Key keak_aesxts_key = zero_aesxts_key; + for (size_t i = 0; i < mBodyKeys.keak_list.getSize(); i++) { - 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 (mBodyKeys.keak_list[i].index == nx::nca::KEY_AESCTR && mBodyKeys.keak_list[i].decrypted) + { + keak_aesctr_key = mBodyKeys.keak_list[i].dec; + } + else if (mBodyKeys.keak_list[i].index == nx::nca::KEY_AESXTS_0 && mBodyKeys.keak_list[i].decrypted) + { + memcpy(keak_aesxts_key.key[0], mBodyKeys.keak_list[i].dec.key, sizeof(crypto::aes::sAes128Key)); + } + else if (mBodyKeys.keak_list[i].index == nx::nca::KEY_AESXTS_1 && mBodyKeys.keak_list[i].decrypted) + { + memcpy(keak_aesxts_key.key[1], mBodyKeys.keak_list[i].dec.key, sizeof(crypto::aes::sAes128Key)); + } + } + + if (keak_aesctr_key != zero_aesctr_key) + { + mBodyKeys.aes_ctr = keak_aesctr_key; + } + if (keak_aesxts_key != zero_aesxts_key) + { + mBodyKeys.aes_xts = keak_aesxts_key; } } @@ -400,7 +437,6 @@ void NcaProcess::generatePartitionConfiguration() } catch (const fnd::Exception& e) { - printf("ugh\n"); info.fail_reason = std::string(e.error()); if (info.reader != nullptr) delete info.reader; @@ -476,9 +512,6 @@ void NcaProcess::validateNcaSignatures() void NcaProcess::displayHeader() { - crypto::aes::sAes128Key zero_key; - memset(zero_key.key, 0, sizeof(zero_key)); - printf("[NCA Header]\n"); printf(" Format Type: %s\n", kFormatVersionStr[mHdr.getFormatVersion()].c_str()); printf(" Dist. Type: %s\n", kDistributionTypeStr[mHdr.getDistributionType()].c_str()); @@ -493,15 +526,31 @@ void NcaProcess::displayHeader() #undef _SPLIT_VER printf(" RightsId: "); fnd::SimpleTextOutput::hexDump(mHdr.getRightsId(), nx::nca::kRightsIdLen); - printf(" Key Area Keys: (Encrypted)\n"); - for (size_t i = 0; i < mHdr.getEncAesKeys().getSize(); i++) + + if (mBodyKeys.keak_list.getSize() > 0) { - if (mHdr.getEncAesKeys()[i] != zero_key) + printf(" Key Area: \n"); + printf(" <--------------------------------------------------------------------------->\n"); + printf(" | IDX | ENCRYPTED KEY | DECRYPTED KEY |\n"); + printf(" |-----|----------------------------------|----------------------------------|\n"); + for (size_t i = 0; i < mBodyKeys.keak_list.getSize(); i++) { - printf(" %2lu: ", i); - fnd::SimpleTextOutput::hexDump(mHdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize); + printf(" | %3lu | ", mBodyKeys.keak_list[i].index); + + for (size_t j = 0; j < 16; j++) printf("%02x", mBodyKeys.keak_list[i].enc.key[j]); + + printf(" | "); + + if (mBodyKeys.keak_list[i].decrypted) + for (size_t j = 0; j < 16; j++) printf("%02x", mBodyKeys.keak_list[i].dec.key[j]); + else + printf(" "); + + printf(" |\n"); } + printf(" <--------------------------------------------------------------------------->\n"); } + /* if (mBodyKeyList.getSize() > 0) diff --git a/programs/nstool/source/NcaProcess.h b/programs/nstool/source/NcaProcess.h index 7b1bdb8..666c9a4 100644 --- a/programs/nstool/source/NcaProcess.h +++ b/programs/nstool/source/NcaProcess.h @@ -55,6 +55,36 @@ private: // crypto struct sKeys { + struct sKeyAreaKey + { + byte_t index; + bool decrypted; + crypto::aes::sAes128Key enc; + crypto::aes::sAes128Key dec; + + void operator=(const sKeyAreaKey& other) + { + index = other.index; + decrypted = other.decrypted; + enc = other.enc; + dec = other.dec; + } + + bool operator==(const sKeyAreaKey& other) const + { + return (index == other.index) \ + && (decrypted == other.decrypted) \ + && (enc == other.enc) \ + && (dec == other.dec); + } + + bool operator!=(const sKeyAreaKey& other) const + { + return !(*this == other); + } + }; + fnd::List keak_list; + sOptional aes_ctr; sOptional aes_xts; } mBodyKeys; From 57d03b607626e591e55e7077209e2141aa85d5be Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 25 May 2018 22:53:19 +0800 Subject: [PATCH 52/59] [nstool] Fixed seek() typo in HashTreeWrappedIFile --- programs/nstool/source/HashTreeWrappedIFile.cpp | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/programs/nstool/source/HashTreeWrappedIFile.cpp b/programs/nstool/source/HashTreeWrappedIFile.cpp index 8015761..dd54449 100644 --- a/programs/nstool/source/HashTreeWrappedIFile.cpp +++ b/programs/nstool/source/HashTreeWrappedIFile.cpp @@ -89,7 +89,7 @@ void HashTreeWrappedIFile::read(byte_t* out, size_t len) } // update offset - mDataOffset += len; + seek(mDataOffset + len); } void HashTreeWrappedIFile::read(byte_t* out, size_t offset, size_t len) @@ -169,7 +169,7 @@ void HashTreeWrappedIFile::initialiseDataLayer(const HashTreeMeta& hdr) void HashTreeWrappedIFile::readData(size_t block_offset, size_t block_num) { - seek(block_offset * mDataBlockSize); + mData->seek(block_offset * mDataBlockSize); crypto::sha::sSha256Hash hash; // determine read size From 93509fac5ab89c893e3a5c962e1798a0c41986d6 Mon Sep 17 00:00:00 2001 From: jakcron Date: Fri, 25 May 2018 22:54:30 +0800 Subject: [PATCH 53/59] [fnd] Implement Windows 64bit size_t support in SimpleFile. --- lib/libfnd/include/fnd/SimpleFile.h | 15 +- lib/libfnd/source/SimpleFile.cpp | 230 +++++++++++++++++++++++++++- 2 files changed, 239 insertions(+), 6 deletions(-) diff --git a/lib/libfnd/include/fnd/SimpleFile.h b/lib/libfnd/include/fnd/SimpleFile.h index 8499dd9..0b50e60 100644 --- a/lib/libfnd/include/fnd/SimpleFile.h +++ b/lib/libfnd/include/fnd/SimpleFile.h @@ -1,7 +1,11 @@ #pragma once #include #include +#ifdef _WIN32 +#include +#else #include +#endif namespace fnd { @@ -34,9 +38,16 @@ namespace fnd bool mOpen; OpenMode mMode; - FILE* mFp; - const char* getOpenModeStr(OpenMode mMode); +#ifdef _WIN32 + HANDLE mFileHandle; + DWORD getOpenModeFlag(OpenMode mode) const; + DWORD getShareModeFlag(OpenMode mode) const; + DWORD getCreationModeFlag(OpenMode mode) const; +#else + FILE* mFp; + const char* getOpenModeStr(OpenMode mode); +#endif }; } diff --git a/lib/libfnd/source/SimpleFile.cpp b/lib/libfnd/source/SimpleFile.cpp index b12156a..d4cf048 100644 --- a/lib/libfnd/source/SimpleFile.cpp +++ b/lib/libfnd/source/SimpleFile.cpp @@ -1,11 +1,16 @@ #include +#include using namespace fnd; SimpleFile::SimpleFile() : mOpen(false), mMode(Read), +#ifdef _WIN32 + mFileHandle() +#else mFp(nullptr) +#endif { } @@ -16,6 +21,29 @@ SimpleFile::~SimpleFile() void SimpleFile::open(const std::string& path, OpenMode mode) { +#ifdef _WIN32 + // convert string to unicode + std::u16string unicodePath = fnd::StringConv::ConvertChar8ToChar16(path); + + // save mode + mMode = mode; + + // open file + mFileHandle = CreateFileW((LPCWSTR)unicodePath.c_str(), + getOpenModeFlag(mMode), + getShareModeFlag(mMode), + 0, + getCreationModeFlag(mMode), + FILE_ATTRIBUTE_NORMAL, + NULL); + // check file handle + if (mFileHandle == INVALID_HANDLE_VALUE) + { + throw fnd::Exception(kModuleName, "Failed to open file."); + } + + +#else //close(); mMode = mode; //printf("fopen(%s,%s);\n", path.c_str(), getOpenModeStr(mMode)); @@ -23,46 +51,140 @@ void SimpleFile::open(const std::string& path, OpenMode mode) if (mFp == nullptr) throw fnd::Exception(kModuleName, "Failed to open file."); mOpen = true; +#endif + seek(0); } bool SimpleFile::isOpen() const { - return mOpen == true && mFp != nullptr; + return mOpen == true; } void SimpleFile::close() { if (isOpen()) { +#ifdef _WIN32 + CloseHandle(mFileHandle); +#else fclose(mFp); + mFp = nullptr; +#endif } - mFp = nullptr; mOpen = false; } size_t SimpleFile::size() { + size_t fsize = 0; +#ifdef _WIN32 + if (mMode != Create) + { + LARGE_INTEGER win_fsize; + if (GetFileSizeEx(mFileHandle, &win_fsize) == false) + { + throw fnd::Exception(kModuleName, "Failed to check filesize"); + } + + fsize = win_fsize.QuadPart; + } + else + { + fsize = 0; + } +#else size_t cur_pos = pos(); fseek(mFp, 0, SEEK_END); - size_t fsize = pos(); + fsize = pos(); seek(cur_pos); +#endif return fsize; } void SimpleFile::seek(size_t offset) { +#ifdef _WIN32 + LARGE_INTEGER win_pos, out; + win_pos.QuadPart = offset; + if (SetFilePointerEx( + mFileHandle, + win_pos, + &out, + FILE_BEGIN + ) == false || out.QuadPart != win_pos.QuadPart) + { + throw fnd::Exception(kModuleName, "Failed to change file offset"); + } +#else fseek(mFp, offset, SEEK_SET); +#endif } size_t SimpleFile::pos() { +#ifdef _WIN32 + LARGE_INTEGER win_pos, out; + win_pos.QuadPart = 0; + if (SetFilePointerEx( + mFileHandle, + win_pos, + &out, + FILE_CURRENT + ) == false) + { + throw fnd::Exception(kModuleName, "Failed to check file offset"); + } + + return out.QuadPart; +#else return ftell(mFp); +#endif } void SimpleFile::read(byte_t* out, size_t len) { +#ifdef _WIN32 + LARGE_INTEGER win_len; + win_len.QuadPart = len; + + static const DWORD kDwordHalf = (MAXDWORD / (DWORD)2) + 1; // 0x80000000 + static const size_t kDwordFull = (size_t)kDwordHalf * (size_t)2; // 0x100000000 + + // if the size is greater than a DWORD, read it in parts, + for (LONG i = 0; i < win_len.HighPart; i++) + { + // since kDwordFull isn't a valid DWORD value, read in two parts + ReadFile( + mFileHandle, + out + i * kDwordFull, + kDwordHalf, + NULL, + NULL + ); + ReadFile( + mFileHandle, + out + i * kDwordFull + kDwordHalf, + kDwordHalf, + NULL, + NULL + ); + } + + // read remainding low part + if (win_len.LowPart > 0) + { + ReadFile( + mFileHandle, + out + win_len.HighPart * kDwordFull, + win_len.LowPart, + NULL, + NULL + ); + } +#else fread(out, len, 1, mFp); +#endif } void SimpleFile::read(byte_t* out, size_t offset, size_t len) @@ -73,7 +195,47 @@ void SimpleFile::read(byte_t* out, size_t offset, size_t len) void SimpleFile::write(const byte_t* out, size_t len) { +#ifdef _WIN32 + LARGE_INTEGER win_len; + win_len.QuadPart = len; + + static const DWORD kDwordHalf = ((DWORD)MAXDWORD / (DWORD)2) + 1; // 0x80000000 + static const size_t kDwordFull = (size_t)kDwordHalf * (size_t)2; // 0x100000000 + + // if the size is greater than a DWORD, read it in parts, + for (LONG i = 0; i < win_len.HighPart; i++) + { + // since kDwordFull isn't a valid DWORD value, read in two parts + WriteFile( + mFileHandle, + out + i * kDwordFull, + kDwordHalf, + NULL, + NULL + ); + WriteFile( + mFileHandle, + out + i * kDwordFull + kDwordHalf, + kDwordHalf, + NULL, + NULL + ); + } + + // read remainding low part + if (win_len.LowPart > 0) + { + WriteFile( + mFileHandle, + out + win_len.HighPart * kDwordFull, + win_len.LowPart, + NULL, + NULL + ); + } +#else fwrite(out, len, 1, mFp); +#endif } void SimpleFile::write(const byte_t* out, size_t offset, size_t len) @@ -82,6 +244,65 @@ void SimpleFile::write(const byte_t* out, size_t offset, size_t len) write(out, len); } +#ifdef _WIN32 +DWORD SimpleFile::getOpenModeFlag(OpenMode mode) const +{ + DWORD flag = 0; + switch (mode) + { + case (Read): + flag = GENERIC_READ; + break; + case (Edit): + flag = GENERIC_READ | GENERIC_WRITE; + break; + case (Create): + flag = GENERIC_WRITE; + break; + default: + throw fnd::Exception(kModuleName, "Unknown open mode"); + } + return flag; +} +DWORD fnd::SimpleFile::getShareModeFlag(OpenMode mode) const +{ + DWORD flag = 0; + switch (mode) + { + case (Read): + flag = FILE_SHARE_READ; + break; + case (Edit): + flag = FILE_SHARE_READ; + break; + case (Create): + flag = 0; + break; + default: + throw fnd::Exception(kModuleName, "Unknown open mode"); + } + return flag; +} +DWORD fnd::SimpleFile::getCreationModeFlag(OpenMode mode) const +{ + DWORD flag = 0; + switch (mode) + { + case (Read): + flag = OPEN_EXISTING; + break; + case (Edit): + flag = OPEN_EXISTING; + break; + case (Create): + flag = CREATE_ALWAYS; + break; + default: + throw fnd::Exception(kModuleName, "Unknown open mode"); + } + return flag; +} +#else const char* SimpleFile::getOpenModeStr(OpenMode mode) { const char* str = ""; @@ -100,4 +321,5 @@ const char* SimpleFile::getOpenModeStr(OpenMode mode) throw fnd::Exception(kModuleName, "Unknown open mode"); } return str; -} \ No newline at end of file +} +#endif \ No newline at end of file From 8b37665be6f4fc793f9ad208234a3ae8edae43a6 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 26 May 2018 11:26:56 +0800 Subject: [PATCH 54/59] [nx|nstool] Changed Romfs FileNode and DirNode to not include an empty name[] variable. --- lib/libnx/include/nx/romfs.h | 6 ++++-- programs/nstool/source/RomfsProcess.cpp | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/libnx/include/nx/romfs.h b/lib/libnx/include/nx/romfs.h index 21e2aeb..62fe0ec 100644 --- a/lib/libnx/include/nx/romfs.h +++ b/lib/libnx/include/nx/romfs.h @@ -42,7 +42,8 @@ namespace nx le_uint32_t file; le_uint32_t hash; le_uint32_t name_size; - char name[]; + char* name() { return ((char*)(this)) + sizeof(sRomfsDirEntry); } + const char* name() const { return ((char*)(this)) + sizeof(sRomfsDirEntry); } }; struct sRomfsFileEntry @@ -53,7 +54,8 @@ namespace nx le_uint64_t size; le_uint32_t hash; le_uint32_t name_size; - char name[]; + char* name() { return ((char*)(this)) + sizeof(sRomfsFileEntry); } + const char* name() const { return ((char*)(this)) + sizeof(sRomfsFileEntry); } }; #pragma pack(pop) } diff --git a/programs/nstool/source/RomfsProcess.cpp b/programs/nstool/source/RomfsProcess.cpp index 1a0f8a3..115f2ce 100644 --- a/programs/nstool/source/RomfsProcess.cpp +++ b/programs/nstool/source/RomfsProcess.cpp @@ -166,7 +166,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir) printf(" name=%s\n", f_node->name); */ - dir.file_list.addElement({std::string(f_node->name, f_node->name_size.get()), mHdr.data_offset.get() + f_node->offset.get(), f_node->size.get()}); + dir.file_list.addElement({std::string(f_node->name(), f_node->name_size.get()), mHdr.data_offset.get() + f_node->offset.get(), f_node->size.get()}); file_addr = f_node->sibling.get(); mFileNum++; @@ -176,7 +176,7 @@ void RomfsProcess::importDirectory(uint32_t dir_offset, sDirectory& dir) { nx::sRomfsDirEntry* c_node = get_dir_node(child_addr); - dir.dir_list.addElement({std::string(c_node->name, c_node->name_size.get())}); + dir.dir_list.addElement({std::string(c_node->name(), c_node->name_size.get())}); importDirectory(child_addr, dir.dir_list.atBack()); child_addr = c_node->sibling.get(); From 76ce22dc69af9604a1ebe2e451ae5edc6e06dd8e Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 26 May 2018 12:07:42 +0800 Subject: [PATCH 55/59] [nx|nstool] Silence format warnings. Cleanup string resource processing. --- lib/libcrypto/source/aes.cpp | 2 +- lib/libfnd/source/StringConv.cpp | 24 +- lib/libnx/source/AciHeader.cpp | 14 +- lib/libnx/source/FacHeader.cpp | 8 +- lib/libnx/source/NcaHeader.cpp | 2 +- lib/libnx/source/NpdmHeader.cpp | 8 +- lib/libnx/source/PfsHeader.cpp | 12 +- lib/libnx/source/SystemCallHandler.cpp | 2 +- programs/nstool/source/CnmtProcess.cpp | 66 +++-- programs/nstool/source/NcaProcess.cpp | 332 +++++++++++++++++------- programs/nstool/source/PfsProcess.cpp | 2 +- programs/nstool/source/RomfsProcess.cpp | 4 +- 12 files changed, 314 insertions(+), 162 deletions(-) diff --git a/lib/libcrypto/source/aes.cpp b/lib/libcrypto/source/aes.cpp index 851434e..43f37d7 100644 --- a/lib/libcrypto/source/aes.cpp +++ b/lib/libcrypto/source/aes.cpp @@ -62,7 +62,7 @@ void crypto::aes::AesIncrementCounter(const uint8_t in[kAesBlockSize], size_t bl uint64_t total = ctr[i] + block_num; // if there wasn't a wrap around, add the two together and exit if (total <= 0xffffffff) { - ctr[i] += block_num; + ctr[i] += (uint32_t)block_num; break; } diff --git a/lib/libfnd/source/StringConv.cpp b/lib/libfnd/source/StringConv.cpp index 8b9fcc6..9d23ab0 100644 --- a/lib/libfnd/source/StringConv.cpp +++ b/lib/libfnd/source/StringConv.cpp @@ -41,7 +41,7 @@ std::u16string StringConv::ConvertChar8ToChar16(const std::string & in) throw std::logic_error("not a UTF-8 string"); } - uni <= 6; + uni <<= 6; uni |= get_utf8_data(1, in[i + j]); } @@ -67,7 +67,7 @@ std::u16string StringConv::ConvertChar8ToChar16(const std::string & in) char32_t uni = unicode[i]; if (uni < kUtf16NonNativeStart) { - utf16.push_back(uni); + utf16.push_back((char16_t)uni); } else { @@ -117,25 +117,25 @@ std::string StringConv::ConvertChar16ToChar8(const std::u16string & in) { if (unicode[i] <= kUtf8AsciiEnd) { - utf8.push_back(unicode[i]); + utf8.push_back((char)unicode[i]); } else if (unicode[i] <= kUtf82ByteEnd) { - utf8.push_back(make_utf8(2, (unicode[i] >> 6))); - utf8.push_back(make_utf8(1, (unicode[i] >> 0))); + utf8.push_back(make_utf8(2, (uint8_t)(unicode[i] >> 6))); + utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 0))); } else if (unicode[i] <= kUtf83ByteEnd) { - utf8.push_back(make_utf8(3, (unicode[i] >> 12))); - utf8.push_back(make_utf8(1, (unicode[i] >> 6))); - utf8.push_back(make_utf8(1, (unicode[i] >> 0))); + utf8.push_back(make_utf8(3, (uint8_t)(unicode[i] >> 12))); + utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 6))); + utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 0))); } else if (unicode[i] <= kUtf84ByteEnd) { - utf8.push_back(make_utf8(4, (unicode[i] >> 18))); - utf8.push_back(make_utf8(1, (unicode[i] >> 12))); - utf8.push_back(make_utf8(1, (unicode[i] >> 6))); - utf8.push_back(make_utf8(1, (unicode[i] >> 0))); + utf8.push_back(make_utf8(4, (uint8_t)(unicode[i] >> 18))); + utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 12))); + utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 6))); + utf8.push_back(make_utf8(1, (uint8_t)(unicode[i] >> 0))); } else { diff --git a/lib/libnx/source/AciHeader.cpp b/lib/libnx/source/AciHeader.cpp index 4905943..5a7308a 100644 --- a/lib/libnx/source/AciHeader.cpp +++ b/lib/libnx/source/AciHeader.cpp @@ -106,12 +106,12 @@ void AciHeader::exportBinary() // set offset/size calculateSectionOffsets(); - hdr->fac.offset = mFac.offset; - hdr->fac.size = mFac.size; - hdr->sac.offset = mSac.offset; - hdr->sac.size = mSac.size; - hdr->kc.offset = mKc.offset; - hdr->kc.size = mKc.size; + hdr->fac.offset = (uint32_t)mFac.offset; + hdr->fac.size = (uint32_t)mFac.size; + hdr->sac.offset = (uint32_t)mSac.offset; + hdr->sac.size = (uint32_t)mSac.size; + hdr->kc.offset = (uint32_t)mKc.offset; + hdr->kc.size = (uint32_t)mKc.size; uint32_t flags = 0; if (mIsProduction) @@ -129,7 +129,7 @@ void AciHeader::exportBinary() else if (mType == TYPE_ACID) { mAcidSize = getAciSize(); - hdr->size = mAcidSize; + hdr->size = (uint32_t)mAcidSize; hdr->program_id_info.program_id_restrict.min = mProgramIdMin; hdr->program_id_info.program_id_restrict.max = mProgramIdMax; } diff --git a/lib/libnx/source/FacHeader.cpp b/lib/libnx/source/FacHeader.cpp index e8f939d..7c09b21 100644 --- a/lib/libnx/source/FacHeader.cpp +++ b/lib/libnx/source/FacHeader.cpp @@ -64,10 +64,10 @@ void nx::FacHeader::exportBinary() hdr->fac_flags = (flag); calculateOffsets(); - hdr->content_owner_ids.start = (mContentOwnerIdPos.offset); - hdr->content_owner_ids.end = (mContentOwnerIdPos.offset + mContentOwnerIdPos.size); - hdr->save_data_owner_ids.start = (mSaveDataOwnerIdPos.offset); - hdr->save_data_owner_ids.end = (mSaveDataOwnerIdPos.offset + mSaveDataOwnerIdPos.size); + hdr->content_owner_ids.start = (uint32_t)(mContentOwnerIdPos.offset); + hdr->content_owner_ids.end = (uint32_t)(mContentOwnerIdPos.offset + mContentOwnerIdPos.size); + hdr->save_data_owner_ids.start = (uint32_t)(mSaveDataOwnerIdPos.offset); + hdr->save_data_owner_ids.end = (uint32_t)(mSaveDataOwnerIdPos.offset + mSaveDataOwnerIdPos.size); } void nx::FacHeader::importBinary(const byte_t * bytes, size_t len) diff --git a/lib/libnx/source/NcaHeader.cpp b/lib/libnx/source/NcaHeader.cpp index 34e5d88..f019276 100644 --- a/lib/libnx/source/NcaHeader.cpp +++ b/lib/libnx/source/NcaHeader.cpp @@ -273,7 +273,7 @@ uint64_t NcaHeader::blockNumToSize(uint32_t block_num) const uint32_t NcaHeader::sizeToBlockNum(uint64_t real_size) const { - return align(real_size, nca::kSectorSize) / nca::kSectorSize; + return (uint32_t)(align(real_size, nca::kSectorSize) / nca::kSectorSize); } bool NcaHeader::isEqual(const NcaHeader & other) const diff --git a/lib/libnx/source/NpdmHeader.cpp b/lib/libnx/source/NpdmHeader.cpp index 28de4ac..4d2a049 100644 --- a/lib/libnx/source/NpdmHeader.cpp +++ b/lib/libnx/source/NpdmHeader.cpp @@ -99,10 +99,10 @@ void nx::NpdmHeader::exportBinary() strncpy(hdr->product_code, mProductCode.c_str(), npdm::kProductCodeMaxLen); calculateOffsets(); - hdr->aci.offset = mAciPos.offset; - hdr->aci.size = mAciPos.size; - hdr->acid.offset = mAcidPos.offset; - hdr->acid.size = mAcidPos.size; + hdr->aci.offset = (uint32_t)mAciPos.offset; + hdr->aci.size = (uint32_t)mAciPos.size; + hdr->acid.offset = (uint32_t)mAcidPos.offset; + hdr->acid.size = (uint32_t)mAcidPos.size; } void nx::NpdmHeader::importBinary(const byte_t * bytes, size_t len) diff --git a/lib/libnx/source/PfsHeader.cpp b/lib/libnx/source/PfsHeader.cpp index 5125a3b..c7b0ecc 100644 --- a/lib/libnx/source/PfsHeader.cpp +++ b/lib/libnx/source/PfsHeader.cpp @@ -44,8 +44,8 @@ void nx::PfsHeader::exportBinary() break; } - hdr->file_num = mFileList.getSize(); - hdr->name_table_size = name_table_size; + hdr->file_num = (uint32_t)mFileList.getSize(); + hdr->name_table_size = (uint32_t)name_table_size; // set file entries if (mFsType == TYPE_PFS0) @@ -59,10 +59,10 @@ void nx::PfsHeader::exportBinary() { raw_files[i].data_offset = (mFileList[i].offset - pfs_header_size); raw_files[i].size = mFileList[i].size; - raw_files[i].name_offset = raw_name_table_pos; + raw_files[i].name_offset = (uint32_t)raw_name_table_pos; strcpy(raw_name_table + raw_name_table_pos, mFileList[i].name.c_str()); - raw_name_table_pos += mFileList[i].name.length() + 1; + raw_name_table_pos += (uint32_t)(mFileList[i].name.length() + 1); } } else if (mFsType == TYPE_HFS0) @@ -76,8 +76,8 @@ void nx::PfsHeader::exportBinary() { raw_files[i].data_offset = (mFileList[i].offset - pfs_header_size); raw_files[i].size = mFileList[i].size; - raw_files[i].name_offset = raw_name_table_pos; - raw_files[i].hash_protected_size = mFileList[i].hash_protected_size; + raw_files[i].name_offset = (uint32_t)raw_name_table_pos; + raw_files[i].hash_protected_size = (uint32_t)mFileList[i].hash_protected_size; raw_files[i].hash = mFileList[i].hash; strcpy(raw_name_table + raw_name_table_pos, mFileList[i].name.c_str()); diff --git a/lib/libnx/source/SystemCallHandler.cpp b/lib/libnx/source/SystemCallHandler.cpp index d14af6c..ee2d313 100644 --- a/lib/libnx/source/SystemCallHandler.cpp +++ b/lib/libnx/source/SystemCallHandler.cpp @@ -56,7 +56,7 @@ void nx::SystemCallHandler::exportKernelCapabilityList(fnd::List entries; for (size_t i = 0; i < kSyscallTotalEntryNum; i++) { - entries[i].setSystemCallUpperBits(i); + entries[i].setSystemCallUpperBits((uint32_t)i); entries[i].setSystemCallLowerBits(0); } diff --git a/programs/nstool/source/CnmtProcess.cpp b/programs/nstool/source/CnmtProcess.cpp index 91d5bd6..bba920a 100644 --- a/programs/nstool/source/CnmtProcess.cpp +++ b/programs/nstool/source/CnmtProcess.cpp @@ -31,11 +31,6 @@ const std::string kContentMetaTypeStr[2][0x80] = } }; -inline const std::string& getContentMetaTypeStr(byte_t index) -{ - return (index < 0x80) ? kContentMetaTypeStr[0][index] : kContentMetaTypeStr[1][index-0x80]; -} - const std::string kUpdateTypeStr[3] = { "ApplyAsDelta", @@ -49,43 +44,58 @@ const std::string kContentMetaAttrStr[3] = "Rebootless" }; + +std::string kUnknownStr = "Unknown"; + inline const char* getBoolStr(bool isTrue) { return isTrue? "TRUE" : "FALSE"; } +inline const char* getContentTypeStr(byte_t i) +{ + return i < 7 ? kContentTypeStr[i].c_str() : kUnknownStr.c_str(); +} + +inline const char* getContentMetaTypeStr(byte_t i) +{ + return (i < 0x80) ? kContentMetaTypeStr[0][i].c_str() : kContentMetaTypeStr[1][i - 0x80].c_str(); +} + void CnmtProcess::displayCmnt() { #define _SPLIT_VER(ver) ( (ver>>26) & 0x3f), ( (ver>>20) & 0x3f), ( (ver>>16) & 0xf), (ver & 0xffff) +#define _HEXDUMP_U(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02X", var[a__a__A]); } while(0) +#define _HEXDUMP_L(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02x", var[a__a__A]); } while(0) printf("[ContentMeta]\n"); - printf(" TitleId: 0x%016" PRIx64 "\n", mCnmt.getTitleId()); - printf(" Version: v%" PRId32 " (%d.%d.%d.%d)\n", mCnmt.getTitleVersion(), _SPLIT_VER(mCnmt.getTitleVersion())); - printf(" Type: %s (%d)\n", getContentMetaTypeStr(mCnmt.getType()).c_str(), mCnmt.getType()); + printf(" TitleId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getTitleId()); + printf(" Version: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)mCnmt.getTitleVersion(), _SPLIT_VER(mCnmt.getTitleVersion())); + printf(" Type: %s (%d)\n", getContentMetaTypeStr(mCnmt.getType()), mCnmt.getType()); printf(" Attributes: %x\n", mCnmt.getAttributes()); printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER))); printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS))); - printf(" RequiredDownloadSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", mCnmt.getRequiredDownloadSystemVersion(), _SPLIT_VER(mCnmt.getRequiredDownloadSystemVersion())); + printf(" RequiredDownloadSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)mCnmt.getRequiredDownloadSystemVersion(), _SPLIT_VER(mCnmt.getRequiredDownloadSystemVersion())); switch(mCnmt.getType()) { case (nx::cnmt::METATYPE_APPLICATION): printf(" ApplicationExtendedHeader:\n"); - printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", mCnmt.getApplicationMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getApplicationMetaExtendedHeader().required_system_version)); - printf(" PatchId: 0x%016" PRIx64 "\n", mCnmt.getApplicationMetaExtendedHeader().patch_id); + printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)mCnmt.getApplicationMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getApplicationMetaExtendedHeader().required_system_version)); + printf(" PatchId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getApplicationMetaExtendedHeader().patch_id); break; case (nx::cnmt::METATYPE_PATCH): printf(" PatchMetaExtendedHeader:\n"); - printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d))\n", mCnmt.getPatchMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getPatchMetaExtendedHeader().required_system_version)); - printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getPatchMetaExtendedHeader().application_id); + printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d))\n", (uint32_t)mCnmt.getPatchMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getPatchMetaExtendedHeader().required_system_version)); + printf(" ApplicationId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getPatchMetaExtendedHeader().application_id); break; case (nx::cnmt::METATYPE_ADD_ON_CONTENT): printf(" AddOnContentMetaExtendedHeader:\n"); - printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", mCnmt.getAddOnContentMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getAddOnContentMetaExtendedHeader().required_system_version)); - printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getAddOnContentMetaExtendedHeader().application_id); + printf(" RequiredSystemVersion: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)mCnmt.getAddOnContentMetaExtendedHeader().required_system_version, _SPLIT_VER(mCnmt.getAddOnContentMetaExtendedHeader().required_system_version)); + printf(" ApplicationId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getAddOnContentMetaExtendedHeader().application_id); break; case (nx::cnmt::METATYPE_DELTA): printf(" DeltaMetaExtendedHeader:\n"); - printf(" ApplicationId: 0x%016" PRIx64 "\n", mCnmt.getDeltaMetaExtendedHeader().application_id); + printf(" ApplicationId: 0x%016" PRIx64 "\n", (uint64_t)mCnmt.getDeltaMetaExtendedHeader().application_id); break; default: break; @@ -96,13 +106,16 @@ void CnmtProcess::displayCmnt() for (size_t i = 0; i < mCnmt.getContentInfo().getSize(); i++) { const nx::ContentMetaBinary::ContentInfo& info = mCnmt.getContentInfo()[i]; - printf(" %d\n", i); - printf(" Type: %s (%d)\n", kContentTypeStr[info.type].c_str(), info.type); + printf(" %d\n", (int)i); + printf(" Type: %s (%d)\n", getContentTypeStr(info.type), info.type); printf(" Id: "); - fnd::SimpleTextOutput::hexDump(info.nca_id, nx::cnmt::kContentIdLen); - printf(" Size: 0x%" PRIx64 "\n", info.size); + _HEXDUMP_L(info.nca_id, nx::cnmt::kContentIdLen); + printf("\n"); + printf(" Size: 0x%" PRIx64 "\n", (uint64_t)info.size); printf(" Hash: "); fnd::SimpleTextOutput::hexDump(info.hash.bytes, sizeof(info.hash)); + _HEXDUMP_L(info.hash.bytes, sizeof(info.hash)); + printf("\n"); } } if (mCnmt.getContentMetaInfo().getSize() > 0) @@ -111,18 +124,21 @@ void CnmtProcess::displayCmnt() for (size_t i = 0; i < mCnmt.getContentMetaInfo().getSize(); i++) { const nx::ContentMetaBinary::ContentMetaInfo& info = mCnmt.getContentMetaInfo()[i]; - printf(" %d\n", i); - printf(" Id: 0x%016" PRIx64 "\n", info.id); - printf(" Version: v%" PRId32 " (%d.%d.%d.%d)\n", info.version, _SPLIT_VER(info.version)); - printf(" Type: %s (%d)\n", getContentMetaTypeStr(info.type).c_str(), info.type); + printf(" %d\n", (int)i); + printf(" Id: 0x%016" PRIx64 "\n", (uint64_t)info.id); + printf(" Version: v%" PRId32 " (%d.%d.%d.%d)\n", (uint32_t)info.version, _SPLIT_VER(info.version)); + printf(" Type: %s (%d)\n", getContentMetaTypeStr(info.type), info.type); printf(" Attributes: %x\n", mCnmt.getAttributes()); printf(" IncludesExFatDriver: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_INCLUDES_EX_FAT_DRIVER))); printf(" Rebootless: %s\n", getBoolStr(_HAS_BIT(mCnmt.getAttributes(), nx::cnmt::ATTRIBUTE_REBOOTLESS))); } } printf(" Digest: "); - fnd::SimpleTextOutput::hexDump(mCnmt.getDigest().data, nx::cnmt::kDigestLen); + _HEXDUMP_L(mCnmt.getDigest().data, nx::cnmt::kDigestLen); + printf("\n"); +#undef _HEXDUMP_L +#undef _HEXDUMP_U #undef _SPLIT_VER } diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index d860938..eeb6452 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -12,74 +12,213 @@ #include "CopiedIFile.h" #include "HashTreeWrappedIFile.h" -std::string kFormatVersionStr[] +const char* getFormatVersionStr(nx::NcaHeader::FormatVersion format_ver) { - "NCA2", - "NCA3" -}; + const char* str; + switch (format_ver) + { + case (nx::NcaHeader::NCA2_FORMAT): + str = "NCA2"; + break; + case (nx::NcaHeader::NCA3_FORMAT): + str = "NCA3"; + break; + default: + str = "Unknown"; + break; + } + return str; +} -std::string kDistributionTypeStr[] +const char* getDistributionTypeStr(nx::nca::DistributionType dist_type) { - "Download", - "Game Card" -}; + const char* str; + switch (dist_type) + { + case (nx::nca::DIST_DOWNLOAD): + str = "Download"; + break; + case (nx::nca::DIST_GAME_CARD): + str = "Game Card"; + break; + default: + str = "Unknown"; + break; + } + return str; +} -std::string kContentTypeStr[] -{ - "Program", - "Meta", - "Control", - "Manual", - "Data", - "PublicData" -}; -std::string kEncryptionTypeStr[] + const char* getContentTypeStr(nx::nca::ContentType cont_type) { - "Auto", - "None", - "AesXts", - "AesCtr", - "AesCtrEx" -}; + const char* str; + switch (cont_type) + { + case (nx::nca::TYPE_PROGRAM): + str = "Program"; + break; + case (nx::nca::TYPE_META): + str = "Meta"; + break; + case (nx::nca::TYPE_CONTROL): + str = "Control"; + break; + case (nx::nca::TYPE_MANUAL): + str = "Manual"; + break; + case (nx::nca::TYPE_DATA): + str = "Data"; + break; + case (nx::nca::TYPE_PUBLIC_DATA): + str = "PublicData"; + break; + default: + str = "Unknown"; + break; + } + return str; +} -std::string kHashTypeStr[] +const char* getEncryptionTypeStr(nx::nca::EncryptionType enc_type) { - "Auto", - "None", - "HierarchicalSha256", - "HierarchicalIntegrity" -}; + const char* str; + switch (enc_type) + { + case (nx::nca::CRYPT_AUTO): + str = "Auto"; + break; + case (nx::nca::CRYPT_NONE): + str = "None"; + break; + case (nx::nca::CRYPT_AESXTS): + str = "AesXts"; + break; + case (nx::nca::CRYPT_AESCTR): + str = "AesCtr"; + break; + case (nx::nca::CRYPT_AESCTREX): + str = "AesCtrEx"; + break; + default: + str = "Unknown"; + break; + } + return str; +} -std::string kFormatTypeStr[] +inline const char* getHashTypeStr(nx::nca::HashType hash_type) { - "RomFs", - "PartitionFs" -}; + const char* str; + switch (hash_type) + { + case (nx::nca::HASH_AUTO): + str = "Auto"; + break; + case (nx::nca::HASH_NONE): + str = "None"; + break; + case (nx::nca::HASH_HIERARCHICAL_SHA256): + str = "HierarchicalSha256"; + break; + case (nx::nca::HASH_HIERARCHICAL_INTERGRITY): + str = "HierarchicalIntegrity"; + break; + default: + str = "Unknown"; + break; + } + return str; +} -std::string kKaekIndexStr[] +inline const char* getFormatTypeStr(nx::nca::FormatType format_type) { - "Application", - "Ocean", - "System" -}; + const char* str; + switch (format_type) + { + case (nx::nca::FORMAT_ROMFS): + str = "RomFs"; + break; + case (nx::nca::FORMAT_PFS0): + str = "PartitionFs"; + break; + default: + str = "Unknown"; + break; + } + return str; +} -std::string kContentTypeForMountStr[] +inline const char* getKaekIndexStr(nx::nca::KeyAreaEncryptionKeyIndex keak_index) { - "program", - "meta", - "control", - "manual", - "data", - "publicdata" -}; + const char* str; + switch (keak_index) + { + case (nx::nca::KAEK_IDX_APPLICATION): + str = "Application"; + break; + case (nx::nca::KAEK_IDX_OCEAN): + str = "Ocean"; + break; + case (nx::nca::KAEK_IDX_SYSTEM): + str = "System"; + break; + default: + str = "Unknown"; + break; + } + return str; +} -std::string kProgramPartitionNameStr[] +inline const char* getContentTypeForMountStr(nx::nca::ContentType cont_type) { - "code", - "data", - "logo" -}; + const char* str; + switch (cont_type) + { + case (nx::nca::TYPE_PROGRAM): + str = "program"; + break; + case (nx::nca::TYPE_META): + str = "meta"; + break; + case (nx::nca::TYPE_CONTROL): + str = "control"; + break; + case (nx::nca::TYPE_MANUAL): + str = "manual"; + break; + case (nx::nca::TYPE_DATA): + str = "data"; + break; + case (nx::nca::TYPE_PUBLIC_DATA): + str = "publicData"; + break; + default: + str = ""; + break; + } + return str; +} + +const char* getProgramPartitionNameStr(size_t i) +{ + const char* str; + switch (i) + { + case (nx::nca::PARTITION_CODE): + str = "code"; + break; + case (nx::nca::PARTITION_DATA): + str = "data"; + break; + case (nx::nca::PARTITION_LOGO): + str = "logo"; + break; + default: + str = ""; + break; + } + return str; +} NcaProcess::NcaProcess() : @@ -411,7 +550,7 @@ void NcaProcess::generatePartitionConfiguration() else if (info.enc_type == nx::nca::CRYPT_AESXTS || info.enc_type == nx::nca::CRYPT_AESCTREX) { error.clear(); - error << "EncryptionType(" << kEncryptionTypeStr[info.enc_type] << "): UNSUPPORTED"; + error << "EncryptionType(" << getEncryptionTypeStr(info.enc_type) << "): UNSUPPORTED"; throw fnd::Exception(kModuleName, error.str()); } else @@ -512,20 +651,27 @@ void NcaProcess::validateNcaSignatures() void NcaProcess::displayHeader() { +#define _HEXDUMP_U(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02X", var[a__a__A]); } while(0) +#define _HEXDUMP_L(var, len) do { for (size_t a__a__A = 0; a__a__A < len; a__a__A++) printf("%02x", var[a__a__A]); } while(0) + printf("[NCA Header]\n"); - printf(" Format Type: %s\n", kFormatVersionStr[mHdr.getFormatVersion()].c_str()); - printf(" Dist. Type: %s\n", kDistributionTypeStr[mHdr.getDistributionType()].c_str()); - printf(" Content Type: %s\n", kContentTypeStr[mHdr.getContentType()].c_str()); + printf(" Format Type: %s\n", getFormatVersionStr(mHdr.getFormatVersion())); + printf(" Dist. Type: %s\n", getDistributionTypeStr(mHdr.getDistributionType())); + printf(" Content Type: %s\n", getContentTypeStr(mHdr.getContentType())); printf(" Key Generation: %d\n", mHdr.getKeyGeneration()); - printf(" Kaek Index: %s (%d)\n", kKaekIndexStr[mHdr.getKaekIndex()].c_str(), mHdr.getKaekIndex()); + printf(" Kaek Index: %s (%d)\n", getKaekIndexStr((nx::nca::KeyAreaEncryptionKeyIndex)mHdr.getKaekIndex()), mHdr.getKaekIndex()); printf(" Size: 0x%" PRIx64 "\n", mHdr.getContentSize()); printf(" ProgID: 0x%016" PRIx64 "\n", mHdr.getProgramId()); printf(" Content Index: %" PRIu32 "\n", mHdr.getContentIndex()); #define _SPLIT_VER(ver) ( (ver>>24) & 0xff), ( (ver>>16) & 0xff), ( (ver>>8) & 0xff) printf(" SdkAddon Ver.: v%" PRIu32 " (%d.%d.%d)\n", mHdr.getSdkAddonVersion(), _SPLIT_VER(mHdr.getSdkAddonVersion())); #undef _SPLIT_VER - printf(" RightsId: "); - fnd::SimpleTextOutput::hexDump(mHdr.getRightsId(), nx::nca::kRightsIdLen); + if (mHdr.hasRightsId()) + { + printf(" RightsId: "); + fnd::SimpleTextOutput::hexDump(mHdr.getRightsId(), nx::nca::kRightsIdLen); + } + if (mBodyKeys.keak_list.getSize() > 0) { @@ -537,12 +683,13 @@ void NcaProcess::displayHeader() { printf(" | %3lu | ", mBodyKeys.keak_list[i].index); - for (size_t j = 0; j < 16; j++) printf("%02x", mBodyKeys.keak_list[i].enc.key[j]); + _HEXDUMP_L(mBodyKeys.keak_list[i].enc.key, 16); + //for (size_t j = 0; j < 16; j++) printf("%02x", mBodyKeys.keak_list[i].enc.key[j]); printf(" | "); if (mBodyKeys.keak_list[i].decrypted) - for (size_t j = 0; j < 16; j++) printf("%02x", mBodyKeys.keak_list[i].dec.key[j]); + _HEXDUMP_L(mBodyKeys.keak_list[i].dec.key, 16); else printf(" "); @@ -550,31 +697,18 @@ void NcaProcess::displayHeader() } printf(" <--------------------------------------------------------------------------->\n"); } - - - /* - if (mBodyKeyList.getSize() > 0) - { - printf(" Key Area Keys:\n"); - for (size_t i = 0; i < mBodyKeyList.getSize(); i++) - { - printf(" %2lu: ", i); - fnd::SimpleTextOutput::hexDump(mBodyKeyList[i].key, crypto::aes::kAes128KeySize); - } - } - */ printf(" Partitions:\n"); for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++) { sPartitionInfo& info = mPartitions[i]; - printf(" %lu:\n", i); - printf(" Offset: 0x%" PRIx64 "\n", info.offset); - printf(" Size: 0x%" PRIx64 "\n", info.size); - printf(" Format Type: %s\n", kFormatTypeStr[info.format_type].c_str()); - printf(" Hash Type: %s\n", kHashTypeStr[info.hash_type].c_str()); - printf(" Enc. Type: %s\n", kEncryptionTypeStr[info.enc_type].c_str()); + printf(" %d:\n", (int)i); + printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)info.offset); + printf(" Size: 0x%" PRIx64 "\n", (uint64_t)info.size); + printf(" Format Type: %s\n", getFormatTypeStr(info.format_type)); + printf(" Hash Type: %s\n", getHashTypeStr(info.hash_type)); + printf(" Enc. Type: %s\n", getEncryptionTypeStr(info.enc_type)); if (info.enc_type == nx::nca::CRYPT_AESCTR) { printf(" AES-CTR: "); @@ -591,19 +725,19 @@ void NcaProcess::displayHeader() //printf(" LayerNum: %d\n", hash_hdr.getLayerInfo().getSize()); for (size_t j = 0; j < hash_hdr.getHashLayerInfo().getSize(); j++) { - printf(" Hash Layer %d:\n", j); - printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getHashLayerInfo()[j].offset); - printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getHashLayerInfo()[j].size); - printf(" BlockSize: 0x%" PRIx32 "\n", hash_hdr.getHashLayerInfo()[j].block_size); + printf(" Hash Layer %d:\n", (int)j); + printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getHashLayerInfo()[j].offset); + printf(" Size: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getHashLayerInfo()[j].size); + printf(" BlockSize: 0x%" PRIx32 "\n", (uint32_t)hash_hdr.getHashLayerInfo()[j].block_size); } printf(" Data Layer:\n"); - printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getDataLayer().offset); - printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getDataLayer().size); - printf(" BlockSize: 0x%" PRIx32 "\n", hash_hdr.getDataLayer().block_size); + printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getDataLayer().offset); + printf(" Size: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getDataLayer().size); + printf(" BlockSize: 0x%" PRIx32 "\n", (uint32_t)hash_hdr.getDataLayer().block_size); for (size_t j = 0; j < hash_hdr.getMasterHashList().getSize(); j++) { - printf(" Master Hash %d: ", j); + printf(" Master Hash %d: ", (int)j); fnd::SimpleTextOutput::hexDump(hash_hdr.getMasterHashList()[j].bytes, sizeof(crypto::sha::sSha256Hash)); } } @@ -613,14 +747,14 @@ void NcaProcess::displayHeader() printf(" HierarchicalSha256 Header:\n"); printf(" Master Hash: "); fnd::SimpleTextOutput::hexDump(hash_hdr.getMasterHashList()[0].bytes, sizeof(crypto::sha::sSha256Hash)); - printf(" HashBlockSize: 0x%x\n", hash_hdr.getDataLayer().block_size); + printf(" HashBlockSize: 0x%" PRIx32 "\n", (uint32_t)hash_hdr.getDataLayer().block_size); //printf(" LayerNum: %d\n", hash_hdr.getLayerInfo().getSize()); printf(" Hash Layer:\n"); - printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getHashLayerInfo()[0].offset); - printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getHashLayerInfo()[0].size); + printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getHashLayerInfo()[0].offset); + printf(" Size: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getHashLayerInfo()[0].size); printf(" Data Layer:\n"); - printf(" Offset: 0x%" PRIx64 "\n", hash_hdr.getDataLayer().offset); - printf(" Size: 0x%" PRIx64 "\n", hash_hdr.getDataLayer().size); + printf(" Offset: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getDataLayer().offset); + printf(" Size: 0x%" PRIx64 "\n", (uint64_t)hash_hdr.getDataLayer().size); } //else //{ @@ -628,6 +762,8 @@ void NcaProcess::displayHeader() // fnd::SimpleTextOutput::hxdStyleDump(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen); //} } +#undef _HEXDUMP_U +#undef _HEXDUMP_L } @@ -641,7 +777,7 @@ void NcaProcess::processPartitions() // if the reader is null, skip if (partition.reader == nullptr) { - printf("[WARNING] NCA Partition %d not readable.", index); + printf("[WARNING] NCA Partition %d not readable.", (int)index); if (partition.fail_reason.empty() == false) { printf(" (%s)", partition.fail_reason.c_str()); @@ -658,11 +794,11 @@ void NcaProcess::processPartitions() pfs.setListFs(mListFs); if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) { - pfs.setMountPointName(kContentTypeForMountStr[mHdr.getContentType()] + ":/" + kProgramPartitionNameStr[i]); + pfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + std::string(getProgramPartitionNameStr(i))); } else { - pfs.setMountPointName(kContentTypeForMountStr[mHdr.getContentType()] + ":/"); + pfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/"); } if (mPartitionPath[index].doExtract) @@ -679,11 +815,11 @@ void NcaProcess::processPartitions() romfs.setListFs(mListFs); if (mHdr.getContentType() == nx::nca::TYPE_PROGRAM) { - romfs.setMountPointName(kContentTypeForMountStr[mHdr.getContentType()] + ":/" + kProgramPartitionNameStr[i]); + romfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/" + std::string(getProgramPartitionNameStr(i))); } else { - romfs.setMountPointName(kContentTypeForMountStr[mHdr.getContentType()] + ":/"); + romfs.setMountPointName(std::string(getContentTypeForMountStr(mHdr.getContentType())) + ":/"); } if (mPartitionPath[index].doExtract) diff --git a/programs/nstool/source/PfsProcess.cpp b/programs/nstool/source/PfsProcess.cpp index b108c2c..c82d3c5 100644 --- a/programs/nstool/source/PfsProcess.cpp +++ b/programs/nstool/source/PfsProcess.cpp @@ -7,7 +7,7 @@ void PfsProcess::displayHeader() { printf("[PartitionFS]\n"); printf(" Type: %s\n", mPfs.getFsType() == mPfs.TYPE_PFS0? "PFS0" : "HFS0"); - printf(" FileNum: %u\n", mPfs.getFileList().getSize()); + printf(" FileNum: %" PRId64 "\n", mPfs.getFileList().getSize()); if (mMountName.empty() == false) printf(" MountPoint: %s%s\n", mMountName.c_str(), mMountName.at(mMountName.length()-1) != '/' ? "/" : ""); } diff --git a/programs/nstool/source/RomfsProcess.cpp b/programs/nstool/source/RomfsProcess.cpp index 115f2ce..ac647f9 100644 --- a/programs/nstool/source/RomfsProcess.cpp +++ b/programs/nstool/source/RomfsProcess.cpp @@ -44,8 +44,8 @@ void RomfsProcess::displayDir(const sDirectory& dir, size_t tab) const void RomfsProcess::displayHeader() { printf("[RomFS]\n"); - printf(" DirNum: %u\n", mDirNum); - printf(" FileNum: %u\n", mFileNum); + printf(" DirNum: %" PRId64 "\n", mDirNum); + printf(" FileNum: %" PRId64 "\n", mFileNum); if (mMountName.empty() == false) printf(" MountPoint: %s%s\n", mMountName.c_str(), mMountName.at(mMountName.length()-1) != '/' ? "/" : ""); } From 634b202b5a15335459f4e1310636fc2da74b63b7 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sat, 26 May 2018 21:13:21 +0800 Subject: [PATCH 56/59] [nstool] Aligned file extraction block sizes For optimisation, IFile wrappers and processes that all used a cache have similar or the same cache sizes. --- .../nstool/source/HashTreeWrappedIFile.cpp | 3 +- programs/nstool/source/HashTreeWrappedIFile.h | 1 + programs/nstool/source/PfsProcess.cpp | 201 +++++++++--------- programs/nstool/source/PfsProcess.h | 2 + programs/nstool/source/RomfsProcess.cpp | 166 ++++++++------- programs/nstool/source/RomfsProcess.h | 3 + 6 files changed, 196 insertions(+), 180 deletions(-) diff --git a/programs/nstool/source/HashTreeWrappedIFile.cpp b/programs/nstool/source/HashTreeWrappedIFile.cpp index dd54449..2b3543a 100644 --- a/programs/nstool/source/HashTreeWrappedIFile.cpp +++ b/programs/nstool/source/HashTreeWrappedIFile.cpp @@ -164,7 +164,8 @@ void HashTreeWrappedIFile::initialiseDataLayer(const HashTreeMeta& hdr) mDataBlockSize = hdr.getDataLayer().block_size; // allocate scratchpad - mScratch.alloc(mDataBlockSize * 0x10); + //mScratch.alloc(mDataBlockSize * 0x10); + mScratch.alloc(align(kFileExportBlockSize, mDataBlockSize)); } void HashTreeWrappedIFile::readData(size_t block_offset, size_t block_num) diff --git a/programs/nstool/source/HashTreeWrappedIFile.h b/programs/nstool/source/HashTreeWrappedIFile.h index 5e92014..456985b 100644 --- a/programs/nstool/source/HashTreeWrappedIFile.h +++ b/programs/nstool/source/HashTreeWrappedIFile.h @@ -21,6 +21,7 @@ public: void write(const byte_t* out, size_t offset, size_t len); private: const std::string kModuleName = "HashTreeWrappedIFile"; + static const size_t kFileExportBlockSize = 0x1000000; std::stringstream mErrorSs; bool mOwnIFile; diff --git a/programs/nstool/source/PfsProcess.cpp b/programs/nstool/source/PfsProcess.cpp index c82d3c5..d7ffe3d 100644 --- a/programs/nstool/source/PfsProcess.cpp +++ b/programs/nstool/source/PfsProcess.cpp @@ -3,104 +3,6 @@ #include "OffsetAdjustedIFile.h" #include "PfsProcess.h" -void PfsProcess::displayHeader() -{ - printf("[PartitionFS]\n"); - printf(" Type: %s\n", mPfs.getFsType() == mPfs.TYPE_PFS0? "PFS0" : "HFS0"); - printf(" FileNum: %" PRId64 "\n", mPfs.getFileList().getSize()); - if (mMountName.empty() == false) - printf(" MountPoint: %s%s\n", mMountName.c_str(), mMountName.at(mMountName.length()-1) != '/' ? "/" : ""); -} - -void PfsProcess::displayFs() -{ - for (size_t i = 0; i < mPfs.getFileList().getSize(); i++) - { - printf(" %s", mPfs.getFileList()[i].name.c_str()); - if (mCliOutputType >= OUTPUT_VERBOSE) - { - if (mPfs.getFsType() == mPfs.TYPE_PFS0) - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size); - else - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size, mPfs.getFileList()[i].hash_protected_size); - } - else - { - printf("\n"); - } - - } -} - -size_t PfsProcess::determineHeaderSize(const nx::sPfsHeader* hdr) -{ - size_t fileEntrySize = 0; - if (std::string(hdr->signature, 4) == nx::pfs::kPfsSig) - fileEntrySize = sizeof(nx::sPfsFile); - else - fileEntrySize = sizeof(nx::sHashedPfsFile); - - return sizeof(nx::sPfsHeader) + hdr->file_num.get() * fileEntrySize + hdr->name_table_size.get(); -} - -bool PfsProcess::validateHeaderMagic(const nx::sPfsHeader* hdr) -{ - return std::string(hdr->signature, 4) == nx::pfs::kPfsSig || std::string(hdr->signature, 4) == nx::pfs::kHashedPfsSig; -} - -void PfsProcess::validateHfs() -{ - fnd::MemoryBlob scratch; - crypto::sha::sSha256Hash hash; - const fnd::List& file = mPfs.getFileList(); - for (size_t i = 0; i < file.getSize(); i++) - { - scratch.alloc(file[i].hash_protected_size); - mReader->read(scratch.getBytes(), file[i].offset, file[i].hash_protected_size); - crypto::sha::Sha256(scratch.getBytes(), scratch.getSize(), hash.bytes); - if (hash != file[i].hash) - { - if (mCliOutputType >= OUTPUT_MINIMAL) - printf("[WARNING] HFS0 %s%s%s: FAIL (bad hash)\n", !mMountName.empty()? mMountName.c_str() : "", !mMountName.empty()? "/" : "", file[i].name.c_str()); - - } - } -} - -void PfsProcess::extractFs() -{ - // allocate scratch memory - fnd::MemoryBlob scratch; - scratch.alloc(kFileExportBlockSize); - - // make extract dir - fnd::io::makeDirectory(mExtractPath); - - fnd::SimpleFile outFile; - const fnd::List& file = mPfs.getFileList(); - - std::string file_path; - for (size_t i = 0; i < file.getSize(); i++) - { - file_path.clear(); - fnd::io::appendToPath(file_path, mExtractPath); - fnd::io::appendToPath(file_path, file[i].name); - outFile.open(file_path, outFile.Create); - mReader->seek(file[i].offset); - for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++) - { - mReader->read(scratch.getBytes(), kFileExportBlockSize); - outFile.write(scratch.getBytes(), kFileExportBlockSize); - } - if (file[i].size % kFileExportBlockSize) - { - mReader->read(scratch.getBytes(), file[i].size % kFileExportBlockSize); - outFile.write(scratch.getBytes(), file[i].size % kFileExportBlockSize); - } - outFile.close(); - } -} - PfsProcess::PfsProcess() : mReader(nullptr), mCliOutputType(OUTPUT_NORMAL), @@ -189,3 +91,106 @@ const nx::PfsHeader& PfsProcess::getPfsHeader() const { return mPfs; } + +void PfsProcess::displayHeader() +{ + printf("[PartitionFS]\n"); + printf(" Type: %s\n", mPfs.getFsType() == mPfs.TYPE_PFS0? "PFS0" : "HFS0"); + printf(" FileNum: %" PRId64 "\n", mPfs.getFileList().getSize()); + if (mMountName.empty() == false) + printf(" MountPoint: %s%s\n", mMountName.c_str(), mMountName.at(mMountName.length()-1) != '/' ? "/" : ""); +} + +void PfsProcess::displayFs() +{ + for (size_t i = 0; i < mPfs.getFileList().getSize(); i++) + { + printf(" %s", mPfs.getFileList()[i].name.c_str()); + if (mCliOutputType >= OUTPUT_VERBOSE) + { + if (mPfs.getFsType() == mPfs.TYPE_PFS0) + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size); + else + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size, mPfs.getFileList()[i].hash_protected_size); + } + else + { + printf("\n"); + } + + } +} + +size_t PfsProcess::determineHeaderSize(const nx::sPfsHeader* hdr) +{ + size_t fileEntrySize = 0; + if (std::string(hdr->signature, 4) == nx::pfs::kPfsSig) + fileEntrySize = sizeof(nx::sPfsFile); + else + fileEntrySize = sizeof(nx::sHashedPfsFile); + + return sizeof(nx::sPfsHeader) + hdr->file_num.get() * fileEntrySize + hdr->name_table_size.get(); +} + +bool PfsProcess::validateHeaderMagic(const nx::sPfsHeader* hdr) +{ + return std::string(hdr->signature, 4) == nx::pfs::kPfsSig || std::string(hdr->signature, 4) == nx::pfs::kHashedPfsSig; +} + +void PfsProcess::validateHfs() +{ + // allocate when validate is invoked + mFileExtractBlock.alloc(kFileExportBlockSize); + + crypto::sha::sSha256Hash hash; + const fnd::List& file = mPfs.getFileList(); + for (size_t i = 0; i < file.getSize(); i++) + { + mFileExtractBlock.alloc(file[i].hash_protected_size); + mReader->read(mFileExtractBlock.getBytes(), file[i].offset, file[i].hash_protected_size); + crypto::sha::Sha256(mFileExtractBlock.getBytes(), mFileExtractBlock.getSize(), hash.bytes); + if (hash != file[i].hash) + { + if (mCliOutputType >= OUTPUT_MINIMAL) + printf("[WARNING] HFS0 %s%s%s: FAIL (bad hash)\n", !mMountName.empty()? mMountName.c_str() : "", !mMountName.empty()? "/" : "", file[i].name.c_str()); + + } + } +} + +void PfsProcess::extractFs() +{ + // allocate only when extractDir is invoked + mFileExtractBlock.alloc(kFileExportBlockSize); + + // make extract dir + fnd::io::makeDirectory(mExtractPath); + + fnd::SimpleFile outFile; + const fnd::List& file = mPfs.getFileList(); + + std::string file_path; + for (size_t i = 0; i < file.getSize(); i++) + { + file_path.clear(); + fnd::io::appendToPath(file_path, mExtractPath); + fnd::io::appendToPath(file_path, file[i].name); + + if (mCliOutputType >= OUTPUT_VERBOSE) + printf("extract=[%s]\n", file_path.c_str()); + + outFile.open(file_path, outFile.Create); + mReader->seek(file[i].offset); + for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++) + { + mReader->read(mFileExtractBlock.getBytes(), kFileExportBlockSize); + outFile.write(mFileExtractBlock.getBytes(), kFileExportBlockSize); + } + if (file[i].size % kFileExportBlockSize) + { + mReader->read(mFileExtractBlock.getBytes(), file[i].size % kFileExportBlockSize); + outFile.write(mFileExtractBlock.getBytes(), file[i].size % kFileExportBlockSize); + } + outFile.close(); + } +} diff --git a/programs/nstool/source/PfsProcess.h b/programs/nstool/source/PfsProcess.h index 18ad507..c2be9da 100644 --- a/programs/nstool/source/PfsProcess.h +++ b/programs/nstool/source/PfsProcess.h @@ -39,6 +39,8 @@ private: std::string mMountName; bool mListFs; + fnd::MemoryBlob mFileExtractBlock; + nx::PfsHeader mPfs; void displayHeader(); diff --git a/programs/nstool/source/RomfsProcess.cpp b/programs/nstool/source/RomfsProcess.cpp index ac647f9..ef224ee 100644 --- a/programs/nstool/source/RomfsProcess.cpp +++ b/programs/nstool/source/RomfsProcess.cpp @@ -4,6 +4,83 @@ #include "OffsetAdjustedIFile.h" #include "RomfsProcess.h" +RomfsProcess::RomfsProcess() : + mReader(nullptr), + mCliOutputType(OUTPUT_NORMAL), + mVerify(false), + mExtractPath(), + mExtract(false), + mMountName(), + mListFs(false), + mDirNum(0), + mFileNum(0) +{ + mRootDir.name.clear(); + mRootDir.dir_list.clear(); + mRootDir.file_list.clear(); +} + +RomfsProcess::~RomfsProcess() +{ + if (mReader != nullptr) + { + delete mReader; + } +} + +void RomfsProcess::process() +{ + if (mReader == nullptr) + { + throw fnd::Exception(kModuleName, "No file reader set."); + } + + resolveRomfs(); + + if (mCliOutputType >= OUTPUT_NORMAL) + displayHeader(); + if (mListFs || mCliOutputType >= OUTPUT_VERBOSE) + displayFs(); + if (mExtract) + extractFs(); +} + +void RomfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) +{ + mReader = new OffsetAdjustedIFile(file, offset, size); +} + +void RomfsProcess::setCliOutputMode(CliOutputType type) +{ + mCliOutputType = type; +} + +void RomfsProcess::setVerifyMode(bool verify) +{ + mVerify = verify; +} + +void RomfsProcess::setMountPointName(const std::string& mount_name) +{ + mMountName = mount_name; +} + +void RomfsProcess::setExtractPath(const std::string& path) +{ + mExtract = true; + mExtractPath = path; +} + +void RomfsProcess::setListFs(bool list_fs) +{ + mListFs = list_fs; +} + +const RomfsProcess::sDirectory& RomfsProcess::getRootDir() const +{ + return mRootDir; +} + void RomfsProcess::printTab(size_t tab) const { for (size_t i = 0; i < tab; i++) @@ -72,8 +149,10 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir) // allocate memory for file extraction +#ifdef NSTOOL_ALLOC_UNIQUE_SCRATCH fnd::MemoryBlob scratch; scratch.alloc(kFileExportBlockSize); +#endif // extract files fnd::SimpleFile outFile; @@ -91,13 +170,13 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir) mReader->seek(dir.file_list[i].offset); for (size_t j = 0; j < (dir.file_list[i].size / kFileExportBlockSize); j++) { - mReader->read(scratch.getBytes(), kFileExportBlockSize); - outFile.write(scratch.getBytes(), kFileExportBlockSize); + mReader->read(mFileExtractBlock.getBytes(), kFileExportBlockSize); + outFile.write(mFileExtractBlock.getBytes(), kFileExportBlockSize); } if (dir.file_list[i].size % kFileExportBlockSize) { - mReader->read(scratch.getBytes(), dir.file_list[i].size % kFileExportBlockSize); - outFile.write(scratch.getBytes(), dir.file_list[i].size % kFileExportBlockSize); + mReader->read(mFileExtractBlock.getBytes(), dir.file_list[i].size % kFileExportBlockSize); + outFile.write(mFileExtractBlock.getBytes(), dir.file_list[i].size % kFileExportBlockSize); } outFile.close(); } @@ -111,6 +190,8 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir) void RomfsProcess::extractFs() { + // allocate only when extractDir is invoked + mFileExtractBlock.alloc(kFileExportBlockSize); extractDir(mExtractPath, mRootDir); } @@ -220,81 +301,4 @@ void RomfsProcess::resolveRomfs() mDirNum = 0; mFileNum = 0; importDirectory(0, mRootDir); -} - -RomfsProcess::RomfsProcess() : - mReader(nullptr), - mCliOutputType(OUTPUT_NORMAL), - mVerify(false), - mExtractPath(), - mExtract(false), - mMountName(), - mListFs(false), - mDirNum(0), - mFileNum(0) -{ - mRootDir.name.clear(); - mRootDir.dir_list.clear(); - mRootDir.file_list.clear(); -} - -RomfsProcess::~RomfsProcess() -{ - if (mReader != nullptr) - { - delete mReader; - } -} - -void RomfsProcess::process() -{ - if (mReader == nullptr) - { - throw fnd::Exception(kModuleName, "No file reader set."); - } - - resolveRomfs(); - - if (mCliOutputType >= OUTPUT_NORMAL) - displayHeader(); - if (mListFs || mCliOutputType >= OUTPUT_VERBOSE) - displayFs(); - if (mExtract) - extractFs(); -} - -void RomfsProcess::setInputFile(fnd::IFile* file, size_t offset, size_t size) -{ - mReader = new OffsetAdjustedIFile(file, offset, size); -} - -void RomfsProcess::setCliOutputMode(CliOutputType type) -{ - mCliOutputType = type; -} - -void RomfsProcess::setVerifyMode(bool verify) -{ - mVerify = verify; -} - -void RomfsProcess::setMountPointName(const std::string& mount_name) -{ - mMountName = mount_name; -} - -void RomfsProcess::setExtractPath(const std::string& path) -{ - mExtract = true; - mExtractPath = path; -} - -void RomfsProcess::setListFs(bool list_fs) -{ - mListFs = list_fs; -} - -const RomfsProcess::sDirectory& RomfsProcess::getRootDir() const -{ - return mRootDir; } \ No newline at end of file diff --git a/programs/nstool/source/RomfsProcess.h b/programs/nstool/source/RomfsProcess.h index 5c2a2e0..00f43f5 100644 --- a/programs/nstool/source/RomfsProcess.h +++ b/programs/nstool/source/RomfsProcess.h @@ -107,6 +107,7 @@ public: private: const std::string kModuleName = "RomfsProcess"; static const size_t kFileExportBlockSize = 0x1000000; + //static const size_t kFileExportBlockSize = 0x1000000; fnd::IFile* mReader; CliOutputType mCliOutputType; @@ -117,6 +118,8 @@ private: std::string mMountName; bool mListFs; + fnd::MemoryBlob mFileExtractBlock; + size_t mDirNum; size_t mFileNum; nx::sRomfsHeader mHdr; From f4d3501f8c6e3712ba13f686febbc9b316374b83 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sun, 27 May 2018 18:16:42 +0800 Subject: [PATCH 57/59] [nstool] Fix bug in AesCtrWrappedIFile. --- programs/nstool/source/AesCtrWrappedIFile.cpp | 72 ++++++++++++------- programs/nstool/source/AesCtrWrappedIFile.h | 7 +- 2 files changed, 49 insertions(+), 30 deletions(-) diff --git a/programs/nstool/source/AesCtrWrappedIFile.cpp b/programs/nstool/source/AesCtrWrappedIFile.cpp index 8ce436e..81fd145 100644 --- a/programs/nstool/source/AesCtrWrappedIFile.cpp +++ b/programs/nstool/source/AesCtrWrappedIFile.cpp @@ -12,7 +12,7 @@ AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile* file, bool ownIfile, const cr mBaseCtr(ctr), mFileOffset(0) { - mScratch.alloc(kAesCtrScratchAllocSize); + mCache.alloc(kCacheSizeAllocSize); } AesCtrWrappedIFile::~AesCtrWrappedIFile() @@ -35,22 +35,27 @@ void AesCtrWrappedIFile::seek(size_t offset) void AesCtrWrappedIFile::read(byte_t* out, size_t len) { - internalSeek(); + //printf("[%x] AesCtrWrappedIFile::read(offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", this, mFileOffset, 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); - } + size_t read_len; + size_t read_pos; - if (len % kAesCtrScratchSize) + size_t cache_reads = (len / kCacheSize) + ((len % kCacheSize) != 0); + + for (size_t i = 0; i < cache_reads; i++) { - 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); + read_len = MIN(len - (i * kCacheSize), kCacheSize); + read_pos = ((mFileOffset >> 4) << 4) + (i * kCacheSize); + + //printf("[%x] AesCtrWrappedIFile::read() CACHE READ: readlen=%" PRIx64 "\n", this, read_len); + + mFile->seek(read_pos); + mFile->read(mCache.getBytes(), kCacheSizeAllocSize); + + crypto::aes::AesIncrementCounter(mBaseCtr.iv, read_pos>>4, mCurrentCtr.iv); + crypto::aes::AesCtr(mCache.getBytes(), kCacheSizeAllocSize, mKey.key, mCurrentCtr.iv, mCache.getBytes()); + + memcpy(out + (i * kCacheSize), mCache.getBytes() + (mFileOffset & 0xf), read_len); } seek(mFileOffset + len); @@ -62,10 +67,32 @@ void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len) read(out, len); } -void AesCtrWrappedIFile::write(const byte_t* out, size_t len) +void AesCtrWrappedIFile::write(const byte_t* in, size_t len) { - internalSeek(); + size_t write_len; + size_t write_pos; + size_t cache_writes = (len / kCacheSize) + ((len % kCacheSize) != 0); + + for (size_t i = 0; i < cache_writes; i++) + { + write_len = MIN(len - (i * kCacheSize), kCacheSize); + write_pos = ((mFileOffset >> 4) << 4) + (i * kCacheSize); + + //printf("[%x] AesCtrWrappedIFile::read() CACHE READ: readlen=%" PRIx64 "\n", this, read_len); + + memcpy(mCache.getBytes() + (mFileOffset & 0xf), in + (i * kCacheSize), write_len); + + crypto::aes::AesIncrementCounter(mBaseCtr.iv, write_pos>>4, mCurrentCtr.iv); + crypto::aes::AesCtr(mCache.getBytes(), kCacheSizeAllocSize, mKey.key, mCurrentCtr.iv, mCache.getBytes()); + + mFile->seek(write_pos); + mFile->write(mCache.getBytes(), kCacheSizeAllocSize); + } + + seek(mFileOffset + len); + + /* for (size_t i = 0; i < (len / kAesCtrScratchSize); i++) { memcpy(mScratch.getBytes() + mBlockOffset, out + (i * kAesCtrScratchSize), kAesCtrScratchSize); @@ -81,19 +108,12 @@ void AesCtrWrappedIFile::write(const byte_t* out, size_t len) crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes()); mFile->write(mScratch.getBytes() + mBlockOffset, write_len); } - + */ seek(mFileOffset + len); } -void AesCtrWrappedIFile::write(const byte_t* out, size_t offset, size_t len) +void AesCtrWrappedIFile::write(const byte_t* in, size_t offset, size_t len) { seek(offset); - write(out, len); -} - -void AesCtrWrappedIFile::internalSeek() -{ - mFile->seek(mFileOffset); - crypto::aes::AesIncrementCounter(mBaseCtr.iv, mFileOffset>>4, mCurrentCtr.iv); - mBlockOffset = mFileOffset & 0xf; + write(in, len); } \ No newline at end of file diff --git a/programs/nstool/source/AesCtrWrappedIFile.h b/programs/nstool/source/AesCtrWrappedIFile.h index 0d30754..2f83e1a 100644 --- a/programs/nstool/source/AesCtrWrappedIFile.h +++ b/programs/nstool/source/AesCtrWrappedIFile.h @@ -17,17 +17,16 @@ public: 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; + static const size_t kCacheSize = 0x10000; + static const size_t kCacheSizeAllocSize = kCacheSize + crypto::aes::kAesBlockSize; bool mOwnIFile; fnd::IFile* mFile; crypto::aes::sAes128Key mKey; crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr; - size_t mBlockOffset; size_t mFileOffset; - fnd::MemoryBlob mScratch; + fnd::MemoryBlob mCache; void internalSeek(); }; \ No newline at end of file From 6960911ab06e857509747238db564c2433d86c88 Mon Sep 17 00:00:00 2001 From: jakcron Date: Sun, 27 May 2018 18:17:34 +0800 Subject: [PATCH 58/59] [nstool] Improve Pfs/Romfs export time. --- .../nstool/source/HashTreeWrappedIFile.cpp | 32 +++++++++++-------- programs/nstool/source/HashTreeWrappedIFile.h | 5 +-- programs/nstool/source/NcaProcess.cpp | 2 +- programs/nstool/source/NpdmProcess.cpp | 2 +- programs/nstool/source/PfsProcess.cpp | 28 ++++++---------- programs/nstool/source/PfsProcess.h | 4 +-- programs/nstool/source/RomfsProcess.cpp | 28 ++++------------ programs/nstool/source/RomfsProcess.h | 5 ++- 8 files changed, 45 insertions(+), 61 deletions(-) diff --git a/programs/nstool/source/HashTreeWrappedIFile.cpp b/programs/nstool/source/HashTreeWrappedIFile.cpp index 2b3543a..dd995f6 100644 --- a/programs/nstool/source/HashTreeWrappedIFile.cpp +++ b/programs/nstool/source/HashTreeWrappedIFile.cpp @@ -50,12 +50,9 @@ void HashTreeWrappedIFile::read(byte_t* out, size_t len) size_t start_block = getOffsetBlock(mDataOffset); size_t block_num = align(offset_in_start_block + len, mDataBlockSize) / mDataBlockSize; - size_t scratch_block_capacity = mScratch.getSize() / mDataBlockSize; - - size_t partial_last_block_num = block_num % scratch_block_capacity; + size_t partial_last_block_num = block_num % mCacheBlockNum; bool has_partial_block_num = partial_last_block_num > 0; - size_t read_iterations = (block_num / scratch_block_capacity) + has_partial_block_num; - + size_t read_iterations = (block_num / mCacheBlockNum) + has_partial_block_num; size_t block_read_len; size_t block_export_offset; @@ -64,7 +61,7 @@ void HashTreeWrappedIFile::read(byte_t* out, size_t len) for (size_t i = 0; i < read_iterations; i++) { // how many blocks to read from source file - block_read_len = (i+1 == read_iterations && has_partial_block_num) ? partial_last_block_num : scratch_block_capacity; + block_read_len = (i+1 == read_iterations && has_partial_block_num) ? partial_last_block_num : mCacheBlockNum; // offset in this current read to copy from block_export_offset = (i == 0) ? offset_in_start_block : 0; @@ -79,10 +76,10 @@ void HashTreeWrappedIFile::read(byte_t* out, size_t len) } // read the blocks - readData(start_block + (i * scratch_block_capacity), block_read_len); + readData(start_block + (i * mCacheBlockNum), block_read_len); // export the section of data that is relevant - memcpy(out + block_export_pos, mScratch.getBytes() + block_export_offset, block_export_size); + memcpy(out + block_export_pos, mCache.getBytes() + block_export_offset, block_export_size); // update export position block_export_pos += block_export_size; @@ -165,7 +162,11 @@ void HashTreeWrappedIFile::initialiseDataLayer(const HashTreeMeta& hdr) // allocate scratchpad //mScratch.alloc(mDataBlockSize * 0x10); - mScratch.alloc(align(kFileExportBlockSize, mDataBlockSize)); + size_t cache_size = align(kDefaultCacheSize, mDataBlockSize); + mCacheBlockNum = cache_size / mDataBlockSize; + //printf("Block Size: 0x%" PRIx64 "\n", mDataBlockSize); + //printf("Cache size: 0x%" PRIx64 ", (block_num: %" PRId64 ")\n", cache_size, mCacheBlockNum); + mCache.alloc(cache_size); } void HashTreeWrappedIFile::readData(size_t block_offset, size_t block_num) @@ -178,7 +179,7 @@ void HashTreeWrappedIFile::readData(size_t block_offset, size_t block_num) if ((block_offset + block_num) == getBlockNum(mData->size())) { read_len = (block_num-1) * mDataBlockSize + getRemanderBlockReadSize(mData->size()); - memset(mScratch.getBytes(), 0, block_num * mDataBlockSize); + memset(mCache.getBytes(), 0, block_num * mDataBlockSize); } else if ((block_offset + block_num) < getBlockNum(mData->size())) { @@ -190,19 +191,24 @@ void HashTreeWrappedIFile::readData(size_t block_offset, size_t block_num) } // read - mData->read(mScratch.getBytes(), block_offset * mDataBlockSize, read_len); + mData->read(mCache.getBytes(), block_offset * mDataBlockSize, read_len); + if (block_num > mCacheBlockNum) + { + throw fnd::Exception(kModuleName, "Read excessive of cache size"); + } + //printf("readlen=0x%" PRIx64 "\n", read_len); // validate blocks size_t validate_size; for (size_t i = 0; i < block_num; i++) { validate_size = mAlignHashCalcToBlock? mDataBlockSize : MIN(read_len - (i * mDataBlockSize), mDataBlockSize); - crypto::sha::Sha256(mScratch.getBytes() + (i * mDataBlockSize), validate_size, hash.bytes); + crypto::sha::Sha256(mCache.getBytes() + (i * mDataBlockSize), validate_size, hash.bytes); if (hash != mDataHashLayer[block_offset + i]) { - mErrorSs << "Hash tree layer verification failed (layer: data, block: " << (block_offset + i) << ", offset: 0x" << std::hex << ((block_offset + i) * mDataBlockSize) << ", size: 0x" << std::hex << validate_size <<")"; + mErrorSs << "Hash tree layer verification failed (layer: data, block: " << (block_offset + i) << " ( " << i << "/" << block_num-1 << " ), offset: 0x" << std::hex << ((block_offset + i) * mDataBlockSize) << ", size: 0x" << std::hex << validate_size <<")"; throw fnd::Exception(kModuleName, mErrorSs.str()); } } diff --git a/programs/nstool/source/HashTreeWrappedIFile.h b/programs/nstool/source/HashTreeWrappedIFile.h index 456985b..cb64f6e 100644 --- a/programs/nstool/source/HashTreeWrappedIFile.h +++ b/programs/nstool/source/HashTreeWrappedIFile.h @@ -21,7 +21,7 @@ public: void write(const byte_t* out, size_t offset, size_t len); private: const std::string kModuleName = "HashTreeWrappedIFile"; - static const size_t kFileExportBlockSize = 0x1000000; + static const size_t kDefaultCacheSize = 0x10000; std::stringstream mErrorSs; bool mOwnIFile; @@ -34,7 +34,8 @@ private: fnd::List mDataHashLayer; bool mAlignHashCalcToBlock; - fnd::MemoryBlob mScratch; + fnd::MemoryBlob mCache; + size_t mCacheBlockNum; inline size_t getOffsetBlock(size_t offset) const { return offset / mDataBlockSize; } inline size_t getOffsetInBlock(size_t offset) const { return offset % mDataBlockSize; } diff --git a/programs/nstool/source/NcaProcess.cpp b/programs/nstool/source/NcaProcess.cpp index eeb6452..5310017 100644 --- a/programs/nstool/source/NcaProcess.cpp +++ b/programs/nstool/source/NcaProcess.cpp @@ -681,7 +681,7 @@ void NcaProcess::displayHeader() printf(" |-----|----------------------------------|----------------------------------|\n"); for (size_t i = 0; i < mBodyKeys.keak_list.getSize(); i++) { - printf(" | %3lu | ", mBodyKeys.keak_list[i].index); + printf(" | %3d | ", mBodyKeys.keak_list[i].index); _HEXDUMP_L(mBodyKeys.keak_list[i].enc.key, 16); //for (size_t j = 0; j < 16; j++) printf("%02x", mBodyKeys.keak_list[i].enc.key[j]); diff --git a/programs/nstool/source/NpdmProcess.cpp b/programs/nstool/source/NpdmProcess.cpp index 5c3c5d7..27b1abe 100644 --- a/programs/nstool/source/NpdmProcess.cpp +++ b/programs/nstool/source/NpdmProcess.cpp @@ -463,7 +463,7 @@ void NpdmProcess::displayAciHdr(const nx::AciHeader& aci) else if (aci.getAciType() == nx::AciBinary::TYPE_ACID) { - printf(" ACID Size: %" PRIx64 "\n", aci.getAcidSize()); + printf(" ACID Size: %" PRIx64 "\n", (uint64_t)aci.getAcidSize()); printf(" Flags: \n"); printf(" Production: %s\n", aci.isProduction() ? "TRUE" : "FALSE"); printf(" UnqualifiedApproval: %s\n", aci.isUnqualifiedApproval() ? "TRUE" : "FALSE"); diff --git a/programs/nstool/source/PfsProcess.cpp b/programs/nstool/source/PfsProcess.cpp index d7ffe3d..3fffc0b 100644 --- a/programs/nstool/source/PfsProcess.cpp +++ b/programs/nstool/source/PfsProcess.cpp @@ -96,7 +96,7 @@ void PfsProcess::displayHeader() { printf("[PartitionFS]\n"); printf(" Type: %s\n", mPfs.getFsType() == mPfs.TYPE_PFS0? "PFS0" : "HFS0"); - printf(" FileNum: %" PRId64 "\n", mPfs.getFileList().getSize()); + printf(" FileNum: %" PRId64 "\n", (uint64_t)mPfs.getFileList().getSize()); if (mMountName.empty() == false) printf(" MountPoint: %s%s\n", mMountName.c_str(), mMountName.at(mMountName.length()-1) != '/' ? "/" : ""); } @@ -109,9 +109,9 @@ void PfsProcess::displayFs() if (mCliOutputType >= OUTPUT_VERBOSE) { if (mPfs.getFsType() == mPfs.TYPE_PFS0) - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size); + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", (uint64_t)mPfs.getFileList()[i].offset, (uint64_t)mPfs.getFileList()[i].size); else - printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", mPfs.getFileList()[i].offset, mPfs.getFileList()[i].size, mPfs.getFileList()[i].hash_protected_size); + printf(" (offset=0x%" PRIx64 ", size=0x%" PRIx64 ", hash_protected_size=0x%" PRIx64 ")\n", (uint64_t)mPfs.getFileList()[i].offset, (uint64_t)mPfs.getFileList()[i].size, (uint64_t)mPfs.getFileList()[i].hash_protected_size); } else { @@ -139,16 +139,13 @@ bool PfsProcess::validateHeaderMagic(const nx::sPfsHeader* hdr) void PfsProcess::validateHfs() { - // allocate when validate is invoked - mFileExtractBlock.alloc(kFileExportBlockSize); - crypto::sha::sSha256Hash hash; const fnd::List& file = mPfs.getFileList(); for (size_t i = 0; i < file.getSize(); i++) { - mFileExtractBlock.alloc(file[i].hash_protected_size); - mReader->read(mFileExtractBlock.getBytes(), file[i].offset, file[i].hash_protected_size); - crypto::sha::Sha256(mFileExtractBlock.getBytes(), mFileExtractBlock.getSize(), hash.bytes); + mCache.alloc(file[i].hash_protected_size); + mReader->read(mCache.getBytes(), file[i].offset, file[i].hash_protected_size); + crypto::sha::Sha256(mCache.getBytes(), mCache.getSize(), hash.bytes); if (hash != file[i].hash) { if (mCliOutputType >= OUTPUT_MINIMAL) @@ -161,7 +158,7 @@ void PfsProcess::validateHfs() void PfsProcess::extractFs() { // allocate only when extractDir is invoked - mFileExtractBlock.alloc(kFileExportBlockSize); + mCache.alloc(kCacheSize); // make extract dir fnd::io::makeDirectory(mExtractPath); @@ -181,15 +178,10 @@ void PfsProcess::extractFs() outFile.open(file_path, outFile.Create); mReader->seek(file[i].offset); - for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++) + for (size_t j = 0; j < ((file[i].size / kCacheSize) + ((file[i].size % kCacheSize) != 0)); j++) { - mReader->read(mFileExtractBlock.getBytes(), kFileExportBlockSize); - outFile.write(mFileExtractBlock.getBytes(), kFileExportBlockSize); - } - if (file[i].size % kFileExportBlockSize) - { - mReader->read(mFileExtractBlock.getBytes(), file[i].size % kFileExportBlockSize); - outFile.write(mFileExtractBlock.getBytes(), file[i].size % kFileExportBlockSize); + mReader->read(mCache.getBytes(), MIN(file[i].size - (kCacheSize * j),kCacheSize)); + outFile.write(mCache.getBytes(), MIN(file[i].size - (kCacheSize * j),kCacheSize)); } outFile.close(); } diff --git a/programs/nstool/source/PfsProcess.h b/programs/nstool/source/PfsProcess.h index c2be9da..5a61f1b 100644 --- a/programs/nstool/source/PfsProcess.h +++ b/programs/nstool/source/PfsProcess.h @@ -28,7 +28,7 @@ public: private: const std::string kModuleName = "PfsProcess"; - static const size_t kFileExportBlockSize = 0x1000000; + static const size_t kCacheSize = 0x10000; fnd::IFile* mReader; CliOutputType mCliOutputType; @@ -39,7 +39,7 @@ private: std::string mMountName; bool mListFs; - fnd::MemoryBlob mFileExtractBlock; + fnd::MemoryBlob mCache; nx::PfsHeader mPfs; diff --git a/programs/nstool/source/RomfsProcess.cpp b/programs/nstool/source/RomfsProcess.cpp index ef224ee..a0642c9 100644 --- a/programs/nstool/source/RomfsProcess.cpp +++ b/programs/nstool/source/RomfsProcess.cpp @@ -121,8 +121,8 @@ void RomfsProcess::displayDir(const sDirectory& dir, size_t tab) const void RomfsProcess::displayHeader() { printf("[RomFS]\n"); - printf(" DirNum: %" PRId64 "\n", mDirNum); - printf(" FileNum: %" PRId64 "\n", mFileNum); + printf(" DirNum: %" PRId64 "\n", (uint64_t)mDirNum); + printf(" FileNum: %" PRId64 "\n", (uint64_t)mFileNum); if (mMountName.empty() == false) printf(" MountPoint: %s%s\n", mMountName.c_str(), mMountName.at(mMountName.length()-1) != '/' ? "/" : ""); } @@ -142,18 +142,9 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir) if (dir.name.empty() == false) fnd::io::appendToPath(dir_path, dir.name); - //printf("dirpath=[%s]\n", dir_path.c_str()); - // make directory fnd::io::makeDirectory(dir_path); - - // allocate memory for file extraction -#ifdef NSTOOL_ALLOC_UNIQUE_SCRATCH - fnd::MemoryBlob scratch; - scratch.alloc(kFileExportBlockSize); -#endif - // extract files fnd::SimpleFile outFile; for (size_t i = 0; i < dir.file_list.getSize(); i++) @@ -168,16 +159,11 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir) outFile.open(file_path, outFile.Create); mReader->seek(dir.file_list[i].offset); - for (size_t j = 0; j < (dir.file_list[i].size / kFileExportBlockSize); j++) + for (size_t j = 0; j < ((dir.file_list[i].size / kCacheSize) + ((dir.file_list[i].size % kCacheSize) != 0)); j++) { - mReader->read(mFileExtractBlock.getBytes(), kFileExportBlockSize); - outFile.write(mFileExtractBlock.getBytes(), kFileExportBlockSize); - } - if (dir.file_list[i].size % kFileExportBlockSize) - { - mReader->read(mFileExtractBlock.getBytes(), dir.file_list[i].size % kFileExportBlockSize); - outFile.write(mFileExtractBlock.getBytes(), dir.file_list[i].size % kFileExportBlockSize); - } + mReader->read(mCache.getBytes(), MIN(dir.file_list[i].size - (kCacheSize * j),kCacheSize)); + outFile.write(mCache.getBytes(), MIN(dir.file_list[i].size - (kCacheSize * j),kCacheSize)); + } outFile.close(); } @@ -191,7 +177,7 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir) void RomfsProcess::extractFs() { // allocate only when extractDir is invoked - mFileExtractBlock.alloc(kFileExportBlockSize); + mCache.alloc(kCacheSize); extractDir(mExtractPath, mRootDir); } diff --git a/programs/nstool/source/RomfsProcess.h b/programs/nstool/source/RomfsProcess.h index 00f43f5..d90220d 100644 --- a/programs/nstool/source/RomfsProcess.h +++ b/programs/nstool/source/RomfsProcess.h @@ -106,8 +106,7 @@ public: const sDirectory& getRootDir() const; private: const std::string kModuleName = "RomfsProcess"; - static const size_t kFileExportBlockSize = 0x1000000; - //static const size_t kFileExportBlockSize = 0x1000000; + static const size_t kCacheSize = 0x10000; fnd::IFile* mReader; CliOutputType mCliOutputType; @@ -118,7 +117,7 @@ private: std::string mMountName; bool mListFs; - fnd::MemoryBlob mFileExtractBlock; + fnd::MemoryBlob mCache; size_t mDirNum; size_t mFileNum; From 68f19c8fd3e22fb84b9df7fd20fbf43738e5456e Mon Sep 17 00:00:00 2001 From: jakcron Date: Sun, 27 May 2018 18:56:47 +0800 Subject: [PATCH 59/59] Update readme. --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index acd06e2..aa7fd1a 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ Tools & Libraries for NX (Nintendo Switch). # Tools -* __nstool__ - read *.npdm, read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp), read/extract *.xci, read/extract *.nca +* __nstool__ - read *.npdm, read/extract PartitionFS (PFS0|HFS0) blobs (including *.nsp), read/extract *.xci, read/extract *.nca, read *.cnmt # Libraries