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
*
2020-12-23 17:48:57 +00:00
* Copyright ( c ) 2020 - 2021 , DarkMatterCore < pabloacurielz @ gmail . com > .
2020-07-03 10:31:22 +01:00
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software ; you can redistribute it and / or modify it
2020-04-16 01:06:41 +01:00
* under the terms and conditions of the GNU General Public License ,
* version 2 , as published by the Free Software Foundation .
*
2020-07-03 10:31:22 +01:00
* nxdumptool is distributed in the hope it will be useful , but WITHOUT
2020-04-16 01:06:41 +01:00
* ANY WARRANTY ; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE . See the GNU General Public License for
* more details .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < http : //www.gnu.org/licenses/>.
*/
2020-07-03 10:31:22 +01:00
# include "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
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
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 ) ;
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
2020-04-22 21:53:20 +01:00
static bool _ncaReadFsSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset , bool lock ) ;
2020-04-30 09:25:03 +01:00
static bool _ncaReadAesCtrExStorageFromBktrSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset , u32 ctr_val , bool lock ) ;
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
2020-04-28 09:58:17 +01:00
static void * _ncaGenerateEncryptedFsSectionBlock ( NcaFsSectionContext * ctx , const void * data , u64 data_size , u64 data_offset , u64 * out_block_size , u64 * out_block_offset , bool lock ) ;
2020-04-11 06:28:26 +01:00
2020-04-21 11:23:33 +01:00
bool ncaAllocateCryptoBuffer ( void )
{
2020-04-22 21:53:20 +01:00
mutexLock ( & g_ncaCryptoBufferMutex ) ;
if ( ! g_ncaCryptoBuffer ) g_ncaCryptoBuffer = malloc ( NCA_CRYPTO_BUFFER_SIZE ) ;
bool ret = ( g_ncaCryptoBuffer ! = NULL ) ;
mutexUnlock ( & g_ncaCryptoBufferMutex ) ;
return ret ;
2020-04-21 11:23:33 +01:00
}
void ncaFreeCryptoBuffer ( void )
{
2020-04-22 21:53:20 +01:00
mutexLock ( & g_ncaCryptoBufferMutex ) ;
2020-04-21 11:23:33 +01:00
if ( g_ncaCryptoBuffer )
{
free ( g_ncaCryptoBuffer ) ;
g_ncaCryptoBuffer = NULL ;
}
2020-04-22 21:53:20 +01:00
mutexUnlock ( & g_ncaCryptoBufferMutex ) ;
2020-04-15 21:50:07 +01:00
}
2020-07-26 05:57:12 +01:00
bool ncaInitializeContext ( NcaContext * out , u8 storage_id , u8 hfs_partition_type , const NcmContentInfo * content_info , Ticket * tik )
2020-04-20 11:39:41 +01:00
{
2020-07-26 05:57:12 +01:00
NcmContentStorage * ncm_storage = NULL ;
if ( ! out | | ( storage_id ! = NcmStorageId_GameCard & & ! ( ncm_storage = titleGetNcmStorageByStorageId ( storage_id ) ) ) | | \
2020-10-14 14:23:49 +01:00
( storage_id = = NcmStorageId_GameCard & & hfs_partition_type > = GameCardHashFileSystemPartitionType_Count ) | | ! content_info | | content_info - > content_type > NcmContentType_DeltaFragment )
2020-04-20 11:39:41 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
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 ) ) ;
2020-04-21 11:23:33 +01:00
utilsGenerateHexStringFromData ( out - > content_id_str , sizeof ( out - > content_id_str ) , out - > content_id . c , sizeof ( out - > content_id . c ) ) ;
2020-04-20 11:39:41 +01:00
2020-10-03 03:37:05 +01:00
utilsGenerateHexStringFromData ( out - > hash_str , sizeof ( out - > hash_str ) , out - > hash , sizeof ( out - > hash ) ) ; /* Placeholder, needs to be manually calculated. */
2020-05-03 00:40:50 +01:00
out - > content_type = content_info - > content_type ;
out - > id_offset = content_info - > id_offset ;
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 )
{
LOGFILE ( " Invalid size for NCA \" %s \" ! " , out - > content_id_str ) ;
return false ;
}
2020-04-20 11:39:41 +01:00
if ( out - > storage_id = = NcmStorageId_GameCard )
{
2020-07-06 01:10:07 +01:00
/* Retrieve gamecard NCA offset. */
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
2020-04-24 10:38:13 +01:00
if ( ! gamecardGetEntryInfoFromHashFileSystemPartitionByName ( hfs_partition_type , nca_filename , & ( out - > gamecard_offset ) , NULL ) )
2020-04-20 11:39:41 +01:00
{
LOGFILE ( " Error retrieving offset for \" %s \" entry in secure hash FS partition! " , nca_filename ) ;
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
{
2020-10-13 15:00:03 +01:00
LOGFILE ( " 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 {
2020-04-21 11:23:33 +01:00
LOGFILE ( " Error retrieving ticket for NCA \" %s \" ! " , out - > content_id_str ) ;
2020-04-20 11:39:41 +01:00
}
}
2020-07-12 16:29:08 +01:00
/* Parse sections. */
2020-04-20 11:39:41 +01:00
for ( u8 i = 0 ; i < NCA_FS_HEADER_COUNT ; i + + )
{
2020-10-13 15:00:03 +01:00
NcaFsInfo * fs_info = & ( out - > header . fs_info [ i ] ) ;
NcaFsSectionContext * fs_ctx = & ( out - > fs_ctx [ i ] ) ;
2020-07-06 01:10:07 +01:00
/* Fill section context. */
2020-10-13 15:00:03 +01:00
fs_ctx - > nca_ctx = out ;
fs_ctx - > section_num = i ;
fs_ctx - > section_type = NcaFsSectionType_Invalid ; /* Placeholder. */
2020-07-29 22:02:21 +01:00
/* Don't proceed if this NCA FS section isn't populated. */
2020-10-13 15:00:03 +01:00
if ( ! ncaIsFsInfoEntryValid ( fs_info ) ) continue ;
2020-07-29 22:02:21 +01:00
/* Calculate section offset and size. */
2020-10-13 15:00:03 +01:00
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 ) ;
2020-07-29 22:02:21 +01:00
/* Check if we're dealing with an invalid offset/size. */
2020-10-13 15:00:03 +01:00
if ( fs_ctx - > section_offset < sizeof ( NcaHeader ) | | ! fs_ctx - > section_size | | \
( fs_ctx - > section_offset + fs_ctx - > section_size ) > out - > content_size ) continue ;
2020-04-21 11:23:33 +01:00
2020-07-06 01:10:07 +01:00
/* Determine encryption type. */
2020-10-13 15:00:03 +01:00
fs_ctx - > encryption_type = ( out - > format_version = = NcaVersion_Nca0 ? NcaEncryptionType_AesXts : fs_ctx - > header . encryption_type ) ;
if ( fs_ctx - > encryption_type = = NcaEncryptionType_Auto )
2020-04-21 11:23:33 +01:00
{
2020-10-13 15:00:03 +01:00
switch ( fs_ctx - > section_num )
2020-04-21 11:23:33 +01:00
{
2020-07-06 01:10:07 +01:00
case 0 : /* ExeFS Partition FS. */
case 1 : /* RomFS. */
2020-10-13 15:00:03 +01:00
fs_ctx - > encryption_type = NcaEncryptionType_AesCtr ;
2020-04-21 11:23:33 +01:00
break ;
2020-07-06 01:10:07 +01:00
case 2 : /* Logo Partition FS. */
2020-10-13 15:00:03 +01:00
fs_ctx - > encryption_type = NcaEncryptionType_None ;
2020-04-21 11:23:33 +01:00
break ;
default :
break ;
}
}
2020-07-06 01:10:07 +01:00
/* Check if we're dealing with an invalid encryption type value. */
2020-10-13 15:00:03 +01:00
if ( fs_ctx - > encryption_type = = NcaEncryptionType_Auto | | fs_ctx - > encryption_type > NcaEncryptionType_AesCtrEx ) continue ;
2020-04-20 11:39:41 +01:00
2020-07-06 01:10:07 +01:00
/* Determine FS section type. */
2020-10-13 15:00:03 +01:00
if ( fs_ctx - > header . fs_type = = NcaFsType_PartitionFs & & fs_ctx - > header . hash_type = = NcaHashType_HierarchicalSha256 )
2020-04-20 11:39:41 +01:00
{
2020-10-13 15:00:03 +01:00
fs_ctx - > section_type = NcaFsSectionType_PartitionFs ;
2020-04-20 11:39:41 +01:00
} else
2020-10-13 15:00:03 +01:00
if ( fs_ctx - > header . fs_type = = NcaFsType_RomFs & & fs_ctx - > header . hash_type = = NcaHashType_HierarchicalIntegrity )
2020-04-20 11:39:41 +01:00
{
2020-10-13 15:00:03 +01:00
fs_ctx - > section_type = ( fs_ctx - > encryption_type = = NcaEncryptionType_AesCtrEx ? NcaFsSectionType_PatchRomFs : NcaFsSectionType_RomFs ) ;
2020-04-20 11:39:41 +01:00
} else
2020-10-13 15:00:03 +01:00
if ( fs_ctx - > header . fs_type = = NcaFsType_RomFs & & fs_ctx - > header . hash_type = = NcaHashType_HierarchicalSha256 & & out - > format_version = = NcaVersion_Nca0 )
2020-04-20 11:39:41 +01:00
{
2020-10-13 15:00:03 +01:00
fs_ctx - > section_type = NcaFsSectionType_Nca0RomFs ;
2020-04-20 11:39:41 +01:00
}
2020-07-06 01:10:07 +01:00
/* Check if we're dealing with an invalid section type value. */
2020-10-13 15:00:03 +01:00
if ( fs_ctx - > section_type > = NcaFsSectionType_Invalid ) continue ;
2020-04-20 11:39:41 +01:00
2020-07-22 09:03:28 +01:00
/* Initialize crypto data. */
2020-10-13 15:00:03 +01:00
if ( ( ! out - > rights_id_available | | ( out - > rights_id_available & & out - > titlekey_retrieved ) ) & & fs_ctx - > encryption_type > NcaEncryptionType_None & & \
fs_ctx - > encryption_type < = NcaEncryptionType_AesCtrEx )
2020-04-20 11:39:41 +01:00
{
2020-10-21 05:27:48 +01:00
/* Initialize the partial AES counter for this section. */
aes128CtrInitializePartialCtr ( fs_ctx - > ctr , fs_ctx - > header . aes_ctr_upper_iv . value , fs_ctx - > section_offset ) ;
2020-04-21 11:23:33 +01:00
2020-07-06 01:10:07 +01:00
/* Initialize AES context. */
2020-04-21 11:23:33 +01:00
if ( out - > rights_id_available )
2020-04-20 11:39:41 +01:00
{
2020-07-22 09:03:28 +01:00
/* AES-128-CTR is always used for FS crypto in NCAs with a rights ID. */
2020-10-13 15:00:03 +01:00
aes128CtrContextCreate ( & ( fs_ctx - > ctr_ctx ) , out - > titlekey , fs_ctx - > ctr ) ;
2020-04-21 11:23:33 +01:00
} else {
2020-10-13 15:00:03 +01:00
if ( fs_ctx - > encryption_type = = NcaEncryptionType_AesXts )
2020-04-21 11:23:33 +01:00
{
2020-07-06 01:10:07 +01:00
/* We need to create two different contexts: one for decryption and another one for encryption. */
2020-10-13 15:00:03 +01:00
aes128XtsContextCreate ( & ( fs_ctx - > xts_decrypt_ctx ) , out - > decrypted_key_area . aes_xts_1 , out - > decrypted_key_area . aes_xts_2 , false ) ;
aes128XtsContextCreate ( & ( fs_ctx - > xts_encrypt_ctx ) , out - > decrypted_key_area . aes_xts_1 , out - > decrypted_key_area . aes_xts_2 , true ) ;
2020-07-22 09:03:28 +01:00
} else
2020-10-13 15:00:03 +01:00
if ( fs_ctx - > encryption_type = = NcaEncryptionType_AesCtr | | fs_ctx - > encryption_type = = NcaEncryptionType_AesCtrEx )
2020-07-22 09:03:28 +01:00
{
2020-10-13 15:00:03 +01:00
aes128CtrContextCreate ( & ( fs_ctx - > ctr_ctx ) , out - > decrypted_key_area . aes_ctr , fs_ctx - > ctr ) ;
2020-04-21 11:23:33 +01:00
}
2020-04-20 11:39:41 +01:00
}
}
2020-07-06 01:10:07 +01:00
/* Enable FS context if we got up to this point. */
2020-10-13 15:00:03 +01:00
fs_ctx - > enabled = true ;
2020-04-20 11:39:41 +01:00
}
return true ;
}
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
{
LOGFILE ( " Invalid parameters! " ) ;
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 ) ;
2020-07-06 01:10:07 +01:00
if ( ! ret ) LOGFILE ( " 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 ) ;
2020-07-06 01:10:07 +01:00
if ( ! ret ) LOGFILE ( " 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 )
{
2020-04-22 21:53:20 +01:00
return _ncaReadFsSection ( ctx , out , read_size , offset , true ) ;
}
2020-04-30 09:25:03 +01:00
bool ncaReadAesCtrExStorageFromBktrSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset , u32 ctr_val )
{
return _ncaReadAesCtrExStorageFromBktrSection ( ctx , out , read_size , offset , ctr_val , true ) ;
}
2020-04-26 09:35:01 +01:00
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
{
return _ncaGenerateEncryptedFsSectionBlock ( ctx , data , data_size , data_offset , out_block_size , out_block_offset , true ) ;
}
bool ncaGenerateHierarchicalSha256Patch ( NcaFsSectionContext * ctx , const void * data , u64 data_size , u64 data_offset , NcaHierarchicalSha256Patch * out )
2020-04-22 21:53:20 +01:00
{
2020-07-22 09:03:28 +01:00
return ncaGenerateHashDataPatch ( ctx , data , data_size , data_offset , out , false ) ;
}
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 )
{
return ncaGenerateHashDataPatch ( ctx , data , data_size , data_offset , out , true ) ;
}
2020-07-22 21:35:23 +01:00
void ncaWriteHierarchicalIntegrityPatchToMemoryBuffer ( NcaContext * ctx , NcaHierarchicalIntegrityPatch * 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 | | ! 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 < 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 ;
2020-10-22 05:38:14 +01:00
LOGFILE ( " Set download distribution type to %s NCA \" %s \" . " , titleGetNcmContentTypeName ( ctx - > content_type ) , ctx - > content_id_str ) ;
2020-10-21 05:27:48 +01:00
}
2020-10-22 05:38:14 +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 )
{
LOGFILE ( " Invalid parameters! " ) ;
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
2020-07-22 09:03:28 +01:00
/* Copy decrypted titlekey to the decrypted NCA key area. */
2020-07-23 22:30:01 +01:00
/* This will be reencrypted at a later stage. */
2020-07-22 09:03:28 +01:00
for ( u8 i = 0 ; i < NCA_FS_HEADER_COUNT ; i + + )
{
/* AES-128-XTS is not used in FS sections from NCAs with titlekey crypto. */
2020-10-13 15:00:03 +01:00
NcaFsSectionContext * fs_ctx = & ( ctx - > fs_ctx [ i ] ) ;
if ( ! fs_ctx - > enabled | | ( fs_ctx - > encryption_type ! = NcaEncryptionType_AesCtr & & fs_ctx - > encryption_type ! = NcaEncryptionType_AesCtrEx ) ) continue ;
u8 * key_ptr = ( fs_ctx - > encryption_type = = NcaEncryptionType_AesCtr ? ctx - > decrypted_key_area . aes_ctr : ctx - > decrypted_key_area . aes_ctr_ex ) ;
2020-07-22 09:03:28 +01:00
memcpy ( key_ptr , ctx - > titlekey , AES_128_KEY_SIZE ) ;
}
2020-04-28 09:58:17 +01:00
2020-10-22 05:38:14 +01:00
/* Encrypt NCA key area. */
if ( ! ncaEncryptKeyArea ( ctx ) )
{
LOGFILE ( " Error encrypting %s NCA \" %s \" key area! " , titleGetNcmContentTypeName ( ctx - > content_type ) , ctx - > content_id_str ) ;
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
LOGFILE ( " Removed titlekey crypto from %s NCA \" %s \" . " , titleGetNcmContentTypeName ( ctx - > content_type ) , ctx - > content_id_str ) ;
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
{
LOGFILE ( " Invalid NCA context! " ) ;
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 } ;
/* 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 ) )
{
LOGFILE ( " Error encrypting NCA \" %s \" header! " , ctx - > content_id_str ) ;
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. */
2020-10-13 15:00:03 +01:00
if ( ctx - > format_version ! = NcaVersion_Nca3 & & ! 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 ) )
{
LOGFILE ( " Error encrypting NCA%u \" %s \" FS section header #%u! " , ctx - > format_version , ctx - > content_id_str , i ) ;
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 ;
memcpy ( ctx - > content_id . c , hash , sizeof ( ctx - > content_id . c ) ) ;
utilsGenerateHexStringFromData ( ctx - > content_id_str , sizeof ( ctx - > content_id_str ) , ctx - > content_id . c , sizeof ( ctx - > content_id . c ) ) ;
memcpy ( ctx - > hash , hash , sizeof ( ctx - > hash ) ) ;
utilsGenerateHexStringFromData ( ctx - > hash_str , sizeof ( ctx - > hash_str ) , ctx - > hash , sizeof ( ctx - > hash ) ) ;
}
2020-10-14 01:15:21 +01:00
const char * ncaGetFsSectionTypeName ( NcaFsSectionContext * ctx )
{
NcaContext * nca_ctx = NULL ;
const char * str = " Invalid " ;
if ( ! ctx | | ! ctx - > enabled | | ! ( nca_ctx = ( NcaContext * ) ctx - > nca_ctx ) ) return str ;
switch ( ctx - > section_type )
{
case NcaFsSectionType_PartitionFs :
str = ( ( nca_ctx - > content_type = = NcmContentType_Program & & ctx - > section_num = = 0 ) ? " ExeFS " : " Partition FS " ) ;
break ;
case NcaFsSectionType_RomFs :
str = " RomFS " ;
break ;
case NcaFsSectionType_PatchRomFs :
str = " Patch RomFS [BKTR] " ;
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
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " Invalid NCA context! " ) ;
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
2020-10-13 15:00:03 +01:00
/* Read NCA header. */
if ( ! ncaReadContentFile ( ctx , & ( ctx - > encrypted_header ) , sizeof ( NcaHeader ) , 0 ) )
{
LOGFILE ( " Failed to read NCA \" %s \" header! " , ctx - > content_id_str ) ;
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
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " Error decrypting NCA \" %s \" header! " , ctx - > content_id_str ) ;
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 ) ) ;
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
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " Error decrypting NCA \" %s \" key area! " , ctx - > content_id_str ) ;
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. */
2020-10-13 15:00:03 +01:00
if ( ctx - > format_version ! = NcaVersion_Nca3 & & ! 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
{
LOGFILE ( " 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 ) ;
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 ) )
{
LOGFILE ( " Error decrypting NCA%u \" %s \" FS section header #%u! " , ctx - > format_version , ctx - > content_id_str , i ) ;
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
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " Invalid NCA context! " ) ;
return false ;
2020-04-22 21:53:20 +01:00
}
2020-04-21 11:23:33 +01:00
2020-07-22 09:03:28 +01:00
Result rc = 0 ;
const u8 * kek_src = NULL ;
u8 key_count = 0 , tmp_kek [ AES_128_KEY_SIZE ] = { 0 } ;
2020-04-21 11:23:33 +01:00
2020-07-22 09:03:28 +01:00
/* Check if we're dealing with a NCA0 with a plain text key area. */
if ( ncaIsVersion0KeyAreaEncrypted ( ctx ) )
2020-04-22 21:53:20 +01:00
{
2020-07-22 09:03:28 +01:00
memcpy ( & ( ctx - > decrypted_key_area ) , & ( ctx - > header . encrypted_key_area ) , NCA_USED_KEY_AREA_SIZE ) ;
return true ;
2020-04-28 09:58:17 +01:00
}
2020-07-22 09:03:28 +01:00
kek_src = keysGetKeyAreaEncryptionKeySource ( ctx - > header . kaek_index ) ;
if ( ! kek_src )
2020-04-21 11:23:33 +01:00
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " Unable to retrieve KAEK source for index 0x%02X! " , ctx - > header . kaek_index ) ;
return false ;
2020-04-22 21:53:20 +01:00
}
2020-07-22 09:03:28 +01:00
rc = splCryptoGenerateAesKek ( kek_src , ctx - > key_generation , 0 , tmp_kek ) ;
if ( R_FAILED ( rc ) )
2020-04-28 09:58:17 +01:00
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " splCryptoGenerateAesKek failed! (0x%08X). " , rc ) ;
return false ;
2020-04-28 09:58:17 +01:00
}
2020-07-22 09:03:28 +01:00
key_count = ( ctx - > format_version = = NcaVersion_Nca0 ? 2 : 4 ) ;
2020-04-29 10:54:40 +01:00
2020-07-22 09:03:28 +01:00
for ( u8 i = 0 ; i < key_count ; i + + )
2020-04-29 10:54:40 +01:00
{
2020-07-22 09:03:28 +01:00
rc = splCryptoGenerateAesKey ( tmp_kek , ( u8 * ) & ( ctx - > header . encrypted_key_area ) + ( i * AES_128_KEY_SIZE ) , ( u8 * ) & ( ctx - > decrypted_key_area ) + ( i * AES_128_KEY_SIZE ) ) ;
if ( R_FAILED ( rc ) )
2020-04-29 10:54:40 +01:00
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " splCryptoGenerateAesKey failed to decrypt NCA key area entry #%u! (0x%08X). " , i , rc ) ;
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 )
{
LOGFILE ( " Invalid NCA context! " ) ;
return false ;
}
2020-04-21 11:23:33 +01:00
2020-07-22 09:03:28 +01:00
u8 key_count = 0 ;
const u8 * kaek = NULL ;
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
{
2020-07-22 09:03:28 +01:00
memcpy ( & ( ctx - > header . encrypted_key_area ) , & ( ctx - > decrypted_key_area ) , NCA_USED_KEY_AREA_SIZE ) ;
2020-04-22 21:53:20 +01:00
return true ;
}
2020-07-22 09:03:28 +01:00
kaek = keysGetKeyAreaEncryptionKey ( ctx - > key_generation , ctx - > header . kaek_index ) ;
if ( ! kaek )
2020-04-22 21:53:20 +01:00
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " Unable to retrieve KAEK for key generation 0x%02X and KAEK index 0x%02X! " , ctx - > key_generation , ctx - > header . kaek_index ) ;
2020-04-22 21:53:20 +01:00
return false ;
}
key_count = ( ctx - > format_version = = NcaVersion_Nca0 ? 2 : 4 ) ;
2020-07-22 09:03:28 +01:00
aes128ContextCreate ( & key_area_ctx , kaek , true ) ;
for ( u8 i = 0 ; i < key_count ; i + + ) aes128EncryptBlock ( & key_area_ctx , ( u8 * ) & ( ctx - > header . encrypted_key_area ) + ( i * AES_128_KEY_SIZE ) , ( u8 * ) & ( ctx - > decrypted_key_area ) + ( i * AES_128_KEY_SIZE ) ) ;
2020-04-21 11:23:33 +01:00
return true ;
}
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 } ;
2020-07-22 09:03:28 +01:00
sha256CalculateHash ( nca0_key_area_hash , & ( ctx - > header . encrypted_key_area ) , NCA_USED_KEY_AREA_SIZE ) ;
2020-04-15 21:50:07 +01:00
if ( ! memcmp ( nca0_key_area_hash , g_nca0KeyAreaHash , SHA256_HASH_SIZE ) ) return false ;
2020-04-11 06:28:26 +01:00
2020-04-15 21:50:07 +01:00
return true ;
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 ;
bool rights_id_available = false ;
for ( u8 i = 0 ; i < 0x10 ; i + + )
{
if ( ctx - > header . rights_id . c [ i ] ! = 0 )
{
rights_id_available = true ;
break ;
}
}
return rights_id_available ;
}
2020-04-22 21:53:20 +01:00
static bool _ncaReadFsSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset , bool lock )
{
if ( lock ) mutexLock ( & g_ncaCryptoBufferMutex ) ;
bool ret = false ;
2020-07-22 21:35:23 +01:00
if ( ! g_ncaCryptoBuffer | | ! ctx | | ! ctx - > enabled | | ! ctx - > nca_ctx | | ctx - > section_num > = NCA_FS_HEADER_COUNT | | ctx - > section_offset < sizeof ( NcaHeader ) | | \
2020-07-22 09:03:28 +01:00
ctx - > section_type > = NcaFsSectionType_Invalid | | ctx - > encryption_type = = NcaEncryptionType_Auto | | ctx - > encryption_type > NcaEncryptionType_AesCtrEx | | ! out | | ! read_size | | \
2020-10-21 05:27:48 +01:00
( offset + read_size ) > ctx - > section_size )
2020-04-22 21:53:20 +01:00
{
LOGFILE ( " Invalid NCA FS section header parameters! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
size_t crypt_res = 0 ;
u64 sector_num = 0 ;
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 ;
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 + read_size ) > nca_ctx - > content_size )
2020-04-22 21:53:20 +01:00
{
LOGFILE ( " Invalid NCA header parameters! " ) ;
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
/* 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 ) ) | | \
2020-04-22 21:53:20 +01:00
( ( ctx - > encryption_type = = NcaEncryptionType_AesCtr | | ctx - > encryption_type = = NcaEncryptionType_AesCtrEx ) & & ! ( content_offset % AES_BLOCK_SIZE ) & & ! ( read_size % AES_BLOCK_SIZE ) ) )
{
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 ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " 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_num ) ;
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 )
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " 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 , \
ctx - > section_num ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
} else
if ( ctx - > encryption_type = = NcaEncryptionType_AesCtr | | ctx - > encryption_type = = NcaEncryptionType_AesCtrEx )
{
2020-10-21 05:27:48 +01:00
aes128CtrUpdatePartialCtr ( ctx - > ctr , content_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 ) )
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " 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 , \
ctx - > section_num ) ;
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 )
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " 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 , \
ctx - > section_num ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-22 21:53:20 +01:00
}
} else
if ( ctx - > encryption_type = = NcaEncryptionType_AesCtr | | ctx - > encryption_type = = NcaEncryptionType_AesCtrEx )
{
2020-10-21 05:27:48 +01:00
aes128CtrUpdatePartialCtr ( ctx - > ctr , block_start_offset ) ;
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 ) ;
ret = ( block_size > NCA_CRYPTO_BUFFER_SIZE ? _ncaReadFsSection ( ctx , ( u8 * ) out + out_chunk_size , read_size - out_chunk_size , offset + out_chunk_size , false ) : true ) ;
2020-07-13 07:36:17 +01:00
end :
2020-04-22 21:53:20 +01:00
if ( lock ) mutexUnlock ( & g_ncaCryptoBufferMutex ) ;
return ret ;
}
2020-04-28 09:58:17 +01:00
2020-04-30 09:25:03 +01:00
static bool _ncaReadAesCtrExStorageFromBktrSection ( NcaFsSectionContext * ctx , void * out , u64 read_size , u64 offset , u32 ctr_val , bool lock )
{
if ( lock ) mutexLock ( & g_ncaCryptoBufferMutex ) ;
bool ret = false ;
2020-07-22 21:35:23 +01:00
if ( ! g_ncaCryptoBuffer | | ! ctx | | ! ctx - > enabled | | ! ctx - > nca_ctx | | ctx - > section_num > = NCA_FS_HEADER_COUNT | | ctx - > section_offset < sizeof ( NcaHeader ) | | \
2020-10-21 05:27:48 +01:00
ctx - > section_type ! = NcaFsSectionType_PatchRomFs | | ctx - > encryption_type ! = NcaEncryptionType_AesCtrEx | | ! out | | ! read_size | | ( offset + read_size ) > ctx - > section_size )
2020-04-30 09:25:03 +01:00
{
LOGFILE ( " Invalid NCA FS section header parameters! " ) ;
2020-07-13 07:36:17 +01:00
goto end ;
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 ;
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
{
LOGFILE ( " 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 ) )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " 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_num ) ;
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 ) )
{
2020-07-22 09:03:28 +01:00
LOGFILE ( " 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 , \
ctx - > section_num ) ;
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 ) ;
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 , false ) : true ) ;
2020-07-13 07:36:17 +01:00
end :
2020-04-30 09:25:03 +01:00
if ( lock ) mutexUnlock ( & g_ncaCryptoBufferMutex ) ;
return ret ;
}
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 )
{
mutexLock ( & g_ncaCryptoBufferMutex ) ;
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 ;
bool success = false ;
if ( ! ctx | | ! ctx - > enabled | | ! ( nca_ctx = ( NcaContext * ) ctx - > nca_ctx ) | | ( ! is_integrity_patch & & ( ctx - > header . hash_type ! = NcaHashType_HierarchicalSha256 | | \
! 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 - > header . hash_type ! = NcaHashType_HierarchicalIntegrity | | \
! ( 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 | | \
2020-10-21 05:27:48 +01:00
( data_offset + data_size ) > last_layer_size | | ! out )
2020-07-22 09:03:28 +01:00
{
LOGFILE ( " Invalid parameters! " ) ;
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
/* 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
{
LOGFILE ( " Invalid hierarchical parent/child layer! " ) ;
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 )
{
LOGFILE ( " Unable to allocate 0x%lX bytes for hierarchical layer #%u data block! (current). " , cur_layer_read_size , i - 1 ) ;
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. */
if ( ! _ncaReadFsSection ( ctx , cur_layer_block , cur_layer_read_size , cur_layer_read_start_offset , false ) )
{
LOGFILE ( " 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 ) ;
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 )
{
LOGFILE ( " Unable to allocate 0x%lX bytes for hierarchical layer #%u data block! (parent). " , parent_layer_read_size , i - 2 ) ;
goto end ;
}
/* Read parent layer block. */
if ( ! _ncaReadFsSection ( ctx , parent_layer_block , parent_layer_read_size , parent_layer_offset + parent_layer_read_start_offset , false ) )
{
LOGFILE ( " 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 ) ;
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 ) ;
sha256CalculateHash ( parent_layer_block + ( k * SHA256_HASH_SIZE ) , cur_layer_block + j , hash_block_size ) ;
}
} 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 ) ;
sha256CalculateHash ( master_hash , cur_layer_block , cur_layer_read_size ) ;
}
/* Reencrypt current layer block. */
cur_layer_patch - > data = _ncaGenerateEncryptedFsSectionBlock ( ctx , cur_layer_block + cur_layer_read_patch_offset , cur_data_size , cur_layer_offset + cur_data_offset , \
& ( cur_layer_patch - > size ) , & ( cur_layer_patch - > offset ) , false ) ;
if ( ! cur_layer_patch - > data )
{
LOGFILE ( " Failed to generate encrypted 0x%lX bytes long hierarchical layer #%u data block! " , cur_data_size , i - 1 ) ;
goto end ;
}
/* 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. */
sha256CalculateHash ( nca_ctx - > header . fs_header_hash [ ctx - > section_num ] . hash , & ( ctx - > header ) , sizeof ( NcaFsHeader ) ) ;
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 ) ;
}
}
mutexUnlock ( & g_ncaCryptoBufferMutex ) ;
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
2020-10-22 05:38:14 +01:00
LOGFILE ( " 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 ) , \
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
}
2020-04-28 09:58:17 +01:00
static void * _ncaGenerateEncryptedFsSectionBlock ( NcaFsSectionContext * ctx , const void * data , u64 data_size , u64 data_offset , u64 * out_block_size , u64 * out_block_offset , bool lock )
{
if ( lock ) mutexLock ( & g_ncaCryptoBufferMutex ) ;
u8 * out = NULL ;
bool success = false ;
2020-07-22 21:35:23 +01:00
if ( ! g_ncaCryptoBuffer | | ! ctx | | ! ctx - > enabled | | ! ctx - > nca_ctx | | ctx - > section_num > = NCA_FS_HEADER_COUNT | | ctx - > section_offset < sizeof ( NcaHeader ) | | \
2020-07-22 09:03:28 +01:00
ctx - > section_type > = NcaFsSectionType_Invalid | | ctx - > encryption_type = = NcaEncryptionType_Auto | | ctx - > encryption_type > = NcaEncryptionType_AesCtrEx | | ! data | | ! data_size | | \
2020-10-21 05:27:48 +01:00
( data_offset + data_size ) > ctx - > section_size | | ! out_block_size | | ! out_block_offset )
2020-04-28 09:58:17 +01:00
{
LOGFILE ( " 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
{
LOGFILE ( " 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 ) ) | | \
2020-04-30 09:25:03 +01:00
( ctx - > encryption_type = = NcaEncryptionType_AesCtr & & ! ( 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 )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " 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 )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " 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_num ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
} else
2020-04-30 09:25:03 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_AesCtr )
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 )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " 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. */
2020-04-28 09:58:17 +01:00
if ( ! _ncaReadFsSection ( ctx , out , block_size , block_start_offset , false ) )
{
LOGFILE ( " Failed to read decrypted NCA \" %s \" FS section #%u data block! " , nca_ctx - > content_id_str , ctx - > section_num ) ;
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 )
{
2020-07-06 01:10:07 +01:00
LOGFILE ( " 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_num ) ;
2020-07-13 07:36:17 +01:00
goto end ;
2020-04-28 09:58:17 +01:00
}
} else
2020-04-30 09:25:03 +01:00
if ( ctx - > encryption_type = = NcaEncryptionType_AesCtr )
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 ;
}
2020-04-29 10:54:40 +01:00
if ( lock ) mutexUnlock ( & g_ncaCryptoBufferMutex ) ;
2020-04-28 09:58:17 +01:00
return out ;
}