2020-04-16 01:06:41 +01:00
/*
2020-07-03 10:31:22 +01:00
* nca . c
2020-04-16 01:06:41 +01:00
*
2022-03-17 12:58:40 +00:00
* Copyright ( c ) 2020 - 2022 , DarkMatterCore < pabloacurielz @ gmail . com > .
2020-07-03 10:31:22 +01:00
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
2021-03-25 19:26:58 +00:00
* nxdumptool is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
2020-04-16 01:06:41 +01:00
*
2021-03-25 19:26:58 +00:00
* nxdumptool is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2020-04-16 01:06:41 +01:00
*
* You should have received a copy of the GNU General Public License
2021-03-25 19:26:58 +00:00
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
2020-04-16 01:06:41 +01:00
*/
2021-03-26 04:35:14 +00:00
# include "nxdt_utils.h"
2020-04-15 21:50:07 +01:00
# include "nca.h"
2020-04-11 06:28:26 +01:00
# include "keys.h"
2020-07-06 01:10:07 +01:00
# include "aes.h"
2020-04-11 06:28:26 +01:00
# include "rsa.h"
2020-04-19 23:44:22 +01:00
# include "gamecard.h"
2020-07-26 05:57:12 +01:00
# include "title.h"
2020-04-11 06:28:26 +01:00
2020-07-06 01:10:07 +01:00
# define NCA_CRYPTO_BUFFER_SIZE 0x800000 /* 8 MiB. */
2020-04-21 11:23:33 +01:00
2020-04-15 21:50:07 +01:00
/* Global variables. */
2020-04-11 06:28:26 +01:00
2020-04-21 11:23:33 +01:00
static u8 * g_ncaCryptoBuffer = NULL ;
2020-04-22 21:53:20 +01:00
static Mutex g_ncaCryptoBufferMutex = 0 ;
2020-04-21 11:23:33 +01:00
2021-05-21 14:34:43 +01:00
/// Used to verify if the key area from a NCA0 is encrypted.
2020-04-15 21:50:07 +01:00
static const u8 g_nca0KeyAreaHash [ SHA256_HASH_SIZE ] = {
0x9A , 0xBB , 0xD2 , 0x11 , 0x86 , 0x00 , 0x21 , 0x9D , 0x7A , 0xDC , 0x5B , 0x43 , 0x95 , 0xF8 , 0x4E , 0xFD ,
0xFF , 0x6B , 0x25 , 0xEF , 0x9F , 0x96 , 0x85 , 0x28 , 0x18 , 0x9E , 0x76 , 0xB0 , 0x92 , 0xF0 , 0x6A , 0xCB
} ;
2020-04-11 06:28:26 +01:00
2021-05-21 14:34:43 +01:00
/// Used to verify the NCA header main signature.
static const u8 g_ncaHeaderMainSignaturePublicExponent [ 3 ] = { 0x01 , 0x00 , 0x01 } ;
2020-04-15 21:50:07 +01:00
/* Function prototypes. */
2020-04-11 06:28:26 +01:00
2020-07-22 09:03:28 +01:00
NX_INLINE bool ncaIsFsInfoEntryValid ( NcaFsInfo * fs_info ) ;
2020-10-13 15:00:03 +01:00
static bool ncaReadDecryptedHeader ( NcaContext * ctx ) ;
2020-04-22 21:53:20 +01:00
static bool ncaDecryptKeyArea ( NcaContext * ctx ) ;
2020-07-22 09:03:28 +01:00
static bool ncaEncryptKeyArea ( NcaContext * ctx ) ;
2021-05-21 14:34:43 +01:00
static bool ncaVerifyMainSignature ( NcaContext * ctx ) ;
2020-07-07 16:20:29 +01:00
NX_INLINE bool ncaIsVersion0KeyAreaEncrypted ( NcaContext * ctx ) ;
2020-04-26 09:35:01 +01:00
NX_INLINE u8 ncaGetKeyGenerationValue ( NcaContext * ctx ) ;
NX_INLINE bool ncaCheckRightsIdAvailability ( NcaContext * ctx ) ;
2020-04-21 11:23:33 +01:00
2022-06-29 07:55:35 +01:00
static bool ncaInitializeFsSectionContext ( NcaContext * nca_ctx , u32 section_idx ) ;
static bool ncaFsSectionValidateHashDataBoundaries ( NcaFsSectionContext * ctx ) ;
2021-05-18 13:32:43 +01:00
static bool _ncaReadFsSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset ) ;
2022-06-29 07:55:35 +01:00
static bool ncaFsSectionCheckHashRegionAccess ( NcaFsSectionContext * ctx , u64 offset , u64 size , u64 * out_chunk_size ) ;
2021-05-18 13:32:43 +01:00
static bool _ncaReadAesCtrExStorageFromBktrSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset , u32 ctr_val ) ;
2020-04-30 09:25:03 +01:00
2022-06-25 20:28:31 +01:00
static void ncaCalculateLayerHash ( void * dst , const void * src , size_t size , bool use_sha3 ) ;
2020-07-22 09:03:28 +01:00
static bool ncaGenerateHashDataPatch ( NcaFsSectionContext * ctx , const void * data , u64 data_size , u64 data_offset , void * out , bool is_integrity_patch ) ;
2020-10-21 05:27:48 +01:00
static bool ncaWritePatchToMemoryBuffer ( NcaContext * ctx , const void * patch , u64 patch_size , u64 patch_offset , void * buf , u64 buf_size , u64 buf_offset ) ;
2020-07-22 21:35:23 +01:00
2022-06-29 07:55:35 +01:00
static void * ncaGenerateEncryptedFsSectionBlock ( NcaFsSectionContext * ctx , const void * data , u64 data_size , u64 data_offset , u64 * out_block_size , u64 * out_block_offset ) ;
2020-04-11 06:28:26 +01:00
2020-04-21 11:23:33 +01:00
bool ncaAllocateCryptoBuffer ( void )
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
SCOPED_LOCK ( & g_ncaCryptoBufferMutex )
{
if ( ! g_ncaCryptoBuffer ) g_ncaCryptoBuffer = malloc ( NCA_CRYPTO_BUFFER_SIZE ) ;
ret = ( g_ncaCryptoBuffer ! = NULL ) ;
}
2020-04-22 21:53:20 +01:00
return ret ;
2020-04-21 11:23:33 +01:00
}
void ncaFreeCryptoBuffer ( void )
{
2021-05-18 13:32:43 +01:00
SCOPED_LOCK ( & g_ncaCryptoBufferMutex )
2020-04-21 11:23:33 +01:00
{
2021-05-18 13:32:43 +01:00
if ( ! g_ncaCryptoBuffer ) break ;
2020-04-21 11:23:33 +01:00
free ( g_ncaCryptoBuffer ) ;
g_ncaCryptoBuffer = NULL ;
}
2020-04-15 21:50:07 +01:00
}
2022-06-29 08:55:46 +01:00
bool ncaInitializeContext ( NcaContext * out , u8 storage_id , u8 hfs_partition_type , const NcmContentInfo * content_info , u32 title_version , Ticket * tik )
2020-04-20 11:39:41 +01:00
{
2020-07-26 05:57:12 +01:00
NcmContentStorage * ncm_storage = NULL ;
2021-04-26 06:51:01 +01:00
u8 valid_fs_section_cnt = 0 ;
2020-07-26 05:57:12 +01:00
if ( ! out | | ( storage_id ! = NcmStorageId_GameCard & & ! ( ncm_storage = titleGetNcmStorageByStorageId ( storage_id ) ) ) | | \
2021-06-05 22:06:29 +01:00
( storage_id = = NcmStorageId_GameCard & & ( ! hfs_partition_type | | hfs_partition_type > = GameCardHashFileSystemPartitionType_Count ) ) | | ! content_info | | \
2022-06-29 07:55:35 +01:00
content_info - > content_type > = NcmContentType_DeltaFragment )
2020-04-20 11:39:41 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-04-20 11:39:41 +01:00
return false ;
}
2020-07-06 01:10:07 +01:00
/* Clear output NCA context. */
memset ( out , 0 , sizeof ( NcaContext ) ) ;
/* Fill NCA context. */
2020-04-20 11:39:41 +01:00
out - > storage_id = storage_id ;
out - > ncm_storage = ( out - > storage_id ! = NcmStorageId_GameCard ? ncm_storage : NULL ) ;
2020-05-03 00:40:50 +01:00
memcpy ( & ( out - > content_id ) , & ( content_info - > content_id ) , sizeof ( NcmContentId ) ) ;
2021-05-11 23:36:15 +01:00
utilsGenerateHexStringFromData ( out - > content_id_str , sizeof ( out - > content_id_str ) , out - > content_id . c , sizeof ( out - > content_id . c ) , false ) ;
2020-04-20 11:39:41 +01:00
2021-05-11 23:36:15 +01:00
utilsGenerateHexStringFromData ( out - > hash_str , sizeof ( out - > hash_str ) , out - > hash , sizeof ( out - > hash ) , false ) ; /* Placeholder, needs to be manually calculated. */
2020-10-03 03:37:05 +01:00
2020-05-03 00:40:50 +01:00
out - > content_type = content_info - > content_type ;
out - > id_offset = content_info - > id_offset ;
2022-06-29 08:55:46 +01:00
out - > title_version = title_version ;
2020-04-20 11:39:41 +01:00
2020-07-26 05:57:12 +01:00
titleConvertNcmContentSizeToU64 ( content_info - > size , & ( out - > content_size ) ) ;
2020-04-21 11:23:33 +01:00
if ( out - > content_size < NCA_FULL_HEADER_LENGTH )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid size for NCA \" %s \" ! " , out - > content_id_str ) ;
2020-04-21 11:23:33 +01:00
return false ;
}
2020-04-20 11:39:41 +01:00
if ( out - > storage_id = = NcmStorageId_GameCard )
{
2021-04-26 00:10:34 +01:00
/* Generate gamecard NCA filename. */
2020-04-20 11:39:41 +01:00
char nca_filename [ 0x30 ] = { 0 } ;
2020-04-21 11:23:33 +01:00
sprintf ( nca_filename , " %s.%s " , out - > content_id_str , out - > content_type = = NcmContentType_Meta ? " cnmt.nca " : " nca " ) ;
2020-04-20 11:39:41 +01:00
2021-04-26 00:10:34 +01:00
/* Retrieve gamecard NCA offset. */
2021-03-07 23:22:49 +00:00
if ( ! gamecardGetHashFileSystemEntryInfoByName ( hfs_partition_type , nca_filename , & ( out - > gamecard_offset ) , NULL ) )
2020-04-20 11:39:41 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Error retrieving offset for \" %s \" entry in secure hash FS partition! " , nca_filename ) ;
2020-04-20 11:39:41 +01:00
return false ;
}
}
2020-10-13 15:00:03 +01:00
/* Read decrypted NCA header and NCA FS section headers. */
if ( ! ncaReadDecryptedHeader ( out ) )
2020-04-20 11:39:41 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to read decrypted NCA \" %s \" header! " , out - > content_id_str ) ;
2020-04-20 11:39:41 +01:00
return false ;
}
if ( out - > rights_id_available )
{
2020-10-14 14:23:49 +01:00
Ticket tmp_tik = { 0 } ;
Ticket * usable_tik = ( tik ? tik : & tmp_tik ) ;
2020-07-06 01:10:07 +01:00
/* Retrieve ticket. */
/* This will return true if it has already been retrieved. */
2020-10-14 14:23:49 +01:00
if ( tikRetrieveTicketByRightsId ( usable_tik , & ( out - > header . rights_id ) , out - > storage_id = = NcmStorageId_GameCard ) )
2020-04-20 11:39:41 +01:00
{
2020-07-06 01:10:07 +01:00
/* Copy decrypted titlekey. */
2020-10-14 14:23:49 +01:00
memcpy ( out - > titlekey , usable_tik - > dec_titlekey , 0x10 ) ;
2020-07-06 01:10:07 +01:00
out - > titlekey_retrieved = true ;
} else {
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Error retrieving ticket for NCA \" %s \" ! " , out - > content_id_str ) ;
2020-04-20 11:39:41 +01:00
}
}
2021-04-26 06:51:01 +01:00
/* Parse NCA FS sections. */
2020-04-20 11:39:41 +01:00
for ( u8 i = 0 ; i < NCA_FS_HEADER_COUNT ; i + + )
{
2022-06-29 07:55:35 +01:00
/* Increase valid NCA FS section count if the FS section is valid. */
if ( ncaInitializeFsSectionContext ( out , i ) ) valid_fs_section_cnt + + ;
2020-04-20 11:39:41 +01:00
}
2021-04-26 06:51:01 +01:00
if ( ! valid_fs_section_cnt ) LOG_MSG ( " Unable to identify any valid FS sections in NCA \" %s \" ! " , out - > content_id_str ) ;
return ( valid_fs_section_cnt > 0 ) ;
2020-04-20 11:39:41 +01:00
}
2020-04-22 21:53:20 +01:00
bool ncaReadContentFile ( NcaContext * ctx , void * out , u64 read_size , u64 offset )
2020-04-21 11:23:33 +01:00
{
2020-10-15 01:06:53 +01:00
if ( ! ctx | | ! * ( ctx - > content_id_str ) | | ( ctx - > storage_id ! = NcmStorageId_GameCard & & ! ctx - > ncm_storage ) | | ( ctx - > storage_id = = NcmStorageId_GameCard & & ! ctx - > gamecard_offset ) | | ! out | | \
2020-10-21 05:27:48 +01:00
! read_size | | ( offset + read_size ) > ctx - > content_size )
2020-04-21 11:23:33 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-04-21 11:23:33 +01:00
return false ;
}
Result rc = 0 ;
bool ret = false ;
if ( ctx - > storage_id ! = NcmStorageId_GameCard )
{
2020-07-06 01:10:07 +01:00
/* Retrieve NCA data normally. */
/* This strips NAX0 crypto from SD card NCAs (not used on eMMC NCAs). */
2020-04-21 11:23:33 +01:00
rc = ncmContentStorageReadContentIdFile ( ctx - > ncm_storage , out , read_size , & ( ctx - > content_id ) , offset ) ;
ret = R_SUCCEEDED ( rc ) ;
2021-03-07 23:22:49 +00:00
if ( ! ret ) LOG_MSG ( " Failed to read 0x%lX bytes block at offset 0x%lX from NCA \" %s \" ! (0x%08X) (ncm). " , read_size , offset , ctx - > content_id_str , rc ) ;
2020-04-21 11:23:33 +01:00
} else {
2020-07-06 01:10:07 +01:00
/* Retrieve NCA data using raw gamecard reads. */
/* Fixes NCA read issues with gamecards under HOS < 4.0.0 when using ncmContentStorageReadContentIdFile(). */
2020-04-24 10:38:13 +01:00
ret = gamecardReadStorage ( out , read_size , ctx - > gamecard_offset + offset ) ;
2021-03-07 23:22:49 +00:00
if ( ! ret ) LOG_MSG ( " Failed to read 0x%lX bytes block at offset 0x%lX from NCA \" %s \" ! (gamecard). " , read_size , offset , ctx - > content_id_str ) ;
2020-04-21 11:23:33 +01:00
}
return ret ;
}
bool ncaReadFsSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset )
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
SCOPED_LOCK ( & g_ncaCryptoBufferMutex ) ret = _ncaReadFsSection ( ctx , out , read_size , offset ) ;
return ret ;
2020-04-22 21:53:20 +01:00
}
2020-04-30 09:25:03 +01:00
bool ncaReadAesCtrExStorageFromBktrSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset , u32 ctr_val )
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
SCOPED_LOCK ( & g_ncaCryptoBufferMutex ) ret = _ncaReadAesCtrExStorageFromBktrSection ( ctx , out , read_size , offset , ctr_val ) ;
return ret ;
2020-04-30 09:25:03 +01:00
}
2020-04-28 09:58:17 +01:00
bool ncaGenerateHierarchicalSha256Patch ( NcaFsSectionContext * ctx , const void * data , u64 data_size , u64 data_offset , NcaHierarchicalSha256Patch * out )
2020-04-22 21:53:20 +01:00
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
SCOPED_LOCK ( & g_ncaCryptoBufferMutex ) ret = ncaGenerateHashDataPatch ( ctx , data , data_size , data_offset , out , false ) ;
return ret ;
2020-07-22 09:03:28 +01:00
}
2020-07-22 21:35:23 +01:00
void ncaWriteHierarchicalSha256PatchToMemoryBuffer ( NcaContext * ctx , NcaHierarchicalSha256Patch * patch , void * buf , u64 buf_size , u64 buf_offset )
{
2020-10-21 05:27:48 +01:00
if ( ! ctx | | ! * ( ctx - > content_id_str ) | | ctx - > content_size < NCA_FULL_HEADER_LENGTH | | ! patch | | patch - > written | | memcmp ( patch - > content_id . c , ctx - > content_id . c , 0x10 ) ! = 0 | | \
! patch - > hash_region_count | | patch - > hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT | | ! buf | | ! buf_size | | ( buf_offset + buf_size ) > ctx - > content_size ) return ;
patch - > written = true ;
2020-07-22 21:35:23 +01:00
2020-10-14 01:15:21 +01:00
for ( u32 i = 0 ; i < patch - > hash_region_count ; i + + )
{
NcaHashDataPatch * hash_region_patch = & ( patch - > hash_region_patch [ i ] ) ;
2020-10-21 05:27:48 +01:00
if ( hash_region_patch - > written ) continue ;
hash_region_patch - > written = ncaWritePatchToMemoryBuffer ( ctx , hash_region_patch - > data , hash_region_patch - > size , hash_region_patch - > offset , buf , buf_size , buf_offset ) ;
if ( ! hash_region_patch - > written ) patch - > written = false ;
2020-10-14 01:15:21 +01:00
}
2020-07-22 21:35:23 +01:00
}
2020-07-22 09:03:28 +01:00
bool ncaGenerateHierarchicalIntegrityPatch ( NcaFsSectionContext * ctx , const void * data , u64 data_size , u64 data_offset , NcaHierarchicalIntegrityPatch * out )
{
2021-05-18 13:32:43 +01:00
bool ret = false ;
SCOPED_LOCK ( & g_ncaCryptoBufferMutex ) ret = ncaGenerateHashDataPatch ( ctx , data , data_size , data_offset , out , true ) ;
return ret ;
2020-07-22 09:03:28 +01:00
}
2020-07-22 21:35:23 +01:00
void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer ( NcaContext * ctx , NcaHierarchicalIntegrityPatch * patch , void * buf , u64 buf_size , u64 buf_offset )
{
2022-03-21 02:19:39 +00:00
if ( ! ctx | | ! * ( ctx - > content_id_str ) | | ctx - > content_size < NCA_FULL_HEADER_LENGTH | | ! patch | | patch - > written | | memcmp ( patch - > content_id . c , ctx - > content_id . c , 0x10 ) ! = 0 | | \
! buf | | ! buf_size | | ( buf_offset + buf_size ) > ctx - > content_size ) return ;
2020-10-21 05:27:48 +01:00
patch - > written = true ;
2020-07-22 21:35:23 +01:00
2020-10-14 01:15:21 +01:00
for ( u32 i = 0 ; i < NCA_IVFC_LEVEL_COUNT ; i + + )
2020-10-03 03:37:05 +01:00
{
2020-10-14 01:15:21 +01:00
NcaHashDataPatch * hash_level_patch = & ( patch - > hash_level_patch [ i ] ) ;
2020-10-21 05:27:48 +01:00
if ( hash_level_patch - > written ) continue ;
hash_level_patch - > written = ncaWritePatchToMemoryBuffer ( ctx , hash_level_patch - > data , hash_level_patch - > size , hash_level_patch - > offset , buf , buf_size , buf_offset ) ;
if ( ! hash_level_patch - > written ) patch - > written = false ;
2020-10-03 03:37:05 +01:00
}
2020-07-29 22:02:21 +01:00
}
2020-10-21 05:27:48 +01:00
void ncaSetDownloadDistributionType ( NcaContext * ctx )
{
if ( ! ctx | | ctx - > content_size < NCA_FULL_HEADER_LENGTH | | ! * ( ctx - > content_id_str ) | | ctx - > content_type > NcmContentType_DeltaFragment | | \
ctx - > header . distribution_type = = NcaDistributionType_Download ) return ;
ctx - > header . distribution_type = NcaDistributionType_Download ;
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Set download distribution type to %s NCA \" %s \" . " , titleGetNcmContentTypeName ( ctx - > content_type ) , ctx - > content_id_str ) ;
2020-10-21 05:27:48 +01:00
}
2021-05-11 07:00:33 +01:00
bool ncaRemoveTitleKeyCrypto ( NcaContext * ctx )
2020-07-22 09:03:28 +01:00
{
2020-10-22 05:38:14 +01:00
if ( ! ctx | | ctx - > content_size < NCA_FULL_HEADER_LENGTH | | ! * ( ctx - > content_id_str ) | | ctx - > content_type > NcmContentType_DeltaFragment )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-10-22 05:38:14 +01:00
return false ;
}
2020-10-21 05:27:48 +01:00
2020-10-22 05:38:14 +01:00
/* Don't proceed if we're not dealing with a NCA with a populated rights ID field, or if we couldn't retrieve the titlekey for it. */
if ( ! ctx - > rights_id_available | | ! ctx - > titlekey_retrieved ) return true ;
2020-04-28 09:58:17 +01:00
2021-04-26 00:10:34 +01:00
/* Copy decrypted titlekey to the decrypted NCA key area. This will be reencrypted at a later stage. */
/* AES-128-XTS is not used in FS sections from NCAs with titlekey crypto. */
/* Patch RomFS sections also use the AES-128-CTR key from the decrypted NCA key area, for some reason. */
memcpy ( ctx - > decrypted_key_area . aes_ctr , ctx - > titlekey , AES_128_KEY_SIZE ) ;
2020-10-22 05:38:14 +01:00
/* Encrypt NCA key area. */
if ( ! ncaEncryptKeyArea ( ctx ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Error encrypting %s NCA \" %s \" key area! " , titleGetNcmContentTypeName ( ctx - > content_type ) , ctx - > content_id_str ) ;
2020-10-22 05:38:14 +01:00
return false ;
}
2020-07-22 09:03:28 +01:00
/* Wipe Rights ID. */
memset ( & ( ctx - > header . rights_id ) , 0 , sizeof ( FsRightsId ) ) ;
2020-04-22 21:53:20 +01:00
2020-07-22 09:03:28 +01:00
/* Update context flags. */
ctx - > rights_id_available = false ;
2020-10-22 05:38:14 +01:00
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Removed titlekey crypto from %s NCA \" %s \" . " , titleGetNcmContentTypeName ( ctx - > content_type ) , ctx - > content_id_str ) ;
2020-10-22 05:38:14 +01:00
return true ;
2020-07-22 09:03:28 +01:00
}
2020-07-23 01:37:02 +01:00
bool ncaEncryptHeader ( NcaContext * ctx )
{
2020-10-15 01:06:53 +01:00
if ( ! ctx | | ! * ( ctx - > content_id_str ) | | ctx - > content_size < NCA_FULL_HEADER_LENGTH )
2020-07-23 01:37:02 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid NCA context! " ) ;
2020-07-23 01:37:02 +01:00
return false ;
}
2020-07-23 22:30:01 +01:00
/* Safety check: don't encrypt the header if we don't need to. */
2020-10-10 22:08:17 +01:00
if ( ! ncaIsHeaderDirty ( ctx ) ) return true ;
2020-07-23 22:30:01 +01:00
2020-07-23 01:37:02 +01:00
size_t crypt_res = 0 ;
const u8 * header_key = keysGetNcaHeaderKey ( ) ;
Aes128XtsContext hdr_aes_ctx = { 0 } , nca0_fs_header_ctx = { 0 } ;
2021-05-11 07:00:33 +01:00
if ( ! header_key )
{
LOG_MSG ( " Failed to retrieve NCA header key! " ) ;
return false ;
}
2020-07-23 01:37:02 +01:00
/* Prepare AES-128-XTS contexts. */
aes128XtsContextCreate ( & hdr_aes_ctx , header_key , header_key + AES_128_KEY_SIZE , true ) ;
if ( ctx - > format_version = = NcaVersion_Nca0 ) aes128XtsContextCreate ( & nca0_fs_header_ctx , ctx - > decrypted_key_area . aes_xts_1 , ctx - > decrypted_key_area . aes_xts_2 , true ) ;
/* Encrypt NCA header. */
2020-10-13 15:00:03 +01:00
crypt_res = aes128XtsNintendoCrypt ( & hdr_aes_ctx , & ( ctx - > encrypted_header ) , & ( ctx - > header ) , sizeof ( NcaHeader ) , 0 , NCA_AES_XTS_SECTOR_SIZE , true ) ;
2020-07-23 01:37:02 +01:00
if ( crypt_res ! = sizeof ( NcaHeader ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Error encrypting NCA \" %s \" header! " , ctx - > content_id_str ) ;
2020-07-23 01:37:02 +01:00
return false ;
}
/* Encrypt NCA FS section headers. */
/* Both NCA2 and NCA3 place the NCA FS section headers right after the NCA header. However, NCA0 places them at the start sector from each NCA FS section. */
for ( u8 i = 0 ; i < NCA_FS_HEADER_COUNT ; i + + )
{
2020-10-13 15:00:03 +01:00
NcaFsInfo * fs_info = & ( ctx - > header . fs_info [ i ] ) ;
NcaFsSectionContext * fs_ctx = & ( ctx - > fs_ctx [ i ] ) ;
2020-07-23 01:37:02 +01:00
/* Don't proceed if this NCA FS section isn't populated. */
2021-04-26 06:51:01 +01:00
if ( ! ncaIsFsInfoEntryValid ( fs_info ) ) continue ;
2020-07-23 01:37:02 +01:00
/* The AES-XTS sector number for each NCA FS header varies depending on the NCA format version. */
/* NCA3 uses sector number 0 for the NCA header, then increases it with each new sector (e.g. making the first NCA FS section header use sector number 2, and so on). */
/* NCA2 uses sector number 0 for each NCA FS section header. */
/* NCA0 uses sector number 0 for the NCA header, then uses sector number 0 for the rest of the data and increases it with each new sector. */
Aes128XtsContext * aes_xts_ctx = ( ctx - > format_version ! = NcaVersion_Nca0 ? & hdr_aes_ctx : & nca0_fs_header_ctx ) ;
2020-10-13 15:00:03 +01:00
u64 sector = ( ctx - > format_version = = NcaVersion_Nca3 ? ( 2U + i ) : ( ctx - > format_version = = NcaVersion_Nca2 ? 0 : ( fs_info - > start_sector - 2 ) ) ) ;
2020-07-23 01:37:02 +01:00
2020-10-13 15:00:03 +01:00
crypt_res = aes128XtsNintendoCrypt ( aes_xts_ctx , & ( fs_ctx - > encrypted_header ) , & ( fs_ctx - > header ) , sizeof ( NcaFsHeader ) , sector , NCA_AES_XTS_SECTOR_SIZE , true ) ;
2020-07-23 01:37:02 +01:00
if ( crypt_res ! = sizeof ( NcaFsHeader ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Error encrypting NCA%u \" %s \" FS section header #%u! " , ctx - > format_version , ctx - > content_id_str , i ) ;
2020-07-23 01:37:02 +01:00
return false ;
}
}
return true ;
}
2020-10-14 01:15:21 +01:00
void ncaWriteEncryptedHeaderDataToMemoryBuffer ( NcaContext * ctx , void * buf , u64 buf_size , u64 buf_offset )
{
2020-10-26 00:03:02 +00:00
/* Return right away if we're dealing with invalid parameters. */
2020-10-14 01:15:21 +01:00
/* In order to avoid taking up too much execution time when this function is called (ideally inside a loop), we won't use ncaIsHeaderDirty() here. Let the user take care of it instead. */
2020-10-26 00:03:02 +00:00
if ( ! ctx | | ctx - > header_written | | ctx - > content_size < NCA_FULL_HEADER_LENGTH | | ! buf | | ! buf_size | | ( buf_offset + buf_size ) > ctx - > content_size ) return ;
ctx - > header_written = true ;
2020-10-14 01:15:21 +01:00
/* Attempt to write the NCA header. */
2020-10-21 05:27:48 +01:00
/* Return right away if the NCA header was only partially written. */
2020-10-26 00:03:02 +00:00
if ( buf_offset < sizeof ( NcaHeader ) & & ! ncaWritePatchToMemoryBuffer ( ctx , & ( ctx - > encrypted_header ) , sizeof ( NcaHeader ) , 0 , buf , buf_size , buf_offset ) )
{
ctx - > header_written = false ;
return ;
}
2020-10-14 01:15:21 +01:00
/* Attempt to write NCA FS section headers. */
for ( u8 i = 0 ; i < NCA_FS_HEADER_COUNT ; i + + )
{
NcaFsSectionContext * fs_ctx = & ( ctx - > fs_ctx [ i ] ) ;
2020-10-26 00:03:02 +00:00
if ( ! fs_ctx - > enabled | | fs_ctx - > header_written ) continue ;
2020-10-14 01:15:21 +01:00
u64 fs_header_offset = ( ctx - > format_version ! = NcaVersion_Nca0 ? ( sizeof ( NcaHeader ) + ( i * sizeof ( NcaFsHeader ) ) ) : fs_ctx - > section_offset ) ;
2020-10-26 00:03:02 +00:00
fs_ctx - > header_written = ncaWritePatchToMemoryBuffer ( ctx , & ( fs_ctx - > encrypted_header ) , sizeof ( NcaFsHeader ) , fs_header_offset , buf , buf_size , buf_offset ) ;
if ( ! fs_ctx - > header_written ) ctx - > header_written = false ;
2020-10-14 01:15:21 +01:00
}
}
2020-10-03 03:37:05 +01:00
void ncaUpdateContentIdAndHash ( NcaContext * ctx , u8 hash [ SHA256_HASH_SIZE ] )
{
if ( ! ctx ) return ;
2021-04-26 00:10:34 +01:00
/* Update content ID. */
2020-10-03 03:37:05 +01:00
memcpy ( ctx - > content_id . c , hash , sizeof ( ctx - > content_id . c ) ) ;
2021-05-11 23:36:15 +01:00
utilsGenerateHexStringFromData ( ctx - > content_id_str , sizeof ( ctx - > content_id_str ) , ctx - > content_id . c , sizeof ( ctx - > content_id . c ) , false ) ;
2020-10-03 03:37:05 +01:00
2021-04-26 00:10:34 +01:00
/* Update content hash. */
2020-10-03 03:37:05 +01:00
memcpy ( ctx - > hash , hash , sizeof ( ctx - > hash ) ) ;
2021-05-11 23:36:15 +01:00
utilsGenerateHexStringFromData ( ctx - > hash_str , sizeof ( ctx - > hash_str ) , ctx - > hash , sizeof ( ctx - > hash ) , false ) ;
2020-10-03 03:37:05 +01:00
}
2020-10-14 01:15:21 +01:00
const char * ncaGetFsSectionTypeName ( NcaFsSectionContext * ctx )
{
NcaContext * nca_ctx = NULL ;
const char * str = " Invalid " ;
2022-06-25 07:03:27 +01:00
bool is_exefs = false ;
2022-06-30 17:46:45 +01:00
if ( ! ctx | | ! ( nca_ctx = ( NcaContext * ) ctx - > nca_ctx ) ) return str ;
2020-10-14 01:15:21 +01:00
2022-06-29 07:55:35 +01:00
is_exefs = ( nca_ctx - > content_type = = NcmContentType_Program & & ctx - > section_idx = = 0 ) ;
2022-06-25 07:03:27 +01:00
2020-10-14 01:15:21 +01:00
switch ( ctx - > section_type )
{
case NcaFsSectionType_PartitionFs :
2022-06-30 17:46:45 +01:00
str = ( is_exefs ? " ExeFS " : " Partition FS " ) ;
2020-10-14 01:15:21 +01:00
break ;
case NcaFsSectionType_RomFs :
2022-06-30 17:46:45 +01:00
str = " RomFS " ;
2020-10-14 01:15:21 +01:00
break ;
case NcaFsSectionType_PatchRomFs :
2022-06-30 17:46:45 +01:00
str = " Patch RomFS " ;
2020-10-14 01:15:21 +01:00
break ;
case NcaFsSectionType_Nca0RomFs :
str = " NCA0 RomFS " ;
break ;
default :
break ;
}
return str ;
}
2020-07-22 09:03:28 +01:00
NX_INLINE bool ncaIsFsInfoEntryValid ( NcaFsInfo * fs_info )
{
if ( ! fs_info ) return false ;
NcaFsInfo tmp_fs_info = { 0 } ;
return ( memcmp ( & tmp_fs_info , fs_info , sizeof ( NcaFsInfo ) ) ! = 0 ) ;
}
2020-10-13 15:00:03 +01:00
static bool ncaReadDecryptedHeader ( NcaContext * ctx )
2020-07-22 09:03:28 +01:00
{
2020-10-15 01:06:53 +01:00
if ( ! ctx | | ! * ( ctx - > content_id_str ) | | ctx - > content_size < NCA_FULL_HEADER_LENGTH )
2020-04-21 11:23:33 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid NCA context! " ) ;
2020-07-22 09:03:28 +01:00
return false ;
2020-04-21 11:23:33 +01:00
}
2020-07-22 09:03:28 +01:00
u32 magic = 0 ;
size_t crypt_res = 0 ;
const u8 * header_key = keysGetNcaHeaderKey ( ) ;
Aes128XtsContext hdr_aes_ctx = { 0 } , nca0_fs_header_ctx = { 0 } ;
2020-04-22 21:53:20 +01:00
2021-05-11 07:00:33 +01:00
if ( ! header_key )
{
LOG_MSG ( " Failed to retrieve NCA header key! " ) ;
return false ;
}
2020-10-13 15:00:03 +01:00
/* Read NCA header. */
if ( ! ncaReadContentFile ( ctx , & ( ctx - > encrypted_header ) , sizeof ( NcaHeader ) , 0 ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to read NCA \" %s \" header! " , ctx - > content_id_str ) ;
2020-10-13 15:00:03 +01:00
return false ;
}
2020-07-22 09:03:28 +01:00
/* Prepare NCA header AES-128-XTS context. */
aes128XtsContextCreate ( & hdr_aes_ctx , header_key , header_key + AES_128_KEY_SIZE , false ) ;
2020-04-21 11:23:33 +01:00
2020-07-22 09:03:28 +01:00
/* Decrypt NCA header. */
2020-10-13 15:00:03 +01:00
crypt_res = aes128XtsNintendoCrypt ( & hdr_aes_ctx , & ( ctx - > header ) , & ( ctx - > encrypted_header ) , sizeof ( NcaHeader ) , 0 , NCA_AES_XTS_SECTOR_SIZE , false ) ;
2020-07-22 09:03:28 +01:00
magic = __builtin_bswap32 ( ctx - > header . magic ) ;
2020-04-28 09:58:17 +01:00
2020-07-22 09:03:28 +01:00
if ( crypt_res ! = sizeof ( NcaHeader ) | | ( magic ! = NCA_NCA3_MAGIC & & magic ! = NCA_NCA2_MAGIC & & magic ! = NCA_NCA0_MAGIC ) | | ctx - > header . content_size ! = ctx - > content_size )
2020-04-21 11:23:33 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Error decrypting NCA \" %s \" header! " , ctx - > content_id_str ) ;
2020-07-22 09:03:28 +01:00
return false ;
2020-04-22 21:53:20 +01:00
}
2020-07-22 09:03:28 +01:00
/* Fill additional NCA context info. */
ctx - > format_version = ( magic = = NCA_NCA3_MAGIC ? NcaVersion_Nca3 : ( magic = = NCA_NCA2_MAGIC ? NcaVersion_Nca2 : NcaVersion_Nca0 ) ) ;
ctx - > key_generation = ncaGetKeyGenerationValue ( ctx ) ;
ctx - > rights_id_available = ncaCheckRightsIdAvailability ( ctx ) ;
2020-10-13 15:00:03 +01:00
sha256CalculateHash ( ctx - > header_hash , & ( ctx - > header ) , sizeof ( NcaHeader ) ) ;
2021-05-21 14:34:43 +01:00
ctx - > valid_main_signature = ncaVerifyMainSignature ( ctx ) ;
2020-07-22 09:03:28 +01:00
/* Decrypt NCA key area (if needed). */
if ( ! ctx - > rights_id_available & & ! ncaDecryptKeyArea ( ctx ) )
2020-04-22 21:53:20 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Error decrypting NCA \" %s \" key area! " , ctx - > content_id_str ) ;
2020-07-22 09:03:28 +01:00
return false ;
2020-04-21 11:23:33 +01:00
}
2020-07-22 09:03:28 +01:00
/* Prepare NCA0 FS header AES-128-XTS context (if needed). */
if ( ctx - > format_version = = NcaVersion_Nca0 ) aes128XtsContextCreate ( & nca0_fs_header_ctx , ctx - > decrypted_key_area . aes_xts_1 , ctx - > decrypted_key_area . aes_xts_2 , false ) ;
/* Read decrypted NCA FS section headers. */
/* Both NCA2 and NCA3 place the NCA FS section headers right after the NCA header. However, NCA0 places them at the start sector from each NCA FS section. */
for ( u8 i = 0 ; i < NCA_FS_HEADER_COUNT ; i + + )
2020-04-21 11:23:33 +01:00
{
2020-10-13 15:00:03 +01:00
NcaFsInfo * fs_info = & ( ctx - > header . fs_info [ i ] ) ;
NcaFsSectionContext * fs_ctx = & ( ctx - > fs_ctx [ i ] ) ;
2020-07-22 09:03:28 +01:00
/* Don't proceed if this NCA FS section isn't populated. */
2021-04-26 06:51:01 +01:00
if ( ! ncaIsFsInfoEntryValid ( fs_info ) ) continue ;
2020-07-22 09:03:28 +01:00
/* Read NCA FS section header. */
2020-10-13 15:00:03 +01:00
u64 fs_header_offset = ( ctx - > format_version ! = NcaVersion_Nca0 ? ( sizeof ( NcaHeader ) + ( i * sizeof ( NcaFsHeader ) ) ) : NCA_FS_SECTOR_OFFSET ( fs_info - > start_sector ) ) ;
if ( ! ncaReadContentFile ( ctx , & ( fs_ctx - > encrypted_header ) , sizeof ( NcaFsHeader ) , fs_header_offset ) )
2020-07-22 09:03:28 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to read NCA%u \" %s \" FS section header #%u at offset 0x%lX! " , ctx - > format_version , ctx - > content_id_str , i , fs_header_offset ) ;
2020-07-22 09:03:28 +01:00
return false ;
}
/* The AES-XTS sector number for each NCA FS header varies depending on the NCA format version. */
/* NCA3 uses sector number 0 for the NCA header, then increases it with each new sector (e.g. making the first NCA FS section header use sector number 2, and so on). */
/* NCA2 uses sector number 0 for each NCA FS section header. */
/* NCA0 uses sector number 0 for the NCA header, then uses sector number 0 for the rest of the data and increases it with each new sector. */
Aes128XtsContext * aes_xts_ctx = ( ctx - > format_version ! = NcaVersion_Nca0 ? & hdr_aes_ctx : & nca0_fs_header_ctx ) ;
2020-10-13 15:00:03 +01:00
u64 sector = ( ctx - > format_version = = NcaVersion_Nca3 ? ( 2U + i ) : ( ctx - > format_version = = NcaVersion_Nca2 ? 0 : ( fs_info - > start_sector - 2 ) ) ) ;
2020-07-22 09:03:28 +01:00
2020-10-13 15:00:03 +01:00
crypt_res = aes128XtsNintendoCrypt ( aes_xts_ctx , & ( fs_ctx - > header ) , & ( fs_ctx - > encrypted_header ) , sizeof ( NcaFsHeader ) , sector , NCA_AES_XTS_SECTOR_SIZE , false ) ;
2020-07-22 09:03:28 +01:00
if ( crypt_res ! = sizeof ( NcaFsHeader ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Error decrypting NCA%u \" %s \" FS section header #%u! " , ctx - > format_version , ctx - > content_id_str , i ) ;
2020-07-22 09:03:28 +01:00
return false ;
}
2020-04-21 11:23:33 +01:00
}
2020-07-22 09:03:28 +01:00
return true ;
}
static bool ncaDecryptKeyArea ( NcaContext * ctx )
{
if ( ! ctx )
2020-04-22 21:53:20 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid NCA context! " ) ;
2020-07-22 09:03:28 +01:00
return false ;
2020-04-22 21:53:20 +01:00
}
2020-04-21 11:23:33 +01:00
2021-05-11 07:00:33 +01:00
const u8 null_key [ AES_128_KEY_SIZE ] = { 0 } ;
2022-06-24 18:39:15 +01:00
u8 key_count = NCA_KEY_AREA_USED_KEY_COUNT ;
if ( ctx - > format_version = = NcaVersion_Nca0 ) key_count - - ;
2020-04-21 11:23:33 +01:00
2021-04-26 00:10:34 +01:00
/* Check if we're dealing with a NCA0 with a plaintext key area. */
2020-07-22 09:03:28 +01:00
if ( ncaIsVersion0KeyAreaEncrypted ( ctx ) )
2020-04-22 21:53:20 +01:00
{
2022-06-24 18:39:15 +01:00
memcpy ( & ( ctx - > decrypted_key_area ) , & ( ctx - > header . encrypted_key_area ) , sizeof ( NcaDecryptedKeyArea ) ) ;
2020-07-22 09:03:28 +01:00
return true ;
2020-04-28 09:58:17 +01:00
}
2021-04-26 00:10:34 +01:00
/* Clear decrypted key area. */
2022-06-24 18:39:15 +01:00
memset ( & ( ctx - > decrypted_key_area ) , 0 , sizeof ( NcaDecryptedKeyArea ) ) ;
2020-04-29 10:54:40 +01:00
2021-04-26 00:10:34 +01:00
/* Process key area. */
2020-07-22 09:03:28 +01:00
for ( u8 i = 0 ; i < key_count ; i + + )
2020-04-29 10:54:40 +01:00
{
2022-06-24 18:39:15 +01:00
const u8 * src_key = ctx - > header . encrypted_key_area . keys [ i ] ;
u8 * dst_key = ctx - > decrypted_key_area . keys [ i ] ;
2021-04-26 00:10:34 +01:00
/* Don't proceed if we're dealing with a null key. */
if ( ! memcmp ( src_key , null_key , AES_128_KEY_SIZE ) ) continue ;
/* Decrypt current key area entry. */
2021-05-11 07:00:33 +01:00
if ( ! keysDecryptNcaKeyAreaEntry ( ctx - > header . kaek_index , ctx - > key_generation , dst_key , src_key ) )
2020-04-29 10:54:40 +01:00
{
2021-05-11 07:00:33 +01:00
LOG_MSG ( " Failed to decrypt NCA key area entry #%u! " , i ) ;
2020-07-22 09:03:28 +01:00
return false ;
2020-04-29 10:54:40 +01:00
}
}
2020-07-22 09:03:28 +01:00
return true ;
2020-04-29 10:54:40 +01:00
}
2020-04-28 09:58:17 +01:00
2020-07-22 09:03:28 +01:00
static bool ncaEncryptKeyArea ( NcaContext * ctx )
2020-04-22 21:53:20 +01:00
{
if ( ! ctx )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid NCA context! " ) ;
2020-04-22 21:53:20 +01:00
return false ;
}
2020-04-21 11:23:33 +01:00
2022-06-24 18:39:15 +01:00
u8 key_count = NCA_KEY_AREA_USED_KEY_COUNT ;
if ( ctx - > format_version = = NcaVersion_Nca0 ) key_count - - ;
2021-04-26 00:10:34 +01:00
const u8 * kaek = NULL , null_key [ AES_128_KEY_SIZE ] = { 0 } ;
2020-07-22 09:03:28 +01:00
Aes128Context key_area_ctx = { 0 } ;
2020-04-22 21:53:20 +01:00
2020-07-22 09:03:28 +01:00
/* Check if we're dealing with a NCA0 with a plaintext key area. */
2020-07-07 16:20:29 +01:00
if ( ncaIsVersion0KeyAreaEncrypted ( ctx ) )
2020-04-22 21:53:20 +01:00
{
2022-06-24 18:39:15 +01:00
memcpy ( & ( ctx - > header . encrypted_key_area ) , & ( ctx - > decrypted_key_area ) , sizeof ( NcaDecryptedKeyArea ) ) ;
2020-04-22 21:53:20 +01:00
return true ;
}
2021-04-26 00:10:34 +01:00
/* Get KAEK for these key generation and KAEK index values. */
2021-05-11 07:00:33 +01:00
kaek = keysGetNcaKeyAreaEncryptionKey ( ctx - > header . kaek_index , ctx - > key_generation ) ;
2020-07-22 09:03:28 +01:00
if ( ! kaek )
2020-04-22 21:53:20 +01:00
{
2021-05-11 07:00:33 +01:00
LOG_MSG ( " Unable to retrieve KAEK for KAEK index 0x%02X and key generation 0x%02X! " , ctx - > header . kaek_index , ctx - > key_generation ) ;
2020-04-22 21:53:20 +01:00
return false ;
}
2021-04-26 00:10:34 +01:00
/* Clear encrypted key area. */
2022-06-24 01:22:01 +01:00
memset ( & ( ctx - > header . encrypted_key_area ) , 0 , sizeof ( NcaEncryptedKeyArea ) ) ;
2020-04-22 21:53:20 +01:00
2021-04-26 00:10:34 +01:00
/* Initialize AES-128-ECB encryption context using the retrieved KAEK. */
2020-07-22 09:03:28 +01:00
aes128ContextCreate ( & key_area_ctx , kaek , true ) ;
2021-04-26 00:10:34 +01:00
/* Process key area. */
for ( u8 i = 0 ; i < key_count ; i + + )
{
2022-06-24 18:39:15 +01:00
const u8 * src_key = ctx - > decrypted_key_area . keys [ i ] ;
u8 * dst_key = ctx - > header . encrypted_key_area . keys [ i ] ;
2021-04-26 00:10:34 +01:00
/* Don't proceed if we're dealing with a null key. */
if ( ! memcmp ( src_key , null_key , AES_128_KEY_SIZE ) ) continue ;
/* Encrypt current key area entry. */
aes128EncryptBlock ( & key_area_ctx , dst_key , src_key ) ;
}
2020-04-21 11:23:33 +01:00
return true ;
}
2021-05-21 14:34:43 +01:00
static bool ncaVerifyMainSignature ( NcaContext * ctx )
{
if ( ! ctx )
{
LOG_MSG ( " Invalid NCA context! " ) ;
return false ;
}
/* Retrieve modulus for the NCA main signature. */
const u8 * modulus = keysGetNcaMainSignatureModulus ( ctx - > header . main_signature_key_generation ) ;
if ( ! modulus ) return false ;
/* Verify NCA signature. */
return rsa2048VerifySha256BasedPssSignature ( & ( ctx - > header . magic ) , NCA_SIGNATURE_AREA_SIZE , ctx - > header . main_signature , modulus , g_ncaHeaderMainSignaturePublicExponent , \
sizeof ( g_ncaHeaderMainSignaturePublicExponent ) ) ;
}
2020-07-07 16:20:29 +01:00
NX_INLINE bool ncaIsVersion0KeyAreaEncrypted ( NcaContext * ctx )
2020-04-15 21:50:07 +01:00
{
if ( ! ctx | | ctx - > format_version ! = NcaVersion_Nca0 ) return false ;
2020-04-11 06:28:26 +01:00
2020-04-15 21:50:07 +01:00
u8 nca0_key_area_hash [ SHA256_HASH_SIZE ] = { 0 } ;
2022-06-24 18:39:15 +01:00
sha256CalculateHash ( nca0_key_area_hash , & ( ctx - > header . encrypted_key_area ) , 4 * AES_128_KEY_SIZE ) ;
2021-04-26 00:10:34 +01:00
return ( memcmp ( nca0_key_area_hash , g_nca0KeyAreaHash , SHA256_HASH_SIZE ) ! = 0 ) ;
2020-04-11 06:28:26 +01:00
}
2020-04-26 09:35:01 +01:00
NX_INLINE u8 ncaGetKeyGenerationValue ( NcaContext * ctx )
2020-04-21 11:23:33 +01:00
{
if ( ! ctx ) return 0 ;
return ( ctx - > header . key_generation > ctx - > header . key_generation_old ? ctx - > header . key_generation : ctx - > header . key_generation_old ) ;
}
2020-04-26 09:35:01 +01:00
NX_INLINE bool ncaCheckRightsIdAvailability ( NcaContext * ctx )
2020-04-21 11:23:33 +01:00
{
if ( ! ctx ) return false ;
for ( u8 i = 0 ; i < 0x10 ; i + + )
{
2021-04-26 00:10:34 +01:00
if ( ctx - > header . rights_id . c [ i ] ) return true ;
2020-04-21 11:23:33 +01:00
}
2021-04-26 00:10:34 +01:00
return false ;
2020-04-21 11:23:33 +01:00
}
2022-06-29 07:55:35 +01:00
static bool ncaInitializeFsSectionContext ( NcaContext * nca_ctx , u32 section_idx )
2020-04-22 21:53:20 +01:00
{
2022-06-29 07:55:35 +01:00
if ( ! nca_ctx | | section_idx > = NCA_FS_HEADER_COUNT )
2020-04-22 21:53:20 +01:00
{
2022-06-29 07:55:35 +01:00
LOG_MSG ( " Invalid parameters! " ) ;
2021-05-18 13:32:43 +01:00
return false ;
2020-04-22 21:53:20 +01:00
}
2022-06-29 07:55:35 +01:00
NcaFsInfo * fs_info = & ( nca_ctx - > header . fs_info [ section_idx ] ) ;
NcaFsSectionContext * fs_ctx = & ( nca_ctx - > fs_ctx [ section_idx ] ) ;
2020-04-22 21:53:20 +01:00
2022-06-29 07:55:35 +01:00
u8 fs_header_hash_calc [ SHA256_HASH_SIZE ] = { 0 } ;
u8 * fs_header_hash = nca_ctx - > header . fs_header_hash [ section_idx ] . hash ;
2020-04-22 21:53:20 +01:00
2022-06-29 07:55:35 +01:00
NcaSparseInfo * sparse_info = & ( fs_ctx - > header . sparse_info ) ;
NcaBucketInfo * sparse_bucket = & ( sparse_info - > bucket ) ;
2020-04-22 21:53:20 +01:00
2022-06-29 07:55:35 +01:00
bool success = false ;
2021-05-18 13:32:43 +01:00
2022-06-29 07:55:35 +01:00
/* Fill section context. */
fs_ctx - > nca_ctx = nca_ctx ;
fs_ctx - > section_idx = section_idx ;
fs_ctx - > section_type = NcaFsSectionType_Invalid ; /* Placeholder. */
fs_ctx - > has_sparse_layer = ( sparse_info - > generation ! = 0 ) ;
2022-06-29 13:41:58 +01:00
fs_ctx - > enabled = false ;
2022-06-29 07:55:35 +01:00
/* Don't proceed if this NCA FS section isn't populated. */
if ( ! ncaIsFsInfoEntryValid ( fs_info ) )
2020-04-22 21:53:20 +01:00
{
2022-06-29 13:41:58 +01:00
//LOG_MSG("Invalid FsInfo entry for section #%u in \"%s\". Skipping FS section.", section_idx, nca_ctx->content_id_str);
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
2022-06-29 07:55:35 +01:00
/* Calculate NCA FS section header hash. Don't proceed if there's a checksum mismatch. */
sha256CalculateHash ( fs_header_hash_calc , & ( fs_ctx - > header ) , sizeof ( NcaFsHeader ) ) ;
if ( memcmp ( fs_header_hash_calc , fs_header_hash , SHA256_HASH_SIZE ) ! = 0 )
2022-06-25 07:03:27 +01:00
{
2022-06-29 07:55:35 +01:00
LOG_MSG ( " Checksum mismatch for FS section header #%u in \" %s \" . Skipping FS section. " , section_idx , nca_ctx - > content_id_str ) ;
goto end ;
}
/* Calculate section offset and size. */
fs_ctx - > section_offset = NCA_FS_SECTOR_OFFSET ( fs_info - > start_sector ) ;
fs_ctx - > section_size = ( NCA_FS_SECTOR_OFFSET ( fs_info - > end_sector ) - fs_ctx - > section_offset ) ;
/* Check if we're dealing with an invalid start offset or an empty size. */
if ( fs_ctx - > section_offset < sizeof ( NcaHeader ) | | ! fs_ctx - > section_size )
{
LOG_MSG ( " Invalid offset/size for FS section #%u in \" %s \" (0x%lX, 0x%lX). Skipping FS section. " , section_idx , nca_ctx - > content_id_str , fs_ctx - > section_offset , \
fs_ctx - > section_size ) ;
goto end ;
}
/* Determine FS section hash type. */
fs_ctx - > hash_type = fs_ctx - > header . hash_type ;
if ( fs_ctx - > hash_type = = NcaHashType_Auto | | fs_ctx - > hash_type = = NcaHashType_AutoSha3 )
{
switch ( fs_ctx - > section_idx )
{
case 0 : /* ExeFS Partition FS. */
case 2 : /* Logo Partition FS. */
fs_ctx - > hash_type = ( fs_ctx - > hash_type = = NcaHashType_Auto ? NcaHashType_HierarchicalSha256 : NcaHashType_HierarchicalSha3256 ) ;
break ;
case 1 : /* RomFS. */
fs_ctx - > hash_type = ( fs_ctx - > hash_type = = NcaHashType_Auto ? NcaHashType_HierarchicalIntegrity : NcaHashType_HierarchicalIntegritySha3 ) ;
break ;
default :
break ;
}
}
if ( fs_ctx - > hash_type = = NcaHashType_Auto | | fs_ctx - > hash_type = = NcaHashType_AutoSha3 | | fs_ctx - > hash_type > NcaHashType_HierarchicalIntegritySha3 )
{
LOG_MSG ( " Invalid hash type for FS section #%u in \" %s \" (0x%02X). Skipping FS section. " , section_idx , nca_ctx - > content_id_str , fs_ctx - > hash_type ) ;
goto end ;
}
/* Determine FS section encryption type. */
fs_ctx - > encryption_type = ( nca_ctx - > format_version = = NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx - > header . encryption_type ) ;
if ( fs_ctx - > encryption_type = = NcaEncryptionType_Auto )
{
switch ( fs_ctx - > section_idx )
{
case 0 : /* ExeFS Partition FS. */
case 1 : /* RomFS. */
fs_ctx - > encryption_type = NcaEncryptionType_AesCtr ;
break ;
case 2 : /* Logo Partition FS. */
fs_ctx - > encryption_type = NcaEncryptionType_None ;
break ;
default :
break ;
}
}
if ( fs_ctx - > encryption_type = = NcaEncryptionType_Auto | | fs_ctx - > encryption_type > NcaEncryptionType_AesCtrExSkipLayerHash )
{
LOG_MSG ( " Invalid encryption type for FS section #%u in \" %s \" (0x%02X). Skipping FS section. " , section_idx , nca_ctx - > content_id_str , fs_ctx - > encryption_type ) ;
goto end ;
}
/* Check if we're dealing with a sparse storage. */
if ( fs_ctx - > has_sparse_layer )
{
/* Check if the sparse bucket is valid. */
u64 raw_storage_offset = sparse_info - > physical_offset ;
u64 raw_storage_size = ( sparse_bucket - > offset + sparse_bucket - > size ) ;
if ( __builtin_bswap32 ( sparse_bucket - > header . magic ) ! = NCA_BKTR_MAGIC | | sparse_bucket - > header . version ! = NCA_BKTR_VERSION | | raw_storage_offset < sizeof ( NcaHeader ) | | \
( ( raw_storage_offset + raw_storage_size ) > nca_ctx - > content_size ) )
2022-06-25 07:03:27 +01:00
{
2022-06-29 07:55:35 +01:00
LOG_DATA ( sparse_info , sizeof ( NcaSparseInfo ) , " Invalid SparseInfo data for FS section #%u in \" %s \" (0x%lX). Skipping FS section. SparseInfo dump: " , section_idx , \
nca_ctx - > content_id_str , nca_ctx - > content_size ) ;
2022-06-25 07:03:27 +01:00
goto end ;
2022-06-29 07:55:35 +01:00
}
if ( ! raw_storage_size | | ! sparse_bucket - > header . entry_count )
2022-06-25 08:22:09 +01:00
{
2022-06-29 07:55:35 +01:00
/* Return true but don't set this FS section as enabled, since we can't really use it. */
LOG_MSG ( " Empty SparseInfo data detected for FS section #%u in \" %s \" . Skipping FS section. " , section_idx , nca_ctx - > content_id_str ) ;
success = true ;
goto end ;
}
/* Set sparse table properties. */
fs_ctx - > sparse_table_offset = ( sparse_info - > physical_offset + sparse_bucket - > offset ) ;
fs_ctx - > sparse_table_size = sparse_bucket - > size ;
/* Check if we're within boundaries. */
if ( ( fs_ctx - > sparse_table_offset + fs_ctx - > sparse_table_size ) > nca_ctx - > content_size )
{
LOG_DATA ( sparse_info , sizeof ( NcaSparseInfo ) , " SparseInfo table for FS section #%u in \" %s \" is out of NCA boundaries (0x%lX). Skipping FS section. SparseInfo dump: " , \
section_idx , nca_ctx - > content_id_str , nca_ctx - > content_size ) ;
goto end ;
}
/* Update section size. */
2022-06-29 13:41:58 +01:00
fs_ctx - > section_size = raw_storage_size ;
2022-06-29 07:55:35 +01:00
}
/* Check if we're within boundaries. */
if ( ( fs_ctx - > section_offset + fs_ctx - > section_size ) > nca_ctx - > content_size )
{
LOG_MSG ( " FS section #%u in \" %s \" is out of NCA boundaries. Skipping FS section. " , section_idx , nca_ctx - > content_id_str ) ;
goto end ;
}
/* Determine FS section type. */
/* TODO: should NcaHashType_None be handled here as well? */
switch ( fs_ctx - > header . fs_type )
{
case NcaFsType_PartitionFs :
if ( ( fs_ctx - > hash_type = = NcaHashType_HierarchicalSha256 | | fs_ctx - > hash_type = = NcaHashType_HierarchicalSha3256 ) & & \
( fs_ctx - > encryption_type < NcaEncryptionType_AesCtrEx | | fs_ctx - > encryption_type = = NcaEncryptionType_AesCtrSkipLayerHash ) )
{
/* Partition FS with None, XTS or CTR encryption. */
fs_ctx - > section_type = NcaFsSectionType_PartitionFs ;
}
2022-06-25 08:22:09 +01:00
2022-06-29 07:55:35 +01:00
break ;
case NcaFsType_RomFs :
if ( fs_ctx - > hash_type = = NcaHashType_HierarchicalIntegrity | | fs_ctx - > hash_type = = NcaHashType_HierarchicalIntegritySha3 )
2022-06-25 08:22:09 +01:00
{
2022-06-29 07:55:35 +01:00
if ( ( fs_ctx - > header . patch_info . indirect_bucket . size > 0 | | fs_ctx - > header . patch_info . aes_ctr_ex_bucket . size > 0 ) & & \
( fs_ctx - > encryption_type = = NcaEncryptionType_None | | fs_ctx - > encryption_type = = NcaEncryptionType_AesCtrEx | | \
fs_ctx - > encryption_type = = NcaEncryptionType_AesCtrExSkipLayerHash ) )
{
/* Patch RomFS. */
fs_ctx - > section_type = NcaFsSectionType_PatchRomFs ;
} else
if ( ! fs_ctx - > header . patch_info . indirect_bucket . size & & ! fs_ctx - > header . patch_info . aes_ctr_ex_bucket . size & & \
( ( fs_ctx - > encryption_type > = NcaEncryptionType_None & & fs_ctx - > encryption_type < = NcaEncryptionType_AesCtr ) | | \
fs_ctx - > encryption_type = = NcaEncryptionType_AesCtrSkipLayerHash ) )
2022-06-25 08:22:09 +01:00
{
2022-06-29 07:55:35 +01:00
/* Regular RomFS. */
fs_ctx - > section_type = NcaFsSectionType_RomFs ;
2022-06-25 08:22:09 +01:00
}
2022-06-29 07:55:35 +01:00
} else
if ( nca_ctx - > format_version = = NcaVersion_Nca0 & & fs_ctx - > hash_type = = NcaHashType_HierarchicalSha256 )
{
/* NCA0 RomFS with XTS encryption. */
fs_ctx - > section_type = NcaFsSectionType_Nca0RomFs ;
2022-06-25 08:22:09 +01:00
}
2022-06-29 07:55:35 +01:00
break ;
default :
break ;
}
if ( fs_ctx - > section_type > = NcaFsSectionType_Invalid )
{
LOG_DATA ( & ( fs_ctx - > header ) , sizeof ( NcaFsHeader ) , " Unable to determine section type for FS section #%u in \" %s \" (0x%02X, 0x%02X). Skipping FS section. FS header dump: " , \
section_idx , nca_ctx - > content_id_str , fs_ctx - > hash_type , fs_ctx - > encryption_type ) ;
goto end ;
}
2022-06-29 13:41:58 +01:00
/* Validate HashData boundaries. */
if ( ! ncaFsSectionValidateHashDataBoundaries ( fs_ctx ) ) goto end ;
/* Get hash layer region size (offset must always be 0). */
fs_ctx - > hash_region . offset = 0 ;
if ( ! ncaGetFsSectionHashTargetProperties ( fs_ctx , & ( fs_ctx - > hash_region . size ) , NULL ) )
{
LOG_MSG ( " Invalid hash type for FS section #%u in \" %s \" (0x%02X). Skipping FS section. " , fs_ctx - > section_idx , nca_ctx - > content_id_str , fs_ctx - > hash_type ) ;
goto end ;
}
/* Check if we're within boundaries. */
if ( fs_ctx - > hash_region . size > fs_ctx - > section_size | | ( fs_ctx - > section_offset + fs_ctx - > hash_region . size ) > nca_ctx - > content_size )
{
LOG_MSG ( " Hash layer region for FS section #%u in \" %s \" is out of NCA boundaries. Skipping FS section. " , section_idx , nca_ctx - > content_id_str ) ;
goto end ;
}
2022-06-29 07:55:35 +01:00
/* Check if we should skip hash layer decryption while reading this FS section. */
fs_ctx - > skip_hash_layer_crypto = ( fs_ctx - > encryption_type = = NcaEncryptionType_AesCtrSkipLayerHash | | fs_ctx - > encryption_type = = NcaEncryptionType_AesCtrExSkipLayerHash ) ;
if ( fs_ctx - > skip_hash_layer_crypto & & fs_ctx - > hash_type = = NcaHashType_None )
{
LOG_MSG ( " NcaHashType_None used with SkipLayerHash crypto for FS section #%u in \" %s \" . Skipping FS section. " , section_idx , nca_ctx - > content_id_str ) ;
goto end ;
}
/* Initialize crypto data. */
if ( ( ! nca_ctx - > rights_id_available | | ( nca_ctx - > rights_id_available & & nca_ctx - > titlekey_retrieved ) ) & & fs_ctx - > encryption_type > NcaEncryptionType_None & & \
fs_ctx - > encryption_type < = NcaEncryptionType_AesCtrExSkipLayerHash )
{
/* Initialize the partial AES counter for this section. */
aes128CtrInitializePartialCtr ( fs_ctx - > ctr , fs_ctx - > header . aes_ctr_upper_iv . value , fs_ctx - > section_offset ) ;
if ( fs_ctx - > has_sparse_layer )
2022-06-25 07:03:27 +01:00
{
2022-06-29 07:55:35 +01:00
/* Initialize the partial AES counter for the sparse info bucket table. */
NcaAesCtrUpperIv sparse_upper_iv = { 0 } ;
memcpy ( sparse_upper_iv . value , fs_ctx - > header . aes_ctr_upper_iv . value , sizeof ( sparse_upper_iv . value ) ) ;
sparse_upper_iv . generation = ( ( u32 ) ( sparse_info - > generation ) < < 16 ) ;
aes128CtrInitializePartialCtr ( fs_ctx - > sparse_ctr , sparse_upper_iv . value , fs_ctx - > sparse_table_offset ) ;
}
/* Initialize AES context. */
if ( nca_ctx - > rights_id_available )
{
/* AES-128-CTR is always used for FS crypto in NCAs with a rights ID. */
aes128CtrContextCreate ( & ( fs_ctx - > ctr_ctx ) , nca_ctx - > titlekey , fs_ctx - > ctr ) ;
if ( fs_ctx - > has_sparse_layer ) aes128CtrContextCreate ( & ( fs_ctx - > sparse_ctr_ctx ) , nca_ctx - > titlekey , fs_ctx - > sparse_ctr ) ;
} else {
if ( fs_ctx - > encryption_type = = NcaEncryptionType_AesXts )
2022-06-25 07:03:27 +01:00
{
2022-06-29 07:55:35 +01:00
/* We need to create two different contexts with AES-128-XTS: one for decryption and another one for encryption. */
aes128XtsContextCreate ( & ( fs_ctx - > xts_decrypt_ctx ) , nca_ctx - > decrypted_key_area . aes_xts_1 , nca_ctx - > decrypted_key_area . aes_xts_2 , false ) ;
aes128XtsContextCreate ( & ( fs_ctx - > xts_encrypt_ctx ) , nca_ctx - > decrypted_key_area . aes_xts_1 , nca_ctx - > decrypted_key_area . aes_xts_2 , true ) ;
} else
if ( fs_ctx - > encryption_type > = NcaEncryptionType_AesCtr & & fs_ctx - > encryption_type < = NcaEncryptionType_AesCtrExSkipLayerHash )
{
/* Patch RomFS sections also use the AES-128-CTR key from the decrypted NCA key area, for some reason. */
aes128CtrContextCreate ( & ( fs_ctx - > ctr_ctx ) , nca_ctx - > decrypted_key_area . aes_ctr , fs_ctx - > ctr ) ;
if ( fs_ctx - > has_sparse_layer ) aes128CtrContextCreate ( & ( fs_ctx - > sparse_ctr_ctx ) , nca_ctx - > decrypted_key_area . aes_ctr , fs_ctx - > sparse_ctr ) ;
}
}
}
/* Enable FS context if we got up to this point. */
fs_ctx - > enabled = success = true ;
end :
return success ;
}
static bool ncaFsSectionValidateHashDataBoundaries ( NcaFsSectionContext * ctx )
{
NcaContext * nca_ctx = ( NcaContext * ) ctx - > nca_ctx ;
bool success = false , valid = true ;
u64 accum = 0 ;
switch ( ctx - > hash_type )
{
case NcaHashType_None :
/* Nothing to validate. */
success = true ;
break ;
case NcaHashType_HierarchicalSha256 :
case NcaHashType_HierarchicalSha3256 :
{
NcaHierarchicalSha256Data * hash_data = & ( ctx - > header . hash_data . hierarchical_sha256_data ) ;
if ( ! hash_data - > hash_block_size | | ! hash_data - > hash_region_count | | hash_data - > hash_region_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT )
{
LOG_DATA ( hash_data , sizeof ( NcaHierarchicalSha256Data ) , " Invalid HierarchicalSha256 data for FS section #%u in \" %s \" . Skipping FS section. Hash data dump: " , \
ctx - > section_idx , nca_ctx - > content_id_str ) ;
break ;
}
for ( u32 i = 0 ; i < hash_data - > hash_region_count ; i + + )
{
/* Validate all hash regions boundaries. Skip the last one if a sparse layer is used. */
NcaRegion * hash_region = & ( hash_data - > hash_region [ i ] ) ;
2022-06-29 13:41:58 +01:00
if ( hash_region - > offset < accum | | ! hash_region - > size | | \
2022-06-29 07:55:35 +01:00
( ( i < ( hash_data - > hash_region_count - 1 ) | | ! ctx - > has_sparse_layer ) & & ( hash_region - > offset + hash_region - > size ) > ctx - > section_size ) )
{
LOG_MSG ( " HierarchicalSha256 region #%u for FS section #%u in \" %s \" is out of NCA boundaries. Skipping FS section. " , \
i , ctx - > section_idx , nca_ctx - > content_id_str ) ;
valid = false ;
break ;
}
2022-06-29 13:41:58 +01:00
accum = ( hash_region - > offset + hash_region - > size ) ;
2022-06-29 07:55:35 +01:00
}
success = valid ;
2022-06-25 07:03:27 +01:00
}
2022-06-29 07:55:35 +01:00
break ;
case NcaHashType_HierarchicalIntegrity :
case NcaHashType_HierarchicalIntegritySha3 :
{
NcaIntegrityMetaInfo * hash_data = & ( ctx - > header . hash_data . integrity_meta_info ) ;
if ( __builtin_bswap32 ( hash_data - > magic ) ! = NCA_IVFC_MAGIC | | hash_data - > master_hash_size ! = SHA256_HASH_SIZE | | \
hash_data - > info_level_hash . max_level_count ! = NCA_IVFC_MAX_LEVEL_COUNT )
{
LOG_DATA ( hash_data , sizeof ( NcaIntegrityMetaInfo ) , " Invalid HierarchicalIntegrity data for FS section #%u in \" %s \" . Skipping FS section. Hash data dump: " , \
ctx - > section_idx , nca_ctx - > content_id_str ) ;
break ;
}
for ( u32 i = 0 ; i < NCA_IVFC_LEVEL_COUNT ; i + + )
{
2022-06-29 13:41:58 +01:00
/* Validate all level informations boundaries. Skip the last one if we're dealing with a Patch RomFS, or if a sparse layer is used. */
NcaHierarchicalIntegrityVerificationLevelInformation * lvl_info = & ( hash_data - > info_level_hash . level_information [ i ] ) ;
if ( lvl_info - > offset < accum | | ! lvl_info - > size | | ! lvl_info - > block_order | | ( ( i < ( NCA_IVFC_LEVEL_COUNT - 1 ) | | \
( ! ctx - > has_sparse_layer & & ctx - > section_type ! = NcaFsSectionType_PatchRomFs ) ) & & ( lvl_info - > offset + lvl_info - > size ) > ctx - > section_size ) )
2022-06-29 07:55:35 +01:00
{
LOG_MSG ( " HierarchicalIntegrity level #%u for FS section #%u in \" %s \" is out of NCA boundaries. Skipping FS section. " , \
i , ctx - > section_idx , nca_ctx - > content_id_str ) ;
valid = false ;
break ;
}
2022-06-29 13:41:58 +01:00
accum = ( lvl_info - > offset + lvl_info - > size ) ;
2022-06-29 07:55:35 +01:00
}
success = valid ;
}
2022-06-25 08:22:09 +01:00
2022-06-29 07:55:35 +01:00
break ;
default :
LOG_MSG ( " Invalid hash type for FS section #%u in \" %s \" (0x%02X). Skipping FS section. " , ctx - > section_idx , nca_ctx - > content_id_str , ctx - > hash_type ) ;
break ;
}
return success ;
}
static bool _ncaReadFsSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset )
{
if ( ! g_ncaCryptoBuffer | | ! ctx | | ! ctx - > enabled | | ! ctx - > nca_ctx | | ctx - > section_idx > = NCA_FS_HEADER_COUNT | | ctx - > section_offset < sizeof ( NcaHeader ) | | \
ctx - > section_type > = NcaFsSectionType_Invalid | | ctx - > encryption_type = = NcaEncryptionType_Auto | | ctx - > encryption_type > NcaEncryptionType_AesCtrExSkipLayerHash | | \
! out | | ! read_size | | ( offset + read_size ) > ctx - > section_size )
{
LOG_MSG ( " Invalid NCA FS section header parameters! " ) ;
return false ;
}
size_t crypt_res = 0 ;
u64 sector_num = 0 ;
NcaContext * nca_ctx = ( NcaContext * ) ctx - > nca_ctx ;
u64 content_offset = ( ctx - > section_offset + offset ) ;
2022-06-30 17:46:45 +01:00
u64 sparse_virtual_offset = ( ( ctx - > has_sparse_layer & & ctx - > cur_sparse_virtual_offset ) ? ( ctx - > section_offset + ctx - > cur_sparse_virtual_offset ) : 0 ) ;
u64 iv_offset = ( sparse_virtual_offset ? sparse_virtual_offset : content_offset ) ;
2022-06-29 07:55:35 +01:00
u64 block_start_offset = 0 , block_end_offset = 0 , block_size = 0 ;
u64 data_start_offset = 0 , chunk_size = 0 , out_chunk_size = 0 ;
bool ret = false ;
if ( ! * ( nca_ctx - > content_id_str ) | | ( nca_ctx - > storage_id ! = NcmStorageId_GameCard & & ! nca_ctx - > ncm_storage ) | | \
( nca_ctx - > storage_id = = NcmStorageId_GameCard & & ! nca_ctx - > gamecard_offset ) | | \
( nca_ctx - > format_version ! = NcaVersion_Nca0 & & nca_ctx - > format_version ! = NcaVersion_Nca2 & & nca_ctx - > format_version ! = NcaVersion_Nca3 ) | | \
( content_offset + read_size ) > nca_ctx - > content_size )
{
LOG_MSG ( " Invalid NCA header parameters! " ) ;
goto end ;
}
/* Check if we're supposed to read a hash layer without encryption. */
if ( ncaFsSectionCheckHashRegionAccess ( ctx , offset , read_size , & block_size ) )
{
/* Read plaintext area. Use NCA-relative offset. */
if ( ! ncaReadContentFile ( nca_ctx , out , block_size , content_offset ) )
{
LOG_MSG ( " Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \" %s \" FS section #%u! (plaintext hash region) (#1). " , block_size , content_offset , \
nca_ctx - > content_id_str , ctx - > section_idx ) ;
2022-06-25 08:22:09 +01:00
goto end ;
2022-06-25 07:03:27 +01:00
}
2022-06-29 07:55:35 +01:00
/* Read remaining encrypted data, if needed. Use FS-section-relative offset. */
2022-06-30 17:46:45 +01:00
if ( sparse_virtual_offset ) ctx - > cur_sparse_virtual_offset + = block_size ;
2022-06-29 07:55:35 +01:00
ret = ( read_size ? _ncaReadFsSection ( ctx , ( u8 * ) out + block_size , read_size - block_size , offset + block_size ) : true ) ;
goto end ;
} else
if ( block_size & & block_size < read_size )
{
/* Read encrypted area. Use FS-section-relative offset. */
if ( ! _ncaReadFsSection ( ctx , out , block_size , offset ) ) goto end ;
/* Update parameters. */
read_size - = block_size ;
content_offset + = block_size ;
/* Read remaining plaintext data. Use NCA-relative offset. */
ret = ncaReadContentFile ( nca_ctx , ( u8 * ) out + block_size , read_size , content_offset ) ;
if ( ! ret ) LOG_MSG ( " Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \" %s \" FS section #%u! (plaintext hash region) (#2). " , read_size , content_offset , \
nca_ctx - > content_id_str , ctx - > section_idx ) ;
goto end ;
2022-06-25 07:03:27 +01:00
}
2020-07-06 01:10:07 +01:00
/* Optimization for reads from plaintext FS sections or reads that are aligned to the AES-CTR / AES-XTS sector size. */
2020-04-22 21:53:20 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_None | | \
2020-04-29 13:59:28 +01:00
( ctx - > encryption_type = = NcaEncryptionType_AesXts & & ! ( content_offset % NCA_AES_XTS_SECTOR_SIZE ) & & ! ( read_size % NCA_AES_XTS_SECTOR_SIZE ) ) | | \
2022-06-24 18:39:15 +01:00
( ctx - > encryption_type > = NcaEncryptionType_AesCtr & & ctx - > encryption_type < = NcaEncryptionType_AesCtrExSkipLayerHash & & ! ( content_offset % AES_BLOCK_SIZE ) & & ! ( read_size % AES_BLOCK_SIZE ) ) )
2020-04-22 21:53:20 +01:00
{
2020-07-06 01:10:07 +01:00
/* Read data. */
2020-04-22 21:53:20 +01:00
if ( ! ncaReadContentFile ( nca_ctx , out , read_size , content_offset ) )
{
2022-06-29 07:55:35 +01:00
LOG_MSG ( " Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \" %s \" FS section #%u! (aligned). " , read_size , content_offset , nca_ctx - > content_id_str , ctx - > section_idx ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
2020-07-06 01:10:07 +01:00
/* Return right away if we're dealing with a plaintext FS section. */
2020-04-22 21:53:20 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_None )
{
ret = true ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
2020-07-06 01:10:07 +01:00
/* Decrypt data. */
2020-04-29 13:59:28 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_AesXts )
2020-04-22 21:53:20 +01:00
{
2020-07-22 09:03:28 +01:00
sector_num = ( ( nca_ctx - > format_version ! = NcaVersion_Nca0 ? offset : ( content_offset - sizeof ( NcaHeader ) ) ) / NCA_AES_XTS_SECTOR_SIZE ) ;
2020-04-22 21:53:20 +01:00
crypt_res = aes128XtsNintendoCrypt ( & ( ctx - > xts_decrypt_ctx ) , out , out , read_size , sector_num , NCA_AES_XTS_SECTOR_SIZE , false ) ;
if ( crypt_res ! = read_size )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \" %s \" FS section #%u! (aligned). " , read_size , content_offset , nca_ctx - > content_id_str , \
2022-06-29 07:55:35 +01:00
ctx - > section_idx ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
} else
2022-06-24 18:39:15 +01:00
if ( ctx - > encryption_type > = NcaEncryptionType_AesCtr & & ctx - > encryption_type < = NcaEncryptionType_AesCtrExSkipLayerHash )
2020-04-22 21:53:20 +01:00
{
2022-06-30 17:46:45 +01:00
aes128CtrUpdatePartialCtr ( ctx - > ctr , iv_offset ) ;
2020-04-22 21:53:20 +01:00
aes128CtrContextResetCtr ( & ( ctx - > ctr_ctx ) , ctx - > ctr ) ;
aes128CtrCrypt ( & ( ctx - > ctr_ctx ) , out , out , read_size ) ;
}
ret = true ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
2020-07-06 01:10:07 +01:00
/* Calculate offsets and block sizes. */
2020-04-29 13:59:28 +01:00
block_start_offset = ALIGN_DOWN ( content_offset , ctx - > encryption_type = = NcaEncryptionType_AesXts ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE ) ;
block_end_offset = ALIGN_UP ( content_offset + read_size , ctx - > encryption_type = = NcaEncryptionType_AesXts ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE ) ;
2020-04-22 21:53:20 +01:00
block_size = ( block_end_offset - block_start_offset ) ;
data_start_offset = ( content_offset - block_start_offset ) ;
chunk_size = ( block_size > NCA_CRYPTO_BUFFER_SIZE ? NCA_CRYPTO_BUFFER_SIZE : block_size ) ;
out_chunk_size = ( block_size > NCA_CRYPTO_BUFFER_SIZE ? ( NCA_CRYPTO_BUFFER_SIZE - data_start_offset ) : read_size ) ;
2020-07-06 01:10:07 +01:00
/* Read data. */
2020-04-22 21:53:20 +01:00
if ( ! ncaReadContentFile ( nca_ctx , g_ncaCryptoBuffer , chunk_size , block_start_offset ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \" %s \" FS section #%u! (unaligned). " , chunk_size , block_start_offset , nca_ctx - > content_id_str , \
2022-06-29 07:55:35 +01:00
ctx - > section_idx ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
2020-07-06 01:10:07 +01:00
/* Decrypt data. */
2020-04-29 13:59:28 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_AesXts )
2020-04-22 21:53:20 +01:00
{
2020-07-22 09:03:28 +01:00
sector_num = ( ( nca_ctx - > format_version ! = NcaVersion_Nca0 ? offset : ( content_offset - sizeof ( NcaHeader ) ) ) / NCA_AES_XTS_SECTOR_SIZE ) ;
2020-04-22 21:53:20 +01:00
crypt_res = aes128XtsNintendoCrypt ( & ( ctx - > xts_decrypt_ctx ) , g_ncaCryptoBuffer , g_ncaCryptoBuffer , chunk_size , sector_num , NCA_AES_XTS_SECTOR_SIZE , false ) ;
if ( crypt_res ! = chunk_size )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to AES-XTS decrypt 0x%lX bytes data block at offset 0x%lX from NCA \" %s \" FS section #%u! (unaligned). " , chunk_size , block_start_offset , nca_ctx - > content_id_str , \
2022-06-29 07:55:35 +01:00
ctx - > section_idx ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
} else
2022-06-24 18:39:15 +01:00
if ( ctx - > encryption_type > = NcaEncryptionType_AesCtr & & ctx - > encryption_type < = NcaEncryptionType_AesCtrExSkipLayerHash )
2020-04-22 21:53:20 +01:00
{
2022-06-30 17:46:45 +01:00
aes128CtrUpdatePartialCtr ( ctx - > ctr , ALIGN_DOWN ( iv_offset , AES_BLOCK_SIZE ) ) ;
2020-04-22 21:53:20 +01:00
aes128CtrContextResetCtr ( & ( ctx - > ctr_ctx ) , ctx - > ctr ) ;
aes128CtrCrypt ( & ( ctx - > ctr_ctx ) , g_ncaCryptoBuffer , g_ncaCryptoBuffer , chunk_size ) ;
}
2020-07-06 01:10:07 +01:00
/* Copy decrypted data. */
2020-04-22 21:53:20 +01:00
memcpy ( out , g_ncaCryptoBuffer + data_start_offset , out_chunk_size ) ;
2022-06-30 17:46:45 +01:00
/* Perform another read if required. */
if ( sparse_virtual_offset & & block_size > NCA_CRYPTO_BUFFER_SIZE ) ctx - > cur_sparse_virtual_offset + = out_chunk_size ;
2021-05-18 13:32:43 +01:00
ret = ( block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadFsSection ( ctx , ( u8 * ) out + out_chunk_size , read_size - out_chunk_size , offset + out_chunk_size ) : true ) ;
2020-04-22 21:53:20 +01:00
2020-07-13 07:36:17 +01:00
end :
2022-06-30 17:46:45 +01:00
if ( ctx - > has_sparse_layer ) ctx - > cur_sparse_virtual_offset = 0 ;
2020-04-22 21:53:20 +01:00
return ret ;
}
2020-04-28 09:58:17 +01:00
2022-06-29 07:55:35 +01:00
static bool ncaFsSectionCheckHashRegionAccess ( NcaFsSectionContext * ctx , u64 offset , u64 size , u64 * out_chunk_size )
{
if ( ! ctx - > skip_hash_layer_crypto ) return false ;
NcaRegion * hash_region = & ( ctx - > hash_region ) ;
/* Check if our region contains the access. */
if ( hash_region - > offset < = offset )
{
if ( offset < ( hash_region - > offset + hash_region - > size ) )
{
if ( ( hash_region - > offset + hash_region - > size ) < = ( offset + size ) )
{
* out_chunk_size = ( ( hash_region - > offset + hash_region - > size ) - offset ) ;
} else {
* out_chunk_size = size ;
}
return true ;
} else {
return false ;
}
} else {
if ( hash_region - > offset < = ( offset + size ) ) * out_chunk_size = ( hash_region - > offset - offset ) ;
return false ;
}
}
2021-05-18 13:32:43 +01:00
static bool _ncaReadAesCtrExStorageFromBktrSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset , u32 ctr_val )
2020-04-30 09:25:03 +01:00
{
2022-06-29 07:55:35 +01:00
if ( ! g_ncaCryptoBuffer | | ! ctx | | ! ctx - > enabled | | ! ctx - > nca_ctx | | ctx - > section_idx > = NCA_FS_HEADER_COUNT | | ctx - > section_offset < sizeof ( NcaHeader ) | | \
2022-06-24 18:39:15 +01:00
ctx - > section_type ! = NcaFsSectionType_PatchRomFs | | ( ctx - > encryption_type ! = NcaEncryptionType_AesCtrEx & & ctx - > encryption_type ! = NcaEncryptionType_AesCtrExSkipLayerHash ) | | \
! out | | ! read_size | | ( offset + read_size ) > ctx - > section_size )
2020-04-30 09:25:03 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid NCA FS section header parameters! " ) ;
2021-05-18 13:32:43 +01:00
return false ;
2020-04-30 09:25:03 +01:00
}
NcaContext * nca_ctx = ( NcaContext * ) ctx - > nca_ctx ;
u64 content_offset = ( ctx - > section_offset + offset ) ;
u64 block_start_offset = 0 , block_end_offset = 0 , block_size = 0 ;
u64 data_start_offset = 0 , chunk_size = 0 , out_chunk_size = 0 ;
2021-05-18 13:32:43 +01:00
bool ret = false ;
2020-10-15 01:06:53 +01:00
if ( ! * ( nca_ctx - > content_id_str ) | | ( nca_ctx - > storage_id ! = NcmStorageId_GameCard & & ! nca_ctx - > ncm_storage ) | | ( nca_ctx - > storage_id = = NcmStorageId_GameCard & & ! nca_ctx - > gamecard_offset ) | | \
2020-10-21 05:27:48 +01:00
( content_offset + read_size ) > nca_ctx - > content_size )
2020-04-30 09:25:03 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid NCA header parameters! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-30 09:25:03 +01:00
}
2020-07-06 01:10:07 +01:00
/* Optimization for reads that are aligned to the AES-CTR sector size. */
2020-04-30 09:25:03 +01:00
if ( ! ( content_offset % AES_BLOCK_SIZE ) & & ! ( read_size % AES_BLOCK_SIZE ) )
{
2020-07-06 01:10:07 +01:00
/* Read data. */
2020-04-30 09:25:03 +01:00
if ( ! ncaReadContentFile ( nca_ctx , out , read_size , content_offset ) )
{
2022-06-29 07:55:35 +01:00
LOG_MSG ( " Failed to read 0x%lX bytes data block at offset 0x%lX from NCA \" %s \" FS section #%u! (aligned). " , read_size , content_offset , nca_ctx - > content_id_str , ctx - > section_idx ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-30 09:25:03 +01:00
}
/* Decrypt data */
2020-10-21 05:27:48 +01:00
aes128CtrUpdatePartialCtrEx ( ctx - > ctr , ctr_val , content_offset ) ;
2020-04-30 09:25:03 +01:00
aes128CtrContextResetCtr ( & ( ctx - > ctr_ctx ) , ctx - > ctr ) ;
aes128CtrCrypt ( & ( ctx - > ctr_ctx ) , out , out , read_size ) ;
ret = true ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-30 09:25:03 +01:00
}
2020-07-06 01:10:07 +01:00
/* Calculate offsets and block sizes. */
2020-04-30 09:25:03 +01:00
block_start_offset = ALIGN_DOWN ( content_offset , AES_BLOCK_SIZE ) ;
block_end_offset = ALIGN_UP ( content_offset + read_size , AES_BLOCK_SIZE ) ;
block_size = ( block_end_offset - block_start_offset ) ;
data_start_offset = ( content_offset - block_start_offset ) ;
chunk_size = ( block_size > NCA_CRYPTO_BUFFER_SIZE ? NCA_CRYPTO_BUFFER_SIZE : block_size ) ;
out_chunk_size = ( block_size > NCA_CRYPTO_BUFFER_SIZE ? ( NCA_CRYPTO_BUFFER_SIZE - data_start_offset ) : read_size ) ;
2020-07-06 01:10:07 +01:00
/* Read data. */
2020-04-30 09:25:03 +01:00
if ( ! ncaReadContentFile ( nca_ctx , g_ncaCryptoBuffer , chunk_size , block_start_offset ) )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to read 0x%lX bytes encrypted data block at offset 0x%lX from NCA \" %s \" FS section #%u! (unaligned). " , chunk_size , block_start_offset , nca_ctx - > content_id_str , \
2022-06-29 07:55:35 +01:00
ctx - > section_idx ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-30 09:25:03 +01:00
}
2020-07-06 01:10:07 +01:00
/* Decrypt data. */
2020-10-21 05:27:48 +01:00
aes128CtrUpdatePartialCtrEx ( ctx - > ctr , ctr_val , block_start_offset ) ;
2020-04-30 09:25:03 +01:00
aes128CtrContextResetCtr ( & ( ctx - > ctr_ctx ) , ctx - > ctr ) ;
aes128CtrCrypt ( & ( ctx - > ctr_ctx ) , g_ncaCryptoBuffer , g_ncaCryptoBuffer , chunk_size ) ;
2020-07-06 01:10:07 +01:00
/* Copy decrypted data. */
2020-04-30 09:25:03 +01:00
memcpy ( out , g_ncaCryptoBuffer + data_start_offset , out_chunk_size ) ;
2021-05-18 13:32:43 +01:00
ret = ( block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadAesCtrExStorageFromBktrSection ( ctx , ( u8 * ) out + out_chunk_size , read_size - out_chunk_size , offset + out_chunk_size , ctr_val ) : true ) ;
2020-04-30 09:25:03 +01:00
2020-07-13 07:36:17 +01:00
end :
2020-04-30 09:25:03 +01:00
return ret ;
}
2022-06-25 20:28:31 +01:00
static void ncaCalculateLayerHash ( void * dst , const void * src , size_t size , bool use_sha3 )
{
if ( use_sha3 )
{
sha256CalculateHash ( dst , src , size ) ;
} else {
sha3256CalculateHash ( dst , src , size ) ;
}
}
2020-07-22 09:03:28 +01:00
/* In this function, the term "layer" is used as a generic way to refer to both HierarchicalSha256 hash regions and HierarchicalIntegrity verification levels. */
static bool ncaGenerateHashDataPatch ( NcaFsSectionContext * ctx , const void * data , u64 data_size , u64 data_offset , void * out , bool is_integrity_patch )
{
NcaContext * nca_ctx = NULL ;
NcaHierarchicalSha256Patch * hierarchical_sha256_patch = ( ! is_integrity_patch ? ( ( NcaHierarchicalSha256Patch * ) out ) : NULL ) ;
NcaHierarchicalIntegrityPatch * hierarchical_integrity_patch = ( is_integrity_patch ? ( ( NcaHierarchicalIntegrityPatch * ) out ) : NULL ) ;
u8 * cur_data = NULL ;
u64 cur_data_offset = data_offset ;
u64 cur_data_size = data_size ;
u32 layer_count = 0 ;
u8 * parent_layer_block = NULL , * cur_layer_block = NULL ;
u64 last_layer_size = 0 ;
2022-06-25 20:28:31 +01:00
bool use_sha3 = false , success = false ;
2020-07-22 09:03:28 +01:00
2022-06-25 07:03:27 +01:00
if ( ! ctx | | ! ctx - > enabled | | ctx - > has_sparse_layer | | ! ( nca_ctx = ( NcaContext * ) ctx - > nca_ctx ) | | ( ! is_integrity_patch & & ( ( ctx - > hash_type ! = NcaHashType_HierarchicalSha256 & & \
ctx - > hash_type ! = NcaHashType_HierarchicalSha3256 ) | | ! ctx - > header . hash_data . hierarchical_sha256_data . hash_block_size | | \
! ( layer_count = ctx - > header . hash_data . hierarchical_sha256_data . hash_region_count ) | | layer_count > NCA_HIERARCHICAL_SHA256_MAX_REGION_COUNT | | \
! ( last_layer_size = ctx - > header . hash_data . hierarchical_sha256_data . hash_region [ layer_count - 1 ] . size ) ) ) | | \
( is_integrity_patch & & ( ( ctx - > hash_type ! = NcaHashType_HierarchicalIntegrity & & ctx - > hash_type ! = NcaHashType_HierarchicalIntegritySha3 ) | | \
2020-07-22 09:03:28 +01:00
! ( layer_count = ( ctx - > header . hash_data . integrity_meta_info . info_level_hash . max_level_count - 1 ) ) | | layer_count ! = NCA_IVFC_LEVEL_COUNT | | \
! ( last_layer_size = ctx - > header . hash_data . integrity_meta_info . info_level_hash . level_information [ NCA_IVFC_LEVEL_COUNT - 1 ] . size ) ) ) | | ! data | | ! data_size | | \
2022-06-25 07:03:27 +01:00
( data_offset + data_size ) > last_layer_size | | ! out | | ctx - > encryption_type = = NcaEncryptionType_Auto | | ctx - > encryption_type = = NcaEncryptionType_AesCtrEx | | \
ctx - > encryption_type > = NcaEncryptionType_AesCtrExSkipLayerHash )
2020-07-22 09:03:28 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid parameters! " ) ;
2020-07-22 09:03:28 +01:00
goto end ;
}
/* Clear output patch. */
2020-10-14 14:23:49 +01:00
if ( ! is_integrity_patch )
{
ncaFreeHierarchicalSha256Patch ( hierarchical_sha256_patch ) ;
} else {
ncaFreeHierarchicalIntegrityPatch ( hierarchical_integrity_patch ) ;
}
2020-07-22 09:03:28 +01:00
2022-06-25 20:28:31 +01:00
/* Check if we should use SHA3-256 instead of SHA-256 for layer hash calculation. */
use_sha3 = ( ctx - > hash_type = = NcaHashType_HierarchicalSha3256 | | ctx - > hash_type = = NcaHashType_HierarchicalIntegritySha3 ) ;
2020-07-22 09:03:28 +01:00
/* Process each layer. */
for ( u32 i = layer_count ; i > 0 ; i - - )
{
u64 hash_block_size = 0 ;
u64 cur_layer_offset = 0 , cur_layer_size = 0 ;
u64 cur_layer_read_start_offset = 0 , cur_layer_read_end_offset = 0 , cur_layer_read_size = 0 , cur_layer_read_patch_offset = 0 ;
u64 parent_layer_offset = 0 , parent_layer_size = 0 ;
2020-10-29 07:51:17 +00:00
u64 parent_layer_read_start_offset = 0 , parent_layer_read_size = 0 ;
2020-07-22 09:03:28 +01:00
NcaHashDataPatch * cur_layer_patch = NULL ;
/* Retrieve current layer properties. */
hash_block_size = ( ! is_integrity_patch ? ctx - > header . hash_data . hierarchical_sha256_data . hash_block_size : \
NCA_IVFC_BLOCK_SIZE ( ctx - > header . hash_data . integrity_meta_info . info_level_hash . level_information [ i - 1 ] . block_order ) ) ;
cur_layer_offset = ( ! is_integrity_patch ? ctx - > header . hash_data . hierarchical_sha256_data . hash_region [ i - 1 ] . offset : \
ctx - > header . hash_data . integrity_meta_info . info_level_hash . level_information [ i - 1 ] . offset ) ;
cur_layer_size = ( ! is_integrity_patch ? ctx - > header . hash_data . hierarchical_sha256_data . hash_region [ i - 1 ] . size : \
ctx - > header . hash_data . integrity_meta_info . info_level_hash . level_information [ i - 1 ] . size ) ;
/* Retrieve parent layer properties. */
/* If this is the master layer, then no properties are retrieved, since it is verified by the master hash from the HashData block in the NCA FS section header. */
if ( i > 1 )
{
parent_layer_offset = ( ! is_integrity_patch ? ctx - > header . hash_data . hierarchical_sha256_data . hash_region [ i - 2 ] . offset : \
ctx - > header . hash_data . integrity_meta_info . info_level_hash . level_information [ i - 2 ] . offset ) ;
parent_layer_size = ( ! is_integrity_patch ? ctx - > header . hash_data . hierarchical_sha256_data . hash_region [ i - 2 ] . size : \
ctx - > header . hash_data . integrity_meta_info . info_level_hash . level_information [ i - 2 ] . size ) ;
}
/* Validate layer properties. */
2020-10-21 05:27:48 +01:00
if ( hash_block_size < = 1 | | ! cur_layer_size | | ( cur_layer_offset + cur_layer_size ) > ctx - > section_size | | ( i > 1 & & ( ! parent_layer_size | | \
( parent_layer_offset + parent_layer_size ) > ctx - > section_size ) ) )
2020-07-22 09:03:28 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid hierarchical parent/child layer! " ) ;
2020-07-22 09:03:28 +01:00
goto end ;
}
/* Retrieve pointer to the current layer patch. */
cur_layer_patch = ( ! is_integrity_patch ? & ( hierarchical_sha256_patch - > hash_region_patch [ i - 1 ] ) : & ( hierarchical_integrity_patch - > hash_level_patch [ i - 1 ] ) ) ;
/* Calculate required offsets and sizes. */
if ( i > 1 )
{
/* HierarchicalSha256 hash region with index 1 through 4, or HierarchicalIntegrity verification level with index 1 through 5. */
cur_layer_read_start_offset = ( cur_layer_offset + ALIGN_DOWN ( cur_data_offset , hash_block_size ) ) ;
cur_layer_read_end_offset = ( cur_layer_offset + ALIGN_UP ( cur_data_offset + cur_data_size , hash_block_size ) ) ;
cur_layer_read_size = ( cur_layer_read_end_offset - cur_layer_read_start_offset ) ;
2020-10-29 07:51:17 +00:00
parent_layer_read_start_offset = ( ( cur_data_offset / hash_block_size ) * SHA256_HASH_SIZE ) ;
parent_layer_read_size = ( ( cur_layer_read_size / hash_block_size ) * SHA256_HASH_SIZE ) ;
2020-07-22 09:03:28 +01:00
} else {
/* HierarchicalSha256 master hash region, or HierarchicalIntegrity master verification level. Both with index 0. */
/* The master hash is calculated over the whole layer and saved to the HashData block from the NCA FS section header. */
cur_layer_read_start_offset = cur_layer_offset ;
cur_layer_read_end_offset = ( cur_layer_offset + cur_layer_size ) ;
cur_layer_read_size = cur_layer_size ;
}
cur_layer_read_patch_offset = ( cur_data_offset - ALIGN_DOWN ( cur_data_offset , hash_block_size ) ) ;
/* Allocate memory for our current layer block. */
cur_layer_block = calloc ( cur_layer_read_size , sizeof ( u8 ) ) ;
if ( ! cur_layer_block )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Unable to allocate 0x%lX bytes for hierarchical layer #%u data block! (current). " , cur_layer_read_size , i - 1 ) ;
2020-07-22 09:03:28 +01:00
goto end ;
}
/* Adjust current layer read size to avoid read errors (if needed). */
if ( cur_layer_read_end_offset > ( cur_layer_offset + cur_layer_size ) )
{
cur_layer_read_end_offset = ( cur_layer_offset + cur_layer_size ) ;
cur_layer_read_size = ( cur_layer_read_end_offset - cur_layer_read_start_offset ) ;
}
/* Read current layer block. */
2021-05-18 13:32:43 +01:00
if ( ! _ncaReadFsSection ( ctx , cur_layer_block , cur_layer_read_size , cur_layer_read_start_offset ) )
2020-07-22 09:03:28 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to read 0x%lX bytes long hierarchical layer #%u data block from offset 0x%lX! (current). " , cur_layer_read_size , i - 1 , cur_layer_read_start_offset ) ;
2020-07-22 09:03:28 +01:00
goto end ;
}
/* Replace current layer block data. */
memcpy ( cur_layer_block + cur_layer_read_patch_offset , ( i = = layer_count ? data : cur_data ) , cur_data_size ) ;
/* Recalculate hashes. */
if ( i > 1 )
{
/* Allocate memory for our parent layer block. */
parent_layer_block = calloc ( parent_layer_read_size , sizeof ( u8 ) ) ;
if ( ! parent_layer_block )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Unable to allocate 0x%lX bytes for hierarchical layer #%u data block! (parent). " , parent_layer_read_size , i - 2 ) ;
2020-07-22 09:03:28 +01:00
goto end ;
}
/* Read parent layer block. */
2021-05-18 13:32:43 +01:00
if ( ! _ncaReadFsSection ( ctx , parent_layer_block , parent_layer_read_size , parent_layer_offset + parent_layer_read_start_offset ) )
2020-07-22 09:03:28 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Failed to read 0x%lX bytes long hierarchical layer #%u data block from offset 0x%lX! (parent). " , parent_layer_read_size , i - 2 , parent_layer_read_start_offset ) ;
2020-07-22 09:03:28 +01:00
goto end ;
}
/* HierarchicalSha256: size is truncated for blocks smaller than the hash block size. */
/* HierarchicalIntegrity: size *isn't* truncated for blocks smaller than the hash block size, so we just keep using the same hash block size throughout the loop. */
/* For these specific cases, the rest of the block should be filled with zeroes (already taken care of by using calloc()). */
for ( u64 j = 0 , k = 0 ; j < cur_layer_read_size ; j + = hash_block_size , k + + )
{
if ( ! is_integrity_patch & & hash_block_size > ( cur_layer_read_size - j ) ) hash_block_size = ( cur_layer_read_size - j ) ;
2022-06-25 20:28:31 +01:00
ncaCalculateLayerHash ( parent_layer_block + ( k * SHA256_HASH_SIZE ) , cur_layer_block + j , hash_block_size , use_sha3 ) ;
2020-07-22 09:03:28 +01:00
}
} else {
/* Recalculate master hash from the HashData area. */
u8 * master_hash = ( ! is_integrity_patch ? ctx - > header . hash_data . hierarchical_sha256_data . master_hash : ctx - > header . hash_data . integrity_meta_info . master_hash ) ;
2022-06-25 20:28:31 +01:00
ncaCalculateLayerHash ( master_hash , cur_layer_block , cur_layer_read_size , use_sha3 ) ;
2020-07-22 09:03:28 +01:00
}
2022-06-25 07:03:27 +01:00
if ( ! ctx - > skip_hash_layer_crypto | | i = = layer_count )
2020-07-22 09:03:28 +01:00
{
2022-06-25 07:03:27 +01:00
/* Reencrypt current layer block (if needed). */
2022-06-29 07:55:35 +01:00
cur_layer_patch - > data = ncaGenerateEncryptedFsSectionBlock ( ctx , cur_layer_block + cur_layer_read_patch_offset , cur_data_size , cur_layer_offset + cur_data_offset , \
2022-06-25 07:03:27 +01:00
& ( cur_layer_patch - > size ) , & ( cur_layer_patch - > offset ) ) ;
if ( ! cur_layer_patch - > data )
{
LOG_MSG ( " Failed to generate encrypted 0x%lX bytes long hierarchical layer #%u data block! " , cur_data_size , i - 1 ) ;
goto end ;
}
} else {
/* Allocate memory for the data block and copy its information. */
cur_layer_patch - > data = malloc ( cur_data_size ) ;
if ( ! cur_layer_patch - > data )
{
LOG_MSG ( " Failed to allocate 0x%lX bytes long buffer for hierarchical layer #%u data block! " , cur_data_size , i - 1 ) ;
goto end ;
}
memcpy ( cur_layer_patch - > data , cur_layer_block + cur_layer_read_patch_offset , cur_data_size ) ;
cur_layer_patch - > size = cur_data_size ;
cur_layer_patch - > offset = ( ctx - > section_offset + cur_layer_offset + cur_data_offset ) ;
2020-07-22 09:03:28 +01:00
}
/* Free current layer block. */
free ( cur_layer_block ) ;
cur_layer_block = NULL ;
if ( i > 1 )
{
/* Free previous layer block (if needed). */
if ( cur_data ) free ( cur_data ) ;
/* Prepare data for the next layer. */
cur_data = parent_layer_block ;
cur_data_offset = parent_layer_read_start_offset ;
cur_data_size = parent_layer_read_size ;
parent_layer_block = NULL ;
}
}
/* Recalculate FS header hash. */
2022-06-29 07:55:35 +01:00
sha256CalculateHash ( nca_ctx - > header . fs_header_hash [ ctx - > section_idx ] . hash , & ( ctx - > header ) , sizeof ( NcaFsHeader ) ) ;
2020-07-22 09:03:28 +01:00
2020-07-22 21:35:23 +01:00
/* Copy content ID. */
memcpy ( ! is_integrity_patch ? & ( hierarchical_sha256_patch - > content_id ) : & ( hierarchical_integrity_patch - > content_id ) , & ( nca_ctx - > content_id ) , sizeof ( NcmContentId ) ) ;
2020-07-22 09:03:28 +01:00
/* Set hash region count (if needed). */
if ( ! is_integrity_patch ) hierarchical_sha256_patch - > hash_region_count = layer_count ;
success = true ;
end :
if ( cur_layer_block ) free ( cur_layer_block ) ;
if ( parent_layer_block ) free ( parent_layer_block ) ;
if ( ! success & & out )
{
if ( ! is_integrity_patch )
{
ncaFreeHierarchicalSha256Patch ( hierarchical_sha256_patch ) ;
} else {
ncaFreeHierarchicalIntegrityPatch ( hierarchical_integrity_patch ) ;
}
}
return success ;
}
2020-10-21 05:27:48 +01:00
static bool ncaWritePatchToMemoryBuffer ( NcaContext * ctx , const void * patch , u64 patch_size , u64 patch_offset , void * buf , u64 buf_size , u64 buf_offset )
2020-07-22 21:35:23 +01:00
{
/* Return right away if we're dealing with invalid parameters, or if the buffer data is not part of the range covered by the patch (last two conditions). */
2020-10-21 05:27:48 +01:00
if ( ! ctx | | ! patch | | ! patch_size | | ( patch_offset + patch_size ) > ctx - > content_size | | ( buf_offset + buf_size ) < = patch_offset | | \
( patch_offset + patch_size ) < = buf_offset ) return false ;
2020-07-22 21:35:23 +01:00
/* Overwrite buffer data using patch data. */
2020-10-14 01:15:21 +01:00
u64 patch_block_offset = ( patch_offset < buf_offset ? ( buf_offset - patch_offset ) : 0 ) ;
u64 patch_remaining_size = ( patch_size - patch_block_offset ) ;
u64 buf_block_offset = ( buf_offset < patch_offset ? ( patch_offset - buf_offset ) : 0 ) ;
u64 buf_remaining_size = ( buf_size - buf_block_offset ) ;
2020-07-22 21:35:23 +01:00
2020-10-14 01:15:21 +01:00
u64 buf_block_size = ( buf_remaining_size < patch_remaining_size ? buf_remaining_size : patch_remaining_size ) ;
2020-07-22 21:35:23 +01:00
2020-10-14 01:15:21 +01:00
memcpy ( ( u8 * ) buf + buf_block_offset , ( const u8 * ) patch + patch_block_offset , buf_block_size ) ;
2020-07-22 21:35:23 +01:00
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Overwrote 0x%lX bytes block at offset 0x%lX from raw %s NCA \" %s \" buffer (size 0x%lX, NCA offset 0x%lX). " , buf_block_size , buf_block_offset , titleGetNcmContentTypeName ( ctx - > content_type ) , \
2020-10-22 05:38:14 +01:00
ctx - > content_id_str , buf_size , buf_offset ) ;
2020-10-21 05:27:48 +01:00
return ( ( patch_block_offset + buf_block_size ) = = patch_size ) ;
2020-07-22 21:35:23 +01:00
}
2022-06-29 07:55:35 +01:00
/// Returns a pointer to a dynamically allocated buffer used to encrypt the input plaintext data, based on the encryption type used by the input NCA FS section, as well as its offset and size.
/// Input offset must be relative to the start of the NCA FS section.
/// Output size and offset are guaranteed to be aligned to the AES sector size used by the encryption type from the FS section.
/// Output offset is relative to the start of the NCA content file, making it easier to use the output encrypted block to seamlessly replace data while dumping a NCA.
/// This function doesn't support Patch RomFS sections, nor sections with Sparse and/or Compressed storage.
static void * ncaGenerateEncryptedFsSectionBlock ( NcaFsSectionContext * ctx , const void * data , u64 data_size , u64 data_offset , u64 * out_block_size , u64 * out_block_offset )
2020-04-28 09:58:17 +01:00
{
u8 * out = NULL ;
bool success = false ;
2022-06-29 07:55:35 +01:00
if ( ! g_ncaCryptoBuffer | | ! ctx | | ! ctx - > enabled | | ctx - > has_sparse_layer | | ! ctx - > nca_ctx | | ctx - > section_idx > = NCA_FS_HEADER_COUNT | | ctx - > section_offset < sizeof ( NcaHeader ) | | \
2022-06-25 07:03:27 +01:00
ctx - > hash_type < = NcaHashType_None | | ctx - > hash_type = = NcaHashType_AutoSha3 | | ctx - > hash_type > NcaHashType_HierarchicalIntegritySha3 | | \
ctx - > encryption_type = = NcaEncryptionType_Auto | | ctx - > encryption_type = = NcaEncryptionType_AesCtrEx | | ctx - > encryption_type > = NcaEncryptionType_AesCtrExSkipLayerHash | | \
ctx - > section_type > = NcaFsSectionType_Invalid | | ! data | | ! data_size | | ( data_offset + data_size ) > ctx - > section_size | | ! out_block_size | | ! out_block_offset )
2020-04-28 09:58:17 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid NCA FS section header parameters! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
size_t crypt_res = 0 ;
u64 sector_num = 0 ;
NcaContext * nca_ctx = ( NcaContext * ) ctx - > nca_ctx ;
u64 content_offset = ( ctx - > section_offset + data_offset ) ;
u64 block_start_offset = 0 , block_end_offset = 0 , block_size = 0 ;
u64 plain_chunk_offset = 0 ;
2020-10-15 01:06:53 +01:00
if ( ! * ( nca_ctx - > content_id_str ) | | ( nca_ctx - > storage_id ! = NcmStorageId_GameCard & & ! nca_ctx - > ncm_storage ) | | ( nca_ctx - > storage_id = = NcmStorageId_GameCard & & ! nca_ctx - > gamecard_offset ) | | \
2020-10-21 05:27:48 +01:00
( nca_ctx - > format_version ! = NcaVersion_Nca0 & & nca_ctx - > format_version ! = NcaVersion_Nca2 & & nca_ctx - > format_version ! = NcaVersion_Nca3 ) | | ( content_offset + data_size ) > nca_ctx - > content_size )
2020-04-28 09:58:17 +01:00
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Invalid NCA header parameters! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
2020-07-06 01:10:07 +01:00
/* Optimization for blocks from plaintext FS sections or blocks that are aligned to the AES-CTR / AES-XTS sector size. */
2020-04-28 09:58:17 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_None | | \
2020-04-29 13:59:28 +01:00
( ctx - > encryption_type = = NcaEncryptionType_AesXts & & ! ( content_offset % NCA_AES_XTS_SECTOR_SIZE ) & & ! ( data_size % NCA_AES_XTS_SECTOR_SIZE ) ) | | \
2022-06-25 07:03:27 +01:00
( ( ctx - > encryption_type = = NcaEncryptionType_AesCtr | | ctx - > encryption_type = = NcaEncryptionType_AesCtrSkipLayerHash ) & & ! ( content_offset % AES_BLOCK_SIZE ) & & ! ( data_size % AES_BLOCK_SIZE ) ) )
2020-04-28 09:58:17 +01:00
{
2020-07-06 01:10:07 +01:00
/* Allocate memory. */
2020-04-28 09:58:17 +01:00
out = malloc ( data_size ) ;
if ( ! out )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Unable to allocate 0x%lX bytes buffer! (aligned). " , data_size ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
2020-07-06 01:10:07 +01:00
/* Copy data. */
2020-04-28 09:58:17 +01:00
memcpy ( out , data , data_size ) ;
2020-07-06 01:10:07 +01:00
/* Encrypt data. */
2020-04-29 13:59:28 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_AesXts )
2020-04-28 09:58:17 +01:00
{
2020-07-22 09:03:28 +01:00
sector_num = ( ( nca_ctx - > format_version ! = NcaVersion_Nca0 ? data_offset : ( content_offset - sizeof ( NcaHeader ) ) ) / NCA_AES_XTS_SECTOR_SIZE ) ;
2020-04-28 09:58:17 +01:00
crypt_res = aes128XtsNintendoCrypt ( & ( ctx - > xts_encrypt_ctx ) , out , out , data_size , sector_num , NCA_AES_XTS_SECTOR_SIZE , true ) ;
if ( crypt_res ! = data_size )
{
2022-06-29 07:55:35 +01:00
LOG_MSG ( " Failed to AES-XTS encrypt 0x%lX bytes data block at offset 0x%lX from NCA \" %s \" FS section #%u! (aligned). " , data_size , content_offset , nca_ctx - > content_id_str , ctx - > section_idx ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
} else
2022-06-25 07:03:27 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_AesCtr | | ctx - > encryption_type = = NcaEncryptionType_AesCtrSkipLayerHash )
2020-04-28 09:58:17 +01:00
{
2020-10-21 05:27:48 +01:00
aes128CtrUpdatePartialCtr ( ctx - > ctr , content_offset ) ;
2020-04-28 09:58:17 +01:00
aes128CtrContextResetCtr ( & ( ctx - > ctr_ctx ) , ctx - > ctr ) ;
aes128CtrCrypt ( & ( ctx - > ctr_ctx ) , out , out , data_size ) ;
}
* out_block_size = data_size ;
* out_block_offset = content_offset ;
success = true ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
2020-07-06 01:10:07 +01:00
/* Calculate block offsets and size. */
2020-04-29 13:59:28 +01:00
block_start_offset = ALIGN_DOWN ( data_offset , ctx - > encryption_type = = NcaEncryptionType_AesXts ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE ) ;
block_end_offset = ALIGN_UP ( data_offset + data_size , ctx - > encryption_type = = NcaEncryptionType_AesXts ? NCA_AES_XTS_SECTOR_SIZE : AES_BLOCK_SIZE ) ;
2020-04-28 09:58:17 +01:00
block_size = ( block_end_offset - block_start_offset ) ;
plain_chunk_offset = ( data_offset - block_start_offset ) ;
content_offset = ( ctx - > section_offset + block_start_offset ) ;
2020-07-06 01:10:07 +01:00
/* Allocate memory. */
2020-04-28 09:58:17 +01:00
out = malloc ( block_size ) ;
if ( ! out )
{
2021-03-07 23:22:49 +00:00
LOG_MSG ( " Unable to allocate 0x%lX bytes buffer! (unaligned). " , block_size ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
2020-07-06 01:10:07 +01:00
/* Read decrypted data using aligned offset and size. */
2021-05-18 13:32:43 +01:00
if ( ! _ncaReadFsSection ( ctx , out , block_size , block_start_offset ) )
2020-04-28 09:58:17 +01:00
{
2022-06-29 07:55:35 +01:00
LOG_MSG ( " Failed to read decrypted NCA \" %s \" FS section #%u data block! " , nca_ctx - > content_id_str , ctx - > section_idx ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
2020-07-06 01:10:07 +01:00
/* Replace plaintext data. */
2020-04-28 09:58:17 +01:00
memcpy ( out + plain_chunk_offset , data , data_size ) ;
2020-07-06 01:10:07 +01:00
/* Reencrypt data. */
2020-04-29 13:59:28 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_AesXts )
2020-04-28 09:58:17 +01:00
{
2020-07-22 09:03:28 +01:00
sector_num = ( ( nca_ctx - > format_version ! = NcaVersion_Nca0 ? block_start_offset : ( content_offset - sizeof ( NcaHeader ) ) ) / NCA_AES_XTS_SECTOR_SIZE ) ;
2020-04-28 09:58:17 +01:00
crypt_res = aes128XtsNintendoCrypt ( & ( ctx - > xts_encrypt_ctx ) , out , out , block_size , sector_num , NCA_AES_XTS_SECTOR_SIZE , true ) ;
if ( crypt_res ! = block_size )
{
2022-06-29 07:55:35 +01:00
LOG_MSG ( " Failed to AES-XTS encrypt 0x%lX bytes data block at offset 0x%lX from NCA \" %s \" FS section #%u! (aligned). " , block_size , content_offset , nca_ctx - > content_id_str , ctx - > section_idx ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
} else
2022-06-25 07:03:27 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_AesCtr | | ctx - > encryption_type = = NcaEncryptionType_AesCtrSkipLayerHash )
2020-04-28 09:58:17 +01:00
{
2020-10-21 05:27:48 +01:00
aes128CtrUpdatePartialCtr ( ctx - > ctr , content_offset ) ;
2020-04-28 09:58:17 +01:00
aes128CtrContextResetCtr ( & ( ctx - > ctr_ctx ) , ctx - > ctr ) ;
aes128CtrCrypt ( & ( ctx - > ctr_ctx ) , out , out , block_size ) ;
}
* out_block_size = block_size ;
* out_block_offset = content_offset ;
success = true ;
2020-07-13 07:36:17 +01:00
end :
2020-04-28 09:58:17 +01:00
if ( ! success & & out )
{
free ( out ) ;
out = NULL ;
}
return out ;
}