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
2022-03-27 00:17:53 +00:00
# include <tc/io/StreamUtil.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>
2022-03-12 05:30:43 +00:00
# include <nn/hac/PartitionFsSnapshotGenerator.h>
# include <nn/hac/RomFsSnapshotGenerator.h>
# include <nn/hac/CombinedFsSnapshotGenerator.h>
2021-10-16 06:13:11 +00:00
2021-11-14 09:58:37 +00:00
# include <nn/hac/BucketTree.h>
# include <nn/hac/define/indirectstorage.h>
# include <nn/hac/SparseStorageStream.h>
2022-03-27 00:17:53 +00:00
namespace nstool { namespace utils {
class ZerosStream : public tc : : io : : IStream
{
public :
ZerosStream ( ) : mPosition ( 0 ) { }
bool canRead ( ) const { return true ; }
bool canWrite ( ) const { return false ; }
bool canSeek ( ) const { return true ; }
int64_t length ( ) { return std : : numeric_limits < int64_t > : : max ( ) ; }
int64_t position ( ) { return mPosition ; }
size_t read ( byte_t * ptr , size_t count ) ;
size_t write ( const byte_t * ptr , size_t count ) ;
int64_t seek ( int64_t offset , tc : : io : : SeekOrigin origin ) ;
void setLength ( int64_t length ) { return ; }
void flush ( ) { return ; }
void dispose ( ) { return ; }
private :
int64_t mPosition ;
} ;
size_t ZerosStream : : read ( byte_t * ptr , size_t count )
{
// track read_count
size_t data_read_count = 0 ;
// get predicted read count
data_read_count = tc : : io : : IOUtil : : getReadableCount ( this - > length ( ) , this - > position ( ) , count ) ;
// if count is 0 just return
if ( data_read_count = = 0 ) return data_read_count ;
// read zeros
memset ( ptr , 0x00 , data_read_count ) ;
// update stream positiion
this - > seek ( data_read_count , tc : : io : : SeekOrigin : : Current ) ;
// return data read count
return data_read_count ;
}
size_t ZerosStream : : write ( const byte_t * ptr , size_t count )
{
throw tc : : NotImplementedException ( " ZerosStream::write() " , " write is not supported for ZerosStream " ) ;
}
int64_t ZerosStream : : seek ( int64_t offset , tc : : io : : SeekOrigin origin )
{
mPosition = tc : : io : : StreamUtil : : getSeekResult ( offset , origin , this - > position ( ) , this - > length ( ) ) ;
return mPosition ;
}
} }
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 ;
}
2022-02-01 05:16:17 +00:00
void nstool : : NcaProcess : : setArchiveJobs ( const std : : vector < nstool : : ArchiveJob > & jobs )
{
mFsProcess . setArchiveJobs ( jobs ) ;
}
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
}
2022-03-12 05:30:43 +00:00
const std : : shared_ptr < tc : : io : : IFileSystem > & 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 " ) ;
2021-11-05 07:31:34 +00:00
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
}
// 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 ;
2022-03-27 00:17:53 +00:00
info . metadata_hash_type = ( nn : : hac : : nca : : MetaDataHashType ) fs_header . meta_data_hash_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
}
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 ( ) ) ;
2019-01-31 09:10:19 +00:00
}
// create reader
try
{
2021-11-05 05:57:35 +00:00
// handle partition encryption and partition compaction (sparse layer)
if ( fs_header . sparse_info . generation . unwrap ( ) ! = 0 )
2019-01-31 09:10:19 +00:00
{
2021-11-14 09:58:37 +00:00
if ( fs_header . sparse_info . bucket . header . st_magic . unwrap ( ) ! = nn : : hac : : bktr : : kBktrStructMagic )
{
throw tc : : Exception ( mModuleName , fmt : : format ( " SparseInfo BucketTree header had invalid magic ID. " ) ) ;
}
if ( fs_header . sparse_info . bucket . header . version . unwrap ( ) ! = nn : : hac : : bktr : : kBktrVersion )
{
throw tc : : Exception ( mModuleName , fmt : : format ( " SparseInfo BucketTree header had unsupported format version. " ) ) ;
}
if ( fs_header . sparse_info . bucket . header . entry_count . unwrap ( ) = = 0 )
{
2022-03-27 00:17:53 +00:00
throw tc : : Exception ( mModuleName , fmt : : format ( " CompactedStream is not present in file. " ) ) ;
2021-11-14 09:58:37 +00:00
}
else
{
int64_t phys_base_offset = fs_header . sparse_info . physical_offset . unwrap ( ) ;
int64_t data_stream_offset = 0 ;
int64_t data_stream_size = fs_header . sparse_info . bucket . offset . unwrap ( ) ; // end of image is where BKTR data begins (ASSUMPTION)
int64_t bktr_stream_offset = fs_header . sparse_info . bucket . offset . unwrap ( ) ;
int64_t bktr_stream_size = fs_header . sparse_info . bucket . size . unwrap ( ) ;
/*
fmt : : print ( " SparseLayer overview: \n " ) ;
fmt : : print ( " phys_base_offset = 0x{:016x} \n " , phys_base_offset ) ;
fmt : : print ( " data_stream_offset = 0x{:016x} \n " , data_stream_offset ) ;
fmt : : print ( " data_stream_size = 0x{:016x} \n " , data_stream_size ) ;
fmt : : print ( " bktr_stream_offset = 0x{:016x} \n " , bktr_stream_offset ) ;
fmt : : print ( " bktr_stream_size = 0x{:016x} \n " , bktr_stream_size ) ;
*/
std : : shared_ptr < tc : : io : : IStream > data_stream = std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( mFile , phys_base_offset + data_stream_offset , data_stream_size ) ) ;
2022-03-27 00:17:53 +00:00
std : : shared_ptr < tc : : io : : IStream > zeros_stream = std : : make_shared < utils : : ZerosStream > ( utils : : ZerosStream ( ) ) ;
2021-11-14 09:58:37 +00:00
std : : shared_ptr < tc : : io : : IStream > bktr_stream = std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( mFile , phys_base_offset + bktr_stream_offset , bktr_stream_size ) ) ;
2022-03-27 00:17:53 +00:00
// determine encryption cfg
nn : : hac : : detail : : aes128_key_t partition_key ;
nn : : hac : : detail : : aes_iv_t data_region_ctr ;
if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtr )
2021-11-14 09:58:37 +00:00
{
if ( mContentKey . aes_ctr . isNull ( ) )
throw tc : : Exception ( mModuleName , " AES-CTR Key was not determined " ) ;
// get partition key
2022-03-27 00:17:53 +00:00
partition_key = mContentKey . aes_ctr . get ( ) ;
2021-11-14 09:58:37 +00:00
}
else if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesXts | | info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtrEx )
{
throw tc : : Exception ( mModuleName , fmt : : format ( " EncryptionType({:s}): UNSUPPORTED " , nn : : hac : : ContentArchiveUtil : : getEncryptionTypeAsString ( info . enc_type ) ) ) ;
}
2022-03-27 00:17:53 +00:00
else if ( info . enc_type ! = nn : : hac : : nca : : EncryptionType : : None )
2021-11-14 09:58:37 +00:00
{
throw tc : : Exception ( mModuleName , fmt : : format ( " EncryptionType({:s}): UNKNOWN " , nn : : hac : : ContentArchiveUtil : : getEncryptionTypeAsString ( info . enc_type ) ) ) ;
}
2022-03-27 00:17:53 +00:00
// decrypt bucket tree if required
if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtr )
{
// get partition counter
nn : : hac : : detail : : aes_iv_t bktr_stream_ctr ;
nn : : hac : : ContentArchiveUtil : : getNcaPartitionAesCtr ( uint32_t ( fs_header . sparse_info . generation . unwrap ( ) ) < < 16 , fs_header . secure_value . unwrap ( ) , bktr_stream_ctr . data ( ) ) ;
tc : : crypto : : IncrementCounterAes128Ctr ( bktr_stream_ctr . data ( ) , ( phys_base_offset + bktr_stream_offset ) > > 4 ) ;
2021-11-14 09:58:37 +00:00
2022-03-27 00:17:53 +00:00
// create decryption stream
bktr_stream = std : : make_shared < tc : : crypto : : Aes128CtrEncryptedStream > ( tc : : crypto : : Aes128CtrEncryptedStream ( bktr_stream , partition_key , bktr_stream_ctr ) ) ;
}
// process bucket tree
nn : : hac : : BucketTree bucket_tree ;
2021-11-14 09:58:37 +00:00
bucket_tree . parse ( bktr_stream , nn : : hac : : indirectstorage : : kNodeSize , sizeof ( nn : : hac : : sIndirectStorageEntry ) , fs_header . sparse_info . bucket . header . entry_count . unwrap ( ) ) ;
2022-03-27 00:17:53 +00:00
// construct un-sparsified stream
std : : vector < std : : shared_ptr < tc : : io : : IStream > > unsparse_stream_list ;
enum FindState {
FindState_Nothing ,
FindState_Data ,
FindState_Zeros
} ;
enum StorageIndex {
StorageIndex_Data = 0 ,
StorageIndex_Zeros = 1 ,
} ;
FindState find_state = FindState_Nothing ;
int64_t region_phys_offset = 0 ;
int64_t region_phys_size = 0 ;
int64_t region_virt_offset = 0 ;
for ( auto itr = bucket_tree . begin ( ) ; itr ! = bucket_tree . end ( ) ; itr + + )
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
nn : : hac : : sIndirectStorageEntry * entry = ( nn : : hac : : sIndirectStorageEntry * ) itr - > data ( ) ;
// check this entry is valid
if ( entry - > storage_index . unwrap ( ) > 1 | | entry - > virt_offset . unwrap ( ) > = info . size )
{
throw tc : : Exception ( " BucketTree IndirectStorageEntry data was invalid. " ) ;
}
2021-11-14 09:58:37 +00:00
2022-03-27 00:17:53 +00:00
if ( entry - > virt_offset . unwrap ( ) & & entry - > storage_index . unwrap ( ) = = StorageIndex_Zeros )
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
throw tc : : Exception ( " CompactedStream is incomplete. " ) ;
}
2021-11-14 09:58:37 +00:00
2022-03-27 00:17:53 +00:00
/*
fmt : : print ( " IndirectStorageEntry \n " ) ;
fmt : : print ( " virt_offset = 0x{:016x} \n " , entry - > virt_offset . unwrap ( ) ) ;
fmt : : print ( " phys_offset = 0x{:016x} \n " , entry - > phys_offset . unwrap ( ) ) ;
fmt : : print ( " storage_index = {:s} \n " , entry - > storage_index . unwrap ( ) = = StorageIndex_Data ? " DataStorage " : " ZeroStorage " ) ;
*/
// process find state if ready...
if ( find_state = = FindState_Data )
{
// data region followed by another data region (shouldn't happen)
if ( entry - > storage_index . unwrap ( ) = = StorageIndex_Data )
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
throw tc : : Exception ( " SparseStorage: A DataRegion was followed by a second DataRegion. " ) ;
2021-11-14 09:58:37 +00:00
}
2022-03-27 00:17:53 +00:00
// data region followed by zeros region
else if ( entry - > storage_index . unwrap ( ) = = StorageIndex_Zeros )
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
region_phys_size = entry - > virt_offset . unwrap ( ) - region_virt_offset ;
if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtr )
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
//fmt::print("Add (enc) data slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list . push_back ( std : : make_shared < tc : : crypto : : Aes128CtrEncryptedStream > ( tc : : crypto : : Aes128CtrEncryptedStream ( std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( data_stream , region_phys_offset , region_phys_size ) ) , partition_key , data_region_ctr ) ) ) ;
2021-11-14 09:58:37 +00:00
}
2022-03-27 00:17:53 +00:00
else
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
//fmt::print("Add (pln) data slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list . push_back ( std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( data_stream , region_phys_offset , region_phys_size ) ) ) ;
}
// reset find state
find_state = FindState_Nothing ;
2021-11-14 09:58:37 +00:00
}
}
2022-03-27 00:17:53 +00:00
else if ( find_state = = FindState_Zeros )
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
// zeros region followed by another zeros region (shouldn't happen)
if ( entry - > storage_index . unwrap ( ) = = StorageIndex_Zeros )
{
throw tc : : Exception ( " SparseStorage: A ZerosRegion was followed by a second ZerosRegion. " ) ;
}
// zeros region followed by data region
else if ( entry - > storage_index . unwrap ( ) = = StorageIndex_Data )
{
region_phys_size = entry - > virt_offset . unwrap ( ) - region_virt_offset ;
2021-11-14 09:58:37 +00:00
2022-03-27 00:17:53 +00:00
//fmt::print("Add zeros slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list . push_back ( std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( zeros_stream , region_phys_offset , region_phys_size ) ) ) ;
// reset find state
find_state = FindState_Nothing ;
}
2021-11-14 09:58:37 +00:00
}
2022-03-27 00:17:53 +00:00
// populate find state if empty
if ( find_state = = FindState_Nothing )
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
// found begin of data region
find_state = entry - > storage_index . unwrap ( ) = = StorageIndex_Data ? FindState_Data : FindState_Zeros ;
region_phys_offset = entry - > phys_offset . unwrap ( ) ;
region_virt_offset = entry - > virt_offset . unwrap ( ) ;
if ( find_state = = FindState_Data & & info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtr )
{
data_region_ctr = info . aes_ctr ;
tc : : crypto : : IncrementCounterAes128Ctr ( data_region_ctr . data ( ) , ( phys_base_offset + region_virt_offset ) > > 4 ) ;
}
2021-11-14 09:58:37 +00:00
}
2022-03-27 00:17:53 +00:00
2021-11-14 09:58:37 +00:00
}
2022-03-27 00:17:53 +00:00
// follow up on trailing data/zeros state
if ( find_state = = FindState_Data )
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
region_phys_size = info . size - region_virt_offset ;
if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtr )
{
//fmt::print("Add (enc) data slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list . push_back ( std : : make_shared < tc : : crypto : : Aes128CtrEncryptedStream > ( tc : : crypto : : Aes128CtrEncryptedStream ( std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( data_stream , region_phys_offset , region_phys_size ) ) , partition_key , data_region_ctr ) ) ) ;
}
else
{
//fmt::print("Add (pln) data slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list . push_back ( std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( data_stream , region_phys_offset , region_phys_size ) ) ) ;
}
find_state = FindState_Nothing ;
2021-11-14 09:58:37 +00:00
}
2022-03-27 00:17:53 +00:00
else if ( find_state = = FindState_Zeros )
2021-11-14 09:58:37 +00:00
{
2022-03-27 00:17:53 +00:00
region_phys_size = info . size - region_virt_offset ;
//fmt::print("Add zeros slice virt_offset=0x{:x}, phys_offset=0x{:x}, size=0x{:x}\n", region_virt_offset, region_phys_offset, region_phys_size);
unsparse_stream_list . push_back ( std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( zeros_stream , region_phys_offset , region_phys_size ) ) ) ;
find_state = FindState_Nothing ;
2021-11-14 09:58:37 +00:00
}
2022-03-27 00:17:53 +00:00
2021-11-14 09:58:37 +00:00
// create logical partition
2022-03-27 00:17:53 +00:00
info . reader = std : : make_shared < tc : : io : : ConcatenatedStream > ( tc : : io : : ConcatenatedStream ( unsparse_stream_list ) ) ;
2021-11-14 09:58:37 +00:00
}
2019-01-31 09:10:19 +00:00
}
else
{
2021-11-05 05:57:35 +00:00
// create raw partition
info . reader = std : : make_shared < tc : : io : : SubStream > ( tc : : io : : SubStream ( mFile , info . offset , info . size ) ) ;
// handle encryption if required reader based on encryption type
if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : None )
{
// no encryption so do nothing
//info.reader = info.reader;
}
else if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtr )
{
if ( mContentKey . aes_ctr . isNull ( ) )
throw tc : : Exception ( mModuleName , " AES-CTR Key was not determined " ) ;
// get partition key
nn : : hac : : detail : : aes128_key_t partition_key = mContentKey . aes_ctr . get ( ) ;
// get partition counter
nn : : hac : : detail : : aes_iv_t partition_ctr = info . aes_ctr ;
2022-03-27 00:17:53 +00:00
tc : : crypto : : IncrementCounterAes128Ctr ( partition_ctr . data ( ) , info . offset > > 4 ) ;
2021-11-05 05:57:35 +00:00
// create decryption stream
info . reader = std : : make_shared < tc : : crypto : : Aes128CtrEncryptedStream > ( tc : : crypto : : Aes128CtrEncryptedStream ( info . reader , partition_key , partition_ctr ) ) ;
}
else if ( info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesXts | | info . enc_type = = nn : : hac : : nca : : EncryptionType : : AesCtrEx )
{
throw tc : : Exception ( mModuleName , fmt : : format ( " EncryptionType({:s}): UNSUPPORTED " , nn : : hac : : ContentArchiveUtil : : getEncryptionTypeAsString ( info . enc_type ) ) ) ;
}
else
{
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 ) :
2022-03-12 05:30:43 +00:00
info . fs_snapshot = nn : : hac : : PartitionFsSnapshotGenerator ( info . reader ) ;
info . fs_reader = std : : make_shared < tc : : io : : VirtualFileSystem > ( tc : : io : : VirtualFileSystem ( info . fs_snapshot ) ) ;
2021-10-15 09:29:29 +00:00
break ;
case ( nn : : hac : : nca : : FormatType : : RomFs ) :
2022-03-12 05:30:43 +00:00
info . fs_snapshot = nn : : hac : : RomFsSnapshotGenerator ( info . reader ) ;
info . fs_reader = std : : make_shared < tc : : io : : VirtualFileSystem > ( tc : : io : : VirtualFileSystem ( info . fs_snapshot ) ) ;
2021-10-15 09:29:29 +00:00
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-11-05 07:31:34 +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 , " " ) ) ;
2021-10-15 09:29:29 +00:00
2019-01-31 09:10:19 +00:00
if ( mContentKey . kak_list [ i ] . decrypted )
2021-11-05 07:31:34 +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 ( ) ) ;
2022-03-27 00:17:53 +00:00
tc : : crypto : : IncrementCounterAes128Ctr ( aes_ctr . data ( ) , info . offset > > 4 ) ;
2021-10-15 09:29:29 +00:00
fmt : : print ( " AesCtr Counter: \n " ) ;
2021-11-05 07:31:34 +00:00
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-11-05 07:31:34 +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-11-05 07:31:34 +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 , " " ) ) ;
2021-10-16 06:13:11 +00:00
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
{
2022-03-12 05:30:43 +00:00
std : : vector < nn : : hac : : CombinedFsSnapshotGenerator : : MountPointInfo > mount_points ;
2021-10-16 06:13:11 +00:00
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 ) ;
}
2022-03-12 05:30:43 +00:00
mount_points . push_back ( { mount_point_name , partition . fs_snapshot } ) ;
2019-01-31 09:10:19 +00:00
}
2021-10-16 06:13:11 +00:00
2022-03-12 05:30:43 +00:00
tc : : io : : VirtualFileSystem : : FileSystemSnapshot fs_snapshot = nn : : hac : : CombinedFsSnapshotGenerator ( mount_points ) ;
2021-10-16 06:13:11 +00:00
2022-03-12 05:30:43 +00:00
std : : shared_ptr < tc : : io : : IFileSystem > nca_fs = std : : make_shared < tc : : io : : VirtualFileSystem > ( tc : : io : : VirtualFileSystem ( fs_snapshot ) ) ;
2021-10-16 06:13:11 +00:00
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
}