mirror of
				https://github.com/jakcron/nstool.git
				synced 2025-11-04 10:34:53 +00:00 
			
		
		
		
	
						commit
						578e3ac54d
					
				
							
								
								
									
										2
									
								
								KEYS.md
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										2
									
								
								LICENSE
									
									
									
									
									
								
							
							
						
						
									
										2
									
								
								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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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, read *.cnmt
 | 
			
		||||
 | 
			
		||||
# 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
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -121,7 +121,6 @@
 | 
			
		|||
  </ItemDefinitionGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ClInclude Include="include\crypto\aes.h" />
 | 
			
		||||
    <ClInclude Include="include\crypto\AesCtrStream.h" />
 | 
			
		||||
    <ClInclude Include="include\crypto\rsa.h" />
 | 
			
		||||
    <ClInclude Include="include\crypto\sha.h" />
 | 
			
		||||
    <ClInclude Include="source\libpolarssl\include\polarssl\aes.h" />
 | 
			
		||||
| 
						 | 
				
			
			@ -137,7 +136,6 @@
 | 
			
		|||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ClCompile Include="source\aes.cpp" />
 | 
			
		||||
    <ClCompile Include="source\AesCtrStream.cpp" />
 | 
			
		||||
    <ClCompile Include="source\libpolarssl\source\polar_aes.c" />
 | 
			
		||||
    <ClCompile Include="source\libpolarssl\source\polar_base64.c" />
 | 
			
		||||
    <ClCompile Include="source\libpolarssl\source\polar_bignum.c" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -27,9 +27,6 @@
 | 
			
		|||
    <ClInclude Include="include\crypto\aes.h">
 | 
			
		||||
      <Filter>Header Files\crypto</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\crypto\AesCtrStream.h">
 | 
			
		||||
      <Filter>Header Files\crypto</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\crypto\rsa.h">
 | 
			
		||||
      <Filter>Header Files\crypto</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
| 
						 | 
				
			
			@ -71,9 +68,6 @@
 | 
			
		|||
    <ClCompile Include="source\aes.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\AesCtrStream.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\rsa.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,165 +0,0 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <vector>
 | 
			
		||||
#include <fnd/Exception.h>
 | 
			
		||||
#include <crypto/aes.h>
 | 
			
		||||
 | 
			
		||||
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<CryptRegion> regions_;
 | 
			
		||||
 | 
			
		||||
			// IO Buffer
 | 
			
		||||
			uint8_t io_buffer_[kIoBufferLen];
 | 
			
		||||
			uint8_t pad_buffer_[kIoBufferLen];
 | 
			
		||||
 | 
			
		||||
			void GenerateXorPad(size_t start);
 | 
			
		||||
		};
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -1,138 +0,0 @@
 | 
			
		|||
