2020-04-15 21:50:07 +01:00
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
# include <mbedtls/base64.h>
# include "keys.h"
# include "util.h"
# include "ui.h"
# include "rsa.h"
# include "nso.h"
/* Extern variables */
extern int breaks ;
extern int font_height ;
extern exefs_ctx_t exeFsContext ;
extern romfs_ctx_t romFsContext ;
extern bktr_ctx_t bktrContext ;
extern nca_keyset_t nca_keyset ;
extern u8 * ncaCtrBuf ;
2020-10-09 10:58:53 +01:00
bool encryptNcaHeader ( nca_header_t * input , u8 * outBuf , u64 outBufSize )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
if ( ! input | | ! outBuf | | ! outBufSize | | outBufSize < NCA_FULL_HEADER_LENGTH | | ( __builtin_bswap32 ( input - > magic ) ! = NCA3_MAGIC & & __builtin_bswap32 ( input - > magic ) ! = NCA2_MAGIC ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid NCA header encryption parameters. " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( ! loadNcaKeyset ( ) ) return false ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u32 i ;
size_t crypt_res ;
Aes128XtsContext hdr_aes_ctx ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u8 header_key_0 [ 16 ] ;
u8 header_key_1 [ 16 ] ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
memcpy ( header_key_0 , nca_keyset . header_key , 16 ) ;
memcpy ( header_key_1 , nca_keyset . header_key + 16 , 16 ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
aes128XtsContextCreate ( & hdr_aes_ctx , header_key_0 , header_key_1 , true ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( __builtin_bswap32 ( input - > magic ) = = NCA3_MAGIC )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
crypt_res = aes128XtsNintendoCrypt ( & hdr_aes_ctx , outBuf , input , NCA_FULL_HEADER_LENGTH , 0 , true ) ;
if ( crypt_res ! = NCA_FULL_HEADER_LENGTH )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid output length for encrypted NCA header! (%u != %lu) " , __func__ , NCA_FULL_HEADER_LENGTH , crypt_res ) ;
return false ;
}
} else
if ( __builtin_bswap32 ( input - > magic ) = = NCA2_MAGIC )
{
crypt_res = aes128XtsNintendoCrypt ( & hdr_aes_ctx , outBuf , input , NCA_HEADER_LENGTH , 0 , true ) ;
if ( crypt_res ! = NCA_HEADER_LENGTH )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid output length for encrypted NCA header! (%u != %lu) " , __func__ , NCA_HEADER_LENGTH , crypt_res ) ;
return false ;
}
for ( i = 0 ; i < NCA_SECTION_HEADER_CNT ; i + + )
{
crypt_res = aes128XtsNintendoCrypt ( & hdr_aes_ctx , outBuf + NCA_HEADER_LENGTH + ( i * NCA_SECTION_HEADER_LENGTH ) , & ( input - > fs_headers [ i ] ) , NCA_SECTION_HEADER_LENGTH , 0 , true ) ;
if ( crypt_res ! = NCA_SECTION_HEADER_LENGTH )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid output length for encrypted NCA header section #%u! (%u != %lu) " , __func__ , i , NCA_SECTION_HEADER_LENGTH , crypt_res ) ;
return false ;
}
}
} else {
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid decrypted NCA magic word! (0x%08X) " , __func__ , __builtin_bswap32 ( input - > magic ) ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
return true ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
bool decryptNcaHeader ( const u8 * ncaBuf , u64 ncaBufSize , nca_header_t * out , title_rights_ctx * rights_info , u8 * decrypted_nca_keys , bool retrieveTitleKeyData )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
if ( ! ncaBuf | | ! ncaBufSize | | ncaBufSize < NCA_FULL_HEADER_LENGTH | | ! out | | ! decrypted_nca_keys )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid NCA header decryption parameters! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( ! loadNcaKeyset ( ) ) return false ;
int ret ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u32 i ;
size_t crypt_res ;
Aes128XtsContext hdr_aes_ctx ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u8 header_key_0 [ 16 ] ;
u8 header_key_1 [ 16 ] ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
bool has_rights_id = false ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
memcpy ( header_key_0 , nca_keyset . header_key , 16 ) ;
memcpy ( header_key_1 , nca_keyset . header_key + 16 , 16 ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
aes128XtsContextCreate ( & hdr_aes_ctx , header_key_0 , header_key_1 , false ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
crypt_res = aes128XtsNintendoCrypt ( & hdr_aes_ctx , out , ncaBuf , NCA_HEADER_LENGTH , 0 , false ) ;
if ( crypt_res ! = NCA_HEADER_LENGTH )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid output length for decrypted NCA header! (%u != %lu) " , __func__ , NCA_HEADER_LENGTH , crypt_res ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
if ( __builtin_bswap32 ( out - > magic ) = = NCA3_MAGIC )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
crypt_res = aes128XtsNintendoCrypt ( & hdr_aes_ctx , out , ncaBuf , NCA_FULL_HEADER_LENGTH , 0 , false ) ;
if ( crypt_res ! = NCA_FULL_HEADER_LENGTH )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid output length for decrypted NCA header! (%u != %lu) " , __func__ , NCA_FULL_HEADER_LENGTH , crypt_res ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
} else
if ( __builtin_bswap32 ( out - > magic ) = = NCA2_MAGIC )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
for ( i = 0 ; i < NCA_SECTION_HEADER_CNT ; i + + )
{
if ( out - > fs_headers [ i ] . _0x148 [ 0 ] ! = 0 | | memcmp ( out - > fs_headers [ i ] . _0x148 , out - > fs_headers [ i ] . _0x148 + 1 , 0xB7 ) )
{
crypt_res = aes128XtsNintendoCrypt ( & hdr_aes_ctx , & ( out - > fs_headers [ i ] ) , ncaBuf + NCA_HEADER_LENGTH + ( i * NCA_SECTION_HEADER_LENGTH ) , NCA_SECTION_HEADER_LENGTH , 0 , false ) ;
if ( crypt_res ! = NCA_SECTION_HEADER_LENGTH )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid output length for decrypted NCA header section #%u! (%u != %lu) " , __func__ , i , NCA_SECTION_HEADER_LENGTH , crypt_res ) ;
return false ;
}
} else {
memset ( & ( out - > fs_headers [ i ] ) , 0 , sizeof ( nca_fs_header_t ) ) ;
}
}
} else {
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid NCA magic word! Wrong header key? (0x%08X) \n Try running Lockpick_RCM to generate the keys file from scratch. " , __func__ , __builtin_bswap32 ( out - > magic ) ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
for ( i = 0 ; i < 0x10 ; i + + )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
if ( out - > rights_id [ i ] ! = 0 )
{
has_rights_id = true ;
break ;
}
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( has_rights_id )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
if ( rights_info ! = NULL )
{
// If we're dealing with a rights info context, retrieve the ticket for the current title
if ( ! rights_info - > has_rights_id )
{
rights_info - > has_rights_id = true ;
memcpy ( rights_info - > rights_id , out - > rights_id , 16 ) ;
convertDataToHexString ( out - > rights_id , 16 , rights_info - > rights_id_str , 33 ) ;
sprintf ( rights_info - > tik_filename , " %s.tik " , rights_info - > rights_id_str ) ;
sprintf ( rights_info - > cert_filename , " %s.cert " , rights_info - > rights_id_str ) ;
if ( retrieveTitleKeyData )
{
ret = retrieveNcaTikTitleKey ( out , ( u8 * ) ( & ( rights_info - > tik_data ) ) , rights_info - > enc_titlekey , rights_info - > dec_titlekey ) ;
if ( ret > = 0 )
{
memset ( decrypted_nca_keys , 0 , NCA_KEY_AREA_SIZE ) ;
memcpy ( decrypted_nca_keys + ( NCA_KEY_AREA_KEY_SIZE * 2 ) , rights_info - > dec_titlekey , 0x10 ) ;
rights_info - > retrieved_tik = true ;
} else {
if ( ret = = - 2 )
{
// We are probably dealing with a pre-installed title
// Let's enable our missing ticket flag - we'll use it to display a prompt asking the user if they want to proceed anyway
rights_info - > missing_tik = true ;
} else {
return false ;
}
}
}
} else {
// Copy what we already have
if ( retrieveTitleKeyData & & rights_info - > retrieved_tik )
{
memset ( decrypted_nca_keys , 0 , NCA_KEY_AREA_SIZE ) ;
memcpy ( decrypted_nca_keys + ( NCA_KEY_AREA_KEY_SIZE * 2 ) , rights_info - > dec_titlekey , 0x10 ) ;
}
}
} else {
// Otherwise, only retrieve the decrypted titlekey. This is used with ExeFS/RomFS section parsing for SD/eMMC titles
if ( retrieveTitleKeyData )
{
u8 tmp_dec_titlekey [ 0x10 ] ;
if ( retrieveNcaTikTitleKey ( out , NULL , NULL , tmp_dec_titlekey ) < 0 ) return false ;
memset ( decrypted_nca_keys , 0 , NCA_KEY_AREA_SIZE ) ;
memcpy ( decrypted_nca_keys + ( NCA_KEY_AREA_KEY_SIZE * 2 ) , tmp_dec_titlekey , 0x10 ) ;
}
}
} else {
if ( ! decryptNcaKeyArea ( out , decrypted_nca_keys ) ) return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
return true ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
bool retrieveTitleKeyFromGameCardTicket ( title_rights_ctx * rights_info , u8 * decrypted_nca_keys )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
if ( ! rights_info | | ! rights_info - > has_rights_id | | ! strlen ( rights_info - > tik_filename ) | | ! decrypted_nca_keys )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to retrieve titlekey from gamecard ticket! " , __func__ ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
// Check if the ticket has already been retrieved from the HFS0 partition in the gamecard
if ( rights_info - > retrieved_tik )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
// Save the decrypted NCA key area keys
memset ( decrypted_nca_keys , 0 , NCA_KEY_AREA_SIZE ) ;
memcpy ( decrypted_nca_keys + ( NCA_KEY_AREA_KEY_SIZE * 2 ) , rights_info - > dec_titlekey , 0x10 ) ;
return true ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
// Load external keys
if ( ! loadExternalKeys ( ) ) return false ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Retrieve ticket
if ( ! readFileFromSecureHfs0PartitionByName ( rights_info - > tik_filename , 0 , & ( rights_info - > tik_data ) , ETICKET_TIK_FILE_SIZE ) ) return false ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Save encrypted titlekey
memcpy ( rights_info - > enc_titlekey , rights_info - > tik_data . titlekey_block , 0x10 ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Decrypt titlekey
u8 crypto_type = rights_info - > rights_id [ 0x0F ] ;
if ( crypto_type ) crypto_type - - ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( crypto_type > = 0x20 )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid NCA keyblob index. " , __func__ ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
Aes128Context titlekey_aes_ctx ;
aes128ContextCreate ( & titlekey_aes_ctx , nca_keyset . titlekeks [ crypto_type ] , false ) ;
aes128DecryptBlock ( & titlekey_aes_ctx , rights_info - > dec_titlekey , rights_info - > enc_titlekey ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Update retrieved ticket flag
rights_info - > retrieved_tik = true ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Save the decrypted NCA key area keys
memset ( decrypted_nca_keys , 0 , NCA_KEY_AREA_SIZE ) ;
memcpy ( decrypted_nca_keys + ( NCA_KEY_AREA_KEY_SIZE * 2 ) , rights_info - > dec_titlekey , 0x10 ) ;
2020-04-15 21:50:07 +01:00
return true ;
}
2020-10-09 10:58:53 +01:00
bool processProgramNca ( NcmContentStorage * ncmStorage , const NcmContentId * ncaId , nca_header_t * dec_nca_header , cnmt_xml_content_info * xml_content_info , nca_program_mod_data * * output , u32 * cur_mod_cnt , u32 idx )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
if ( ! ncmStorage | | ! ncaId | | ! dec_nca_header | | ! xml_content_info | | ! output | | ! cur_mod_cnt )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to process Program NCA! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( dec_nca_header - > fs_headers [ 0 ] . partition_type ! = NCA_FS_HEADER_PARTITION_PFS0 | | dec_nca_header - > fs_headers [ 0 ] . fs_type ! = NCA_FS_HEADER_FSTYPE_PFS0 )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: Program NCA section #0 doesn't hold a PFS0 partition! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( ! dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . pfs0_size )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid size for PFS0 partition in Program NCA section #0! " , __func__ ) ;
return false ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( dec_nca_header - > fs_headers [ 0 ] . crypt_type ! = NCA_FS_HEADER_CRYPT_CTR )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid AES crypt type for Program NCA section #0! (0x%02X) " , __func__ , dec_nca_header - > fs_headers [ 0 ] . crypt_type ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
u32 i ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u64 section_offset ;
u64 hash_table_offset ;
u64 nca_pfs0_offset ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
pfs0_header nca_pfs0_header ;
pfs0_file_entry * nca_pfs0_entries = NULL ;
u64 nca_pfs0_data_offset ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
npdm_t npdm_header ;
bool found_meta = false ;
u64 meta_offset ;
u64 acid_pubkey_offset ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u64 block_hash_table_offset ;
u64 block_hash_table_end_offset ;
u64 block_start_offset [ 2 ] = { 0 , 0 } ;
u64 block_size [ 2 ] = { 0 , 0 } ;
u8 block_hash [ 2 ] [ SHA256_HASH_SIZE ] ;
u8 * block_data [ 2 ] = { NULL , NULL } ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u64 sig_write_size [ 2 ] = { 0 , 0 } ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u8 * hash_table = NULL ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
Aes128CtrContext aes_ctx ;
section_offset = ( ( u64 ) dec_nca_header - > section_entries [ 0 ] . media_start_offset * ( u64 ) MEDIA_UNIT_SIZE ) ;
hash_table_offset = ( section_offset + dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . hash_table_offset ) ;
nca_pfs0_offset = ( section_offset + dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . pfs0_offset ) ;
if ( ! section_offset | | section_offset < NCA_FULL_HEADER_LENGTH | | ! hash_table_offset | | hash_table_offset < section_offset | | ! nca_pfs0_offset | | nca_pfs0_offset < = hash_table_offset )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid offsets for Program NCA section #0! " , __func__ ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
// Generate initial CTR
unsigned char ctr [ 0x10 ] ;
u64 ofs = ( section_offset > > 4 ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
for ( i = 0 ; i < 0x8 ; i + + )
{
ctr [ i ] = dec_nca_header - > fs_headers [ 0 ] . section_ctr [ 0x08 - i - 1 ] ;
ctr [ 0x10 - i - 1 ] = ( unsigned char ) ( ofs & 0xFF ) ;
ofs > > = 8 ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u8 ctr_key [ NCA_KEY_AREA_KEY_SIZE ] ;
memcpy ( ctr_key , xml_content_info - > decrypted_nca_keys + ( NCA_KEY_AREA_KEY_SIZE * 2 ) , NCA_KEY_AREA_KEY_SIZE ) ;
aes128CtrContextCreate ( & aes_ctx , ctr_key , ctr ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( ! processNcaCtrSectionBlock ( ncmStorage , ncaId , & aes_ctx , nca_pfs0_offset , & nca_pfs0_header , sizeof ( pfs0_header ) , false ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to read Program NCA section #0 PFS0 partition header! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( __builtin_bswap32 ( nca_pfs0_header . magic ) ! = PFS0_MAGIC )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X) \n Try running Lockpick_RCM to generate the keys file from scratch. " , __func__ , __builtin_bswap32 ( nca_pfs0_header . magic ) ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
if ( ! nca_pfs0_header . file_cnt | | ! nca_pfs0_header . str_table_size )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: Program NCA section #0 PFS0 partition is empty! Wrong KAEK? \n Try running Lockpick_RCM to generate the keys file from scratch. " , __func__ ) ;
return false ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
nca_pfs0_entries = calloc ( nca_pfs0_header . file_cnt , sizeof ( pfs0_file_entry ) ) ;
if ( ! nca_pfs0_entries )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to allocate memory for Program NCA section #0 PFS0 partition entries! " , __func__ ) ;
return false ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( ! processNcaCtrSectionBlock ( ncmStorage , ncaId , & aes_ctx , nca_pfs0_offset + sizeof ( pfs0_header ) , nca_pfs0_entries , ( u64 ) nca_pfs0_header . file_cnt * sizeof ( pfs0_file_entry ) , false ) )
{
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to read Program NCA section #0 PFS0 partition entries! " , __func__ ) ;
free ( nca_pfs0_entries ) ;
return false ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
nca_pfs0_data_offset = ( nca_pfs0_offset + sizeof ( pfs0_header ) + ( ( u64 ) nca_pfs0_header . file_cnt * sizeof ( pfs0_file_entry ) ) + ( u64 ) nca_pfs0_header . str_table_size ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Looking for META magic
for ( i = 0 ; i < nca_pfs0_header . file_cnt ; i + + )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
u64 nca_pfs0_cur_file_offset = ( nca_pfs0_data_offset + nca_pfs0_entries [ i ] . file_offset ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Read and decrypt NPDM header
if ( ! processNcaCtrSectionBlock ( ncmStorage , ncaId , & aes_ctx , nca_pfs0_cur_file_offset , & npdm_header , sizeof ( npdm_t ) , false ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to read Program NCA section #0 PFS0 entry #%u! " , __func__ , i ) ;
free ( nca_pfs0_entries ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( __builtin_bswap32 ( npdm_header . magic ) = = META_MAGIC )
{
found_meta = true ;
meta_offset = nca_pfs0_cur_file_offset ;
acid_pubkey_offset = ( meta_offset + ( u64 ) npdm_header . acid_offset + ( u64 ) NPDM_SIGNATURE_SIZE ) ;
break ;
}
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
free ( nca_pfs0_entries ) ;
if ( ! found_meta )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to find NPDM entry in Program NCA section #0 PFS0 partition! " , __func__ ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
// Calculate block offsets
block_hash_table_offset = ( hash_table_offset + ( ( ( acid_pubkey_offset - nca_pfs0_offset ) / ( u64 ) dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ) ) * ( u64 ) SHA256_HASH_SIZE ) ;
block_hash_table_end_offset = ( hash_table_offset + ( ( ( acid_pubkey_offset + ( u64 ) NPDM_SIGNATURE_SIZE - nca_pfs0_offset ) / ( u64 ) dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ) * ( u64 ) SHA256_HASH_SIZE ) ) ;
block_start_offset [ 0 ] = ( nca_pfs0_offset + ( ( ( acid_pubkey_offset - nca_pfs0_offset ) / ( u64 ) dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ) * ( u64 ) dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Make sure our block doesn't pass PFS0 end offset
if ( ( block_start_offset [ 0 ] - nca_pfs0_offset + dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ) > dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . pfs0_size )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
block_size [ 0 ] = ( dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . pfs0_size - ( block_start_offset [ 0 ] - nca_pfs0_offset ) ) ;
2020-04-15 21:50:07 +01:00
} else {
2020-10-09 10:58:53 +01:00
block_size [ 0 ] = ( u64 ) dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
block_data [ 0 ] = malloc ( block_size [ 0 ] ) ;
if ( ! block_data [ 0 ] )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 0! " , __func__ ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
// Read and decrypt block
if ( ! processNcaCtrSectionBlock ( ncmStorage , ncaId , & aes_ctx , block_start_offset [ 0 ] , block_data [ 0 ] , block_size [ 0 ] , false ) )
{
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to read Program NCA section #0 PFS0 NPDM block 0! " , __func__ ) ;
free ( block_data [ 0 ] ) ;
return false ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Make sure that 1 block will cover all patched bytes, otherwise we'll have to recalculate another hash block
if ( block_hash_table_offset ! = block_hash_table_end_offset )
{
sig_write_size [ 1 ] = ( acid_pubkey_offset - block_start_offset [ 0 ] + ( u64 ) NPDM_SIGNATURE_SIZE - block_size [ 0 ] ) ;
sig_write_size [ 0 ] = ( ( u64 ) NPDM_SIGNATURE_SIZE - sig_write_size [ 1 ] ) ;
} else {
sig_write_size [ 0 ] = ( u64 ) NPDM_SIGNATURE_SIZE ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Patch ACID public key changing it to a self-generated pubkey
memcpy ( block_data [ 0 ] + ( acid_pubkey_offset - block_start_offset [ 0 ] ) , rsa_get_public_key ( ) , sig_write_size [ 0 ] ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Calculate new block hash
sha256CalculateHash ( block_hash [ 0 ] , block_data [ 0 ] , block_size [ 0 ] ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( block_hash_table_offset ! = block_hash_table_end_offset )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
block_start_offset [ 1 ] = ( nca_pfs0_offset + ( ( ( acid_pubkey_offset + ( u64 ) NPDM_SIGNATURE_SIZE - nca_pfs0_offset ) / ( u64 ) dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ) * ( u64 ) dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ) ) ;
if ( ( block_start_offset [ 1 ] - nca_pfs0_offset + dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ) > dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . pfs0_size )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
block_size [ 1 ] = ( dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . pfs0_size - ( block_start_offset [ 1 ] - nca_pfs0_offset ) ) ;
} else {
block_size [ 1 ] = ( u64 ) dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . block_size ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
block_data [ 1 ] = malloc ( block_size [ 1 ] ) ;
if ( ! block_data [ 1 ] )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to allocate memory for Program NCA section #0 PFS0 NPDM block 1! " , __func__ ) ;
free ( block_data [ 0 ] ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
if ( ! processNcaCtrSectionBlock ( ncmStorage , ncaId , & aes_ctx , block_start_offset [ 1 ] , block_data [ 1 ] , block_size [ 1 ] , false ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to read Program NCA section #0 PFS0 NPDM block 1! " , __func__ ) ;
free ( block_data [ 0 ] ) ;
free ( block_data [ 1 ] ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
memcpy ( block_data [ 1 ] , rsa_get_public_key ( ) + sig_write_size [ 0 ] , sig_write_size [ 1 ] ) ;
sha256CalculateHash ( block_hash [ 1 ] , block_data [ 1 ] , block_size [ 1 ] ) ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
hash_table = malloc ( dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . hash_table_size ) ;
if ( ! hash_table )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to allocate memory for Program NCA section #0 PFS0 hash table! " , __func__ ) ;
free ( block_data [ 0 ] ) ;
if ( block_data [ 1 ] ) free ( block_data [ 1 ] ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
if ( ! processNcaCtrSectionBlock ( ncmStorage , ncaId , & aes_ctx , hash_table_offset , hash_table , dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . hash_table_size , false ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to read Program NCA section #0 PFS0 hash table! " , __func__ ) ;
free ( block_data [ 0 ] ) ;
if ( block_data [ 1 ] ) free ( block_data [ 1 ] ) ;
free ( hash_table ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
// Update block hashes
memcpy ( hash_table + ( block_hash_table_offset - hash_table_offset ) , block_hash [ 0 ] , SHA256_HASH_SIZE ) ;
if ( block_hash_table_offset ! = block_hash_table_end_offset ) memcpy ( hash_table + ( block_hash_table_end_offset - hash_table_offset ) , block_hash [ 1 ] , SHA256_HASH_SIZE ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Calculate PFS0 superblock master hash
sha256CalculateHash ( dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . master_hash , hash_table , dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . hash_table_size ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Calculate section hash
sha256CalculateHash ( dec_nca_header - > section_hashes [ 0 ] , & ( dec_nca_header - > fs_headers [ 0 ] ) , sizeof ( nca_fs_header_t ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Recreate NPDM signature
if ( ! rsa_sign ( & ( dec_nca_header - > magic ) , NPDM_SIGNATURE_AREA_SIZE , dec_nca_header - > npdm_key_sig , NPDM_SIGNATURE_SIZE ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to recreate Program NCA NPDM signature! " , __func__ ) ;
free ( block_data [ 0 ] ) ;
if ( block_data [ 1 ] ) free ( block_data [ 1 ] ) ;
free ( hash_table ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
// Reencrypt relevant data blocks
if ( ! processNcaCtrSectionBlock ( ncmStorage , ncaId , & aes_ctx , block_start_offset [ 0 ] , block_data [ 0 ] , block_size [ 0 ] , true ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to encrypt Program NCA section #0 PFS0 NPDM block 0! " , __func__ ) ;
free ( block_data [ 0 ] ) ;
if ( block_data [ 1 ] ) free ( block_data [ 1 ] ) ;
free ( hash_table ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
if ( block_hash_table_offset ! = block_hash_table_end_offset )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
if ( ! processNcaCtrSectionBlock ( ncmStorage , ncaId , & aes_ctx , block_start_offset [ 1 ] , block_data [ 1 ] , block_size [ 1 ] , true ) )
{
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to encrypt Program NCA section #0 PFS0 NPDM block 1! " , __func__ ) ;
free ( block_data [ 0 ] ) ;
free ( block_data [ 1 ] ) ;
free ( hash_table ) ;
return false ;
}
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( ! processNcaCtrSectionBlock ( ncmStorage , ncaId , & aes_ctx , hash_table_offset , hash_table , dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . hash_table_size , true ) )
2020-04-15 21:50:07 +01:00
{
breaks + + ;
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to encrypt Program NCA section #0 PFS0 hash table! " , __func__ ) ;
2020-04-15 21:50:07 +01:00
free ( block_data [ 0 ] ) ;
if ( block_data [ 1 ] ) free ( block_data [ 1 ] ) ;
free ( hash_table ) ;
2020-10-09 10:58:53 +01:00
return false ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Save data to the output struct so we can write it later
// The caller function must free these data pointers
nca_program_mod_data * tmp_mod_data = realloc ( * output , ( * cur_mod_cnt + 1 ) * sizeof ( nca_program_mod_data ) ) ;
if ( ! tmp_mod_data )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to reallocate Program NCA mod data buffer! " , __func__ ) ;
free ( block_data [ 0 ] ) ;
if ( block_data [ 1 ] ) free ( block_data [ 1 ] ) ;
free ( hash_table ) ;
return false ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
memset ( & ( tmp_mod_data [ * cur_mod_cnt ] ) , 0 , sizeof ( nca_program_mod_data ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
tmp_mod_data [ * cur_mod_cnt ] . nca_index = idx ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
tmp_mod_data [ * cur_mod_cnt ] . hash_table = hash_table ;
tmp_mod_data [ * cur_mod_cnt ] . hash_table_offset = hash_table_offset ;
tmp_mod_data [ * cur_mod_cnt ] . hash_table_size = dec_nca_header - > fs_headers [ 0 ] . pfs0_superblock . hash_table_size ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
tmp_mod_data [ * cur_mod_cnt ] . block_mod_cnt = ( block_hash_table_offset ! = block_hash_table_end_offset ? 2 : 1 ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
tmp_mod_data [ * cur_mod_cnt ] . block_data [ 0 ] = block_data [ 0 ] ;
tmp_mod_data [ * cur_mod_cnt ] . block_offset [ 0 ] = block_start_offset [ 0 ] ;
tmp_mod_data [ * cur_mod_cnt ] . block_size [ 0 ] = block_size [ 0 ] ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( block_hash_table_offset ! = block_hash_table_end_offset )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
tmp_mod_data [ * cur_mod_cnt ] . block_data [ 1 ] = block_data [ 1 ] ;
tmp_mod_data [ * cur_mod_cnt ] . block_offset [ 1 ] = block_start_offset [ 1 ] ;
tmp_mod_data [ * cur_mod_cnt ] . block_size [ 1 ] = block_size [ 1 ] ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
* output = tmp_mod_data ;
tmp_mod_data = NULL ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
* cur_mod_cnt + = 1 ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
return true ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
bool retrieveCnmtNcaData ( NcmStorageId curStorageId , u8 * ncaBuf , cnmt_xml_program_info * xml_program_info , cnmt_xml_content_info * xml_content_info , u32 cnmtNcaIndex , nca_cnmt_mod_data * output , title_rights_ctx * rights_info )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
if ( ! ncaBuf | | ! xml_program_info | | ! xml_content_info | | ! output | | ! rights_info )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to retrieve CNMT NCA! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
nca_header_t dec_header ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u32 i , j , k = 0 ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u64 section_offset ;
u64 section_size ;
u8 * section_data = NULL ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
Aes128CtrContext aes_ctx ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u64 nca_pfs0_offset ;
u64 nca_pfs0_str_table_offset ;
u64 nca_pfs0_data_offset ;
pfs0_header nca_pfs0_header ;
pfs0_file_entry * nca_pfs0_entries = NULL ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
bool found_cnmt = false ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u64 title_cnmt_offset ;
u64 title_cnmt_size ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
cnmt_header title_cnmt_header ;
cnmt_extended_header title_cnmt_extended_header ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u64 digest_offset ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Generate filename for our required CNMT file
char cnmtFileName [ 50 ] = { ' \0 ' } ;
snprintf ( cnmtFileName , MAX_CHARACTERS ( cnmtFileName ) , " %s_%016lx.cnmt " , getTitleType ( xml_program_info - > type ) , xml_program_info - > title_id ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Decrypt the NCA header
// Don't retrieve the ticket and/or titlekey if we're dealing with a Patch with titlekey crypto bundled with the inserted gamecard
if ( ! decryptNcaHeader ( ncaBuf , xml_content_info [ cnmtNcaIndex ] . size , & dec_header , rights_info , xml_content_info [ cnmtNcaIndex ] . decrypted_nca_keys , ( curStorageId ! = NcmStorageId_GameCard ) ) ) return false ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( dec_header . fs_headers [ 0 ] . partition_type ! = NCA_FS_HEADER_PARTITION_PFS0 | | dec_header . fs_headers [ 0 ] . fs_type ! = NCA_FS_HEADER_FSTYPE_PFS0 )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: CNMT NCA section #0 doesn't hold a PFS0 partition! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( ! dec_header . fs_headers [ 0 ] . pfs0_superblock . pfs0_size )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid size for PFS0 partition in CNMT NCA section #0! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( dec_header . fs_headers [ 0 ] . crypt_type ! = NCA_FS_HEADER_CRYPT_CTR )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid AES crypt type for CNMT NCA section #0! (0x%02X) " , __func__ , dec_header . fs_headers [ 0 ] . crypt_type ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
bool has_rights_id = false ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
for ( i = 0 ; i < 0x10 ; i + + )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
if ( dec_header . rights_id [ i ] ! = 0 )
{
has_rights_id = true ;
2020-04-15 21:50:07 +01:00
break ;
2020-10-09 10:58:53 +01:00
}
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
// CNMT NCAs never use titlekey crypto
if ( has_rights_id )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: Rights ID field in CNMT NCA header not empty! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
// Modify distribution type
if ( curStorageId = = NcmStorageId_GameCard ) dec_header . distribution = 0 ;
section_offset = ( ( u64 ) dec_header . section_entries [ 0 ] . media_start_offset * ( u64 ) MEDIA_UNIT_SIZE ) ;
section_size = ( ( ( u64 ) dec_header . section_entries [ 0 ] . media_end_offset * ( u64 ) MEDIA_UNIT_SIZE ) - section_offset ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( ! section_offset | | section_offset < NCA_FULL_HEADER_LENGTH | | ! section_size )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid offset/size for CNMT NCA section #0! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
// Generate initial CTR
unsigned char ctr [ 0x10 ] ;
u64 ofs = ( section_offset > > 4 ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
for ( i = 0 ; i < 0x8 ; i + + )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
ctr [ i ] = dec_header . fs_headers [ 0 ] . section_ctr [ 0x08 - i - 1 ] ;
ctr [ 0x10 - i - 1 ] = ( unsigned char ) ( ofs & 0xFF ) ;
ofs > > = 8 ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
u8 ctr_key [ NCA_KEY_AREA_KEY_SIZE ] ;
memcpy ( ctr_key , xml_content_info [ cnmtNcaIndex ] . decrypted_nca_keys + ( NCA_KEY_AREA_KEY_SIZE * 2 ) , NCA_KEY_AREA_KEY_SIZE ) ;
aes128CtrContextCreate ( & aes_ctx , ctr_key , ctr ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
section_data = malloc ( section_size ) ;
if ( ! section_data )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to allocate memory for the decrypted CNMT NCA section #0! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
aes128CtrCrypt ( & aes_ctx , section_data , ncaBuf + section_offset , section_size ) ;
nca_pfs0_offset = dec_header . fs_headers [ 0 ] . pfs0_superblock . pfs0_offset ;
memcpy ( & nca_pfs0_header , section_data + nca_pfs0_offset , sizeof ( pfs0_header ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( __builtin_bswap32 ( nca_pfs0_header . magic ) ! = PFS0_MAGIC )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid magic word for CNMT NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X) \n Try running Lockpick_RCM to generate the keys file from scratch. " , __func__ , __builtin_bswap32 ( nca_pfs0_header . magic ) ) ;
free ( section_data ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
if ( ! nca_pfs0_header . file_cnt | | ! nca_pfs0_header . str_table_size )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: CNMT NCA section #0 PFS0 partition is empty! Wrong KAEK? \n Try running Lockpick_RCM to generate the keys file from scratch. " , __func__ ) ;
free ( section_data ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
nca_pfs0_entries = calloc ( nca_pfs0_header . file_cnt , sizeof ( pfs0_file_entry ) ) ;
if ( ! nca_pfs0_entries )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to allocate memory for CNMT NCA section #0 PFS0 partition entries! " , __func__ ) ;
free ( section_data ) ;
2020-04-15 21:50:07 +01:00
return false ;
}
2020-10-09 10:58:53 +01:00
memcpy ( nca_pfs0_entries , section_data + nca_pfs0_offset + sizeof ( pfs0_header ) , ( u64 ) nca_pfs0_header . file_cnt * sizeof ( pfs0_file_entry ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
nca_pfs0_str_table_offset = ( nca_pfs0_offset + sizeof ( pfs0_header ) + ( ( u64 ) nca_pfs0_header . file_cnt * sizeof ( pfs0_file_entry ) ) ) ;
nca_pfs0_data_offset = ( nca_pfs0_str_table_offset + ( u64 ) nca_pfs0_header . str_table_size ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Look for the CNMT
for ( i = 0 ; i < nca_pfs0_header . file_cnt ; i + + )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
u64 filename_offset = ( nca_pfs0_str_table_offset + nca_pfs0_entries [ i ] . filename_offset ) ;
if ( ! strncasecmp ( ( char * ) section_data + filename_offset , cnmtFileName , strlen ( cnmtFileName ) ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
found_cnmt = true ;
title_cnmt_offset = ( nca_pfs0_data_offset + nca_pfs0_entries [ i ] . file_offset ) ;
title_cnmt_size = nca_pfs0_entries [ i ] . file_size ;
2020-04-15 21:50:07 +01:00
break ;
}
}
2020-10-09 10:58:53 +01:00
free ( nca_pfs0_entries ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
if ( ! found_cnmt )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to find file \" %s \" in PFS0 partition from CNMT NCA section #0! " , __func__ , cnmtFileName ) ;
free ( section_data ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
memcpy ( & title_cnmt_header , section_data + title_cnmt_offset , sizeof ( cnmt_header ) ) ;
memcpy ( & title_cnmt_extended_header , section_data + title_cnmt_offset + sizeof ( cnmt_header ) , sizeof ( cnmt_extended_header ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Fill information for our CNMT XML
digest_offset = ( title_cnmt_offset + title_cnmt_size - ( u64 ) SHA256_HASH_SIZE ) ;
memcpy ( xml_program_info - > digest , section_data + digest_offset , SHA256_HASH_SIZE ) ;
convertDataToHexString ( xml_program_info - > digest , SHA256_HASH_SIZE , xml_program_info - > digest_str , ( SHA256_HASH_SIZE * 2 ) + 1 ) ;
xml_content_info [ cnmtNcaIndex ] . keyblob = ( dec_header . crypto_type2 > dec_header . crypto_type ? dec_header . crypto_type2 : dec_header . crypto_type ) ;
xml_program_info - > required_dl_sysver = title_cnmt_header . required_dl_sysver ;
xml_program_info - > min_keyblob = ( rights_info - > has_rights_id ? rights_info - > rights_id [ 15 ] : xml_content_info [ cnmtNcaIndex ] . keyblob ) ;
xml_program_info - > min_sysver = title_cnmt_extended_header . min_sysver ;
xml_program_info - > patch_tid = title_cnmt_extended_header . patch_tid ;
xml_program_info - > min_appver = title_cnmt_extended_header . min_appver ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Retrieve the ID offset and content record offset for each of our NCAs (except the CNMT NCA)
// Also wipe each of the content records we're gonna replace
for ( i = 0 ; i < ( xml_program_info - > nca_cnt - 1 ) ; i + + ) // Discard CNMT NCA
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
for ( j = 0 ; j < title_cnmt_header . content_cnt ; j + + )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
cnmt_content_record cnt_record ;
memcpy ( & cnt_record , section_data + title_cnmt_offset + sizeof ( cnmt_header ) + ( u64 ) title_cnmt_header . extended_header_size + ( j * sizeof ( cnmt_content_record ) ) , sizeof ( cnmt_content_record ) ) ;
if ( memcmp ( xml_content_info [ i ] . nca_id , cnt_record . nca_id , SHA256_HASH_SIZE / 2 ) ! = 0 ) continue ;
// Save content record offset
xml_content_info [ i ] . cnt_record_offset = ( j * sizeof ( cnmt_content_record ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Empty CNMT content record
memset ( section_data + title_cnmt_offset + sizeof ( cnmt_header ) + ( u64 ) title_cnmt_header . extended_header_size + ( j * sizeof ( cnmt_content_record ) ) , 0 , sizeof ( cnmt_content_record ) ) ;
// Increase counter
k + + ;
break ;
2020-04-15 21:50:07 +01:00
}
}
2020-10-09 10:58:53 +01:00
// Verify counter
if ( k ! = ( xml_program_info - > nca_cnt - 1 ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid content record entries in the CNMT NCA! " , __func__ ) ;
free ( section_data ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
// Replace input buffer data in-place
memcpy ( ncaBuf , & dec_header , NCA_FULL_HEADER_LENGTH ) ;
memcpy ( ncaBuf + section_offset , section_data , section_size ) ;
free ( section_data ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Update offsets
nca_pfs0_offset + = section_offset ;
title_cnmt_offset + = section_offset ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Save data to output struct
output - > section_offset = section_offset ;
output - > section_size = section_size ;
output - > hash_table_offset = ( section_offset + dec_header . fs_headers [ 0 ] . pfs0_superblock . hash_table_offset ) ;
output - > hash_block_size = dec_header . fs_headers [ 0 ] . pfs0_superblock . block_size ;
output - > hash_block_cnt = ( dec_header . fs_headers [ 0 ] . pfs0_superblock . hash_table_size / SHA256_HASH_SIZE ) ;
output - > pfs0_offset = nca_pfs0_offset ;
output - > pfs0_size = dec_header . fs_headers [ 0 ] . pfs0_superblock . pfs0_size ;
output - > title_cnmt_offset = title_cnmt_offset ;
output - > title_cnmt_size = title_cnmt_size ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
return true ;
}
bool patchCnmtNca ( u8 * ncaBuf , u64 ncaBufSize , cnmt_xml_program_info * xml_program_info , cnmt_xml_content_info * xml_content_info , nca_cnmt_mod_data * cnmt_mod )
{
if ( ! ncaBuf | | ! ncaBufSize | | ! xml_program_info | | xml_program_info - > nca_cnt < = 1 | | ! xml_content_info | | ! cnmt_mod )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to patch CNMT NCA! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
u32 i ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u32 nca_cnt = ( xml_program_info - > nca_cnt - 1 ) ; // Discard CNMT NCA
cnmt_header title_cnmt_header ;
cnmt_content_record title_cnmt_content_record ;
u64 title_cnmt_content_records_offset ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
nca_header_t dec_header ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
Aes128CtrContext aes_ctx ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Copy CNMT header
memcpy ( & title_cnmt_header , ncaBuf + cnmt_mod - > title_cnmt_offset , sizeof ( cnmt_header ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Calculate the start offset for the content records
title_cnmt_content_records_offset = ( cnmt_mod - > title_cnmt_offset + sizeof ( cnmt_header ) + ( u64 ) title_cnmt_header . extended_header_size ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Write content records
for ( i = 0 ; i < nca_cnt ; i + + )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
memset ( & title_cnmt_content_record , 0 , sizeof ( cnmt_content_record ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
memcpy ( title_cnmt_content_record . hash , xml_content_info [ i ] . hash , SHA256_HASH_SIZE ) ;
memcpy ( title_cnmt_content_record . nca_id , xml_content_info [ i ] . nca_id , SHA256_HASH_SIZE / 2 ) ;
convertU64ToNcaSize ( xml_content_info [ i ] . size , title_cnmt_content_record . size ) ;
title_cnmt_content_record . type = xml_content_info [ i ] . type ;
title_cnmt_content_record . id_offset = xml_content_info [ i ] . id_offset ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
memcpy ( ncaBuf + title_cnmt_content_records_offset + xml_content_info [ i ] . cnt_record_offset , & title_cnmt_content_record , sizeof ( cnmt_content_record ) ) ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
// Recalculate block hashes
for ( i = 0 ; i < cnmt_mod - > hash_block_cnt ; i + + )
{
u64 blk_offset = ( ( u64 ) i * cnmt_mod - > hash_block_size ) ;
u64 rest_size = ( cnmt_mod - > pfs0_size - blk_offset ) ;
u64 blk_size = ( rest_size > cnmt_mod - > hash_block_size ? cnmt_mod - > hash_block_size : rest_size ) ;
sha256CalculateHash ( ncaBuf + cnmt_mod - > hash_table_offset + ( i * SHA256_HASH_SIZE ) , ncaBuf + cnmt_mod - > pfs0_offset + blk_offset , blk_size ) ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Copy header to struct
memcpy ( & dec_header , ncaBuf , sizeof ( nca_header_t ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Calculate PFS0 superblock master hash
sha256CalculateHash ( dec_header . fs_headers [ 0 ] . pfs0_superblock . master_hash , ncaBuf + cnmt_mod - > hash_table_offset , dec_header . fs_headers [ 0 ] . pfs0_superblock . hash_table_size ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Calculate section hash
sha256CalculateHash ( dec_header . section_hashes [ 0 ] , & ( dec_header . fs_headers [ 0 ] ) , sizeof ( nca_fs_header_t ) ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Generate initial CTR
unsigned char ctr [ 0x10 ] ;
u64 ofs = ( cnmt_mod - > section_offset > > 4 ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
for ( i = 0 ; i < 0x8 ; i + + )
{
ctr [ i ] = dec_header . fs_headers [ 0 ] . section_ctr [ 0x08 - i - 1 ] ;
ctr [ 0x10 - i - 1 ] = ( unsigned char ) ( ofs & 0xFF ) ;
ofs > > = 8 ;
}
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
u8 ctr_key [ NCA_KEY_AREA_KEY_SIZE ] ;
memcpy ( ctr_key , xml_content_info [ xml_program_info - > nca_cnt - 1 ] . decrypted_nca_keys + ( NCA_KEY_AREA_KEY_SIZE * 2 ) , NCA_KEY_AREA_KEY_SIZE ) ;
aes128CtrContextCreate ( & aes_ctx , ctr_key , ctr ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Reencrypt CNMT NCA
if ( ! encryptNcaHeader ( & dec_header , ncaBuf , ncaBufSize ) )
2020-04-15 21:50:07 +01:00
{
2020-10-09 10:58:53 +01:00
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to encrypt modified CNMT NCA header! " , __func__ ) ;
return false ;
2020-04-15 21:50:07 +01:00
}
2020-10-09 10:58:53 +01:00
aes128CtrCrypt ( & aes_ctx , ncaBuf + cnmt_mod - > section_offset , ncaBuf + cnmt_mod - > section_offset , cnmt_mod - > section_size ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
// Calculate CNMT NCA SHA-256 checksum and fill information for our CNMT XML
sha256CalculateHash ( xml_content_info [ xml_program_info - > nca_cnt - 1 ] . hash , ncaBuf , ncaBufSize ) ;
convertDataToHexString ( xml_content_info [ xml_program_info - > nca_cnt - 1 ] . hash , SHA256_HASH_SIZE , xml_content_info [ xml_program_info - > nca_cnt - 1 ] . hash_str , ( SHA256_HASH_SIZE * 2 ) + 1 ) ;
memcpy ( xml_content_info [ xml_program_info - > nca_cnt - 1 ] . nca_id , xml_content_info [ xml_program_info - > nca_cnt - 1 ] . hash , SHA256_HASH_SIZE / 2 ) ;
convertDataToHexString ( xml_content_info [ xml_program_info - > nca_cnt - 1 ] . nca_id , SHA256_HASH_SIZE / 2 , xml_content_info [ xml_program_info - > nca_cnt - 1 ] . nca_id_str , SHA256_HASH_SIZE + 1 ) ;
2020-04-15 21:50:07 +01:00
2020-10-09 10:58:53 +01:00
return true ;
}