2020-02-26 09:05:21 +00:00
# include "NcaProcess.h"
# include "MetaProcess.h"
2021-10-19 08:22:59 +00:00
# include "util.h"
2020-02-26 09:05:21 +00:00
2021-10-16 06:13:11 +00:00
# include <tc/crypto/detail/BlockUtilImpl.h>
2020-02-26 09:05:21 +00:00
# include <nn/hac/ContentArchiveUtil.h>
2019-01-31 09:10:19 +00:00
# include <nn/hac/AesKeygen.h>
2021-10-16 06:13:11 +00:00
# include <nn/hac/HierarchicalSha256Stream.h>
# include <nn/hac/HierarchicalIntegrityStream.h>
2021-10-15 09:29:29 +00:00
# include <nn/hac/PartitionFsMetaGenerator.h>
# include <nn/hac/RomFsMetaGenerator.h>
2021-10-16 06:13:11 +00:00
# include <nn/hac/CombinedFsMetaGenerator.h>
2019-01-31 09:10:19 +00:00
2021-09-28 11:15:54 +00:00
nstool : : NcaProcess : : NcaProcess ( ) :
2021-10-15 09:29:29 +00:00
mModuleName ( " nstool::NcaProcess " ) ,
2019-01-31 09:10:19 +00:00
mFile ( ) ,
2021-09-28 11:15:54 +00:00
mCliOutputMode ( true , false , false , false ) ,
2019-01-31 09:10:19 +00:00
mVerify ( false ) ,
2021-10-15 09:29:29 +00:00
mFileSystem ( ) ,
mFsProcess ( )
2019-01-31 09:10:19 +00:00
{
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : process ( )
2019-01-31 09:10:19 +00:00
{
// import header
importHeader ( ) ;
// determine keys
generateNcaBodyEncryptionKeys ( ) ;
// import/generate fs header data
generatePartitionConfiguration ( ) ;
// validate signatures
if ( mVerify )
validateNcaSignatures ( ) ;
// display header
2021-09-28 11:15:54 +00:00
if ( mCliOutputMode . show_basic_info )
2019-01-31 09:10:19 +00:00
displayHeader ( ) ;
// process partition
processPartitions ( ) ;
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : setInputFile ( const std : : shared_ptr < tc : : io : : IStream > & file )
2019-01-31 09:10:19 +00:00
{
mFile = file ;
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : setKeyCfg ( const KeyBag & keycfg )
2019-01-31 09:10:19 +00:00
{
mKeyCfg = keycfg ;
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : setCliOutputMode ( CliOutputMode type )
2019-01-31 09:10:19 +00:00
{
mCliOutputMode = type ;
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : setVerifyMode ( bool verify )
2019-01-31 09:10:19 +00:00
{
mVerify = verify ;
}
2021-10-15 09:29:29 +00:00
void nstool : : NcaProcess : : setShowFsTree ( bool show_fs_tree )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mFsProcess . setShowFsTree ( show_fs_tree ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
void nstool : : NcaProcess : : setFsRootLabel ( const std : : string & root_label )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mFsProcess . setFsRootLabel ( root_label ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
void nstool : : NcaProcess : : setExtractJobs ( const std : : vector < nstool : : ExtractJob > & extract_jobs )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mFsProcess . setExtractJobs ( extract_jobs ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
const std : : shared_ptr < tc : : io : : IStorage > & nstool : : NcaProcess : : getFileSystem ( ) const
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
return mFileSystem ;
2019-01-31 09:10:19 +00:00
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : importHeader ( )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
if ( mFile = = nullptr )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( mModuleName , " No file reader set. " ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
if ( mFile - > canRead ( ) = = false | | mFile - > canSeek ( ) = = false )
{
throw tc : : NotSupportedException ( mModuleName , " Input stream requires read/seek permissions. " ) ;
}
2019-01-31 09:10:19 +00:00
// read header block
2021-10-15 09:29:29 +00:00
if ( mFile - > length ( ) < tc : : io : : IOUtil : : castSizeToInt64 ( sizeof ( nn : : hac : : sContentArchiveHeaderBlock ) ) )
{
throw tc : : Exception ( mModuleName , " Corrupt NCA: File too small. " ) ;
}
mFile - > seek ( 0 , tc : : io : : SeekOrigin : : Begin ) ;
mFile - > read ( ( byte_t * ) ( & mHdrBlock ) , sizeof ( nn : : hac : : sContentArchiveHeaderBlock ) ) ;
2019-01-31 09:10:19 +00:00
// decrypt header block
2021-10-15 09:29:29 +00:00
if ( mKeyCfg . nca_header_key . isNull ( ) )
{
throw tc : : Exception ( mModuleName , " Failed to decrypt NCA header. (nca_header_key could not be loaded) " ) ;
}
nn : : hac : : ContentArchiveUtil : : decryptContentArchiveHeader ( ( byte_t * ) & mHdrBlock , ( byte_t * ) & mHdrBlock , mKeyCfg . nca_header_key . get ( ) ) ;
2019-01-31 09:10:19 +00:00
// generate header hash
2021-10-15 09:29:29 +00:00
tc : : crypto : : GenerateSha256Hash ( mHdrHash . data ( ) , ( byte_t * ) & mHdrBlock . header , sizeof ( nn : : hac : : sContentArchiveHeader ) ) ;
2019-01-31 09:10:19 +00:00
// proccess main header
mHdr . fromBytes ( ( byte_t * ) & mHdrBlock . header , sizeof ( nn : : hac : : sContentArchiveHeader ) ) ;
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : generateNcaBodyEncryptionKeys ( )
2019-01-31 09:10:19 +00:00
{
// create zeros key
2021-09-28 11:15:54 +00:00
KeyBag : : aes128_key_t zero_aesctr_key ;
2021-10-15 09:29:29 +00:00
memset ( zero_aesctr_key . data ( ) , 0 , zero_aesctr_key . size ( ) ) ;
2019-01-31 09:10:19 +00:00
// get key data from header
2021-10-19 08:22:59 +00:00
byte_t masterkey_rev = nn : : hac : : AesKeygen : : getMasterKeyRevisionFromKeyGeneration ( mHdr . getKeyGeneration ( ) ) ;
2019-01-31 09:10:19 +00:00
byte_t keak_index = mHdr . getKeyAreaEncryptionKeyIndex ( ) ;
// process key area
sKeys : : sKeyAreaKey kak ;
2021-10-16 06:13:11 +00:00
for ( size_t i = 0 ; i < mHdr . getKeyArea ( ) . size ( ) ; i + + )
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
if ( mHdr . getKeyArea ( ) [ i ] ! = zero_aesctr_key )
2019-01-31 09:10:19 +00:00
{
kak . index = ( byte_t ) i ;
2021-10-16 06:13:11 +00:00
kak . enc = mHdr . getKeyArea ( ) [ i ] ;
2021-10-15 09:29:29 +00:00
kak . decrypted = false ;
2019-01-31 09:10:19 +00:00
// key[0-3]
2021-10-15 09:29:29 +00:00
if ( i < 4 & & mKeyCfg . nca_key_area_encryption_key [ keak_index ] . find ( masterkey_rev ) ! = mKeyCfg . nca_key_area_encryption_key [ keak_index ] . end ( ) )
2019-01-31 09:10:19 +00:00
{
kak . decrypted = true ;
2021-10-15 09:29:29 +00:00
nn : : hac : : AesKeygen : : generateKey ( kak . dec . data ( ) , kak . enc . data ( ) , mKeyCfg . nca_key_area_encryption_key [ keak_index ] [ masterkey_rev ] . data ( ) ) ;
2019-01-31 09:10:19 +00:00
}
// key[KEY_AESCTR_HW]
2021-10-15 09:29:29 +00:00
else if ( i = = nn : : hac : : nca : : KEY_AESCTR_HW & & mKeyCfg . nca_key_area_encryption_key_hw [ keak_index ] . find ( masterkey_rev ) ! = mKeyCfg . nca_key_area_encryption_key_hw [ keak_index ] . end ( ) )
2019-01-31 09:10:19 +00:00
{
kak . decrypted = true ;
2021-10-15 09:29:29 +00:00
nn : : hac : : AesKeygen : : generateKey ( kak . dec . data ( ) , kak . enc . data ( ) , mKeyCfg . nca_key_area_encryption_key_hw [ keak_index ] [ masterkey_rev ] . data ( ) ) ;
2019-01-31 09:10:19 +00:00
}
else
{
kak . decrypted = false ;
}
2021-09-28 11:15:54 +00:00
mContentKey . kak_list . push_back ( kak ) ;
2019-01-31 09:10:19 +00:00
}
}
2021-10-15 09:29:29 +00:00
// clear content key
mContentKey . aes_ctr = tc : : Optional < nn : : hac : : detail : : aes128_key_t > ( ) ;
2019-01-31 09:10:19 +00:00
// if this has a rights id, the key needs to be sourced from a ticket
if ( mHdr . hasRightsId ( ) = = true )
{
2021-09-28 11:15:54 +00:00
KeyBag : : aes128_key_t tmp_key ;
2021-10-15 09:29:29 +00:00
if ( mKeyCfg . external_content_keys . find ( mHdr . getRightsId ( ) ) ! = mKeyCfg . external_content_keys . end ( ) )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mContentKey . aes_ctr = mKeyCfg . external_content_keys [ mHdr . getRightsId ( ) ] ;
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
else if ( mKeyCfg . fallback_content_key . isSet ( ) )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
mContentKey . aes_ctr = mKeyCfg . fallback_content_key . get ( ) ;
}
else if ( mKeyCfg . fallback_enc_content_key . isSet ( ) )
{
tmp_key = mKeyCfg . fallback_enc_content_key . get ( ) ;
if ( mKeyCfg . etik_common_key . find ( masterkey_rev ) ! = mKeyCfg . etik_common_key . end ( ) )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
nn : : hac : : AesKeygen : : generateKey ( tmp_key . data ( ) , tmp_key . data ( ) , mKeyCfg . etik_common_key [ masterkey_rev ] . data ( ) ) ;
mContentKey . aes_ctr = tmp_key ;
2019-01-31 09:10:19 +00:00
}
}
}
2021-10-15 09:29:29 +00:00
// otherwise used decrypt key area
2019-01-31 09:10:19 +00:00
else
{
for ( size_t i = 0 ; i < mContentKey . kak_list . size ( ) ; i + + )
{
if ( mContentKey . kak_list [ i ] . index = = nn : : hac : : nca : : KEY_AESCTR & & mContentKey . kak_list [ i ] . decrypted )
{
2021-10-15 09:29:29 +00:00
mContentKey . aes_ctr = mContentKey . kak_list [ i ] . dec ;
2019-01-31 09:10:19 +00:00
}
}
}
// if the keys weren't generated, check if the keys were supplied by the user
2021-09-28 11:15:54 +00:00
if ( mContentKey . aes_ctr . isNull ( ) )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
if ( mKeyCfg . fallback_content_key . isSet ( ) )
{
mContentKey . aes_ctr = mKeyCfg . fallback_content_key . get ( ) ;
}
2019-01-31 09:10:19 +00:00
}
2021-09-28 11:15:54 +00:00
if ( mCliOutputMode . show_keydata )
2019-01-31 09:10:19 +00:00
{
2021-09-28 11:15:54 +00:00
if ( mContentKey . aes_ctr . isSet ( ) )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt : : print ( " [NCA Content Key] \n " ) ;
fmt : : print ( " AES-CTR Key: {:s} \n " , tc : : cli : : FormatUtil : : formatBytesAsString ( mContentKey . aes_ctr . get ( ) . data ( ) , mContentKey . aes_ctr . get ( ) . size ( ) , true , " : " ) ) ;
2019-01-31 09:10:19 +00:00
}
}
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : generatePartitionConfiguration ( )
2019-01-31 09:10:19 +00:00
{
for ( size_t i = 0 ; i < mHdr . getPartitionEntryList ( ) . size ( ) ; i + + )
{
// get reference to relevant structures
const nn : : hac : : ContentArchiveHeader : : sPartitionEntry & partition = mHdr . getPartitionEntryList ( ) [ i ] ;
2020-03-19 11:03:52 +00:00
nn : : hac : : sContentArchiveFsHeader & fs_header = mHdrBlock . fs_header [ partition . header_index ] ;
2019-01-31 09:10:19 +00:00
// output structure
sPartitionInfo & info = mPartitions [ partition . header_index ] ;
// validate header hash
2021-10-15 09:29:29 +00:00
nn : : hac : : detail : : sha256_hash_t fs_header_hash ;
tc : : crypto : : GenerateSha256Hash ( fs_header_hash . data ( ) , ( const byte_t * ) & mHdrBlock . fs_header [ partition . header_index ] , sizeof ( nn : : hac : : sContentArchiveFsHeader ) ) ;
if ( fs_header_hash ! = partition . fs_header_hash )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( mModuleName , fmt : : format ( " NCA FS Header [{:d}] Hash: FAIL " , partition . header_index ) ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
if ( fs_header . version . unwrap ( ) ! = nn : : hac : : nca : : kDefaultFsHeaderVersion )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( mModuleName , fmt : : format ( " NCA FS Header [{:d}] Version({:d}): UNSUPPORTED " , partition . header_index , fs_header . version . unwrap ( ) ) ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-27 11:12:24 +00:00
// detect compacted partition (sparse layer)
2021-10-16 08:40:14 +00:00
if ( fs_header . sparse_info . generation . unwrap ( ) ! = 0 )
{
2021-10-27 11:12:24 +00:00
// TODO
2021-10-16 08:40:14 +00:00
}
2019-01-31 09:10:19 +00:00
// setup AES-CTR
2021-10-15 09:29:29 +00:00
nn : : hac : : ContentArchiveUtil : : getNcaPartitionAesCtr ( & fs_header , info . aes_ctr . data ( ) ) ;
2019-01-31 09:10:19 +00:00
2021-10-16 06:13:11 +00:00
// save partition configinfo
info . offset = partition . offset ;
info . size = partition . size ;
2019-01-31 09:10:19 +00:00
info . format_type = ( nn : : hac : : nca : : FormatType ) fs_header . format_type ;
info . hash_type = ( nn : : hac : : nca : : HashType ) fs_header . hash_type ;
info . enc_type = ( nn : : hac : : nca : : EncryptionType ) fs_header . encryption_type ;
2020-03-17 11:11:28 +00:00
if ( info . hash_type = = nn : : hac : : nca : : HashType : : HierarchicalSha256 )
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
info . hierarchicalsha256_hdr . fromBytes ( fs_header . hash_info . data ( ) , fs_header . hash_info . size ( ) ) ;
/*
2019-01-31 09:10:19 +00:00
nn : : hac : : HierarchicalSha256Header hdr ;
// import raw data
2021-10-15 09:29:29 +00:00
hdr . fromBytes ( fs_header . hash_info . data ( ) , fs_header . hash_info . size ( ) ) ;
2019-01-31 09:10:19 +00:00
for ( size_t i = 0 ; i < hdr . getLayerInfo ( ) . size ( ) ; i + + )
{
2021-10-15 09:29:29 +00:00
nn : : hac : : HierarchicalValidatedStream : : StreamInfo : : LayerInfo layer ;
2019-01-31 09:10:19 +00:00
layer . offset = hdr . getLayerInfo ( ) [ i ] . offset ;
layer . size = hdr . getLayerInfo ( ) [ i ] . size ;
layer . block_size = hdr . getHashBlockSize ( ) ;
if ( i + 1 = = hdr . getLayerInfo ( ) . size ( ) )
{
2021-10-15 09:29:29 +00:00
info . hashed_stream_info . data_layer_info = layer ;
2019-01-31 09:10:19 +00:00
}
else
{
2021-10-15 09:29:29 +00:00
info . hashed_stream_info . hash_layer_info . push_back ( layer ) ;
2019-01-31 09:10:19 +00:00
}
}
2021-10-15 09:29:29 +00:00
info . hashed_stream_info . master_hash_data = tc : : ByteData ( hdr . getMasterHash ( ) . data ( ) , hdr . getMasterHash ( ) . size ( ) ) ;
2021-10-16 06:13:11 +00:00
info . hashed_stream_info . align_partial_block_to_blocksize = false ;
*/
2019-01-31 09:10:19 +00:00
}
2020-03-17 11:11:28 +00:00
else if ( info . hash_type = = nn : : hac : : nca : : HashType : : HierarchicalIntegrity )
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
info . hierarchicalintegrity_hdr . fromBytes ( fs_header . hash_info . data ( ) , fs_header . hash_info . size ( ) ) ;
/*
2020-03-19 11:03:52 +00:00
// info.hash_tree_meta.importData(fs_header.hash_info, nn::hac::nca::kHashInfoLen, LayeredIntegrityMetadata::HASH_TYPE_INTEGRITY);
2019-01-31 09:10:19 +00:00
nn : : hac : : HierarchicalIntegrityHeader hdr ;
2021-10-15 09:29:29 +00:00
hdr . fromBytes ( fs_header . hash_info . data ( ) , fs_header . hash_info . size ( ) ) ;
2019-01-31 09:10:19 +00:00
for ( size_t i = 0 ; i < hdr . getLayerInfo ( ) . size ( ) ; i + + )
{
2021-10-15 09:29:29 +00:00
nn : : hac : : HierarchicalValidatedStream : : StreamInfo : : LayerInfo layer ;
2019-01-31 09:10:19 +00:00
layer . offset = hdr . getLayerInfo ( ) [ i ] . offset ;
layer . size = hdr . getLayerInfo ( ) [ i ] . size ;
2021-09-28 11:15:54 +00:00
layer . block_size = ( 1 < < hdr . getLayerInfo ( ) [ i ] . block_size ) ;
2019-01-31 09:10:19 +00:00
if ( i + 1 = = hdr . getLayerInfo ( ) . size ( ) )
{
2021-10-15 09:29:29 +00:00
info . hashed_stream_info . data_layer_info = layer ;
2019-01-31 09:10:19 +00:00
}
else
{
2021-10-15 09:29:29 +00:00
info . hashed_stream_info . hash_layer_info . push_back ( layer ) ;
2019-01-31 09:10:19 +00:00
}
}
2021-10-15 09:29:29 +00:00
info . hashed_stream_info . master_hash_data = tc : : ByteData ( hdr . getMasterHashList ( ) . size ( ) * sizeof ( nn : : hac : : detail : : sha256_hash_t ) ) ;
for ( size_t i = 0 ; i < hdr . getMasterHashList ( ) . size ( ) ; i + + )
{
( ( nn : : hac : : detail : : sha256_hash_t * ) info . hashed_stream_info . master_hash_data . data ( ) ) [ i ] = hdr . getMasterHashList ( ) [ i ] ;
}
2021-10-16 06:13:11 +00:00
info . hashed_stream_info . align_partial_block_to_blocksize = false ;
*/
2019-01-31 09:10:19 +00:00
}
// create reader
try
{
// create reader based on encryption type0
2020-03-17 11:11:28 +00:00
if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : None )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
info . reader = std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( mFile , info . offset , info . size ) ) ;
2019-01-31 09:10:19 +00:00
}
2020-03-17 11:11:28 +00:00
else if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtr )
2019-01-31 09:10:19 +00:00
{
2021-09-28 11:15:54 +00:00
if ( mContentKey . aes_ctr . isNull ( ) )
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( mModuleName , " AES-CTR Key was not determined " ) ;
//info.reader = new fnd::OffsetAdjustedIFile(new fnd::AesCtrWrappedIFile(mFile, mContentKey.aes_ctr.get(), info.aes_ctr), info.offset, info.size);
info . reader = std : : make_shared < tc : : crypto : : Aes128CtrEncryptedStream > ( tc : : crypto : : Aes128CtrEncryptedStream ( mFile , mContentKey . aes_ctr . get ( ) , info . aes_ctr ) ) ;
info . reader = std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( info . reader , info . offset , info . size ) ) ;
2019-01-31 09:10:19 +00:00
}
2020-03-17 11:11:28 +00:00
else if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesXts | | info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtrEx )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( mModuleName , fmt : : format ( " EncryptionType({:s}): UNSUPPORTED " , nn : : hac : : ContentArchiveUtil : : getEncryptionTypeAsString ( info . enc_type ) ) ) ;
2019-01-31 09:10:19 +00:00
}
else
{
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( mModuleName , fmt : : format ( " EncryptionType({:s}): UNKNOWN " , nn : : hac : : ContentArchiveUtil : : getEncryptionTypeAsString ( info . enc_type ) ) ) ;
2019-01-31 09:10:19 +00:00
}
// filter out unrecognised hash types, and hash based readers
2021-10-16 06:13:11 +00:00
switch ( info . hash_type )
2019-01-31 09:10:19 +00:00
{
2021-10-16 07:41:45 +00:00
case ( nn : : hac : : nca : : HashType : : None ) :
break ;
2021-10-16 06:13:11 +00:00
case ( nn : : hac : : nca : : HashType : : HierarchicalSha256 ) :
info . reader = std : : make_shared < nn : : hac : : HierarchicalSha256Stream > ( nn : : hac : : HierarchicalSha256Stream ( info . reader , info . hierarchicalsha256_hdr ) ) ;
break ;
case ( nn : : hac : : nca : : HashType : : HierarchicalIntegrity ) :
info . reader = std : : make_shared < nn : : hac : : HierarchicalIntegrityStream > ( nn : : hac : : HierarchicalIntegrityStream ( info . reader , info . hierarchicalintegrity_hdr ) ) ;
break ;
default :
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( mModuleName , fmt : : format ( " HashType({:s}): UNKNOWN " , nn : : hac : : ContentArchiveUtil : : getHashTypeAsString ( info . hash_type ) ) ) ;
}
// filter out unrecognised format types
switch ( info . format_type )
{
case ( nn : : hac : : nca : : FormatType : : PartitionFs ) :
info . fs_meta = nn : : hac : : PartitionFsMetaGenerator ( info . reader ) ;
info . fs_reader = std : : make_shared < tc : : io : : VirtualFileSystem > ( tc : : io : : VirtualFileSystem ( info . fs_meta ) ) ;
break ;
case ( nn : : hac : : nca : : FormatType : : RomFs ) :
info . fs_meta = nn : : hac : : RomFsMetaGenerator ( info . reader ) ;
info . fs_reader = std : : make_shared < tc : : io : : VirtualFileSystem > ( tc : : io : : VirtualFileSystem ( info . fs_meta ) ) ;
break ;
default :
throw tc : : Exception ( mModuleName , fmt : : format ( " FormatType({:s}): UNKNOWN " , nn : : hac : : ContentArchiveUtil : : getFormatTypeAsString ( info . format_type ) ) ) ;
2019-01-31 09:10:19 +00:00
}
}
2021-09-28 11:15:54 +00:00
catch ( const tc : : Exception & e )
2019-01-31 09:10:19 +00:00
{
info . fail_reason = std : : string ( e . error ( ) ) ;
}
}
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : validateNcaSignatures ( )
2019-01-31 09:10:19 +00:00
{
// validate signature[0]
2021-10-15 09:29:29 +00:00
if ( mKeyCfg . nca_header_sign0_key . find ( mHdr . getSignatureKeyGeneration ( ) ) ! = mKeyCfg . nca_header_sign0_key . end ( ) )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
if ( tc : : crypto : : VerifyRsa2048PssSha256 ( mHdrBlock . signature_main . data ( ) , mHdrHash . data ( ) , mKeyCfg . nca_header_sign0_key [ mHdr . getSignatureKeyGeneration ( ) ] ) = = false )
{
fmt : : print ( " [WARNING] NCA Header Main Signature: FAIL \n " ) ;
}
}
else
{
fmt : : print ( " [WARNING] NCA Header Main Signature: FAIL (could not load header key) \n " ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
2019-01-31 09:10:19 +00:00
// validate signature[1]
2020-03-17 11:11:28 +00:00
if ( mHdr . getContentType ( ) = = nn : : hac : : nca : : ContentType : : Program )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
try {
if ( mPartitions [ nn : : hac : : nca : : PARTITION_CODE ] . format_type = = nn : : hac : : nca : : FormatType : : PartitionFs )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
if ( mPartitions [ nn : : hac : : nca : : PARTITION_CODE ] . fs_reader ! = nullptr )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
std : : shared_ptr < tc : : io : : IStream > npdm_file ;
try {
mPartitions [ nn : : hac : : nca : : PARTITION_CODE ] . fs_reader - > openFile ( tc : : io : : Path ( kNpdmExefsPath ) , tc : : io : : FileMode : : Open , tc : : io : : FileAccess : : Read , npdm_file ) ;
}
catch ( tc : : io : : FileNotFoundException & ) {
throw tc : : Exception ( fmt : : format ( " \" {:s} \" not present in ExeFs " , kNpdmExefsPath ) ) ;
}
2019-01-31 09:10:19 +00:00
MetaProcess npdm ;
2021-10-15 09:29:29 +00:00
npdm . setInputFile ( npdm_file ) ;
2020-03-10 11:02:56 +00:00
npdm . setKeyCfg ( mKeyCfg ) ;
npdm . setVerifyMode ( true ) ;
2021-10-15 09:29:29 +00:00
npdm . setCliOutputMode ( CliOutputMode ( false , false , false , false ) ) ;
2019-01-31 09:10:19 +00:00
npdm . process ( ) ;
2021-10-15 09:29:29 +00:00
if ( tc : : crypto : : VerifyRsa2048PssSha256 ( mHdrBlock . signature_acid . data ( ) , mHdrHash . data ( ) , npdm . getMeta ( ) . getAccessControlInfoDesc ( ) . getContentArchiveHeaderSignature2Key ( ) ) = = false )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( " Bad signature " ) ;
2019-01-31 09:10:19 +00:00
}
}
else
{
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( " ExeFs was not mounted " ) ;
2019-01-31 09:10:19 +00:00
}
}
else
{
2021-10-15 09:29:29 +00:00
throw tc : : Exception ( " No ExeFs partition " ) ;
2019-01-31 09:10:19 +00:00
}
}
2021-10-15 09:29:29 +00:00
catch ( tc : : Exception & e ) {
fmt : : print ( " [WARNING] NCA Header ACID Signature: FAIL ({:s}) \n " , e . error ( ) ) ;
2019-01-31 09:10:19 +00:00
}
}
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : displayHeader ( )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt : : print ( " [NCA Header] \n " ) ;
fmt : : print ( " Format Type: {:s} \n " , nn : : hac : : ContentArchiveUtil : : getFormatHeaderVersionAsString ( ( nn : : hac : : nca : : HeaderFormatVersion ) mHdr . getFormatVersion ( ) ) ) ;
fmt : : print ( " Dist. Type: {:s} \n " , nn : : hac : : ContentArchiveUtil : : getDistributionTypeAsString ( mHdr . getDistributionType ( ) ) ) ;
fmt : : print ( " Content Type: {:s} \n " , nn : : hac : : ContentArchiveUtil : : getContentTypeAsString ( mHdr . getContentType ( ) ) ) ;
fmt : : print ( " Key Generation: {:d} \n " , mHdr . getKeyGeneration ( ) ) ;
fmt : : print ( " Sig. Generation: {:d} \n " , mHdr . getSignatureKeyGeneration ( ) ) ;
fmt : : print ( " Kaek Index: {:s} ({:d}) \n " , nn : : hac : : ContentArchiveUtil : : getKeyAreaEncryptionKeyIndexAsString ( ( nn : : hac : : nca : : KeyAreaEncryptionKeyIndex ) mHdr . getKeyAreaEncryptionKeyIndex ( ) ) , mHdr . getKeyAreaEncryptionKeyIndex ( ) ) ;
fmt : : print ( " Size: 0x{:x} \n " , mHdr . getContentSize ( ) ) ;
fmt : : print ( " ProgID: 0x{:016x} \n " , mHdr . getProgramId ( ) ) ;
fmt : : print ( " Content Index: {:d} \n " , mHdr . getContentIndex ( ) ) ;
fmt : : print ( " SdkAddon Ver.: {:s} (v{:d}) \n " , nn : : hac : : ContentArchiveUtil : : getSdkAddonVersionAsString ( mHdr . getSdkAddonVersion ( ) ) , mHdr . getSdkAddonVersion ( ) ) ;
2019-01-31 09:10:19 +00:00
if ( mHdr . hasRightsId ( ) )
{
2021-10-15 09:29:29 +00:00
fmt : : print ( " RightsId: {:s} \n " , tc : : cli : : FormatUtil : : formatBytesAsString ( mHdr . getRightsId ( ) . data ( ) , mHdr . getRightsId ( ) . size ( ) , true , " " ) ) ;
2019-01-31 09:10:19 +00:00
}
2021-09-28 11:15:54 +00:00
if ( mContentKey . kak_list . size ( ) > 0 & & mCliOutputMode . show_keydata )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt : : print ( " Key Area: \n " ) ;
fmt : : print ( " <---------------------------------------------------------------------------------------------------------> \n " ) ;
fmt : : print ( " | IDX | ENCRYPTED KEY | DECRYPTED KEY | \n " ) ;
fmt : : print ( " |-----|-------------------------------------------------|-------------------------------------------------| \n " ) ;
2019-01-31 09:10:19 +00:00
for ( size_t i = 0 ; i < mContentKey . kak_list . size ( ) ; i + + )
{
2021-10-15 09:29:29 +00:00
fmt : : print ( " | {:3d} | {:s} | " , mContentKey . kak_list [ i ] . index , tc : : cli : : FormatUtil : : formatBytesAsString ( mContentKey . kak_list [ i ] . enc . data ( ) , mContentKey . kak_list [ i ] . enc . size ( ) , true , " : " ) ) ;
2019-01-31 09:10:19 +00:00
if ( mContentKey . kak_list [ i ] . decrypted )
2021-10-15 09:29:29 +00:00
fmt : : print ( " {:s} " , tc : : cli : : FormatUtil : : formatBytesAsString ( mContentKey . kak_list [ i ] . dec . data ( ) , mContentKey . kak_list [ i ] . dec . size ( ) , true , " : " ) ) ;
2019-01-31 09:10:19 +00:00
else
2021-10-16 06:28:35 +00:00
fmt : : print ( " <unable to decrypt> " ) ;
2019-01-31 09:10:19 +00:00
2021-10-15 09:29:29 +00:00
fmt : : print ( " | \n " ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
fmt : : print ( " <---------------------------------------------------------------------------------------------------------> \n " ) ;
2019-01-31 09:10:19 +00:00
}
2021-09-28 11:15:54 +00:00
if ( mCliOutputMode . show_layout )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt : : print ( " Partitions: \n " ) ;
2019-01-31 09:10:19 +00:00
for ( size_t i = 0 ; i < mHdr . getPartitionEntryList ( ) . size ( ) ; i + + )
{
uint32_t index = mHdr . getPartitionEntryList ( ) [ i ] . header_index ;
sPartitionInfo & info = mPartitions [ index ] ;
if ( info . size = = 0 ) continue ;
2021-10-15 09:29:29 +00:00
fmt : : print ( " {:d}: \n " , index ) ;
fmt : : print ( " Offset: 0x{:x} \n " , info . offset ) ;
fmt : : print ( " Size: 0x{:x} \n " , info . size ) ;
fmt : : print ( " Format Type: {:s} \n " , nn : : hac : : ContentArchiveUtil : : getFormatTypeAsString ( info . format_type ) ) ;
fmt : : print ( " Hash Type: {:s} \n " , nn : : hac : : ContentArchiveUtil : : getHashTypeAsString ( info . hash_type ) ) ;
fmt : : print ( " Enc. Type: {:s} \n " , nn : : hac : : ContentArchiveUtil : : getEncryptionTypeAsString ( info . enc_type ) ) ;
2020-03-17 11:11:28 +00:00
if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtr )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
nn : : hac : : detail : : aes_iv_t aes_ctr ;
2021-10-16 06:13:11 +00:00
memcpy ( aes_ctr . data ( ) , info . aes_ctr . data ( ) , aes_ctr . size ( ) ) ;
tc : : crypto : : detail : : incr_counter < 16 > ( aes_ctr . data ( ) , info . offset > > 4 ) ;
2021-10-15 09:29:29 +00:00
fmt : : print ( " AesCtr Counter: \n " ) ;
fmt : : print ( " {:s} \n " , tc : : cli : : FormatUtil : : formatBytesAsString ( aes_ctr . data ( ) , aes_ctr . size ( ) , true , " : " ) ) ;
2019-01-31 09:10:19 +00:00
}
2020-03-17 11:11:28 +00:00
if ( info . hash_type = = nn : : hac : : nca : : HashType : : HierarchicalIntegrity )
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
auto hash_hdr = info . hierarchicalintegrity_hdr ;
2021-10-15 09:29:29 +00:00
fmt : : print ( " HierarchicalIntegrity Header: \n " ) ;
2021-10-16 06:13:11 +00:00
for ( size_t j = 0 ; j < hash_hdr . getLayerInfo ( ) . size ( ) ; j + + )
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
if ( j + 1 = = hash_hdr . getLayerInfo ( ) . size ( ) )
{
fmt : : print ( " Data Layer: \n " ) ;
}
else
{
fmt : : print ( " Hash Layer {:d}: \n " , j ) ;
}
fmt : : print ( " Offset: 0x{:x} \n " , hash_hdr . getLayerInfo ( ) [ j ] . offset ) ;
fmt : : print ( " Size: 0x{:x} \n " , hash_hdr . getLayerInfo ( ) [ j ] . size ) ;
fmt : : print ( " BlockSize: 0x{:x} \n " , hash_hdr . getLayerInfo ( ) [ j ] . block_size ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-16 06:13:11 +00:00
for ( size_t j = 0 ; j < hash_hdr . getMasterHashList ( ) . size ( ) ; j + + )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt : : print ( " Master Hash {:d}: \n " , j ) ;
2021-10-16 06:13:11 +00:00
fmt : : print ( " {:s} \n " , tc : : cli : : FormatUtil : : formatBytesAsString ( hash_hdr . getMasterHashList ( ) [ j ] . data ( ) , 0x10 , true , " : " ) ) ;
fmt : : print ( " {:s} \n " , tc : : cli : : FormatUtil : : formatBytesAsString ( hash_hdr . getMasterHashList ( ) [ j ] . data ( ) + 0x10 , 0x10 , true , " : " ) ) ;
2019-01-31 09:10:19 +00:00
}
}
2020-03-17 11:11:28 +00:00
else if ( info . hash_type = = nn : : hac : : nca : : HashType : : HierarchicalSha256 )
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
auto hash_hdr = info . hierarchicalsha256_hdr ;
2021-10-15 09:29:29 +00:00
fmt : : print ( " HierarchicalSha256 Header: \n " ) ;
fmt : : print ( " Master Hash: \n " ) ;
2021-10-16 06:13:11 +00:00
fmt : : print ( " {:s} \n " , tc : : cli : : FormatUtil : : formatBytesAsString ( hash_hdr . getMasterHash ( ) . data ( ) , 0x10 , true , " : " ) ) ;
fmt : : print ( " {:s} \n " , tc : : cli : : FormatUtil : : formatBytesAsString ( hash_hdr . getMasterHash ( ) . data ( ) + 0x10 , 0x10 , true , " : " ) ) ;
fmt : : print ( " HashBlockSize: 0x{:x} \n " , hash_hdr . getHashBlockSize ( ) ) ;
for ( size_t j = 0 ; j < hash_hdr . getLayerInfo ( ) . size ( ) ; j + + )
{
if ( j + 1 = = hash_hdr . getLayerInfo ( ) . size ( ) )
{
fmt : : print ( " Data Layer: \n " ) ;
}
else
{
fmt : : print ( " Hash Layer {:d}: \n " , j ) ;
}
fmt : : print ( " Offset: 0x{:x} \n " , hash_hdr . getLayerInfo ( ) [ j ] . offset ) ;
fmt : : print ( " Size: 0x{:x} \n " , hash_hdr . getLayerInfo ( ) [ j ] . size ) ;
}
2019-01-31 09:10:19 +00:00
}
}
}
}
2021-09-28 11:15:54 +00:00
void nstool : : NcaProcess : : processPartitions ( )
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
std : : vector < nn : : hac : : CombinedFsMetaGenerator : : MountPointInfo > mount_points ;
2019-01-31 09:10:19 +00:00
for ( size_t i = 0 ; i < mHdr . getPartitionEntryList ( ) . size ( ) ; i + + )
{
uint32_t index = mHdr . getPartitionEntryList ( ) [ i ] . header_index ;
struct sPartitionInfo & partition = mPartitions [ index ] ;
// if the reader is null, skip
2021-10-15 09:29:29 +00:00
if ( partition . fs_reader = = nullptr )
2019-01-31 09:10:19 +00:00
{
2021-10-15 09:29:29 +00:00
fmt : : print ( " [WARNING] NCA Partition {:d} not readable. " , index ) ;
2019-01-31 09:10:19 +00:00
if ( partition . fail_reason . empty ( ) = = false )
{
2021-10-15 09:29:29 +00:00
fmt : : print ( " ({:s}) " , partition . fail_reason ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-15 09:29:29 +00:00
fmt : : print ( " \n " ) ;
2019-01-31 09:10:19 +00:00
continue ;
}
2021-10-16 06:13:11 +00:00
std : : string mount_point_name ;
/*
if ( mHdr . getContentType ( ) = = nn : : hac : : nca : : ContentType : : Program )
{
mount_point_name = nn : : hac : : ContentArchiveUtil : : getProgramContentParititionIndexAsString ( ( nn : : hac : : nca : : ProgramContentPartitionIndex ) index ) ;
}
else
*/
{
mount_point_name = fmt : : format ( " {:d} " , index ) ;
}
mount_points . push_back ( { mount_point_name , partition . fs_meta } ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-16 06:13:11 +00:00
tc : : io : : VirtualFileSystem : : FileSystemMeta fs_meta = nn : : hac : : CombinedFsMetaGenerator ( mount_points ) ;
std : : shared_ptr < tc : : io : : IStorage > nca_fs = std : : make_shared < tc : : io : : VirtualFileSystem > ( tc : : io : : VirtualFileSystem ( fs_meta ) ) ;
mFsProcess . setInputFileSystem ( nca_fs ) ;
mFsProcess . setFsFormatName ( " ContentArchive " ) ;
mFsProcess . setFsRootLabel ( getContentTypeForMountStr ( mHdr . getContentType ( ) ) ) ;
mFsProcess . process ( ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-16 06:13:11 +00:00
std : : string nstool : : NcaProcess : : getContentTypeForMountStr ( nn : : hac : : nca : : ContentType cont_type ) const
2019-01-31 09:10:19 +00:00
{
2021-10-16 06:13:11 +00:00
std : : string str ;
2019-01-31 09:10:19 +00:00
switch ( cont_type )
{
2020-03-17 11:11:28 +00:00
case ( nn : : hac : : nca : : ContentType : : Program ) :
2019-01-31 09:10:19 +00:00
str = " program " ;
break ;
2020-03-17 11:11:28 +00:00
case ( nn : : hac : : nca : : ContentType : : Meta ) :
2019-01-31 09:10:19 +00:00
str = " meta " ;
break ;
2020-03-17 11:11:28 +00:00
case ( nn : : hac : : nca : : ContentType : : Control ) :
2019-01-31 09:10:19 +00:00
str = " control " ;
break ;
2020-03-17 11:11:28 +00:00
case ( nn : : hac : : nca : : ContentType : : Manual ) :
2019-01-31 09:10:19 +00:00
str = " manual " ;
break ;
2020-03-17 11:11:28 +00:00
case ( nn : : hac : : nca : : ContentType : : Data ) :
2019-01-31 09:10:19 +00:00
str = " data " ;
break ;
2020-03-17 11:11:28 +00:00
case ( nn : : hac : : nca : : ContentType : : PublicData ) :
2019-01-31 09:10:19 +00:00
str = " publicdata " ;
break ;
default :
str = " " ;
break ;
}
return str ;
2018-08-12 05:23:39 +00:00
}