mirror of
				https://github.com/jakcron/nstool.git
				synced 2025-11-04 02:25:00 +00:00 
			
		
		
		
	Merge pull request #10 from jakcron/nca-development
[nx|nstool] Nca development
This commit is contained in:
		
						commit
						c7d78d0f1e
					
				| 
						 | 
				
			
			@ -83,6 +83,7 @@ namespace nx
 | 
			
		|||
		void setContentIndex(uint32_t index);
 | 
			
		||||
		uint32_t getSdkAddonVersion() const;
 | 
			
		||||
		void setSdkAddonVersion(uint32_t version);
 | 
			
		||||
		bool hasRightsId() const;
 | 
			
		||||
		const byte_t* getRightsId() const;
 | 
			
		||||
		void setRightsId(const byte_t* rights_id);
 | 
			
		||||
		const fnd::List<sPartition>& getPartitions() const;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,5 +9,6 @@ namespace nx
 | 
			
		|||
		static inline size_t sectorToOffset(size_t sector_index) { return sector_index * nx::nca::kSectorSize; }
 | 
			
		||||
		static void decryptNcaHeader(const byte_t* src, byte_t* dst, const crypto::aes::sAesXts128Key& key);
 | 
			
		||||
		static byte_t getMasterKeyRevisionFromKeyGeneration(byte_t key_generation);
 | 
			
		||||
		static void getNcaPartitionAesCtr(const nx::sNcaFsHeader* hdr, byte_t* ctr);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								lib/libnx/include/nx/hierarchicalsha256.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								lib/libnx/include/nx/hierarchicalsha256.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <fnd/types.h>
 | 
			
		||||
#include <crypto/sha.h>
 | 
			
		||||
#include <fnd/ISerialiseableBinary.h>
 | 
			
		||||
 | 
			
		||||