#include <crypto/AesCtrStream.h>
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
		}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,6 +6,8 @@ namespace fnd
 | 
			
		|||
	class IFile
 | 
			
		||||
	{
 | 
			
		||||
	public:
 | 
			
		||||
		inline virtual ~IFile() {}
 | 
			
		||||
 | 
			
		||||
		virtual size_t size() = 0;
 | 
			
		||||
		virtual void seek(size_t offset) = 0;
 | 
			
		||||
		virtual void read(byte_t* out, size_t len) = 0;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,7 +1,11 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <fnd/IFile.h>
 | 
			
		||||
#include <string>
 | 
			
		||||
#ifdef _WIN32
 | 
			
		||||
#include <Windows.h>
 | 
			
		||||
#else
 | 
			
		||||
#include <cstdio>
 | 
			
		||||
#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
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -44,5 +44,5 @@ const char* Exception::module() const noexcept
 | 
			
		|||
 | 
			
		||||
const char * fnd::Exception::error() const noexcept
 | 
			
		||||
{
 | 
			
		||||
	return nullptr;
 | 
			
		||||
	return error_.c_str();
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,11 +1,16 @@
 | 
			
		|||
#include <fnd/SimpleFile.h>
 | 
			
		||||
#include <fnd/StringConv.h>
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
}
 | 
			
		||||
#endif
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
		{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										260
									
								
								lib/libnx/include/nx/ContentMetaBinary.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										260
									
								
								lib/libnx/include/nx/ContentMetaBinary.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,260 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <fnd/MemoryBlob.h>
 | 
			
		||||
#include <fnd/List.h>
 | 
			
		||||
#include <nx/cnmt.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
namespace nx
 | 
			
		||||
{
 | 
			
		||||
	class ContentMetaBinary :
 | 
			
		||||
		public fnd::ISerialiseableBinary
 | 
			
		||||
	{
 | 
			
		||||
	public:
 | 
			
		||||
		struct ContentInfo
 | 
			
		||||
		{
 | 
			
		||||
			crypto::sha::sSha256Hash hash;
 | 
			
		||||
			byte_t nca_id[cnmt::kContentIdLen];
 | 
			
		||||
			size_t size;
 | 
			
		||||
			cnmt::ContentType type;
 | 
			
		||||
 | 
			
		||||
			ContentInfo& operator=(const ContentInfo& other)
 | 
			
		||||
			{
 | 
			
		||||
				hash = other.hash;
 | 
			
		||||
				memcpy(nca_id, other.nca_id, cnmt::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, cnmt::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;
 | 
			
		||||
			cnmt::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);
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		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);
 | 
			
		||||
 | 
			
		||||
		// 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);
 | 
			
		||||
 | 
			
		||||
		cnmt::ContentMetaType getType() const;
 | 
			
		||||
		void setType(cnmt::ContentMetaType type);
 | 
			
		||||
 | 
			
		||||
		byte_t getAttributes() const;
 | 
			
		||||
		void setAttributes(byte_t attributes);
 | 
			
		||||
 | 
			
		||||
		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<nx::ContentMetaBinary::ContentInfo>& getContentInfo() const;
 | 
			
		||||
		void setContentInfo(const fnd::List<nx::ContentMetaBinary::ContentInfo>& info);
 | 
			
		||||
 | 
			
		||||
		const fnd::List<nx::ContentMetaBinary::ContentMetaInfo>& getContentMetaInfo() const;
 | 
			
		||||
		void setContentMetaInfo(const fnd::List<nx::ContentMetaBinary::ContentMetaInfo>& 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;
 | 
			
		||||
		cnmt::ContentMetaType mType;
 | 
			
		||||
		byte_t mAttributes;
 | 
			
		||||
		uint32_t mRequiredDownloadSystemVersion;
 | 
			
		||||
		fnd::MemoryBlob mExtendedHeader;
 | 
			
		||||
 | 
			
		||||
		ApplicationMetaExtendedHeader mApplicationMetaExtendedHeader;
 | 
			
		||||
		PatchMetaExtendedHeader mPatchMetaExtendedHeader;
 | 
			
		||||
		AddOnContentMetaExtendedHeader mAddOnContentMetaExtendedHeader;
 | 
			
		||||
		DeltaMetaExtendedHeader mDeltaMetaExtendedHeader;
 | 
			
		||||
 | 
			
		||||
		fnd::List<nx::ContentMetaBinary::ContentInfo> mContentInfo;
 | 
			
		||||
		fnd::List<nx::ContentMetaBinary::ContentMetaInfo> 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) + cnmt::kDigestLen; }
 | 
			
		||||
 | 
			
		||||
		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);
 | 
			
		||||
	};
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										75
									
								
								lib/libnx/include/nx/HierarchicalIntegrityHeader.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										75
									
								
								lib/libnx/include/nx/HierarchicalIntegrityHeader.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,75 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <nx/hierarchicalintegrity.h>
 | 
			
		||||
#include <fnd/MemoryBlob.h>
 | 
			
		||||
#include <fnd/List.h>
 | 
			
		||||
#include <fnd/ISerialiseableBinary.h>
 | 
			
		||||
 | 
			
		||||
namespace nx
 | 
			
		||||
{
 | 
			
		||||
	class HierarchicalIntegrityHeader :
 | 
			
		||||
		public fnd::ISerialiseableBinary
 | 
			
		||||
	{
 | 
			
		||||
	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);
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		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<sLayer>& getLayerInfo() const;
 | 
			
		||||
		void setLayerInfo(const fnd::List<sLayer>& layer_info);
 | 
			
		||||
 | 
			
		||||
		const fnd::List<crypto::sha::sSha256Hash>& getMasterHashList() const;
 | 
			
		||||
		void setMasterHashList(const fnd::List<crypto::sha::sSha256Hash>& master_hash_list);
 | 
			
		||||
	private:
 | 
			
		||||
		const std::string kModuleName = "HIERARCHICAL_INTEGRITY_HEADER";
 | 
			
		||||
 | 
			
		||||
		// binary
 | 
			
		||||
		fnd::MemoryBlob mBinaryBlob;
 | 
			
		||||
 | 
			
		||||
		// data
 | 
			
		||||
		fnd::List<sLayer> mLayerInfo;
 | 
			
		||||
		fnd::List<crypto::sha::sSha256Hash> mMasterHashList;
 | 
			
		||||
 | 
			
		||||
		bool isEqual(const HierarchicalIntegrityHeader& other) const;
 | 
			
		||||
		void copyFrom(const HierarchicalIntegrityHeader& other);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										77
									
								
								lib/libnx/include/nx/HierarchicalSha256Header.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										77
									
								
								lib/libnx/include/nx/HierarchicalSha256Header.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,77 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <nx/hierarchicalsha256.h>
 | 
			
		||||
#include <fnd/MemoryBlob.h>
 | 
			
		||||
#include <fnd/List.h>
 | 
			
		||||
#include <fnd/ISerialiseableBinary.h>
 | 
			
		||||
 | 
			
		||||
namespace nx
 | 
			
		||||
{
 | 
			
		||||
	class HierarchicalSha256Header :
 | 
			
		||||
		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) const
 | 
			
		||||
			{
 | 
			
		||||
				return (offset == other.offset && size == other.size);
 | 
			
		||||
			}
 | 
			
		||||
 | 
			
		||||
			bool operator!=(const sLayer& other) const
 | 
			
		||||
			{
 | 
			
		||||
				return !(*this == other);
 | 
			
		||||
			}
 | 
			
		||||
		};
 | 
			
		||||
 | 
			
		||||
		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();
 | 
			
		||||
 | 
			
		||||
		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<sLayer>& getLayerInfo() const;
 | 
			
		||||
		void setLayerInfo(const fnd::List<sLayer>& layer_info);
 | 
			
		||||
	private:
 | 
			
		||||
		const std::string kModuleName = "HIERARCHICAL_SHA256_HEADER";
 | 
			
		||||
 | 
			
		||||
		// binary
 | 
			
		||||
		fnd::MemoryBlob mBinaryBlob;
 | 
			
		||||
 | 
			
		||||
		// data
 | 
			
		||||
		crypto::sha::sSha256Hash mMasterHash;
 | 
			
		||||
		size_t mHashBlockSize;
 | 
			
		||||
		fnd::List<sLayer> mLayerInfo;
 | 
			
		||||
 | 
			
		||||
		bool isEqual(const HierarchicalSha256Header& other) const;
 | 
			
		||||
		void copyFrom(const HierarchicalSha256Header& other);
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										138
									
								
								lib/libnx/include/nx/cnmt.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										138
									
								
								lib/libnx/include/nx/cnmt.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,138 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <fnd/types.h>
 | 
			
		||||
#include <crypto/aes.h>
 | 
			
		||||
#include <crypto/sha.h>
 | 
			
		||||
#include <fnd/ISerialiseableBinary.h>
 | 
			
		||||
 | 
			
		||||
namespace nx
 | 
			
		||||
{
 | 
			
		||||
	namespace cnmt
 | 
			
		||||
	{
 | 
			
		||||
		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_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_download_system_version;
 | 
			
		||||
		byte_t reserved_2[4];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	struct sContentInfo
 | 
			
		||||
	{
 | 
			
		||||
		crypto::sha::sSha256Hash content_hash;
 | 
			
		||||
		byte_t content_id[cnmt::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 patch_id;
 | 
			
		||||
		le_uint32_t required_system_version;
 | 
			
		||||
		byte_t reserved[4];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	struct sPatchMetaExtendedHeader
 | 
			
		||||
	{
 | 
			
		||||
		le_uint64_t application_id;
 | 
			
		||||
		le_uint32_t required_system_version;
 | 
			
		||||
		le_uint32_t extended_data_size;
 | 
			
		||||
		byte_t reserved[8];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	struct sAddOnContentMetaExtendedHeader
 | 
			
		||||
	{
 | 
			
		||||
		le_uint64_t application_id;
 | 
			
		||||
		le_uint32_t required_system_version;
 | 
			
		||||
		byte_t reserved[4];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	struct sDeltaMetaExtendedHeader
 | 
			
		||||
	{
 | 
			
		||||
		le_uint64_t application_id;
 | 
			
		||||
		le_uint32_t extended_data_size;
 | 
			
		||||
		byte_t reserved[4];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
	struct sDigest
 | 
			
		||||
	{
 | 
			
		||||
		byte_t data[cnmt::kDigestLen];
 | 
			
		||||
	};
 | 
			
		||||
#pragma pack(pop)
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										35
									
								
								lib/libnx/include/nx/hierarchicalintegrity.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								lib/libnx/include/nx/hierarchicalintegrity.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,35 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <fnd/types.h>
 | 
			
		||||
#include <crypto/sha.h>
 | 
			
		||||
#include <fnd/ISerialiseableBinary.h>
 | 
			
		||||
 | 
			
		||||
namespace nx
 | 
			
		||||
{
 | 
			
		||||
	// Also known to the public as IVFC
 | 
			
		||||
	namespace hierarchicalintegrity
 | 
			
		||||
	{
 | 
			
		||||
		const std::string kStructSig = "IVFC";
 | 
			
		||||
		static const uint32_t kRomfsTypeId = 0x20000;
 | 
			
		||||
		static const size_t kDefaultLayerNum = 6;
 | 
			
		||||
		static const size_t kHeaderAlignLen = 0x20;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
#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 sHierarchicalIntegrityLayerInfo // sizeof(0x18)
 | 
			
		||||
	{
 | 
			
		||||
		le_uint64_t offset;
 | 
			
		||||
		le_uint64_t size;
 | 
			
		||||
		le_uint32_t block_size;
 | 
			
		||||
		byte_t reserved[4];
 | 
			
		||||
	};
 | 
			
		||||
#pragma pack(pop)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -8,7 +8,8 @@ namespace nx
 | 
			
		|||
{
 | 
			
		||||
	namespace hierarchicalsha256
 | 
			
		||||
	{
 | 
			
		||||
		static const size_t kDefaultLevelNum = 2;
 | 
			
		||||
		static const size_t kDefaultLayerNum = 2;
 | 
			
		||||
		static const size_t kMaxLayerNum = 2;
 | 
			
		||||
	}
 | 
			
		||||
	
 | 
			
		||||
#pragma pack(push,1)
 | 
			
		||||
| 
						 | 
				
			
			@ -16,12 +17,12 @@ namespace nx
 | 
			
		|||
	{
 | 
			
		||||
		crypto::sha::sSha256Hash master_hash;
 | 
			
		||||
		le_uint32_t hash_block_size;
 | 
			
		||||
		le_uint32_t hash_level_num;
 | 
			
		||||
		struct sLayout
 | 
			
		||||
		le_uint32_t layer_num;
 | 
			
		||||
		struct sLayer
 | 
			
		||||
		{
 | 
			
		||||
			le_uint64_t offset;
 | 
			
		||||
			le_uint64_t size;
 | 
			
		||||
		} hash_data, hash_target;
 | 
			
		||||
		} layer[hierarchicalsha256::kMaxLayerNum];
 | 
			
		||||
	};
 | 
			
		||||
#pragma pack(pop)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,36 +0,0 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <fnd/types.h>
 | 
			
		||||
#include <crypto/aes.h>
 | 
			
		||||
#include <crypto/sha.h>
 | 
			
		||||
#include <fnd/ISerialiseableBinary.h>
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,8 +5,6 @@
 | 
			
		|||
#include <crypto/sha.h>
 | 
			
		||||
#include <crypto/rsa.h>
 | 
			
		||||
#include <fnd/ISerialiseableBinary.h>
 | 
			
		||||
#include <nx/ivfc.h>
 | 
			
		||||
#include <nx/hierarchicalsha256.h>
 | 
			
		||||
 | 
			
		||||
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::sIvfcHeader ivfc_header;
 | 
			
		||||
		};
 | 
			
		||||
		crypto::aes::sAesIvCtr base_ctr;
 | 
			
		||||
		byte_t hash_superblock[nca::kFsHeaderHashSuperblockLen];
 | 
			
		||||
		byte_t aes_ctr_upper[8];
 | 
			
		||||
		byte_t reserved_1[0xB8];
 | 
			
		||||
	};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -24,14 +24,19 @@
 | 
			
		|||
    <ClInclude Include="include\nx\AcidBinary.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\AciHeader.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\AesKeygen.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\cnmt.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\ContentMetaBinary.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\FacBinary.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\FacHeader.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\HandleTableSizeEntry.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\HandleTableSizeHandler.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\HierarchicalIntegrityHeader.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\hierarchicalsha256.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\HierarchicalSha256Header.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\IKernelCapabilityHandler.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\InteruptEntry.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\InteruptHandler.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\ivfc.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\hierarchicalintegrity.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\KcBinary.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\KernelCapability.h" />
 | 
			
		||||
    <ClInclude Include="include\nx\KernelVersionEntry.h" />
 | 
			
		||||
| 
						 | 
				
			
			@ -67,10 +72,13 @@
 | 
			
		|||
    <ClCompile Include="source\AcidBinary.cpp" />
 | 
			
		||||
    <ClCompile Include="source\AciHeader.cpp" />
 | 
			
		||||
    <ClCompile Include="source\AesKeygen.cpp" />
 | 
			
		||||
    <ClCompile Include="source\ContentMetaBinary.cpp" />
 | 
			
		||||
    <ClCompile Include="source\FacBinary.cpp" />
 | 
			
		||||
    <ClCompile Include="source\FacHeader.cpp" />
 | 
			
		||||
    <ClCompile Include="source\HandleTableSizeEntry.cpp" />
 | 
			
		||||
    <ClCompile Include="source\HandleTableSizeHandler.cpp" />
 | 
			
		||||
    <ClCompile Include="source\HierarchicalIntegrityHeader.cpp" />
 | 
			
		||||
    <ClCompile Include="source\HierarchicalSha256Header.cpp" />
 | 
			
		||||
    <ClCompile Include="source\InteruptEntry.cpp" />
 | 
			
		||||
    <ClCompile Include="source\InteruptHandler.cpp" />
 | 
			
		||||
    <ClCompile Include="source\KcBinary.cpp" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -123,9 +123,6 @@
 | 
			
		|||
    <ClInclude Include="include\nx\AesKeygen.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\nx\ivfc.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\nx\NcaUtils.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
| 
						 | 
				
			
			@ -141,6 +138,24 @@
 | 
			
		|||
    <ClInclude Include="include\nx\XciUtils.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\nx\hierarchicalsha256.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\nx\ContentMetaBinary.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\nx\cnmt.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\nx\hierarchicalintegrity.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\nx\HierarchicalSha256Header.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="include\nx\HierarchicalIntegrityHeader.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ClCompile Include="source\AciBinary.cpp">
 | 
			
		||||
| 
						 | 
				
			
			@ -242,6 +257,15 @@
 | 
			
		|||
    <ClCompile Include="source\XciUtils.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\ContentMetaBinary.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\HierarchicalIntegrityHeader.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\HierarchicalSha256Header.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <None Include="makefile" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
	}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										382
									
								
								lib/libnx/source/ContentMetaBinary.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										382
									
								
								lib/libnx/source/ContentMetaBinary.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,382 @@
 | 
			
		|||
#include <nx/ContentMetaBinary.h>
 | 
			
		||||
 | 
			
		||||
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 = (cnmt::ContentMetaType)hdr->type;
 | 
			
		||||
	mAttributes = hdr->attributes;
 | 
			
		||||
	mRequiredDownloadSystemVersion = hdr->required_download_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());
 | 
			
		||||
 | 
			
		||||
		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());
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// 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, cnmt::kContentIdLen);
 | 
			
		||||
			mContentInfo[i].size = (uint64_t)(info[i].size_lower.get()) | (uint64_t)(info[i].size_higher.get()) << 32;
 | 
			
		||||
			mContentInfo[i].type = (cnmt::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 = (cnmt::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), cnmt::kDigestLen);
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nx::ContentMetaBinary::clear()
 | 
			
		||||
{
 | 
			
		||||
	mBinaryBlob.clear();
 | 
			
		||||
	mTitleId = 0;
 | 
			
		||||
	mTitleVersion = 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, cnmt::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::cnmt::ContentMetaType nx::ContentMetaBinary::getType() const
 | 
			
		||||
{
 | 
			
		||||
	return mType;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nx::ContentMetaBinary::setType(cnmt::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::getRequiredDownloadSystemVersion() const
 | 
			
		||||
{
 | 
			
		||||
	return mRequiredDownloadSystemVersion;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nx::ContentMetaBinary::setRequiredDownloadSystemVersion(uint32_t 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::ContentInfo>& nx::ContentMetaBinary::getContentInfo() const
 | 
			
		||||
{
 | 
			
		||||
	return mContentInfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nx::ContentMetaBinary::setContentInfo(const fnd::List<nx::ContentMetaBinary::ContentInfo>& info)
 | 
			
		||||
{
 | 
			
		||||
	mContentInfo = info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fnd::List<nx::ContentMetaBinary::ContentMetaInfo>& nx::ContentMetaBinary::getContentMetaInfo() const
 | 
			
		||||
{
 | 
			
		||||
	return mContentMetaInfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nx::ContentMetaBinary::setContentMetaInfo(const fnd::List<nx::ContentMetaBinary::ContentMetaInfo>& 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, cnmt::kDigestLen);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
bool nx::ContentMetaBinary::validateExtendedHeaderSize(cnmt::ContentMetaType type, size_t exhdrSize) const
 | 
			
		||||
{
 | 
			
		||||
	bool validSize = false;
 | 
			
		||||
 | 
			
		||||
	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(cnmt::ContentMetaType type, const byte_t * data) const
 | 
			
		||||
{
 | 
			
		||||
	size_t exdata_len = 0;
 | 
			
		||||
	if (type == cnmt::METATYPE_PATCH)
 | 
			
		||||
	{
 | 
			
		||||
		const sPatchMetaExtendedHeader* exhdr = (const sPatchMetaExtendedHeader*)(data);
 | 
			
		||||
		exdata_len = exhdr->extended_data_size.get();
 | 
			
		||||
	}
 | 
			
		||||
	else if (type == cnmt::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) const
 | 
			
		||||
{
 | 
			
		||||
	// 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((cnmt::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((cnmt::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) \
 | 
			
		||||
		&& (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, cnmt::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;
 | 
			
		||||
		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, cnmt::kDigestLen);
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										156
									
								
								lib/libnx/source/HierarchicalIntegrityHeader.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								lib/libnx/source/HierarchicalIntegrityHeader.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,156 @@
 | 
			
		|||
#include <sstream>
 | 
			
		||||
#include <nx/HierarchicalIntegrityHeader.h>
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
{
 | 
			
		||||
	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()
 | 
			
		||||
{
 | 
			
		||||
	mLayerInfo.clear();
 | 
			
		||||
	mMasterHashList.clear();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fnd::List<nx::HierarchicalIntegrityHeader::sLayer>& nx::HierarchicalIntegrityHeader::getLayerInfo() const
 | 
			
		||||
{
 | 
			
		||||
	return mLayerInfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nx::HierarchicalIntegrityHeader::setLayerInfo(const fnd::List<sLayer>& layer_info)
 | 
			
		||||
{
 | 
			
		||||
	mLayerInfo = layer_info;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
const fnd::List<crypto::sha::sSha256Hash>& nx::HierarchicalIntegrityHeader::getMasterHashList() const
 | 
			
		||||
{
 | 
			
		||||
	return mMasterHashList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nx::HierarchicalIntegrityHeader::setMasterHashList(const fnd::List<crypto::sha::sSha256Hash>& 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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										133
									
								
								lib/libnx/source/HierarchicalSha256Header.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										133
									
								
								lib/libnx/source/HierarchicalSha256Header.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,133 @@
 | 
			
		|||
#include <sstream>
 | 
			
		||||
#include <nx/HierarchicalSha256Header.h>
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
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)
 | 
			
		||||
{
 | 
			
		||||
	std::stringstream error_str;
 | 
			
		||||
 | 
			
		||||
	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::kDefaultLayerNum)
 | 
			
		||||
	{
 | 
			
		||||
		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;
 | 
			
		||||
	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::sLayer>& nx::HierarchicalSha256Header::getLayerInfo() const
 | 
			
		||||
{
 | 
			
		||||
	return mLayerInfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void nx::HierarchicalSha256Header::setLayerInfo(const fnd::List<sLayer>& 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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -56,7 +56,7 @@ void nx::SystemCallHandler::exportKernelCapabilityList(fnd::List<KernelCapabilit
 | 
			
		|||
	fnd::List<SystemCallEntry> entries;
 | 
			
		||||
	for (size_t i = 0; i < kSyscallTotalEntryNum; i++)
 | 
			
		||||
	{
 | 
			
		||||
		entries[i].setSystemCallUpperBits(i);
 | 
			
		||||
		entries[i].setSystemCallUpperBits((uint32_t)i);
 | 
			
		||||
		entries[i].setSystemCallLowerBits(0);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -162,9 +162,15 @@
 | 
			
		|||
    </ProjectReference>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ClInclude Include="source\AesCtrWrappedIFile.h" />
 | 
			
		||||
    <ClInclude Include="source\CnmtProcess.h" />
 | 
			
		||||
    <ClInclude Include="source\CopiedIFile.h" />
 | 
			
		||||
    <ClInclude Include="source\HashTreeMeta.h" />
 | 
			
		||||
    <ClInclude Include="source\HashTreeWrappedIFile.h" />
 | 
			
		||||
    <ClInclude Include="source\NcaProcess.h" />
 | 
			
		||||
    <ClInclude Include="source\NpdmProcess.h" />
 | 
			
		||||
    <ClInclude Include="source\nstool.h" />
 | 
			
		||||
    <ClInclude Include="source\OffsetAdjustedIFile.h" />
 | 
			
		||||
    <ClInclude Include="source\PfsProcess.h" />
 | 
			
		||||
    <ClInclude Include="source\RomfsProcess.h" />
 | 
			
		||||
    <ClInclude Include="source\UserSettings.h" />
 | 
			
		||||
| 
						 | 
				
			
			@ -172,8 +178,14 @@
 | 
			
		|||
    <ClInclude Include="source\XciProcess.h" />
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ClCompile Include="source\AesCtrWrappedIFile.cpp" />
 | 
			
		||||
    <ClCompile Include="source\CnmtProcess.cpp" />
 | 
			
		||||
    <ClCompile Include="source\HashTreeMeta.cpp" />
 | 
			
		||||
    <ClCompile Include="source\HashTreeWrappedIFile.cpp" />
 | 
			
		||||
    <ClCompile Include="source\main.cpp" />
 | 
			
		||||
    <ClCompile Include="source\NcaProcess.cpp" />
 | 
			
		||||
    <ClCompile Include="source\NpdmProcess.cpp" />
 | 
			
		||||
    <ClCompile Include="source\OffsetAdjustedIFile.cpp" />
 | 
			
		||||
    <ClCompile Include="source\PfsProcess.cpp" />
 | 
			
		||||
    <ClCompile Include="source\RomfsProcess.cpp" />
 | 
			
		||||
    <ClCompile Include="source\UserSettings.cpp" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -39,6 +39,24 @@
 | 
			
		|||
    <ClInclude Include="source\XciProcess.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="source\AesCtrWrappedIFile.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="source\CopiedIFile.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="source\OffsetAdjustedIFile.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="source\CnmtProcess.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="source\HashTreeMeta.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
    <ClInclude Include="source\HashTreeWrappedIFile.h">
 | 
			
		||||
      <Filter>Header Files</Filter>
 | 
			
		||||
    </ClInclude>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <ClCompile Include="source\main.cpp">
 | 
			
		||||
| 
						 | 
				
			
			@ -59,6 +77,24 @@
 | 
			
		|||
    <ClCompile Include="source\RomfsProcess.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\AesCtrWrappedIFile.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\NcaProcess.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\OffsetAdjustedIFile.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\CnmtProcess.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\HashTreeMeta.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
    <ClCompile Include="source\HashTreeWrappedIFile.cpp">
 | 
			
		||||
      <Filter>Source Files</Filter>
 | 
			
		||||
    </ClCompile>
 | 
			
		||||
  </ItemGroup>
 | 
			
		||||
  <ItemGroup>
 | 
			
		||||
    <None Include="makefile" />
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,42 +1,64 @@
 | 
			
		|||
#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) :
 | 
			
		||||
	AesCtrWrappedIFile(file, false, key, ctr)
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
	mCache.alloc(kCacheSizeAllocSize);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
AesCtrWrappedIFile::~AesCtrWrappedIFile()
 | 
			
		||||
{
 | 
			
		||||
	if (mOwnIFile)
 | 
			
		||||
	{
 | 
			
		||||
		delete mFile;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
size_t AesCtrWrappedIFile::size()
 | 
			
		||||
{
 | 
			
		||||
	return mFile.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;
 | 
			
		||||
	mFileOffset = offset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AesCtrWrappedIFile::read(byte_t* out, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	for (size_t i = 0; i < (len / kAesCtrScratchSize); i++)
 | 
			
		||||
	//printf("[%x] AesCtrWrappedIFile::read(offset=0x%" PRIx64 ", size=0x%" PRIx64 ")\n", this, mFileOffset, len);
 | 
			
		||||
 | 
			
		||||
	size_t read_len;
 | 
			
		||||
	size_t read_pos; 
 | 
			
		||||
 | 
			
		||||
	size_t cache_reads = (len / kCacheSize) + ((len % kCacheSize) != 0);
 | 
			
		||||
 | 
			
		||||
	for (size_t i = 0; i < cache_reads; 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);
 | 
			
		||||
		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);
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	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);
 | 
			
		||||
	}
 | 
			
		||||
	seek(mFileOffset + len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void AesCtrWrappedIFile::read(byte_t* out, size_t offset, size_t len)
 | 
			
		||||
| 
						 | 
				
			
			@ -45,13 +67,37 @@ 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)
 | 
			
		||||
{
 | 
			
		||||
	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);
 | 
			
		||||
		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,12 +106,14 @@ 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);
 | 
			
		||||
	}
 | 
			
		||||
	*/
 | 
			
		||||
	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);
 | 
			
		||||
	write(in, len);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -5,7 +5,9 @@
 | 
			
		|||
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);
 | 
			
		||||
	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);
 | 
			
		||||
| 
						 | 
				
			
			@ -15,13 +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;
 | 
			
		||||
 | 
			
		||||
	fnd::IFile& mFile;
 | 
			
		||||
	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();
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										198
									
								
								programs/nstool/source/CnmtProcess.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										198
									
								
								programs/nstool/source/CnmtProcess.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,198 @@
 | 
			
		|||
#include <fnd/SimpleTextOutput.h>
 | 
			
		||||
#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"
 | 
			
		||||
	}
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const std::string kUpdateTypeStr[3] =
 | 
			
		||||
{
 | 
			
		||||
	"ApplyAsDelta",
 | 
			
		||||
	"Overwrite",
 | 
			
		||||
	"Create"
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const std::string kContentMetaAttrStr[3] =
 | 
			
		||||
{
 | 
			
		||||
	"IncludesExFatDriver",
 | 
			
		||||
	"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", (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", (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", (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", (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", (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", (uint64_t)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", (int)i);
 | 
			
		||||
			printf("      Type:         %s (%d)\n", getContentTypeStr(info.type), info.type);
 | 
			
		||||
			printf("      Id:           ");
 | 
			
		||||
			_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)
 | 
			
		||||
	{
 | 
			
		||||
		printf("  ContentMetaInfo:\n");
 | 
			
		||||
		for (size_t i = 0; i < mCnmt.getContentMetaInfo().getSize(); i++)
 | 
			
		||||
		{
 | 
			
		||||
			const nx::ContentMetaBinary::ContentMetaInfo& info = mCnmt.getContentMetaInfo()[i];
 | 
			
		||||
			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:   ");
 | 
			
		||||
	_HEXDUMP_L(mCnmt.getDigest().data, nx::cnmt::kDigestLen);
 | 
			
		||||
	printf("\n");
 | 
			
		||||
 | 
			
		||||
#undef _HEXDUMP_L
 | 
			
		||||
#undef _HEXDUMP_U
 | 
			
		||||
#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;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										33
									
								
								programs/nstool/source/CnmtProcess.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										33
									
								
								programs/nstool/source/CnmtProcess.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,33 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <string>
 | 
			
		||||
#include <fnd/types.h>
 | 
			
		||||
#include <fnd/IFile.h>
 | 
			
		||||
#include <nx/ContentMetaBinary.h>
 | 
			
		||||
 | 
			
		||||
#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();
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										17
									
								
								programs/nstool/source/CopiedIFile.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								programs/nstool/source/CopiedIFile.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,17 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <fnd/IFile.h>
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										141
									
								
								programs/nstool/source/HashTreeMeta.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										141
									
								
								programs/nstool/source/HashTreeMeta.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,141 @@
 | 
			
		|||
#include "HashTreeMeta.h"
 | 
			
		||||
 | 
			
		||||
HashTreeMeta::HashTreeMeta() :
 | 
			
		||||
	mLayerInfo(),
 | 
			
		||||
	mDataLayer(),
 | 
			
		||||
	mMasterHashList(),
 | 
			
		||||
	mDoAlignHashToBlock(false)
 | 
			
		||||
{
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
HashTreeMeta::HashTreeMeta(const nx::HierarchicalIntegrityHeader& hdr) :
 | 
			
		||||
	mLayerInfo(),
 | 
			
		||||
	mDataLayer(),
 | 
			
		||||
	mMasterHashList(),
 | 
			
		||||
	mDoAlignHashToBlock(false)
 | 
			
		||||
{
 | 
			
		||||
	importHierarchicalIntergityHeader(hdr);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
HashTreeMeta::HashTreeMeta(const nx::HierarchicalSha256Header& hdr) :
 | 
			
		||||
	mLayerInfo(),
 | 
			
		||||
	mDataLayer(),
 | 
			
		||||
	mMasterHashList(),
 | 
			
		||||
	mDoAlignHashToBlock(false)
 | 
			
		||||
{
 | 
			
		||||
	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)
 | 
			
		||||
{
 | 
			
		||||
	mDoAlignHashToBlock = true;
 | 
			
		||||
	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)
 | 
			
		||||
{
 | 
			
		||||
	mDoAlignHashToBlock = false;
 | 
			
		||||
	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::sLayer>& HashTreeMeta::getHashLayerInfo() const
 | 
			
		||||
{
 | 
			
		||||
	return mLayerInfo;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void HashTreeMeta::setHashLayerInfo(const fnd::List<sLayer>& 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<crypto::sha::sSha256Hash>&  HashTreeMeta::getMasterHashList() const
 | 
			
		||||
{
 | 
			
		||||
	return mMasterHashList;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void  HashTreeMeta::setMasterHashList(const fnd::List<crypto::sha::sSha256Hash>& master_hash_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) \
 | 
			
		||||
		&& (mDoAlignHashToBlock == other.mDoAlignHashToBlock);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void  HashTreeMeta::copyFrom(const HashTreeMeta& other)
 | 
			
		||||
{
 | 
			
		||||
	mLayerInfo = other.mLayerInfo;
 | 
			
		||||
	mDataLayer = other.mDataLayer;
 | 
			
		||||
	mMasterHashList = other.mMasterHashList;
 | 
			
		||||
	mDoAlignHashToBlock = other.mDoAlignHashToBlock;
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										64
									
								
								programs/nstool/source/HashTreeMeta.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										64
									
								
								programs/nstool/source/HashTreeMeta.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,64 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <nx/HierarchicalIntegrityHeader.h>
 | 
			
		||||
#include <nx/HierarchicalSha256Header.h>
 | 
			
		||||
 | 
			
		||||
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<sLayer>& getHashLayerInfo() const;
 | 
			
		||||
	void setHashLayerInfo(const fnd::List<sLayer>& layer_info);
 | 
			
		||||
 | 
			
		||||
	const sLayer& getDataLayer() const;
 | 
			
		||||
	void setDataLayer(const sLayer& data_info);
 | 
			
		||||
 | 
			
		||||
	const fnd::List<crypto::sha::sSha256Hash>& getMasterHashList() const;
 | 
			
		||||
	void setMasterHashList(const fnd::List<crypto::sha::sSha256Hash>& master_hash_list);
 | 
			
		||||
 | 
			
		||||
	bool getAlignHashToBlock() const;
 | 
			
		||||
	void setAlignHashToBlock(bool doAlign);
 | 
			
		||||
private:
 | 
			
		||||
 | 
			
		||||
	// data
 | 
			
		||||
	fnd::List<sLayer> mLayerInfo;
 | 
			
		||||
	sLayer mDataLayer;
 | 
			
		||||
	fnd::List<crypto::sha::sSha256Hash> mMasterHashList;
 | 
			
		||||
	bool mDoAlignHashToBlock;	
 | 
			
		||||
 | 
			
		||||
	bool isEqual(const HashTreeMeta& other) const;
 | 
			
		||||
	void copyFrom(const HashTreeMeta& other);
 | 
			
		||||
};
 | 
			
		||||
							
								
								
									
										215
									
								
								programs/nstool/source/HashTreeWrappedIFile.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										215
									
								
								programs/nstool/source/HashTreeWrappedIFile.cpp
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,215 @@
 | 
			
		|||
#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 partial_last_block_num = block_num % mCacheBlockNum;
 | 
			
		||||
	bool has_partial_block_num = partial_last_block_num > 0;
 | 
			
		||||
	size_t read_iterations = (block_num / mCacheBlockNum) + 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 : mCacheBlockNum;
 | 
			
		||||
 | 
			
		||||
		// 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 * mCacheBlockNum), block_read_len);
 | 
			
		||||
 | 
			
		||||
		// export the section of data that is relevant
 | 
			
		||||
		memcpy(out + block_export_pos, mCache.getBytes() + block_export_offset, block_export_size);
 | 
			
		||||
 | 
			
		||||
		// update export position
 | 
			
		||||
		block_export_pos += block_export_size;
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
	// update offset
 | 
			
		||||
	seek(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);
 | 
			
		||||
	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)
 | 
			
		||||
{
 | 
			
		||||
	mData->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(mCache.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(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(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) << " ( " << i << "/" << block_num-1 << " ), offset: 0x" << std::hex << ((block_offset + i) * mDataBlockSize) << ", size: 0x" << std::hex <<  validate_size <<")";
 | 
			
		||||
			throw fnd::Exception(kModuleName, mErrorSs.str());
 | 
			
		||||
		}
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										47
									
								
								programs/nstool/source/HashTreeWrappedIFile.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								programs/nstool/source/HashTreeWrappedIFile.h
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,47 @@
 | 
			
		|||
#pragma once
 | 
			
		||||
#include <sstream>
 | 
			
		||||
#include <fnd/IFile.h>
 | 
			
		||||
#include <fnd/MemoryBlob.h>
 | 
			
		||||
#include <crypto/sha.h>
 | 
			
		||||
#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";
 | 
			
		||||
	static const size_t kDefaultCacheSize = 0x10000;
 | 
			
		||||
	std::stringstream mErrorSs;
 | 
			
		||||
 | 
			
		||||
	bool mOwnIFile;
 | 
			
		||||
	fnd::IFile* mFile;	
 | 
			
		||||
 | 
			
		||||
	// data file
 | 
			
		||||
	fnd::IFile* mData;
 | 
			
		||||
	size_t mDataOffset;
 | 
			
		||||
	size_t mDataBlockSize;
 | 
			
		||||
	fnd::List<crypto::sha::sSha256Hash> mDataHashLayer;
 | 
			
		||||
	bool mAlignHashCalcToBlock;
 | 
			
		||||
 | 
			
		||||
	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; }
 | 
			
		||||
	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);
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,16 +0,0 @@
 | 
			
		|||
#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";
 | 
			
		||||
 | 
			
		||||
};
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load diff
											
										
									
								
							| 
						 | 
				
			
			@ -3,6 +3,8 @@
 | 
			
		|||
#include <fnd/types.h>
 | 
			
		||||
#include <fnd/SimpleFile.h>
 | 
			
		||||
#include <nx/NcaHeader.h>
 | 
			
		||||
#include "HashTreeMeta.h"
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
#include "nstool.h"
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -15,8 +17,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);
 | 
			
		||||
| 
						 | 
				
			
			@ -30,10 +31,10 @@ public:
 | 
			
		|||
 | 
			
		||||
private:
 | 
			
		||||
	const std::string kModuleName = "NcaProcess";
 | 
			
		||||
	const std::string kNpdmExefsPath = "main.npdm";
 | 
			
		||||
 | 
			
		||||
	// user options
 | 
			
		||||
	fnd::IFile* mReader;
 | 
			
		||||
	size_t mOffset;
 | 
			
		||||
	const sKeyset* mKeyset;
 | 
			
		||||
	CliOutputType mCliOutputType;
 | 
			
		||||
	bool mVerify;
 | 
			
		||||
| 
						 | 
				
			
			@ -54,15 +55,58 @@ 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<sKeyAreaKey> keak_list;
 | 
			
		||||
 | 
			
		||||
		sOptional<crypto::aes::sAes128Key> aes_ctr;
 | 
			
		||||
		sOptional<crypto::aes::sAesXts128Key> aes_xts;
 | 
			
		||||
	} mBodyKeys;
 | 
			
		||||
	
 | 
			
		||||
	struct sPartitionInfo
 | 
			
		||||
	{
 | 
			
		||||
		fnd::IFile* reader;
 | 
			
		||||
		std::string fail_reason;
 | 
			
		||||
		size_t offset;
 | 
			
		||||
		size_t size;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	void displayHeader();
 | 
			
		||||
		// meta data
 | 
			
		||||
		nx::nca::FormatType format_type;
 | 
			
		||||
		nx::nca::HashType hash_type;
 | 
			
		||||
		nx::nca::EncryptionType enc_type;
 | 
			
		||||
		HashTreeMeta hash_tree_meta;
 | 
			
		||||
		crypto::aes::sAesIvCtr aes_ctr;
 | 
			
		||||
	} mPartitions[nx::nca::kPartitionNum];
 | 
			
		||||
 | 
			
		||||
	void generateNcaBodyEncryptionKeys();
 | 
			
		||||
 | 
			
		||||
	void generatePartitionConfiguration();
 | 
			
		||||
	void validateNcaSignatures();
 | 
			
		||||
	void displayHeader();
 | 
			
		||||
	void processPartitions();
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,5 @@
 | 
			
		|||
#include "OffsetAdjustedIFile.h"
 | 
			
		||||
#include "NpdmProcess.h"
 | 
			
		||||
#include <fnd/SimpleFile.h>
 | 
			
		||||
#include <fnd/MemoryBlob.h>
 | 
			
		||||
 | 
			
		||||
const std::string kInstructionType[2] = { "32Bit", "64Bit" };
 | 
			
		||||
const std::string kProcAddrSpace[4] = { "Unknown", "64Bit", "32Bit", "32Bit no reserved" };
 | 
			
		||||
| 
						 | 
				
			
			@ -464,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");
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
#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) :
 | 
			
		||||
	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;
 | 
			
		||||
| 
						 | 
				
			
			@ -16,14 +35,15 @@ size_t OffsetAdjustedIFile::size()
 | 
			
		|||
 | 
			
		||||
void OffsetAdjustedIFile::seek(size_t offset)
 | 
			
		||||
{
 | 
			
		||||
	mCurrentOffset = offset;
 | 
			
		||||
	mFile.seek(offset + mBaseOffset);
 | 
			
		||||
	mCurrentOffset = MIN(offset, mSize);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OffsetAdjustedIFile::read(byte_t* out, size_t len)
 | 
			
		||||
{
 | 
			
		||||
	seek(mCurrentOffset);
 | 
			
		||||
	mFile.read(out, len);
 | 
			
		||||
	// assert proper position in file
 | 
			
		||||
	mFile->seek(mCurrentOffset + mBaseOffset);
 | 
			
		||||
	mFile->read(out, len);
 | 
			
		||||
	seek(mCurrentOffset + len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OffsetAdjustedIFile::read(byte_t* out, size_t offset, size_t len)
 | 
			
		||||
| 
						 | 
				
			
			@ -34,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);
 | 
			
		||||
	mFile.write(out, len);
 | 
			
		||||
	// assert proper position in file
 | 
			
		||||
	mFile->seek(mCurrentOffset + mBaseOffset);
 | 
			
		||||
	mFile->write(out, len);
 | 
			
		||||
	seek(mCurrentOffset + len);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void OffsetAdjustedIFile::write(const byte_t* out, size_t offset, size_t len)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -3,7 +3,9 @@
 | 
			
		|||
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);
 | 
			
		||||
	OffsetAdjustedIFile(fnd::IFile* file, bool ownIFile, size_t offset, size_t size);
 | 
			
		||||
	~OffsetAdjustedIFile();
 | 
			
		||||
 | 
			
		||||
	size_t size();
 | 
			
		||||
	void seek(size_t offset);
 | 
			
		||||
| 
						 | 
				
			
			@ -12,7 +14,8 @@ 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;
 | 
			
		||||
	bool mOwnIFile;
 | 
			
		||||
	fnd::IFile* mFile;
 | 
			
		||||
	size_t mBaseOffset, mCurrentOffset;
 | 
			
		||||
	size_t mSize;
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,104 +1,10 @@
 | 
			
		|||
#include "PfsProcess.h"
 | 
			
		||||
#include <fnd/SimpleFile.h>
 | 
			
		||||
#include <fnd/io.h>
 | 
			
		||||
 | 
			
		||||
void PfsProcess::displayHeader()
 | 
			
		||||
{
 | 
			
		||||
	printf("[PartitionFS]\n");
 | 
			
		||||
	printf("  Type:        %s\n", mPfs.getFsType() == mPfs.TYPE_PFS0? "PFS0" : "HFS0");
 | 
			
		||||
	printf("  FileNum:     %u\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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PfsProcess::validateHfs()
 | 
			
		||||
{
 | 
			
		||||
	fnd::MemoryBlob scratch;
 | 
			
		||||
	crypto::sha::sSha256Hash hash;
 | 
			
		||||
	const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
 | 
			
		||||
	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);
 | 
			
		||||
		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<nx::PfsHeader::sFile>& 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(mOffset + 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();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
#include "OffsetAdjustedIFile.h"
 | 
			
		||||
#include "PfsProcess.h"
 | 
			
		||||
 | 
			
		||||
PfsProcess::PfsProcess() :
 | 
			
		||||
	mReader(nullptr),
 | 
			
		||||
	mOffset(0),
 | 
			
		||||
	mKeyset(nullptr),
 | 
			
		||||
	mCliOutputType(OUTPUT_NORMAL),
 | 
			
		||||
	mVerify(false),
 | 
			
		||||
	mExtractPath(),
 | 
			
		||||
| 
						 | 
				
			
			@ -107,7 +13,14 @@ PfsProcess::PfsProcess() :
 | 
			
		|||
	mListFs(false),
 | 
			
		||||
	mPfs()
 | 
			
		||||
{
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
PfsProcess::~PfsProcess()
 | 
			
		||||
{
 | 
			
		||||
	if (mReader != nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		delete mReader;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void PfsProcess::process()
 | 
			
		||||
| 
						 | 
				
			
			@ -121,12 +34,16 @@ 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");
 | 
			
		||||
	}
 | 
			
		||||
	size_t pfsHeaderSize = determineHeaderSize(((nx::sPfsHeader*)scratch.getBytes()));
 | 
			
		||||
	
 | 
			
		||||
	// 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)
 | 
			
		||||
| 
						 | 
				
			
			@ -139,19 +56,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)
 | 
			
		||||
| 
						 | 
				
			
			@ -184,3 +91,98 @@ 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", (uint64_t)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", (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", (uint64_t)mPfs.getFileList()[i].offset, (uint64_t)mPfs.getFileList()[i].size, (uint64_t)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()
 | 
			
		||||
{
 | 
			
		||||
	crypto::sha::sSha256Hash hash;
 | 
			
		||||
	const fnd::List<nx::PfsHeader::sFile>& file = mPfs.getFileList();
 | 
			
		||||
	for (size_t i = 0; i < file.getSize(); i++)
 | 
			
		||||
	{
 | 
			
		||||
		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)
 | 
			
		||||
				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
 | 
			
		||||
	mCache.alloc(kCacheSize);
 | 
			
		||||
 | 
			
		||||
	// make extract dir
 | 
			
		||||
	fnd::io::makeDirectory(mExtractPath);
 | 
			
		||||
 | 
			
		||||
	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++)
 | 
			
		||||
	{
 | 
			
		||||
		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 / kCacheSize) + ((file[i].size % kCacheSize) != 0)); j++)
 | 
			
		||||
		{
 | 
			
		||||
			mReader->read(mCache.getBytes(), MIN(file[i].size - (kCacheSize * j),kCacheSize));
 | 
			
		||||
			outFile.write(mCache.getBytes(), MIN(file[i].size - (kCacheSize * j),kCacheSize));
 | 
			
		||||
		}		
 | 
			
		||||
		outFile.close();
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -29,25 +28,25 @@ public:
 | 
			
		|||
 | 
			
		||||
private:
 | 
			
		||||
	const std::string kModuleName = "PfsProcess";
 | 
			
		||||
	static const size_t kFileExportBlockSize = 0x1000000;
 | 
			
		||||
	static const size_t kCacheSize = 0x10000;
 | 
			
		||||
 | 
			
		||||
	fnd::IFile* mReader;
 | 
			
		||||
	size_t mOffset;
 | 
			
		||||
	const sKeyset* mKeyset;
 | 
			
		||||
	CliOutputType mCliOutputType;
 | 
			
		||||
	bool mVerify;
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
	std::string mExtractPath;
 | 
			
		||||
	bool mExtract;
 | 
			
		||||
	std::string mMountName;
 | 
			
		||||
	bool mListFs;
 | 
			
		||||
 | 
			
		||||
	fnd::MemoryBlob mCache;
 | 
			
		||||
 | 
			
		||||
	nx::PfsHeader mPfs;
 | 
			
		||||
 | 
			
		||||
	void displayHeader();
 | 
			
		||||
	void displayFs();
 | 
			
		||||
	size_t determineHeaderSize(const nx::sPfsHeader* hdr);
 | 
			
		||||
	bool validateHeaderMagic(const nx::sPfsHeader* hdr);
 | 
			
		||||
	void validateHfs();
 | 
			
		||||
	void extractFs();
 | 
			
		||||
};
 | 
			
		||||
| 
						 | 
				
			
			@ -1,7 +1,85 @@
 | 
			
		|||
#include "RomfsProcess.h"
 | 
			
		||||
#include <fnd/SimpleTextOutput.h>
 | 
			
		||||
#include <fnd/SimpleFile.h>
 | 
			
		||||
#include <fnd/io.h>
 | 
			
		||||
#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
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -43,8 +121,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", (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) != '/' ? "/" : "");
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -64,16 +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
 | 
			
		||||
	fnd::MemoryBlob scratch;
 | 
			
		||||
	scratch.alloc(kFileExportBlockSize);
 | 
			
		||||
 | 
			
		||||
	// extract files
 | 
			
		||||
	fnd::SimpleFile outFile;
 | 
			
		||||
	for (size_t i = 0; i < dir.file_list.getSize(); i++)
 | 
			
		||||
| 
						 | 
				
			
			@ -87,17 +158,12 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir)
 | 
			
		|||
		
 | 
			
		||||
		
 | 
			
		||||
		outFile.open(file_path, outFile.Create);
 | 
			
		||||
		mReader->seek(mOffset + dir.file_list[i].offset);
 | 
			
		||||
		for (size_t j = 0; j < (dir.file_list[i].size / kFileExportBlockSize); j++)
 | 
			
		||||
		mReader->seek(dir.file_list[i].offset);
 | 
			
		||||
		for (size_t j = 0; j < ((dir.file_list[i].size / kCacheSize) + ((dir.file_list[i].size % kCacheSize) != 0)); j++)
 | 
			
		||||
		{
 | 
			
		||||
			mReader->read(scratch.getBytes(), kFileExportBlockSize);
 | 
			
		||||
			outFile.write(scratch.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(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();
 | 
			
		||||
	}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -110,6 +176,8 @@ void RomfsProcess::extractDir(const std::string& path, const sDirectory& dir)
 | 
			
		|||
 | 
			
		||||
void RomfsProcess::extractFs()
 | 
			
		||||
{
 | 
			
		||||
	// allocate only when extractDir is invoked
 | 
			
		||||
	mCache.alloc(kCacheSize);
 | 
			
		||||
	extractDir(mExtractPath, mRootDir);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -165,7 +233,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++;
 | 
			
		||||
| 
						 | 
				
			
			@ -175,7 +243,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();
 | 
			
		||||
| 
						 | 
				
			
			@ -186,7 +254,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 +264,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());
 | 
			
		||||
	
 | 
			
		||||
| 
						 | 
				
			
			@ -219,85 +287,4 @@ void RomfsProcess::resolveRomfs()
 | 
			
		|||
	mDirNum = 0;
 | 
			
		||||
	mFileNum = 0;
 | 
			
		||||
	importDirectory(0, mRootDir);
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
RomfsProcess::RomfsProcess() :
 | 
			
		||||
	mReader(nullptr),
 | 
			
		||||
	mOffset(0),
 | 
			
		||||
	mKeyset(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();
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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& reader)
 | 
			
		||||
{
 | 
			
		||||
	mReader = &reader;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RomfsProcess::setInputFileOffset(size_t offset)
 | 
			
		||||
{
 | 
			
		||||
	mOffset = offset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void RomfsProcess::setKeyset(const sKeyset* keyset)
 | 
			
		||||
{
 | 
			
		||||
	mKeyset = keyset;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -107,11 +106,9 @@ public:
 | 
			
		|||
	const sDirectory& getRootDir() const;
 | 
			
		||||
private:
 | 
			
		||||
	const std::string kModuleName = "RomfsProcess";
 | 
			
		||||
	static const size_t kFileExportBlockSize = 0x1000000;
 | 
			
		||||
	static const size_t kCacheSize = 0x10000;
 | 
			
		||||
 | 
			
		||||
	fnd::IFile* mReader;
 | 
			
		||||
	size_t mOffset;
 | 
			
		||||
	const sKeyset* mKeyset;
 | 
			
		||||
	CliOutputType mCliOutputType;
 | 
			
		||||
	bool mVerify;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -120,6 +117,8 @@ private:
 | 
			
		|||
	std::string mMountName;
 | 
			
		||||
	bool mListFs;
 | 
			
		||||
 | 
			
		||||
	fnd::MemoryBlob mCache;
 | 
			
		||||
 | 
			
		||||
	size_t mDirNum;
 | 
			
		||||
	size_t mFileNum;
 | 
			
		||||
	nx::sRomfsHeader mHdr;
 | 
			
		||||
| 
						 | 
				
			
			@ -138,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();
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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, cnmt]\n");
 | 
			
		||||
	printf("      -y, --verify    Verify file\n");
 | 
			
		||||
	printf("      -v, --verbose   Verbose output\n");
 | 
			
		||||
	printf("      -q, --quiet     Minimal output\n");
 | 
			
		||||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			@ -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));
 | 
			
		||||
		}
 | 
			
		||||
| 
						 | 
				
			
			@ -565,17 +565,20 @@ 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 == "nsp")
 | 
			
		||||
		type = FILE_NSP;
 | 
			
		||||
	else if (str == "partitionfs" || str == "hashedpartitionfs"  \
 | 
			
		||||
			 || str == "pfs" || str == "pfs0" \
 | 
			
		||||
			 || str == "hfs" || str == "hfs0")
 | 
			
		||||
		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 == "cnmt")
 | 
			
		||||
		type = FILE_CNMT;
 | 
			
		||||
	else
 | 
			
		||||
		type = FILE_INVALID;
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			@ -587,14 +590,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 +605,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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -1,6 +1,7 @@
 | 
			
		|||
#include "XciProcess.h"
 | 
			
		||||
#include <fnd/SimpleTextOutput.h>
 | 
			
		||||
#include <nx/XciUtils.h>
 | 
			
		||||
#include "OffsetAdjustedIFile.h"
 | 
			
		||||
#include "XciProcess.h"
 | 
			
		||||
 | 
			
		||||
inline const char* getBoolStr(bool isTrue)
 | 
			
		||||
{
 | 
			
		||||
| 
						 | 
				
			
			@ -103,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());
 | 
			
		||||
| 
						 | 
				
			
			@ -136,13 +139,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 +158,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 +175,6 @@ void XciProcess::processPartitionPfs()
 | 
			
		|||
 | 
			
		||||
XciProcess::XciProcess() :
 | 
			
		||||
	mReader(nullptr),
 | 
			
		||||
	mOffset(0),
 | 
			
		||||
	mKeyset(nullptr),
 | 
			
		||||
	mCliOutputType(OUTPUT_NORMAL),
 | 
			
		||||
	mVerify(false),
 | 
			
		||||
| 
						 | 
				
			
			@ -189,6 +189,14 @@ XciProcess::XciProcess() :
 | 
			
		|||
	mSecurePath.doExtract = false;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
XciProcess::~XciProcess()
 | 
			
		||||
{
 | 
			
		||||
	if (mReader != nullptr)
 | 
			
		||||
	{
 | 
			
		||||
		delete mReader;
 | 
			
		||||
	}
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
void XciProcess::process()
 | 
			
		||||
{
 | 
			
		||||
	fnd::MemoryBlob scratch;
 | 
			
		||||
| 
						 | 
				
			
			@ -199,7 +207,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 +235,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)
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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;
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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());
 | 
			
		||||
| 
						 | 
				
			
			@ -37,12 +38,11 @@ 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;
 | 
			
		||||
 | 
			
		||||
			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());
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -13,10 +13,12 @@ static const size_t kNcaKeakNum = nx::nca::kKeyAreaEncryptionKeyNum;
 | 
			
		|||
enum FileType
 | 
			
		||||
{
 | 
			
		||||
	FILE_XCI,
 | 
			
		||||
	FILE_NSP,
 | 
			
		||||
	FILE_PARTITIONFS,
 | 
			
		||||
	FILE_ROMFS,
 | 
			
		||||
	FILE_NCA,
 | 
			
		||||
	FILE_NPDM,
 | 
			
		||||
	FILE_CNMT,
 | 
			
		||||
	FILE_INVALID = -1,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
		Reference in a new issue