namespace nx
 | 
			
		||||
{
 | 
			
		||||
	namespace hierarchicalsha256
 | 
			
		||||
	{
 | 
			
		||||
		static const size_t kDefaultLevelNum = 2;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
#pragma pack(push,1)
 | 
			
		||||
	struct sHierarchicalSha256Header
 | 
			
		||||
	{
 | 
			
		||||
		crypto::sha::sSha256Hash master_hash;
 | 
			
		||||
		le_uint32_t hash_block_size;
 | 
			
		||||
		le_uint32_t hash_level_num;
 | 
			
		||||
		struct sLayout
 | 
			
		||||
		{
 | 
			
		||||
			le_uint64_t offset;
 | 
			
		||||
			le_uint64_t size;
 | 
			
		||||
		} hash_data, hash_target;
 | 
			
		||||
	};
 | 
			
		||||
#pragma pack(pop)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -11,7 +11,7 @@ namespace nx
 | 
			
		|||
	namespace ivfc
 | 
			
		||||
	{
 | 
			
		||||
		const std::string kIvfcSig = "IVFC";
 | 
			
		||||
		static const size_t kMaxIvfcLevel = 4;
 | 
			
		||||
		static const size_t kMaxIvfcLevel = 7;
 | 
			
		||||
		static const uint32_t kIvfcId = 0x20000;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
| 
						 | 
				
			
			@ -24,13 +24,13 @@ namespace nx
 | 
			
		|||
		le_uint32_t level_num;
 | 
			
		||||
		struct sIvfcLevelHeader
 | 
			
		||||
		{
 | 
			
		||||
			uint64_t logical_offset;
 | 
			
		||||
			uint64_t hash_data_size;
 | 
			
		||||
			uint32_t block_size;
 | 
			
		||||
			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 unk_0xA0[0x20];
 | 
			
		||||
		byte_t master_hash[0x20];
 | 
			
		||||
		byte_t reserved_00[0x8];
 | 
			
		||||
		crypto::sha::sSha256Hash master_hash;
 | 
			
		||||
	};
 | 
			
		||||
#pragma pack(pop)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,8 +3,10 @@
 | 
			
		|||
#include <fnd/types.h>
 | 
			
		||||
#include <crypto/aes.h>
 | 
			
		||||
#include <crypto/sha.h>
 | 
			
		||||
#include <crypto/rsa.h>
 | 
			
		||||
#include <fnd/ISerialiseableBinary.h>
 | 
			
		||||
#include <nx/ivfc.h>
 | 
			
		||||
#include <nx/hierarchicalsha256.h>
 | 
			
		||||
 | 
			
		||||
namespace nx
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -19,6 +21,8 @@ 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 uint16_t kDefaultFsHeaderVersion = 2;
 | 
			
		||||
 | 
			
		||||
		enum ProgramPartitionId
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			@ -40,6 +44,7 @@ namespace nx
 | 
			
		|||
			TYPE_CONTROL,
 | 
			
		||||
			TYPE_MANUAL,
 | 
			
		||||
			TYPE_DATA,
 | 
			
		||||
			TYPE_PUBLIC_DATA
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		enum KeyBankIndex
 | 
			
		||||
| 
						 | 
				
			
			@ -67,7 +72,7 @@ namespace nx
 | 
			
		|||
		enum HashType
 | 
			
		||||
		{
 | 
			
		||||
			HASH_AUTO,
 | 
			
		||||
			HASH_UNK1,
 | 
			
		||||
			HASH_NONE,
 | 
			
		||||
			HASH_HIERARCHICAL_SHA256,
 | 
			
		||||
			HASH_HIERARCHICAL_INTERGRITY // IVFC
 | 
			
		||||
		};
 | 
			
		||||
| 
						 | 
				
			
			@ -78,7 +83,7 @@ namespace nx
 | 
			
		|||
			CRYPT_NONE,
 | 
			
		||||
			CRYPT_AESXTS,
 | 
			
		||||
			CRYPT_AESCTR,
 | 
			
		||||
			CRYPT_BKTR
 | 
			
		||||
			CRYPT_AESCTREX
 | 
			
		||||
		};	
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
| 
						 | 
				
			
			@ -88,7 +93,7 @@ namespace nx
 | 
			
		|||
		char signature[4];
 | 
			
		||||
		byte_t distribution_type;
 | 
			
		||||
		byte_t content_type;
 | 
			
		||||
		byte_t key_generation; // KeyGeneration
 | 
			
		||||
		byte_t key_generation;
 | 
			
		||||
		byte_t key_area_encryption_key_index;
 | 
			
		||||
		le_uint64_t content_size;
 | 
			
		||||
		le_uint64_t program_id;
 | 
			
		||||
| 
						 | 
				
			
			@ -110,23 +115,26 @@ namespace nx
 | 
			
		|||
 | 
			
		||||
	struct sNcaFsHeader
 | 
			
		||||
	{
 | 
			
		||||
		le_uint16_t version; // usually 0x0002
 | 
			
		||||
		byte_t format_type; // RomFs(0x00), PartitionFs(0x01)
 | 
			
		||||
		byte_t hash_type; // HashTypeAuto(0x00), HashTypeHierarchicalSha256(0x02), HashTypeHierarchicalIntegrity(0x03).RomFs uses (0x03) this is forced, PartitionFs uses (0x02).
 | 
			
		||||
		byte_t encryption_type; // EncryptionTypeAuto(0x00), EncryptionTypeNone(0x01), EncryptionTypeAesCtr(0x03)
 | 
			
		||||
		byte_t reserved[3];
 | 
			
		||||
		le_uint16_t version;
 | 
			
		||||
		byte_t format_type;
 | 
			
		||||
		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::sIvfcHeader ivfc_header;
 | 
			
		||||
		};
 | 
			
		||||
		crypto::aes::sAesIvCtr base_ctr;
 | 
			
		||||
		byte_t reserved_1[0xB8];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	struct sHierarchicalSha256Header
 | 
			
		||||
	struct sNcaHeaderBlock
 | 
			
		||||
	{
 | 
			
		||||
		byte_t master_hash[0x20];
 | 
			
		||||
		le_uint32_t hash_block_size;
 | 
			
		||||
		le_uint32_t unk_0x02;
 | 
			
		||||
		struct sLayout
 | 
			
		||||
		{
 | 
			
		||||
			le_uint64_t offset;
 | 
			
		||||
			le_uint64_t size;
 | 
			
		||||
		} hash_data, hash_target;
 | 
			
		||||
		byte_t signature_main[crypto::rsa::kRsa2048Size];
 | 
			
		||||
		byte_t signature_acid[crypto::rsa::kRsa2048Size];
 | 
			
		||||
		sNcaHeader header;
 | 
			
		||||
		sNcaFsHeader fs_header[nx::nca::kPartitionNum];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
#pragma pack(pop)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -219,6 +219,19 @@ void nx::NcaHeader::setSdkAddonVersion(uint32_t version)
 | 
			
		|||
	mSdkAddonVersion = version;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nx::NcaHeader::hasRightsId() const
 | 
			
		||||
{
 | 
			
		||||
	bool rightsIdIsSet = false;
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < nca::kRightsIdLen; i++)
 | 
			
		||||
	{
 | 
			
		||||
		if (mRightsId[i] != 0)
 | 
			
		||||
			rightsIdIsSet = true;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	return rightsIdIsSet;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const byte_t* nx::NcaHeader::getRightsId() const
 | 
			
		||||
{
 | 
			
		||||
	return mRightsId;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -46,4 +46,12 @@ byte_t nx::NcaUtils::getMasterKeyRevisionFromKeyGeneration(byte_t key_generation
 | 
			
		|||
	}
 | 
			
		||||
 | 
			
		||||
	return masterkey_rev;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nx::NcaUtils::getNcaPartitionAesCtr(const nx::sNcaFsHeader* hdr, byte_t* ctr)
 | 
			
		||||
{
 | 
			
		||||
	for (size_t i = 0; i < 16; i++)
 | 
			
		||||
	{
 | 
			
		||||
		ctr[15-i] = hdr->base_ctr.iv[i];
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										71
									
								
								programs/nstool/source/AesCtrWrappedIFile.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										71
									
								
								programs/nstool/source/AesCtrWrappedIFile.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,71 @@
 | 
			
		|||
#include "AesCtrWrappedIFile.h"
 | 
			
		||||
 | 
			
		||||
AesCtrWrappedIFile::AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr) :
 | 
			
		||||
	mFile(file),
 | 
			
		||||
	mKey(key),
 | 
			
		||||
	mBaseCtr(ctr)
 | 
			
		||||
{
 | 
			
		||||
	mScratch.alloc(kAesCtrScratchAllocSize);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t AesCtrWrappedIFile::size()
 | 
			
		||||
{
 | 
			
		||||
	return mFile.size();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AesCtrWrappedIFile::seek(size_t offset)
 | 
			
		||||
{
 | 
			
		||||
	mFile.seek(offset);
 | 
			
		||||
	crypto::aes::AesIncrementCounter(mBaseCtr.iv, offset>>4, mCurrentCtr.iv);
 | 
			
		||||
	mBlockOffset = offset & 0xf;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AesCtrWrappedIFile::read(byte_t* out, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
 | 
			
		||||
	{
 | 
			
		||||
		mFile.read(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
 | 
			
		||||
		crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
 | 
			
		||||
		memcpy(out + (i * kAesCtrScratchSize), mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (len % kAesCtrScratchSize)
 | 
			
		||||
	{
 | 
			
		||||
		size_t read_len = len % kAesCtrScratchSize;
 | 
			
		||||
		size_t read_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize); 
 | 
			
		||||
		mFile.read(mScratch.getBytes() + mBlockOffset, read_len);
 | 
			
		||||
		crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
 | 
			
		||||
		memcpy(out + read_pos, mScratch.getBytes() + mBlockOffset, read_len);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	seek(offset);
 | 
			
		||||
	read(out, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AesCtrWrappedIFile::write(const byte_t* out, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
 | 
			
		||||
	{
 | 
			
		||||
		memcpy(mScratch.getBytes() + mBlockOffset, out + (i * kAesCtrScratchSize), kAesCtrScratchSize);
 | 
			
		||||
		crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
 | 
			
		||||
		mFile.write(mScratch.getBytes() + mBlockOffset, kAesCtrScratchSize);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	if (len % kAesCtrScratchSize)
 | 
			
		||||
	{
 | 
			
		||||
		size_t write_len = len % kAesCtrScratchSize;
 | 
			
		||||
		size_t write_pos = ((len / kAesCtrScratchSize) * kAesCtrScratchSize); 
 | 
			
		||||
		memcpy(mScratch.getBytes() + mBlockOffset, out + write_pos, write_len);
 | 
			
		||||
		crypto::aes::AesCtr(mScratch.getBytes(), kAesCtrScratchAllocSize, mKey.key, mCurrentCtr.iv, mScratch.getBytes());
 | 
			
		||||
		mFile.write(mScratch.getBytes() + mBlockOffset, write_len);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AesCtrWrappedIFile::write(const byte_t* out, size_t offset, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	seek(offset);
 | 
			
		||||
	write(out, len);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										27
									
								
								programs/nstool/source/AesCtrWrappedIFile.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								programs/nstool/source/AesCtrWrappedIFile.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
#include <fnd/IFile.h>
 | 
			
		||||
#include <fnd/MemoryBlob.h>
 | 
			
		||||
#include <crypto/aes.h>
 | 
			
		||||
 | 
			
		||||
class AesCtrWrappedIFile : public fnd::IFile
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	AesCtrWrappedIFile(fnd::IFile& file, const crypto::aes::sAes128Key& key, const crypto::aes::sAesIvCtr& ctr);
 | 
			
		||||
 | 
			
		||||
	size_t size();
 | 
			
		||||
	void seek(size_t offset);
 | 
			
		||||
	void read(byte_t* out, size_t len);
 | 
			
		||||
	void read(byte_t* out, size_t offset, size_t len);
 | 
			
		||||
	void write(const byte_t* out, size_t len);
 | 
			
		||||
	void write(const byte_t* out, size_t offset, size_t len);
 | 
			
		||||
private:
 | 
			
		||||
	const std::string kModuleName = "AesCtrWrappedIFile";
 | 
			
		||||
	static const size_t kAesCtrScratchSize = 0x1000000;
 | 
			
		||||
	static const size_t kAesCtrScratchAllocSize = kAesCtrScratchSize + crypto::aes::kAesBlockSize;
 | 
			
		||||
 | 
			
		||||
	fnd::IFile& mFile;
 | 
			
		||||
	crypto::aes::sAes128Key mKey;
 | 
			
		||||
	crypto::aes::sAesIvCtr mBaseCtr, mCurrentCtr;
 | 
			
		||||
	size_t mBlockOffset;
 | 
			
		||||
 | 
			
		||||
	fnd::MemoryBlob mScratch;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										0
									
								
								programs/nstool/source/NcaPartitionProcess.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										0
									
								
								programs/nstool/source/NcaPartitionProcess.cpp
									
									
									
									
									
										Normal file
									
								
							
							
								
								
									
										16
									
								
								programs/nstool/source/NcaPartitionProcess.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								programs/nstool/source/NcaPartitionProcess.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,16 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <fnd/types.h>
 | 
			
		||||
#include <fnd/SimpleFile.h>
 | 
			
		||||
#include <nx/NcaHeader.h>
 | 
			
		||||
 | 
			
		||||
#include "nstool.h"
 | 
			
		||||
 | 
			
		||||
class NcaPartitionProcess
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	NcaPartitionProcess();
 | 
			
		||||
private:
 | 
			
		||||
	const std::string kModuleName = "NcaPartitionProcess";
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										476
									
								
								programs/nstool/source/NcaProcess.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										476
									
								
								programs/nstool/source/NcaProcess.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,476 @@
 | 
			
		|||
#include <fnd/SimpleTextOutput.h>
 | 
			
		||||
#include <nx/NcaUtils.h>
 | 
			
		||||
#include <nx/AesKeygen.h>
 | 
			
		||||
#include "NcaProcess.h"
 | 
			
		||||
#include "PfsProcess.h"
 | 
			
		||||
#include "RomfsProcess.h"
 | 
			
		||||
#include "OffsetAdjustedIFile.h"
 | 
			
		||||
#include "AesCtrWrappedIFile.h"
 | 
			
		||||
 | 
			
		||||
std::string kFormatVersionStr[]
 | 
			
		||||
{
 | 
			
		||||
	"NCA2",
 | 
			
		||||
	"NCA3"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::string kDistributionTypeStr[]
 | 
			
		||||
{
 | 
			
		||||
	"Download",
 | 
			
		||||
	"Game Card"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::string kContentTypeStr[]
 | 
			
		||||
{
 | 
			
		||||
	"Program",
 | 
			
		||||
	"Meta",
 | 
			
		||||
	"Control",
 | 
			
		||||
	"Manual",
 | 
			
		||||
	"Data",
 | 
			
		||||
	"PublicData"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::string kEncryptionTypeStr[]
 | 
			
		||||
{
 | 
			
		||||
	"Auto",
 | 
			
		||||
	"None",
 | 
			
		||||
	"AesXts",
 | 
			
		||||
	"AesCtr",
 | 
			
		||||
	"AesCtrEx"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::string kHashTypeStr[]
 | 
			
		||||
{
 | 
			
		||||
	"Auto",
 | 
			
		||||
	"None",
 | 
			
		||||
	"HierarchicalSha256",
 | 
			
		||||
	"HierarchicalIntegrity"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::string kFormatTypeStr[]
 | 
			
		||||
{
 | 
			
		||||
	"RomFs",
 | 
			
		||||
	"PartitionFs"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
std::string kKaekIndexStr[]
 | 
			
		||||
{
 | 
			
		||||
	"Application",
 | 
			
		||||
	"Ocean",
 | 
			
		||||
	"System"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
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());
 | 
			
		||||
	printf("  Content Type:    %s\n", kContentTypeStr[mHdr.getContentType()].c_str());
 | 
			
		||||
	printf("  Key Generation:  %d\n", mHdr.getKeyGeneration());
 | 
			
		||||
	printf("  Kaek Index:      %s (%d)\n", kKaekIndexStr[mHdr.getKaekIndex()].c_str(), mHdr.getKaekIndex());
 | 
			
		||||
	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);
 | 
			
		||||
	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 (mHdr.getEncAesKeys()[i] != zero_key)
 | 
			
		||||
		{
 | 
			
		||||
			printf("    %2lu: ", i);
 | 
			
		||||
			fnd::SimpleTextOutput::hexDump(mHdr.getEncAesKeys()[i].key, crypto::aes::kAes128KeySize);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	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++)
 | 
			
		||||
	{
 | 
			
		||||
		const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i];
 | 
			
		||||
		nx::sNcaFsHeader& fs_header = mHdrBlock.fs_header[partition.index];
 | 
			
		||||
 | 
			
		||||
		printf("    %lu:\n", i);
 | 
			
		||||
		printf("      Index:       %d\n", partition.index);
 | 
			
		||||
		printf("      Offset:      0x%" PRIx64 "\n", partition.offset);
 | 
			
		||||
		printf("      Size:        0x%" PRIx64 "\n", partition.size);
 | 
			
		||||
		
 | 
			
		||||
		
 | 
			
		||||
		crypto::sha::sSha256Hash ncaFsHeaderHash;
 | 
			
		||||
		crypto::sha::Sha256((byte_t*)&fs_header, sizeof(nx::sNcaFsHeader), ncaFsHeaderHash.bytes);
 | 
			
		||||
		if (partition.hash.compare(ncaFsHeaderHash) == false)
 | 
			
		||||
		{
 | 
			
		||||
			throw fnd::Exception(kModuleName, "NcaFsHeader has bad sha256 hash");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		//fnd::SimpleTextOutput::hxdStyleDump((byte_t*)&fs_header, sizeof(nx::sNcaFsHeader));
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
		printf("      FsHeader:\n");
 | 
			
		||||
		printf("        Version:     0x%d\n", fs_header.version.get());
 | 
			
		||||
		printf("        Format Type: %s\n", kFormatTypeStr[fs_header.format_type].c_str());
 | 
			
		||||
		printf("        Hash Type:   %s\n", kHashTypeStr[fs_header.hash_type].c_str());
 | 
			
		||||
		printf("        Enc. Type:   %s\n", kEncryptionTypeStr[fs_header.encryption_type].c_str());
 | 
			
		||||
		if (fs_header.encryption_type == nx::nca::CRYPT_AESCTR)
 | 
			
		||||
		{
 | 
			
		||||
			printf("        CTR:         ");
 | 
			
		||||
			crypto::aes::sAesIvCtr ctr;
 | 
			
		||||
			nx::NcaUtils::getNcaPartitionAesCtr(&fs_header, ctr.iv);
 | 
			
		||||
			crypto::aes::AesIncrementCounter(ctr.iv, partition.offset>>4, ctr.iv);
 | 
			
		||||
			fnd::SimpleTextOutput::hexDump(ctr.iv, sizeof(crypto::aes::sAesIvCtr));
 | 
			
		||||
		}
 | 
			
		||||
		if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_INTERGRITY)
 | 
			
		||||
		{
 | 
			
		||||
			printf("      HierarchicalIntegrity Header:\n");
 | 
			
		||||
			printf("        Id:                0x%x\n", fs_header.ivfc_header.id.get());
 | 
			
		||||
			printf("        MasterHashSize:    0x%x\n", fs_header.ivfc_header.master_hash_size.get());
 | 
			
		||||
			printf("        LevelNum:          %d\n", fs_header.ivfc_header.level_num.get());
 | 
			
		||||
			for (size_t i = 0; i < fs_header.ivfc_header.level_num.get(); i++)
 | 
			
		||||
			{
 | 
			
		||||
				printf("        Level %d:\n", i);
 | 
			
		||||
				printf("          LogicalOffset:   0x%" PRIx64 "\n", fs_header.ivfc_header.level_header[i].logical_offset.get());
 | 
			
		||||
				printf("          HashDataSize:    0x%" PRIx64 "\n", fs_header.ivfc_header.level_header[i].hash_data_size.get());
 | 
			
		||||
				printf("          BlockSize:       0x%" PRIx32 "\n", fs_header.ivfc_header.level_header[i].block_size.get());
 | 
			
		||||
			}
 | 
			
		||||
			printf("        Master Hash:       ");
 | 
			
		||||
			fnd::SimpleTextOutput::hexDump(fs_header.ivfc_header.master_hash.bytes, 0x20);
 | 
			
		||||
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
		else if (fs_header.hash_type == nx::nca::HASH_HIERARCHICAL_SHA256)
 | 
			
		||||
		{
 | 
			
		||||
			nx::sHierarchicalSha256Header& hash_hdr = fs_header.hierarchicalsha256_header;
 | 
			
		||||
			printf("      HierarchicalSha256 Header:\n");
 | 
			
		||||
			printf("        Master Hash:       ");
 | 
			
		||||
			fnd::SimpleTextOutput::hexDump(hash_hdr.master_hash.bytes, 0x20);
 | 
			
		||||
			printf("        HashBlockSize:     0x%x\n", hash_hdr.hash_block_size.get());
 | 
			
		||||
			printf("        HashLevelNum:      0x%x\n", hash_hdr.hash_level_num.get());
 | 
			
		||||
			printf("        HashDataOffset:    0x%" PRIx64 "\n", hash_hdr.hash_data.offset.get());
 | 
			
		||||
			printf("        HashDataSize:      0x%" PRIx64 "\n", hash_hdr.hash_data.size.get());
 | 
			
		||||
			printf("        HashTargetOffset:  0x%" PRIx64 "\n", hash_hdr.hash_target.offset.get());
 | 
			
		||||
			printf("        HashTargetSize:    0x%" PRIx64 "\n", hash_hdr.hash_target.size.get());
 | 
			
		||||
		
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			printf("      Hash Superblock:\n");
 | 
			
		||||
			fnd::SimpleTextOutput::hxdStyleDump(fs_header.hash_superblock, nx::nca::kFsHeaderHashSuperblockLen);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NcaProcess::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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		// if the reader is null, skip
 | 
			
		||||
		if (partitionReader == nullptr)
 | 
			
		||||
		{
 | 
			
		||||
			printf("[WARNING] NCA Partition %d not readable\n", partition.index);
 | 
			
		||||
			continue;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		size_t data_offset = 0;
 | 
			
		||||
		switch (fs_header.hash_type)
 | 
			
		||||
		{
 | 
			
		||||
			case (nx::nca::HASH_HIERARCHICAL_SHA256):
 | 
			
		||||
				data_offset = fs_header.hierarchicalsha256_header.hash_target.offset.get();
 | 
			
		||||
				break;
 | 
			
		||||
			case (nx::nca::HASH_HIERARCHICAL_INTERGRITY):
 | 
			
		||||
				data_offset = fs_header.ivfc_header.level_header[5].logical_offset.get();
 | 
			
		||||
				break;
 | 
			
		||||
			case (nx::nca::HASH_NONE):
 | 
			
		||||
				data_offset = 0;
 | 
			
		||||
				break;
 | 
			
		||||
			default:
 | 
			
		||||
				throw fnd::Exception(kModuleName, "Unknown hash type");
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		if (fs_header.format_type == nx::nca::FORMAT_PFS0)
 | 
			
		||||
		{
 | 
			
		||||
			PfsProcess pfs;
 | 
			
		||||
			pfs.setInputFile(*partitionReader);
 | 
			
		||||
			pfs.setInputFileOffset(partition.offset + data_offset);
 | 
			
		||||
			pfs.setCliOutputMode(mCliOutputType);
 | 
			
		||||
			pfs.setListFs(mListFs);
 | 
			
		||||
			if (mPartitionPath[partition.index].doExtract)
 | 
			
		||||
				pfs.setExtractPath(mPartitionPath[partition.index].path);
 | 
			
		||||
			pfs.process();
 | 
			
		||||
		}
 | 
			
		||||
		else if (fs_header.format_type == nx::nca::FORMAT_ROMFS)
 | 
			
		||||
		{
 | 
			
		||||
			RomfsProcess romfs;
 | 
			
		||||
			romfs.setInputFile(*partitionReader);
 | 
			
		||||
			romfs.setInputFileOffset(partition.offset + data_offset);
 | 
			
		||||
			romfs.setCliOutputMode(mCliOutputType);
 | 
			
		||||
			romfs.setListFs(mListFs);
 | 
			
		||||
			if (mPartitionPath[partition.index].doExtract)
 | 
			
		||||
				romfs.setExtractPath(mPartitionPath[partition.index].path);
 | 
			
		||||
			romfs.process();
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			throw fnd::Exception(kModuleName, "Unknown format type");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NcaProcess::NcaProcess() :
 | 
			
		||||
	mReader(nullptr),
 | 
			
		||||
	mOffset(0),
 | 
			
		||||
	mKeyset(nullptr),
 | 
			
		||||
	mCliOutputType(OUTPUT_NORMAL),
 | 
			
		||||
	mVerify(false),
 | 
			
		||||
	mListFs(false)
 | 
			
		||||
{
 | 
			
		||||
	mPartitionPath[0].doExtract = false;
 | 
			
		||||
	mPartitionPath[1].doExtract = false;
 | 
			
		||||
	mPartitionPath[2].doExtract = false;
 | 
			
		||||
	mPartitionPath[3].doExtract = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
NcaProcess::~NcaProcess()
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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, mOffset, 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);
 | 
			
		||||
 | 
			
		||||
	// validate signature[0]
 | 
			
		||||
	if (mVerify)
 | 
			
		||||
	{
 | 
			
		||||
		if (crypto::rsa::pss::rsaVerify(mKeyset->nca.header_sign_key, crypto::sha::HASH_SHA256, mHdrHash.bytes, mHdrBlock.signature_main) != 0)
 | 
			
		||||
		{
 | 
			
		||||
			// this is minimal even though it's a warning because it's a validation method
 | 
			
		||||
			if (mCliOutputType >= OUTPUT_MINIMAL)
 | 
			
		||||
				printf("[WARNING] NCA Header Main Signature: FAIL \n");
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// proccess main header
 | 
			
		||||
	mHdr.importBinary((byte_t*)&mHdrBlock.header, sizeof(nx::sNcaHeader));
 | 
			
		||||
 | 
			
		||||
	// validate fs headers
 | 
			
		||||
	if (mVerify)
 | 
			
		||||
	{
 | 
			
		||||
		crypto::sha::sSha256Hash calc_hash;
 | 
			
		||||
 | 
			
		||||
		for (size_t i = 0; i < mHdr.getPartitions().getSize(); i++)
 | 
			
		||||
		{
 | 
			
		||||
			const nx::NcaHeader::sPartition& partition = mHdr.getPartitions()[i];
 | 
			
		||||
 | 
			
		||||
			crypto::sha::Sha256((const byte_t*)&mHdrBlock.fs_header[partition.index], sizeof(nx::sNcaFsHeader), calc_hash.bytes);
 | 
			
		||||
 | 
			
		||||
			if (calc_hash.compare(partition.hash) == false)
 | 
			
		||||
			{
 | 
			
		||||
				// this is minimal even though it's a warning because it's a validation method
 | 
			
		||||
				if (mCliOutputType >= OUTPUT_MINIMAL)
 | 
			
		||||
					printf("[WARNING] NCA FsHeader[%d] Hash: FAIL \n", partition.index);
 | 
			
		||||
			}
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// determine keys
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
	/*
 | 
			
		||||
	NCA is a file container
 | 
			
		||||
	A hashed and signed file container
 | 
			
		||||
 | 
			
		||||
	To verify a NCA: (R=regular step)
 | 
			
		||||
	1 - decrypt header (R)
 | 
			
		||||
	2 - verify signature[0]
 | 
			
		||||
	3 - validate hashes of fs_headers
 | 
			
		||||
	4 - determine how to read/decrypt the partitions (R)
 | 
			
		||||
	5 - validate the partitions depending on their hash method
 | 
			
		||||
	6 - if this NCA is a Program or Patch, open main.npdm from partition0
 | 
			
		||||
	7 - validate ACID
 | 
			
		||||
	8 - use public key in ACID to verify NCA signature[1]
 | 
			
		||||
 | 
			
		||||
	Things to consider
 | 
			
		||||
	* because of the manditory steps between verifcation steps
 | 
			
		||||
	  the NCA should be ready to be pulled to pieces before any printing is done
 | 
			
		||||
	  so the verification text can be presented without interuption
 | 
			
		||||
 | 
			
		||||
	*/
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	// decrypt key area
 | 
			
		||||
	generateNcaBodyEncryptionKeys();
 | 
			
		||||
 | 
			
		||||
	if (mCliOutputType >= OUTPUT_NORMAL)
 | 
			
		||||
		displayHeader();
 | 
			
		||||
 | 
			
		||||
	processPartitions();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NcaProcess::setInputFile(fnd::IFile* reader)
 | 
			
		||||
{
 | 
			
		||||
	mReader = reader;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void NcaProcess::setInputFileOffset(size_t offset)
 | 
			
		||||
{
 | 
			
		||||
	mOffset = offset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -2,7 +2,6 @@
 | 
			
		|||
#include <string>
 | 
			
		||||
#include <fnd/types.h>
 | 
			
		||||
#include <fnd/SimpleFile.h>
 | 
			
		||||
#include <nx/nca.h>
 | 
			
		||||
#include <nx/NcaHeader.h>
 | 
			
		||||
 | 
			
		||||
#include "nstool.h"
 | 
			
		||||
| 
						 | 
				
			
			@ -16,18 +15,54 @@ 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);
 | 
			
		||||
	void setVerifyMode(bool verify);
 | 
			
		||||
 | 
			
		||||
	// nca specfic
 | 
			
		||||
	void setPartition0ExtractPath(const std::string& path);
 | 
			
		||||
	void setPartition1ExtractPath(const std::string& path);
 | 
			
		||||
	void setPartition2ExtractPath(const std::string& path);
 | 
			
		||||
	void setPartition3ExtractPath(const std::string& path);
 | 
			
		||||
	void setListFs(bool list_fs);
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const std::string kModuleName = "NcaProcess";
 | 
			
		||||
 | 
			
		||||
	byte_t mRawHeader[nx::nca::kHeaderSize];
 | 
			
		||||
	std::string mPath;
 | 
			
		||||
	// user options
 | 
			
		||||
	fnd::IFile* mReader;
 | 
			
		||||
	size_t mOffset;
 | 
			
		||||
	const sKeyset* mKeyset;
 | 
			
		||||
	CliOutputType mCliOutputType;
 | 
			
		||||
	bool mVerify;
 | 
			
		||||
 | 
			
		||||
	struct sExtract
 | 
			
		||||
	{
 | 
			
		||||
		std::string path;
 | 
			
		||||
		bool doExtract;
 | 
			
		||||
	} mPartitionPath[nx::nca::kPartitionNum];
 | 
			
		||||
 | 
			
		||||
	bool mListFs;
 | 
			
		||||
 | 
			
		||||
	// data
 | 
			
		||||
	nx::sNcaHeaderBlock mHdrBlock;
 | 
			
		||||
	crypto::sha::sSha256Hash mHdrHash;
 | 
			
		||||
	nx::NcaHeader mHdr;
 | 
			
		||||
 | 
			
		||||
	// crypto
 | 
			
		||||
	struct sKeys
 | 
			
		||||
	{
 | 
			
		||||
		sOptional<crypto::aes::sAes128Key> aes_ctr;
 | 
			
		||||
		sOptional<crypto::aes::sAesXts128Key> aes_xts;
 | 
			
		||||
	} mBodyKeys;
 | 
			
		||||
	
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	void displayHeader();
 | 
			
		||||
 | 
			
		||||
	void generateNcaBodyEncryptionKeys();
 | 
			
		||||
 | 
			
		||||
	void processPartitions();
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										45
									
								
								programs/nstool/source/OffsetAdjustedIFile.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								programs/nstool/source/OffsetAdjustedIFile.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,45 @@
 | 
			
		|||
#include "OffsetAdjustedIFile.h"
 | 
			
		||||
 | 
			
		||||
OffsetAdjustedIFile::OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size) :
 | 
			
		||||
	mFile(file),
 | 
			
		||||
	mBaseOffset(offset),
 | 
			
		||||
	mCurrentOffset(0),
 | 
			
		||||
	mSize(size)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t OffsetAdjustedIFile::size()
 | 
			
		||||
{
 | 
			
		||||
	return mSize;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OffsetAdjustedIFile::seek(size_t offset)
 | 
			
		||||
{
 | 
			
		||||
	mCurrentOffset = offset;
 | 
			
		||||
	mFile.seek(offset + mBaseOffset);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OffsetAdjustedIFile::read(byte_t* out, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	seek(mCurrentOffset);
 | 
			
		||||
	mFile.read(out, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	seek(offset);
 | 
			
		||||
	read(out, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OffsetAdjustedIFile::write(const byte_t* out, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	seek(mCurrentOffset);
 | 
			
		||||
	mFile.write(out, len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	seek(offset);
 | 
			
		||||
	write(out, len);
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										18
									
								
								programs/nstool/source/OffsetAdjustedIFile.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								programs/nstool/source/OffsetAdjustedIFile.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,18 @@
 | 
			
		|||
#include <fnd/IFile.h>
 | 
			
		||||
 | 
			
		||||
class OffsetAdjustedIFile : public fnd::IFile
 | 
			
		||||
{
 | 
			
		||||
public:
 | 
			
		||||
	OffsetAdjustedIFile(fnd::IFile& file, size_t offset, size_t size);
 | 
			
		||||
 | 
			
		||||
	size_t size();
 | 
			
		||||
	void seek(size_t offset);
 | 
			
		||||
	void read(byte_t* out, size_t len);
 | 
			
		||||
	void read(byte_t* out, size_t offset, size_t len);
 | 
			
		||||
	void write(const byte_t* out, size_t len);
 | 
			
		||||
	void write(const byte_t* out, size_t offset, size_t len);
 | 
			
		||||
private:
 | 
			
		||||
	fnd::IFile& mFile;
 | 
			
		||||
	size_t mBaseOffset, mCurrentOffset;
 | 
			
		||||
	size_t mSize;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -73,9 +73,13 @@ void PfsProcess::extractFs()
 | 
			
		|||
	fnd::SimpleFile outFile;
 | 
			
		||||
	const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
 | 
			
		||||
 | 
			
		||||
	std::string file_path;
 | 
			
		||||
	for (size_t i = 0; i < file.getSize(); i++)
 | 
			
		||||
	{
 | 
			
		||||
		outFile.open(mExtractPath + kPathSeparator + file[i].name, outFile.Create);
 | 
			
		||||
		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(mOffset + file[i].offset);
 | 
			
		||||
		for (size_t j = 0; j < (file[i].size / kFileExportBlockSize); j++)
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -52,7 +52,6 @@ void UserSettings::showHelp()
 | 
			
		|||
	printf("    nstool [--listfs] [--fsdir <dir>] <file>\n");
 | 
			
		||||
	printf("      --listfs        Print file system\n");
 | 
			
		||||
	printf("      --fsdir         Extract file system to directory\n");
 | 
			
		||||
	/*
 | 
			
		||||
	printf("\n  NCA (Nintendo Content Archive)\n");
 | 
			
		||||
	printf("    nstool [--listfs] [--bodykey <key> --titlekey <key>] [--part0 <dir> ...] <.nca file>\n");
 | 
			
		||||
	printf("      --listfs        Print file system in embedded partitions\n");
 | 
			
		||||
| 
						 | 
				
			
			@ -62,7 +61,6 @@ void UserSettings::showHelp()
 | 
			
		|||
	printf("      --part1         Extract \"partition 1\" to directory \n");
 | 
			
		||||
	printf("      --part2         Extract \"partition 2\" to directory \n");
 | 
			
		||||
	printf("      --part3         Extract \"partition 3\" to directory \n");
 | 
			
		||||
	*/
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const std::string UserSettings::getInputPath() const
 | 
			
		||||
| 
						 | 
				
			
			@ -115,6 +113,26 @@ const sOptional<std::string>& UserSettings::getFsPath() const
 | 
			
		|||
	return mFsPath;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const sOptional<std::string>& UserSettings::getPart0Path() const
 | 
			
		||||
{
 | 
			
		||||
	return mPart0Path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const sOptional<std::string>& UserSettings::getPart1Path() const
 | 
			
		||||
{
 | 
			
		||||
	return mPart1Path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const sOptional<std::string>& UserSettings::getPart2Path() const
 | 
			
		||||
{
 | 
			
		||||
	return mPart2Path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const sOptional<std::string>& UserSettings::getPart3Path() const
 | 
			
		||||
{
 | 
			
		||||
	return mPart3Path;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -226,10 +244,28 @@ void UserSettings::populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args)
 | 
			
		|||
			cmd_args.nca_bodykey = args[i+1];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else if (args[i] == "-o")
 | 
			
		||||
		else if (args[i] == "--part0")
 | 
			
		||||
		{
 | 
			
		||||
			if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
 | 
			
		||||
			cmd_args.output_path = args[i+1];
 | 
			
		||||
			cmd_args.part0_path = args[i+1];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else if (args[i] == "--part1")
 | 
			
		||||
		{
 | 
			
		||||
			if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
 | 
			
		||||
			cmd_args.part1_path = args[i+1];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else if (args[i] == "--part2")
 | 
			
		||||
		{
 | 
			
		||||
			if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
 | 
			
		||||
			cmd_args.part2_path = args[i+1];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else if (args[i] == "--part3")
 | 
			
		||||
		{
 | 
			
		||||
			if (!hasParamter) throw fnd::Exception(kModuleName, args[i] + " requries a parameter.");
 | 
			
		||||
			cmd_args.part3_path = args[i+1];
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
		else
 | 
			
		||||
| 
						 | 
				
			
			@ -393,7 +429,14 @@ void UserSettings::populateKeyset(sCmdArgs& args)
 | 
			
		|||
 | 
			
		||||
	if (args.nca_titlekey.isSet)
 | 
			
		||||
	{
 | 
			
		||||
		decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key.key, sizeof(crypto::aes::sAes128Key));
 | 
			
		||||
		if (args.nca_bodykey.var.length() == (sizeof(crypto::aes::sAes128Key)*2))
 | 
			
		||||
		{
 | 
			
		||||
			decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesctr.key, sizeof(crypto::aes::sAes128Key));
 | 
			
		||||
		}
 | 
			
		||||
		else
 | 
			
		||||
		{
 | 
			
		||||
			decodeHexStringToBytes("--titlekey", args.nca_titlekey.var, mKeyset.nca.manual_title_key_aesxts.key[0], sizeof(crypto::aes::sAesXts128Key));
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
#undef _SAVE_KEYDATA
 | 
			
		||||
| 
						 | 
				
			
			@ -475,6 +518,10 @@ void UserSettings::populateUserSettings(sCmdArgs& args)
 | 
			
		|||
	mNormalPath = args.normal_path;
 | 
			
		||||
	mSecurePath = args.secure_path;
 | 
			
		||||
	mFsPath = args.fs_path;
 | 
			
		||||
	mPart0Path = args.part0_path;
 | 
			
		||||
	mPart1Path = args.part1_path;
 | 
			
		||||
	mPart2Path = args.part2_path;
 | 
			
		||||
	mPart3Path = args.part3_path;
 | 
			
		||||
 | 
			
		||||
	// determine output path
 | 
			
		||||
	if (args.verbose_output.isSet)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -26,6 +26,10 @@ public:
 | 
			
		|||
	const sOptional<std::string>& getNormalPath() const;
 | 
			
		||||
	const sOptional<std::string>& getSecurePath() const;
 | 
			
		||||
	const sOptional<std::string>& getFsPath() const;
 | 
			
		||||
	const sOptional<std::string>& getPart0Path() const;
 | 
			
		||||
	const sOptional<std::string>& getPart1Path() const;
 | 
			
		||||
	const sOptional<std::string>& getPart2Path() const;
 | 
			
		||||
	const sOptional<std::string>& getPart3Path() const;
 | 
			
		||||
 | 
			
		||||
private:
 | 
			
		||||
	const std::string kModuleName = "UserSettings";
 | 
			
		||||
| 
						 | 
				
			
			@ -33,7 +37,6 @@ private:
 | 
			
		|||
	struct sCmdArgs
 | 
			
		||||
	{
 | 
			
		||||
		sOptional<std::string> input_path;
 | 
			
		||||
		sOptional<std::string> output_path;
 | 
			
		||||
		sOptional<bool> devkit_keys;
 | 
			
		||||
		sOptional<std::string> keyset_path;
 | 
			
		||||
		sOptional<std::string> file_type;
 | 
			
		||||
| 
						 | 
				
			
			@ -47,11 +50,14 @@ private:
 | 
			
		|||
		sOptional<std::string> fs_path;
 | 
			
		||||
		sOptional<std::string> nca_titlekey;
 | 
			
		||||
		sOptional<std::string> nca_bodykey;
 | 
			
		||||
		sOptional<std::string> part0_path;
 | 
			
		||||
		sOptional<std::string> part1_path;
 | 
			
		||||
		sOptional<std::string> part2_path;
 | 
			
		||||
		sOptional<std::string> part3_path;
 | 
			
		||||
 | 
			
		||||
		void clear()
 | 
			
		||||
		{
 | 
			
		||||
			input_path.isSet = false;
 | 
			
		||||
			output_path.isSet = false;
 | 
			
		||||
			devkit_keys.isSet = false;
 | 
			
		||||
			keyset_path.isSet = false;
 | 
			
		||||
			file_type.isSet = false;
 | 
			
		||||
| 
						 | 
				
			
			@ -65,6 +71,10 @@ private:
 | 
			
		|||
			fs_path.isSet = false;
 | 
			
		||||
			nca_titlekey.isSet = false;
 | 
			
		||||
			nca_bodykey.isSet = false;
 | 
			
		||||
			part0_path.isSet = false;
 | 
			
		||||
			part1_path.isSet = false;
 | 
			
		||||
			part2_path.isSet = false;
 | 
			
		||||
			part3_path.isSet = false;
 | 
			
		||||
		}
 | 
			
		||||
	};
 | 
			
		||||
	
 | 
			
		||||
| 
						 | 
				
			
			@ -80,6 +90,11 @@ private:
 | 
			
		|||
	sOptional<std::string> mSecurePath;
 | 
			
		||||
	sOptional<std::string> mFsPath;
 | 
			
		||||
 | 
			
		||||
	sOptional<std::string> mPart0Path;
 | 
			
		||||
	sOptional<std::string> mPart1Path;
 | 
			
		||||
	sOptional<std::string> mPart2Path;
 | 
			
		||||
	sOptional<std::string> mPart3Path;
 | 
			
		||||
 | 
			
		||||
	void populateCmdArgs(int argc, char** argv, sCmdArgs& cmd_args);
 | 
			
		||||
	void populateKeyset(sCmdArgs& args);
 | 
			
		||||
	void populateUserSettings(sCmdArgs& args);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -4,7 +4,7 @@
 | 
			
		|||
#include "XciProcess.h"
 | 
			
		||||
#include "PfsProcess.h"
 | 
			
		||||
#include "RomfsProcess.h"
 | 
			
		||||
//#include "NcaProcess.h"
 | 
			
		||||
#include "NcaProcess.h"
 | 
			
		||||
#include "NpdmProcess.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -54,7 +54,6 @@ int main(int argc, char** argv)
 | 
			
		|||
		}
 | 
			
		||||
		else if (user_set.getFileType() == FILE_ROMFS)
 | 
			
		||||
		{
 | 
			
		||||
			
 | 
			
		||||
			RomfsProcess romfs;
 | 
			
		||||
 | 
			
		||||
			romfs.setInputFile(inputFile);
 | 
			
		||||
| 
						 | 
				
			
			@ -67,20 +66,28 @@ int main(int argc, char** argv)
 | 
			
		|||
			romfs.setListFs(user_set.isListFs());
 | 
			
		||||
 | 
			
		||||
			romfs.process();
 | 
			
		||||
			
 | 
			
		||||
		}
 | 
			
		||||
		else if (user_set.getFileType() == FILE_NCA)
 | 
			
		||||
		{
 | 
			
		||||
			/*
 | 
			
		||||
			NcaProcess nca;
 | 
			
		||||
 | 
			
		||||
			nca.setNcaPath(user_set.getInputPath());
 | 
			
		||||
			nca.setKeyset(user_set.getKeyset());
 | 
			
		||||
			nca.setInputFile(&inputFile);
 | 
			
		||||
			nca.setKeyset(&user_set.getKeyset());
 | 
			
		||||
			nca.setCliOutputMode(user_set.getCliOutputType());
 | 
			
		||||
			nca.setVerifyMode(user_set.isVerifyFile());
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
			if (user_set.getPart0Path().isSet)
 | 
			
		||||
				nca.setPartition0ExtractPath(user_set.getPart0Path().var);
 | 
			
		||||
			if (user_set.getPart1Path().isSet)
 | 
			
		||||
				nca.setPartition1ExtractPath(user_set.getPart1Path().var);
 | 
			
		||||
			if (user_set.getPart2Path().isSet)
 | 
			
		||||
				nca.setPartition2ExtractPath(user_set.getPart2Path().var);
 | 
			
		||||
			if (user_set.getPart3Path().isSet)
 | 
			
		||||
				nca.setPartition3ExtractPath(user_set.getPart3Path().var);
 | 
			
		||||
			nca.setListFs(user_set.isListFs());
 | 
			
		||||
 | 
			
		||||
			nca.process();
 | 
			
		||||
			*/
 | 
			
		||||
		}
 | 
			
		||||
		else if (user_set.getFileType() == FILE_NPDM)
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -9,11 +9,6 @@
 | 
			
		|||
static const size_t kMasterKeyNum = 0x20;
 | 
			
		||||
static const size_t kNcaKeakNum = nx::nca::kKeyAreaEncryptionKeyNum;
 | 
			
		||||
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
const std::string kPathSeparator = "\\";
 | 
			
		||||
#else
 | 
			
		||||
const std::string kPathSeparator = "/";
 | 
			
		||||
#endif
 | 
			
		||||
 | 
			
		||||
enum FileType
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -63,7 +58,8 @@ struct sKeyset
 | 
			
		|||
		crypto::aes::sAesXts128Key header_key;
 | 
			
		||||
		crypto::aes::sAes128Key key_area_key[kNcaKeakNum][kMasterKeyNum];
 | 
			
		||||
 | 
			
		||||
		crypto::aes::sAes128Key manual_title_key;
 | 
			
		||||
		crypto::aes::sAes128Key manual_title_key_aesctr;
 | 
			
		||||
		crypto::aes::sAesXts128Key manual_title_key_aesxts;
 | 
			
		||||
		crypto::aes::sAes128Key manual_body_key_aesctr;
 | 
			
		||||
		crypto::aes::sAesXts128Key manual_body_key_aesxts;
 | 
			
		||||
	} nca;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue