2020-10-22 05:38:14 +01:00
/*
* main . c
*
* Copyright ( c ) 2020 , DarkMatterCore < pabloacurielz @ gmail . com > .
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
2021-03-25 19:26:58 +00:00
* nxdumptool is free software : you can redistribute it and / or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation , either version 3 of the License , or
* ( at your option ) any later version .
2020-10-22 05:38:14 +01:00
*
2021-03-25 19:26:58 +00:00
* nxdumptool is distributed in the hope that it will be useful ,
* but WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the
* GNU General Public License for more details .
2020-10-22 05:38:14 +01:00
*
* You should have received a copy of the GNU General Public License
2021-03-25 19:26:58 +00:00
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
2020-10-22 05:38:14 +01:00
*/
2021-03-26 04:35:14 +00:00
# include "nxdt_utils.h"
2020-10-22 05:38:14 +01:00
# include "gamecard.h"
# include "title.h"
# include "cnmt.h"
# include "program_info.h"
# include "nacp.h"
# include "legal_info.h"
# include "cert.h"
# include "usb.h"
2022-06-27 03:58:03 +01:00
# define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
# define OUTPATH " / nsp / "
2020-10-22 05:38:14 +01:00
2021-09-04 05:17:17 +01:00
bool g_borealisInitialized = false ;
2021-04-26 00:10:34 +01:00
static PadState g_padState = { 0 } ;
2021-02-16 12:22:14 +00:00
typedef struct
{
void * data ;
size_t data_written ;
size_t total_size ;
bool error ;
bool transfer_cancelled ;
} ThreadSharedData ;
2020-10-22 05:38:14 +01:00
static const char * dump_type_strings [ ] = {
" dump base application " ,
" dump update " ,
2022-12-04 10:29:47 +00:00
" dump dlc " ,
" dump dlc update "
2020-10-22 05:38:14 +01:00
} ;
static const u32 dump_type_strings_count = MAX_ELEMENTS ( dump_type_strings ) ;
typedef struct {
char str [ 64 ] ;
2022-06-27 03:58:03 +01:00
u32 val ;
2020-10-22 05:38:14 +01:00
} options_t ;
static options_t options [ ] = {
2022-06-27 03:58:03 +01:00
{ " set download distribution type " , 0 } ,
{ " remove console specific data " , 1 } ,
{ " remove titlekey crypto (overrides previous option) " , 0 } ,
{ " disable linked account requirement " , 0 } ,
{ " enable screenshots " , 0 } ,
{ " enable video capture " , 0 } ,
{ " disable hdcp " , 0 } ,
2022-07-10 18:45:18 +01:00
{ " append authoringtool data " , 1 } ,
2022-06-27 03:58:03 +01:00
{ " output device " , 0 }
2020-10-22 05:38:14 +01:00
} ;
static const u32 options_count = MAX_ELEMENTS ( options ) ;
2021-02-16 12:22:14 +00:00
static Mutex g_conMutex = 0 ;
2022-06-27 03:58:03 +01:00
static UsbHsFsDevice * ums_devices = NULL ;
static u32 ums_device_count = 0 ;
2021-04-26 00:10:34 +01:00
static void utilsScanPads ( void )
{
padUpdate ( & g_padState ) ;
}
static u64 utilsGetButtonsDown ( void )
{
return padGetButtonsDown ( & g_padState ) ;
}
static u64 utilsGetButtonsHeld ( void )
{
return padGetButtons ( & g_padState ) ;
}
static void utilsWaitForButtonPress ( u64 flag )
{
/* Don't consider stick movement as button inputs. */
if ( ! flag ) flag = ~ ( HidNpadButton_StickLLeft | HidNpadButton_StickLRight | HidNpadButton_StickLUp | HidNpadButton_StickLDown | HidNpadButton_StickRLeft | HidNpadButton_StickRRight | \
HidNpadButton_StickRUp | HidNpadButton_StickRDown ) ;
2022-07-05 02:04:28 +01:00
2021-04-26 00:10:34 +01:00
while ( appletMainLoop ( ) )
{
utilsScanPads ( ) ;
if ( utilsGetButtonsDown ( ) & flag ) break ;
}
}
2020-10-22 05:38:14 +01:00
static void consolePrint ( const char * text , . . . )
{
2021-02-16 12:22:14 +00:00
mutexLock ( & g_conMutex ) ;
2020-10-22 05:38:14 +01:00
va_list v ;
va_start ( v , text ) ;
vfprintf ( stdout , text , v ) ;
va_end ( v ) ;
2021-02-16 12:22:14 +00:00
mutexUnlock ( & g_conMutex ) ;
}
static void consoleRefresh ( void )
{
mutexLock ( & g_conMutex ) ;
2022-07-14 13:10:03 +01:00
fflush ( stdout ) ;
2020-10-22 05:38:14 +01:00
consoleUpdate ( NULL ) ;
2021-02-16 12:22:14 +00:00
mutexUnlock ( & g_conMutex ) ;
2020-10-22 05:38:14 +01:00
}
2021-02-16 12:22:14 +00:00
static void dump_thread_func ( void * arg )
2020-10-22 05:38:14 +01:00
{
2021-02-16 12:22:14 +00:00
ThreadSharedData * shared_data = ( ThreadSharedData * ) arg ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
TitleInfo * title_info = NULL ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
bool set_download_type = ( options [ 0 ] . val = = 1 ) ;
bool remove_console_data = ( options [ 1 ] . val = = 1 ) ;
bool remove_titlekey_crypto = ( options [ 2 ] . val = = 1 ) ;
bool patch_sua = ( options [ 3 ] . val = = 1 ) ;
bool patch_screenshot = ( options [ 4 ] . val = = 1 ) ;
bool patch_video_capture = ( options [ 5 ] . val = = 1 ) ;
bool patch_hdcp = ( options [ 6 ] . val = = 1 ) ;
bool append_authoringtool_data = ( options [ 7 ] . val = = 1 ) ;
u32 output_device = options [ 8 ] . val ;
2021-02-16 12:22:14 +00:00
bool success = false ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
UsbHsFsDevice * ums_device = ( output_device < 2 ? NULL : & ( ums_devices [ output_device - 2 ] ) ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
u64 free_space = 0 ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
u8 * buf = NULL ;
char * dump_name = NULL , * path = NULL ;
2022-06-27 03:58:03 +01:00
FILE * fd = NULL ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
NcaContext * nca_ctx = NULL ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
NcaContext * meta_nca_ctx = NULL ;
ContentMetaContext cnmt_ctx = { 0 } ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
ProgramInfoContext * program_info_ctx = NULL ;
2021-02-16 12:22:14 +00:00
u32 program_idx = 0 , program_count = 0 ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
NacpContext * nacp_ctx = NULL ;
2021-02-16 12:22:14 +00:00
u32 control_idx = 0 , control_count = 0 ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
LegalInfoContext * legal_info_ctx = NULL ;
2021-02-16 12:22:14 +00:00
u32 legal_info_idx = 0 , legal_info_count = 0 ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
Ticket tik = { 0 } ;
TikCommonBlock * tik_common_block = NULL ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
u8 * raw_cert_chain = NULL ;
u64 raw_cert_chain_size = 0 ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
PartitionFileSystemFileContext pfs_file_ctx = { 0 } ;
pfsInitializeFileContext ( & pfs_file_ctx ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
char entry_name [ 64 ] = { 0 } ;
2020-10-26 00:03:02 +00:00
u64 nsp_header_size = 0 , nsp_size = 0 , nsp_offset = 0 ;
2020-10-26 06:39:33 +00:00
char * tmp_name = NULL ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
Sha256Context sha256_ctx = { 0 } ;
u8 sha256_hash [ SHA256_HASH_SIZE ] = { 0 } ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
if ( ! shared_data | | ! ( title_info = ( TitleInfo * ) shared_data - > data ) | | ! title_info - > content_count | | ! title_info - > content_infos ) goto end ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( ums_device & & ums_device - > write_protect )
{
consolePrint ( " device \" %s \" has write protection enabled! \n " , ums_device - > name ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
/* Allocate memory for the dump process. */
if ( ! ( buf = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ) )
{
consolePrint ( " buf alloc failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
/* Generate output path. */
2022-06-27 03:58:03 +01:00
if ( ! ( dump_name = titleGenerateFileName ( title_info , TitleNamingConvention_Full , output_device = = 0 ? TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly : TitleFileNameIllegalCharReplaceType_IllegalFsChars ) ) )
2020-10-22 05:38:14 +01:00
{
consolePrint ( " title generate file name failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 ) sprintf ( entry_name , " %s " OUTPATH , ums_device ? ums_device - > name : DEVOPTAB_SDMC_DEVICE ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( ! ( path = utilsGeneratePath ( entry_name , dump_name , " .nsp " ) ) )
2020-10-22 05:38:14 +01:00
{
consolePrint ( " generate path failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
// get device free space
if ( output_device ! = 1 & & ! utilsGetFileSystemStatsByPath ( path , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( ! ( nca_ctx = calloc ( title_info - > content_count , sizeof ( NcaContext ) ) ) )
{
consolePrint ( " nca ctx calloc failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-08-03 07:37:04 +01:00
// determine if we should initialize programinfo ctx
2022-06-26 02:34:31 +01:00
if ( append_authoringtool_data )
2020-10-22 05:38:14 +01:00
{
2021-08-03 07:37:04 +01:00
program_count = titleGetContentCountByType ( title_info , NcmContentType_Program ) ;
if ( program_count & & ! ( program_info_ctx = calloc ( program_count , sizeof ( ProgramInfoContext ) ) ) )
{
consolePrint ( " program info ctx calloc failed \n " ) ;
goto end ;
}
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2021-08-03 07:37:04 +01:00
// determine if we should initialize nacp ctx
if ( patch_sua | | patch_screenshot | | patch_video_capture | | patch_hdcp | | append_authoringtool_data )
2020-10-22 05:38:14 +01:00
{
2021-08-03 07:37:04 +01:00
control_count = titleGetContentCountByType ( title_info , NcmContentType_Control ) ;
if ( control_count & & ! ( nacp_ctx = calloc ( control_count , sizeof ( NacpContext ) ) ) )
{
consolePrint ( " nacp ctx calloc failed \n " ) ;
goto end ;
}
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2021-08-03 07:37:04 +01:00
// determine if we should initialize legalinfo ctx
if ( append_authoringtool_data )
2020-10-22 05:38:14 +01:00
{
2021-08-03 07:37:04 +01:00
legal_info_count = titleGetContentCountByType ( title_info , NcmContentType_LegalInformation ) ;
if ( legal_info_count & & ! ( legal_info_ctx = calloc ( legal_info_count , sizeof ( LegalInfoContext ) ) ) )
{
consolePrint ( " legal info ctx calloc failed \n " ) ;
goto end ;
}
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// set meta nca as the last nca
meta_nca_ctx = & ( nca_ctx [ title_info - > content_count - 1 ] ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( ! ncaInitializeContext ( meta_nca_ctx , title_info - > storage_id , ( title_info - > storage_id = = NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0 ) , \
2022-06-29 08:55:46 +01:00
titleGetContentInfoByTypeAndIdOffset ( title_info , NcmContentType_Meta , 0 ) , title_info - > version . value , & tik ) )
2020-10-22 05:38:14 +01:00
{
consolePrint ( " Meta nca initialize ctx failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
consolePrint ( " Meta nca initialize ctx succeeded \n " ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( ! cnmtInitializeContext ( & cnmt_ctx , meta_nca_ctx ) )
{
consolePrint ( " cnmt initialize ctx failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
consolePrint ( " cnmt initialize ctx succeeded (%s) \n " , meta_nca_ctx - > content_id_str ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// initialize nca context
// initialize content type context
// generate nca patches (if needed)
// generate content type xml
for ( u32 i = 0 , j = 0 ; i < title_info - > content_count ; i + + )
{
// skip meta nca since we already initialized it
NcmContentInfo * content_info = & ( title_info - > content_infos [ i ] ) ;
if ( content_info - > content_type = = NcmContentType_Meta ) continue ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
NcaContext * cur_nca_ctx = & ( nca_ctx [ j ] ) ;
2022-06-29 08:55:46 +01:00
if ( ! ncaInitializeContext ( cur_nca_ctx , title_info - > storage_id , ( title_info - > storage_id = = NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0 ) , content_info , title_info - > version . value , & tik ) )
2020-10-22 05:38:14 +01:00
{
consolePrint ( " %s #%u initialize nca ctx failed \n " , titleGetNcmContentTypeName ( content_info - > content_type ) , content_info - > id_offset ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
consolePrint ( " %s #%u initialize nca ctx succeeded \n " , titleGetNcmContentTypeName ( content_info - > content_type ) , content_info - > id_offset ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// don't go any further with this nca if we can't access its fs data because it's pointless
2021-06-23 19:27:06 +01:00
// TODO: add preload warning
2020-10-22 05:38:14 +01:00
if ( cur_nca_ctx - > rights_id_available & & ! cur_nca_ctx - > titlekey_retrieved )
{
j + + ;
continue ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// set download distribution type
// has no effect if this nca uses NcaDistributionType_Download
if ( set_download_type ) ncaSetDownloadDistributionType ( cur_nca_ctx ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// remove titlekey crypto
// has no effect if this nca doesn't use titlekey crypto
2021-05-11 07:00:33 +01:00
if ( remove_titlekey_crypto & & ! ncaRemoveTitleKeyCrypto ( cur_nca_ctx ) )
2020-10-22 05:38:14 +01:00
{
consolePrint ( " nca remove titlekey crypto failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
if ( ! cur_nca_ctx - > fs_ctx [ 0 ] . has_sparse_layer )
2020-10-22 05:38:14 +01:00
{
2022-06-29 13:41:58 +01:00
switch ( content_info - > content_type )
2020-10-22 05:38:14 +01:00
{
2022-06-29 13:41:58 +01:00
case NcmContentType_Program :
2020-10-22 05:38:14 +01:00
{
2022-06-29 13:41:58 +01:00
// don't proceed if we didn't allocate programinfo ctx or if we're dealing with a sparse layer
if ( ! program_count | | ! program_info_ctx ) break ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
ProgramInfoContext * cur_program_info_ctx = & ( program_info_ctx [ program_idx ] ) ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
if ( ! programInfoInitializeContext ( cur_program_info_ctx , cur_nca_ctx ) )
{
consolePrint ( " initialize program info ctx failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
if ( ! programInfoGenerateAuthoringToolXml ( cur_program_info_ctx ) )
{
consolePrint ( " program info xml failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
program_idx + + ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
consolePrint ( " initialize program info ctx succeeded (%s) \n " , cur_nca_ctx - > content_id_str ) ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
break ;
2020-10-22 05:38:14 +01:00
}
2022-06-29 13:41:58 +01:00
case NcmContentType_Control :
2020-10-22 05:38:14 +01:00
{
2022-06-29 13:41:58 +01:00
// don't proceed if we didn't allocate nacp ctx
if ( ! control_count | | ! nacp_ctx ) break ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
NacpContext * cur_nacp_ctx = & ( nacp_ctx [ control_idx ] ) ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
if ( ! nacpInitializeContext ( cur_nacp_ctx , cur_nca_ctx ) )
{
consolePrint ( " initialize nacp ctx failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
if ( ! nacpGenerateNcaPatch ( cur_nacp_ctx , patch_sua , patch_screenshot , patch_video_capture , patch_hdcp ) )
{
consolePrint ( " nacp nca patch failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
if ( append_authoringtool_data & & ! nacpGenerateAuthoringToolXml ( cur_nacp_ctx , title_info - > version . value , cnmtGetRequiredTitleVersion ( & cnmt_ctx ) ) )
{
consolePrint ( " nacp xml failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
control_idx + + ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
consolePrint ( " initialize nacp ctx succeeded (%s) \n " , cur_nca_ctx - > content_id_str ) ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
break ;
2020-10-22 05:38:14 +01:00
}
2022-06-29 13:41:58 +01:00
case NcmContentType_LegalInformation :
2020-10-22 05:38:14 +01:00
{
2022-06-29 13:41:58 +01:00
// don't proceed if we didn't allocate legalinfo ctx
if ( ! legal_info_count | | ! legal_info_ctx ) break ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
LegalInfoContext * cur_legal_info_ctx = & ( legal_info_ctx [ legal_info_idx ] ) ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
if ( ! legalInfoInitializeContext ( cur_legal_info_ctx , cur_nca_ctx ) )
{
consolePrint ( " initialize legal info ctx failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
legal_info_idx + + ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
consolePrint ( " initialize legal info ctx succeeded (%s) \n " , cur_nca_ctx - > content_id_str ) ;
2022-07-05 02:04:28 +01:00
2022-06-29 13:41:58 +01:00
break ;
2020-10-22 05:38:14 +01:00
}
2022-06-29 13:41:58 +01:00
default :
break ;
2020-10-22 05:38:14 +01:00
}
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( ! ncaEncryptHeader ( cur_nca_ctx ) )
{
consolePrint ( " %s #%u encrypt nca header failed \n " , titleGetNcmContentTypeName ( content_info - > content_type ) , content_info - > id_offset ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
j + + ;
}
2022-07-05 02:04:28 +01:00
2022-07-14 13:10:03 +01:00
consoleRefresh ( ) ;
2020-10-22 05:38:14 +01:00
// generate cnmt xml right away even though we don't yet have all the data we need
// This is because we need its size to calculate the full nsp size
2021-08-03 07:37:04 +01:00
if ( append_authoringtool_data & & ! cnmtGenerateAuthoringToolXml ( & cnmt_ctx , nca_ctx , title_info - > content_count ) )
2020-10-22 05:38:14 +01:00
{
consolePrint ( " cnmt xml #1 failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
bool retrieve_tik_cert = ( ! remove_titlekey_crypto & & tik . size > 0 ) ;
if ( retrieve_tik_cert )
{
if ( ! ( tik_common_block = tikGetCommonBlock ( tik . data ) ) )
{
consolePrint ( " tik common block failed " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( remove_console_data & & tik_common_block - > titlekey_type = = TikTitleKeyType_Personalized )
{
if ( ! tikConvertPersonalizedTicketToCommonTicket ( & tik , & raw_cert_chain , & raw_cert_chain_size ) )
{
consolePrint ( " tik convert failed \n " ) ;
goto end ;
}
} else {
raw_cert_chain = ( title_info - > storage_id = = NcmStorageId_GameCard ? certRetrieveRawCertificateChainFromGameCardByRightsId ( & ( tik_common_block - > rights_id ) , & raw_cert_chain_size ) : \
certGenerateRawCertificateChainBySignatureIssuer ( tik_common_block - > issuer , & raw_cert_chain_size ) ) ;
if ( ! raw_cert_chain )
{
consolePrint ( " cert failed \n " ) ;
goto end ;
}
}
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// add nca info
for ( u32 i = 0 ; i < title_info - > content_count ; i + + )
{
NcaContext * cur_nca_ctx = & ( nca_ctx [ i ] ) ;
sprintf ( entry_name , " %s.%s " , cur_nca_ctx - > content_id_str , cur_nca_ctx - > content_type = = NcmContentType_Meta ? " cnmt.nca " : " nca " ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( ! pfsAddEntryInformationToFileContext ( & pfs_file_ctx , entry_name , cur_nca_ctx - > content_size , NULL ) )
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// add cnmt xml info
2021-08-03 07:37:04 +01:00
if ( append_authoringtool_data )
2020-10-22 05:38:14 +01:00
{
2021-08-03 07:37:04 +01:00
sprintf ( entry_name , " %s.cnmt.xml " , meta_nca_ctx - > content_id_str ) ;
if ( ! pfsAddEntryInformationToFileContext ( & pfs_file_ctx , entry_name , cnmt_ctx . authoring_tool_xml_size , & ( meta_nca_ctx - > content_type_ctx_data_idx ) ) )
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// add content type ctx data info
2021-08-03 07:37:04 +01:00
u32 limit = append_authoringtool_data ? ( title_info - > content_count - 1 ) : 0 ;
for ( u32 i = 0 ; i < limit ; i + + )
2020-10-22 05:38:14 +01:00
{
bool ret = false ;
NcaContext * cur_nca_ctx = & ( nca_ctx [ i ] ) ;
if ( ! cur_nca_ctx - > content_type_ctx ) continue ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
switch ( cur_nca_ctx - > content_type )
{
case NcmContentType_Program :
{
ProgramInfoContext * cur_program_info_ctx = ( ProgramInfoContext * ) cur_nca_ctx - > content_type_ctx ;
sprintf ( entry_name , " %s.programinfo.xml " , cur_nca_ctx - > content_id_str ) ;
ret = pfsAddEntryInformationToFileContext ( & pfs_file_ctx , entry_name , cur_program_info_ctx - > authoring_tool_xml_size , & ( cur_nca_ctx - > content_type_ctx_data_idx ) ) ;
break ;
}
case NcmContentType_Control :
{
NacpContext * cur_nacp_ctx = ( NacpContext * ) cur_nca_ctx - > content_type_ctx ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
for ( u8 j = 0 ; j < cur_nacp_ctx - > icon_count ; j + + )
{
NacpIconContext * icon_ctx = & ( cur_nacp_ctx - > icon_ctx [ j ] ) ;
sprintf ( entry_name , " %s.nx.%s.jpg " , cur_nca_ctx - > content_id_str , nacpGetLanguageString ( icon_ctx - > language ) ) ;
if ( ! pfsAddEntryInformationToFileContext ( & pfs_file_ctx , entry_name , icon_ctx - > icon_size , j = = 0 ? & ( cur_nca_ctx - > content_type_ctx_data_idx ) : NULL ) )
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
sprintf ( entry_name , " %s.nacp.xml " , cur_nca_ctx - > content_id_str ) ;
2021-03-20 07:13:16 +00:00
ret = pfsAddEntryInformationToFileContext ( & pfs_file_ctx , entry_name , cur_nacp_ctx - > authoring_tool_xml_size , ! cur_nacp_ctx - > icon_count ? & ( cur_nca_ctx - > content_type_ctx_data_idx ) : NULL ) ;
2020-10-22 05:38:14 +01:00
break ;
}
case NcmContentType_LegalInformation :
{
LegalInfoContext * cur_legal_info_ctx = ( LegalInfoContext * ) cur_nca_ctx - > content_type_ctx ;
sprintf ( entry_name , " %s.legalinfo.xml " , cur_nca_ctx - > content_id_str ) ;
ret = pfsAddEntryInformationToFileContext ( & pfs_file_ctx , entry_name , cur_legal_info_ctx - > authoring_tool_xml_size , & ( cur_nca_ctx - > content_type_ctx_data_idx ) ) ;
break ;
}
default :
break ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( ! ret )
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// add ticket and cert info
if ( retrieve_tik_cert )
{
sprintf ( entry_name , " %s.tik " , tik . rights_id_str ) ;
if ( ! pfsAddEntryInformationToFileContext ( & pfs_file_ctx , entry_name , tik . size , NULL ) )
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
sprintf ( entry_name , " %s.cert " , tik . rights_id_str ) ;
if ( ! pfsAddEntryInformationToFileContext ( & pfs_file_ctx , entry_name , raw_cert_chain_size , NULL ) )
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// write buffer to memory buffer
if ( ! pfsWriteFileContextHeaderToMemoryBuffer ( & pfs_file_ctx , buf , BLOCK_SIZE , & nsp_header_size ) )
{
consolePrint ( " pfs write header to mem #1 failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
nsp_size = ( nsp_header_size + pfs_file_ctx . fs_size ) ;
consolePrint ( " nsp header size: 0x%lX | nsp size: 0x%lX \n " , nsp_header_size , nsp_size ) ;
2022-07-14 13:10:03 +01:00
consoleRefresh ( ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 )
2020-10-26 06:39:33 +00:00
{
2022-06-27 03:58:03 +01:00
if ( nsp_size > = free_space )
{
consolePrint ( " nsp size exceeds free space \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( ums_device & & ums_device - > fs_type < UsbHsFsDeviceFileSystemType_exFAT & & nsp_size > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
utilsCreateDirectoryTree ( path , false ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( ! ums_device & & nsp_size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( path ) )
{
consolePrint ( " create concatenation file failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( ! ( fd = fopen ( path , " wb " ) ) )
{
consolePrint ( " fopen failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
// write placeholder header
memset ( buf , 0 , nsp_header_size ) ;
fwrite ( buf , 1 , nsp_header_size , fd ) ;
} else {
if ( ! usbSendFileProperties ( nsp_size , path , ( u32 ) nsp_header_size ) )
{
consolePrint ( " usb send file properties (header) failed \n " ) ;
goto end ;
}
2020-10-26 06:39:33 +00:00
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
consolePrint ( " dump process started, please wait. hold b to cancel. \n " ) ;
2022-07-14 13:10:03 +01:00
consoleRefresh ( ) ;
2022-07-05 02:04:28 +01:00
2020-10-26 00:03:02 +00:00
nsp_offset + = nsp_header_size ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
// set nsp size
shared_data - > total_size = nsp_size ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// write ncas
for ( u32 i = 0 ; i < title_info - > content_count ; i + + )
{
NcaContext * cur_nca_ctx = & ( nca_ctx [ i ] ) ;
u64 blksize = BLOCK_SIZE ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
memset ( & sha256_ctx , 0 , sizeof ( Sha256Context ) ) ;
sha256ContextCreate ( & sha256_ctx ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( cur_nca_ctx - > content_type = = NcmContentType_Meta & & ( ! cnmtGenerateNcaPatch ( & cnmt_ctx ) | | ! ncaEncryptHeader ( cur_nca_ctx ) ) )
{
consolePrint ( " cnmt generate patch failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
bool dirty_header = ncaIsHeaderDirty ( cur_nca_ctx ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( output_device = = 1 )
2020-10-26 06:39:33 +00:00
{
2022-06-27 03:58:03 +01:00
tmp_name = pfsGetEntryNameByIndexFromFileContext ( & pfs_file_ctx , i ) ;
if ( ! usbSendFilePropertiesCommon ( cur_nca_ctx - > content_size , tmp_name ) )
{
consolePrint ( " usb send file properties \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
2020-10-26 06:39:33 +00:00
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
for ( u64 offset = 0 ; offset < cur_nca_ctx - > content_size ; offset + = blksize , nsp_offset + = blksize , shared_data - > data_written + = blksize )
2020-10-22 05:38:14 +01:00
{
2021-02-16 12:22:14 +00:00
if ( shared_data - > transfer_cancelled )
{
2022-06-27 03:58:03 +01:00
if ( output_device = = 1 ) usbCancelFileTransfer ( ) ;
2021-02-16 12:22:14 +00:00
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( ( cur_nca_ctx - > content_size - offset ) < blksize ) blksize = ( cur_nca_ctx - > content_size - offset ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// read nca chunk
if ( ! ncaReadContentFile ( cur_nca_ctx , buf , blksize , offset ) )
{
consolePrint ( " nca read failed at 0x%lX for \" %s \" \n " , offset , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( dirty_header )
{
// write re-encrypted headers
2020-10-26 00:03:02 +00:00
if ( ! cur_nca_ctx - > header_written ) ncaWriteEncryptedHeaderDataToMemoryBuffer ( cur_nca_ctx , buf , blksize , offset ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( cur_nca_ctx - > content_type_ctx_patch )
{
// write content type context patch
switch ( cur_nca_ctx - > content_type )
{
case NcmContentType_Meta :
cnmtWriteNcaPatch ( & cnmt_ctx , buf , blksize , offset ) ;
break ;
case NcmContentType_Control :
2020-10-28 22:48:46 +00:00
nacpWriteNcaPatch ( ( NacpContext * ) cur_nca_ctx - > content_type_ctx , buf , blksize , offset ) ;
2020-10-22 05:38:14 +01:00
break ;
default :
break ;
}
}
2022-07-05 02:04:28 +01:00
2020-10-26 00:03:02 +00:00
// update flag to avoid entering this code block if it's not needed anymore
dirty_header = ( ! cur_nca_ctx - > header_written | | cur_nca_ctx - > content_type_ctx_patch ) ;
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// update hash calculation
sha256ContextUpdate ( & sha256_ctx , buf , blksize ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// write nca chunk
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 )
2020-10-26 06:39:33 +00:00
{
2022-06-27 03:58:03 +01:00
fwrite ( buf , 1 , blksize , fd ) ;
} else {
if ( ! usbSendFileData ( buf , blksize ) )
{
consolePrint ( " send file data failed \n " ) ;
goto end ;
}
2020-10-26 06:39:33 +00:00
}
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// get hash
sha256ContextGetHash ( & sha256_ctx , sha256_hash ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// update content id and hash
ncaUpdateContentIdAndHash ( cur_nca_ctx , sha256_hash ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// update cnmt
if ( ! cnmtUpdateContentInfo ( & cnmt_ctx , cur_nca_ctx ) )
{
consolePrint ( " cnmt update content info failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// update pfs entry name
if ( ! pfsUpdateEntryNameFromFileContext ( & pfs_file_ctx , i , cur_nca_ctx - > content_id_str ) )
{
consolePrint ( " pfs update entry name failed for nca \" %s \" \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
}
2022-07-05 02:04:28 +01:00
2021-08-03 07:37:04 +01:00
if ( append_authoringtool_data )
2020-10-26 06:39:33 +00:00
{
2021-08-03 07:37:04 +01:00
// regenerate cnmt xml
if ( ! cnmtGenerateAuthoringToolXml ( & cnmt_ctx , nca_ctx , title_info - > content_count ) )
{
consolePrint ( " cnmt xml #2 failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2021-08-03 07:37:04 +01:00
// write cnmt xml
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 )
2021-08-03 07:37:04 +01:00
{
2022-06-27 03:58:03 +01:00
fwrite ( cnmt_ctx . authoring_tool_xml , 1 , cnmt_ctx . authoring_tool_xml_size , fd ) ;
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext ( & pfs_file_ctx , meta_nca_ctx - > content_type_ctx_data_idx ) ;
if ( ! usbSendFilePropertiesCommon ( cnmt_ctx . authoring_tool_xml_size , tmp_name ) | | ! usbSendFileData ( cnmt_ctx . authoring_tool_xml , cnmt_ctx . authoring_tool_xml_size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
2021-08-03 07:37:04 +01:00
}
2022-07-05 02:04:28 +01:00
2021-08-03 07:37:04 +01:00
nsp_offset + = cnmt_ctx . authoring_tool_xml_size ;
shared_data - > data_written + = cnmt_ctx . authoring_tool_xml_size ;
2022-07-05 02:04:28 +01:00
2021-08-03 07:37:04 +01:00
// update cnmt xml pfs entry name
if ( ! pfsUpdateEntryNameFromFileContext ( & pfs_file_ctx , meta_nca_ctx - > content_type_ctx_data_idx , meta_nca_ctx - > content_id_str ) )
{
consolePrint ( " pfs update entry name cnmt xml failed \n " ) ;
goto end ;
}
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// write content type ctx data
2021-08-03 07:37:04 +01:00
for ( u32 i = 0 ; i < limit ; i + + )
2020-10-22 05:38:14 +01:00
{
NcaContext * cur_nca_ctx = & ( nca_ctx [ i ] ) ;
if ( ! cur_nca_ctx - > content_type_ctx ) continue ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
char * authoring_tool_xml = NULL ;
u64 authoring_tool_xml_size = 0 ;
u32 data_idx = cur_nca_ctx - > content_type_ctx_data_idx ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
switch ( cur_nca_ctx - > content_type )
{
case NcmContentType_Program :
{
ProgramInfoContext * cur_program_info_ctx = ( ProgramInfoContext * ) cur_nca_ctx - > content_type_ctx ;
authoring_tool_xml = cur_program_info_ctx - > authoring_tool_xml ;
authoring_tool_xml_size = cur_program_info_ctx - > authoring_tool_xml_size ;
break ;
}
case NcmContentType_Control :
{
NacpContext * cur_nacp_ctx = ( NacpContext * ) cur_nca_ctx - > content_type_ctx ;
authoring_tool_xml = cur_nacp_ctx - > authoring_tool_xml ;
authoring_tool_xml_size = cur_nacp_ctx - > authoring_tool_xml_size ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// loop through available icons
for ( u8 j = 0 ; j < cur_nacp_ctx - > icon_count ; j + + )
{
NacpIconContext * icon_ctx = & ( cur_nacp_ctx - > icon_ctx [ j ] ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// write icon
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 )
2020-10-26 06:39:33 +00:00
{
2022-06-27 03:58:03 +01:00
fwrite ( icon_ctx - > icon_data , 1 , icon_ctx - > icon_size , fd ) ;
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext ( & pfs_file_ctx , data_idx ) ;
if ( ! usbSendFilePropertiesCommon ( icon_ctx - > icon_size , tmp_name ) | | ! usbSendFileData ( icon_ctx - > icon_data , icon_ctx - > icon_size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
2020-10-26 06:39:33 +00:00
}
2022-07-05 02:04:28 +01:00
2020-10-26 00:03:02 +00:00
nsp_offset + = icon_ctx - > icon_size ;
2021-02-16 12:22:14 +00:00
shared_data - > data_written + = icon_ctx - > icon_size ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// update pfs entry name
if ( ! pfsUpdateEntryNameFromFileContext ( & pfs_file_ctx , data_idx + + , cur_nca_ctx - > content_id_str ) )
{
consolePrint ( " pfs update entry name failed for icon \" %s \" (%u) \n " , cur_nca_ctx - > content_id_str , icon_ctx - > language ) ;
goto end ;
}
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
break ;
}
case NcmContentType_LegalInformation :
{
LegalInfoContext * cur_legal_info_ctx = ( LegalInfoContext * ) cur_nca_ctx - > content_type_ctx ;
authoring_tool_xml = cur_legal_info_ctx - > authoring_tool_xml ;
authoring_tool_xml_size = cur_legal_info_ctx - > authoring_tool_xml_size ;
break ;
}
default :
break ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// write xml
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 )
2020-10-26 06:39:33 +00:00
{
2022-06-27 03:58:03 +01:00
fwrite ( authoring_tool_xml , 1 , authoring_tool_xml_size , fd ) ;
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext ( & pfs_file_ctx , data_idx ) ;
if ( ! usbSendFilePropertiesCommon ( authoring_tool_xml_size , tmp_name ) | | ! usbSendFileData ( authoring_tool_xml , authoring_tool_xml_size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
2020-10-26 06:39:33 +00:00
}
2022-07-05 02:04:28 +01:00
2020-10-26 00:03:02 +00:00
nsp_offset + = authoring_tool_xml_size ;
2021-02-16 12:22:14 +00:00
shared_data - > data_written + = authoring_tool_xml_size ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// update pfs entry name
if ( ! pfsUpdateEntryNameFromFileContext ( & pfs_file_ctx , data_idx , cur_nca_ctx - > content_id_str ) )
{
consolePrint ( " pfs update entry name failed for xml \" %s \" \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( retrieve_tik_cert )
{
// write ticket
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 )
2020-10-26 06:39:33 +00:00
{
2022-06-27 03:58:03 +01:00
fwrite ( tik . data , 1 , tik . size , fd ) ;
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext ( & pfs_file_ctx , pfs_file_ctx . header . entry_count - 2 ) ;
if ( ! usbSendFilePropertiesCommon ( tik . size , tmp_name ) | | ! usbSendFileData ( tik . data , tik . size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
2020-10-26 06:39:33 +00:00
}
2022-07-05 02:04:28 +01:00
2020-10-26 00:03:02 +00:00
nsp_offset + = tik . size ;
2021-02-16 12:22:14 +00:00
shared_data - > data_written + = tik . size ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// write cert
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 )
2020-10-26 06:39:33 +00:00
{
2022-06-27 03:58:03 +01:00
fwrite ( raw_cert_chain , 1 , raw_cert_chain_size , fd ) ;
} else {
tmp_name = pfsGetEntryNameByIndexFromFileContext ( & pfs_file_ctx , pfs_file_ctx . header . entry_count - 1 ) ;
if ( ! usbSendFilePropertiesCommon ( raw_cert_chain_size , tmp_name ) | | ! usbSendFileData ( raw_cert_chain , raw_cert_chain_size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
2020-10-26 06:39:33 +00:00
}
2022-07-05 02:04:28 +01:00
2020-10-26 00:03:02 +00:00
nsp_offset + = raw_cert_chain_size ;
2021-02-16 12:22:14 +00:00
shared_data - > data_written + = raw_cert_chain_size ;
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
// write new pfs0 header
if ( ! pfsWriteFileContextHeaderToMemoryBuffer ( & pfs_file_ctx , buf , BLOCK_SIZE , & nsp_header_size ) )
{
consolePrint ( " pfs write header to mem #2 failed \n " ) ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 )
2020-10-26 06:39:33 +00:00
{
2022-06-27 03:58:03 +01:00
rewind ( fd ) ;
fwrite ( buf , 1 , nsp_header_size , fd ) ;
} else {
if ( ! usbSendNspHeader ( buf , ( u32 ) nsp_header_size ) )
{
consolePrint ( " send nsp header failed \n " ) ;
goto end ;
}
2020-10-26 06:39:33 +00:00
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
shared_data - > data_written + = nsp_header_size ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
success = true ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
end :
2021-02-16 12:22:14 +00:00
consoleRefresh ( ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
if ( ! success & & ! shared_data - > transfer_cancelled ) shared_data - > error = true ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( fd )
{
fclose ( fd ) ;
if ( ! ums_device & & ! success ) utilsRemoveConcatenationFile ( path ) ;
utilsCommitSdCardFileSystemChanges ( ) ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
pfsFreeFileContext ( & pfs_file_ctx ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( raw_cert_chain ) free ( raw_cert_chain ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( legal_info_ctx )
{
for ( u32 i = 0 ; i < legal_info_count ; i + + ) legalInfoFreeContext ( & ( legal_info_ctx [ i ] ) ) ;
free ( legal_info_ctx ) ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( nacp_ctx )
{
for ( u32 i = 0 ; i < control_count ; i + + ) nacpFreeContext ( & ( nacp_ctx [ i ] ) ) ;
free ( nacp_ctx ) ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( program_info_ctx )
{
for ( u32 i = 0 ; i < program_count ; i + + ) programInfoFreeContext ( & ( program_info_ctx [ i ] ) ) ;
free ( program_info_ctx ) ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
cnmtFreeContext ( & cnmt_ctx ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( nca_ctx ) free ( nca_ctx ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( path ) free ( path ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( dump_name ) free ( dump_name ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( buf ) free ( buf ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
threadExit ( ) ;
}
static void nspDump ( TitleInfo * title_info )
{
if ( ! title_info ) return ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
TitleApplicationMetadata * app_metadata = title_info - > app_metadata ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
ThreadSharedData shared_data = { 0 } ;
Thread dump_thread = { 0 } ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
time_t start = 0 , btn_cancel_start_tmr = 0 , btn_cancel_end_tmr = 0 ;
2021-06-25 22:05:02 +01:00
bool btn_cancel_cur_state = false , btn_cancel_prev_state = false ;
u8 usb_host_speed = UsbHostSpeed_None ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
u64 prev_size = 0 ;
u8 prev_time = 0 , percent = 0 ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
u32 output_device = options [ 8 ] . val ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consoleClear ( ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consolePrint ( " %s info: \n \n " , title_info - > meta_key . type = = NcmContentMetaType_Application ? " base application " : \
2022-12-04 10:29:47 +00:00
( title_info - > meta_key . type = = NcmContentMetaType_Patch ? " update " : \
( title_info - > meta_key . type = = NcmContentMetaType_AddOnContent ? " dlc " : " dlc update " ) ) ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
if ( app_metadata )
{
consolePrint ( " name: %s \n " , app_metadata - > lang_entry . name ) ;
consolePrint ( " publisher: %s \n " , app_metadata - > lang_entry . author ) ;
}
2022-07-05 02:04:28 +01:00
2022-07-12 01:27:03 +01:00
consolePrint ( " source storage: %s \n " , titleGetNcmStorageIdName ( title_info - > storage_id ) ) ;
2021-02-16 12:22:14 +00:00
consolePrint ( " title id: %016lX \n " , title_info - > meta_key . id ) ;
2022-03-17 12:37:24 +00:00
consolePrint ( " version: %u (%u.%u.%u-%u.%u) \n " , title_info - > version . value , title_info - > version . system_version . major , title_info - > version . system_version . minor , title_info - > version . system_version . micro , title_info - > version . system_version . major_relstep , \
title_info - > version . system_version . minor_relstep ) ;
2021-02-16 12:22:14 +00:00
consolePrint ( " content count: %u \n " , title_info - > content_count ) ;
consolePrint ( " size: %s \n " , title_info - > size_str ) ;
consolePrint ( " ______________________________ \n \n " ) ;
consolePrint ( " dump options: \n \n " ) ;
2022-06-27 03:58:03 +01:00
for ( u32 i = 0 ; i < ( options_count - 1 ) ; i + + ) consolePrint ( " %s: %s \n " , options [ i ] . str , options [ i ] . val ? " yes " : " no " ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
consolePrint ( " %s: " , options [ options_count - 1 ] . str ) ;
switch ( options [ options_count - 1 ] . val )
2021-02-16 12:22:14 +00:00
{
2022-06-27 03:58:03 +01:00
case 0 :
consolePrint ( " %s \n " , DEVOPTAB_SDMC_DEVICE ) ;
break ;
case 1 :
consolePrint ( " usb host (pc) \n " ) ;
break ;
default :
consolePrint ( " ums device #%u \n " , options [ options_count - 1 ] . val - 1 ) ;
break ;
2021-02-16 12:22:14 +00:00
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
consolePrint ( " ______________________________ \n \n " ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( output_device = = 1 )
2021-02-16 12:22:14 +00:00
{
2022-06-27 03:58:03 +01:00
// make sure we have a valid usb session
consolePrint ( " waiting for usb connection... " ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
start = time ( NULL ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
while ( true )
{
time_t now = time ( NULL ) ;
if ( ( now - start ) > = 10 ) break ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
consolePrint ( " %lu " , now - start ) ;
consoleRefresh ( ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( ( usb_host_speed = usbIsReady ( ) ) ) break ;
utilsSleep ( 1 ) ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
consolePrint ( " \n " ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( ! usb_host_speed )
{
consolePrint ( " usb connection failed \n " ) ;
return ;
}
2021-02-16 12:22:14 +00:00
}
2022-07-05 02:04:28 +01:00
2022-07-14 13:10:03 +01:00
consoleRefresh ( ) ;
2021-02-16 12:22:14 +00:00
// create dump thread
shared_data . data = title_info ;
utilsCreateThread ( & dump_thread , dump_thread_func , & shared_data , 2 ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
while ( ! shared_data . total_size & & ! shared_data . error ) svcSleepThread ( 10000000 ) ; // 10 ms
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
if ( shared_data . error )
{
utilsJoinThread ( & dump_thread ) ;
return ;
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
// start dump
start = time ( NULL ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
while ( shared_data . data_written < shared_data . total_size )
{
if ( shared_data . error ) break ;
2022-07-05 02:04:28 +01:00
2022-02-06 02:30:42 +00:00
struct tm ts = { 0 } ;
2021-02-16 12:22:14 +00:00
time_t now = time ( NULL ) ;
2022-02-06 02:30:42 +00:00
localtime_r ( & now , & ts ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
size_t size = shared_data . data_written ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
utilsScanPads ( ) ;
btn_cancel_cur_state = ( utilsGetButtonsHeld ( ) & HidNpadButton_B ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
if ( btn_cancel_cur_state & & btn_cancel_cur_state ! = btn_cancel_prev_state )
{
btn_cancel_start_tmr = now ;
} else
if ( btn_cancel_cur_state & & btn_cancel_cur_state = = btn_cancel_prev_state )
{
btn_cancel_end_tmr = now ;
if ( ( btn_cancel_end_tmr - btn_cancel_start_tmr ) > = 3 )
{
shared_data . transfer_cancelled = true ;
break ;
}
} else {
btn_cancel_start_tmr = btn_cancel_end_tmr = 0 ;
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
btn_cancel_prev_state = btn_cancel_cur_state ;
2022-07-05 02:04:28 +01:00
2022-02-06 02:30:42 +00:00
if ( prev_time = = ts . tm_sec | | prev_size = = size ) continue ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
percent = ( u8 ) ( ( size * 100 ) / shared_data . total_size ) ;
2022-07-05 02:04:28 +01:00
2022-02-06 02:30:42 +00:00
prev_time = ts . tm_sec ;
2021-02-16 12:22:14 +00:00
prev_size = size ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consolePrint ( " %lu / %lu (%u%%) | Time elapsed: %lu \n " , size , shared_data . total_size , percent , ( now - start ) ) ;
consoleRefresh ( ) ;
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
start = ( time ( NULL ) - start ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consolePrint ( " \n waiting for thread to join \n " ) ;
utilsJoinThread ( & dump_thread ) ;
consolePrint ( " dump_thread done: %lu \n " , time ( NULL ) ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
if ( shared_data . error )
{
2022-06-27 03:58:03 +01:00
consolePrint ( " i/o error \n " ) ;
2021-02-16 12:22:14 +00:00
return ;
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
if ( shared_data . transfer_cancelled )
{
consolePrint ( " process cancelled \n " ) ;
return ;
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consolePrint ( " process completed in %lu seconds \n " , start ) ;
2020-10-22 05:38:14 +01:00
}
int main ( int argc , char * argv [ ] )
{
int ret = 0 ;
2022-07-05 02:04:28 +01:00
2021-05-18 13:32:43 +01:00
if ( ! utilsInitializeResources ( argc , ( const char * * ) argv ) )
2020-10-22 05:38:14 +01:00
{
ret = - 1 ;
goto out ;
}
2022-07-05 02:04:28 +01:00
2021-04-26 00:10:34 +01:00
/* Configure input. */
/* Up to 8 different, full controller inputs. */
/* Individual Joy-Cons not supported. */
padConfigureInput ( 8 , HidNpadStyleSet_NpadFullCtrl ) ;
padInitializeWithMask ( & g_padState , 0x1000000FFUL ) ;
2022-07-05 02:04:28 +01:00
2021-03-08 14:44:11 +00:00
consoleInit ( NULL ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
u32 app_count = 0 ;
TitleApplicationMetadata * * app_metadata = NULL ;
TitleUserApplicationData user_app_data = { 0 } ;
TitleInfo * title_info = NULL ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
u32 menu = 0 , selected_idx = 0 , scroll = 0 , page_size = 30 ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
u32 title_idx = 0 , title_scroll = 0 ;
u32 type_idx = 0 , type_scroll = 0 ;
u32 list_count = 0 , list_idx = 0 ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
u64 device_total_fs_size = 0 , device_free_fs_size = 0 ;
char device_total_fs_size_str [ 36 ] = { 0 } , device_free_fs_size_str [ 32 ] = { 0 } , device_info [ 0x300 ] = { 0 } ;
bool device_retrieved_size = false , device_retrieved_info = false ;
2022-07-05 02:04:28 +01:00
2022-02-19 05:03:06 +00:00
bool applet_status = true ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
app_metadata = titleGetApplicationMetadataEntries ( false , & app_count ) ;
if ( ! app_metadata | | ! app_count )
{
consolePrint ( " app metadata failed \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
consolePrint ( " app metadata succeeded \n " ) ;
2021-02-16 12:22:14 +00:00
consoleRefresh ( ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
ums_devices = umsGetDevices ( & ums_device_count ) ;
2022-07-05 02:04:28 +01:00
2022-02-19 05:03:06 +00:00
while ( ( applet_status = appletMainLoop ( ) ) )
2020-10-22 05:38:14 +01:00
{
consoleClear ( ) ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consolePrint ( " press b to %s. \n " , menu = = 0 ? " exit " : " go back " ) ;
2022-06-27 03:58:03 +01:00
if ( ums_device_count ) consolePrint ( " press x to safely remove all ums devices. \n " ) ;
2021-02-16 12:22:14 +00:00
consolePrint ( " ______________________________ \n \n " ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( menu = = 0 )
{
2021-02-16 12:22:14 +00:00
consolePrint ( " title: %u / %u \n " , selected_idx + 1 , app_count ) ;
consolePrint ( " selected title: %016lX - %s \n " , app_metadata [ selected_idx ] - > title_id , app_metadata [ selected_idx ] - > lang_entry . name ) ;
2020-10-22 05:38:14 +01:00
} else {
2021-02-16 12:22:14 +00:00
consolePrint ( " title info: \n \n " ) ;
consolePrint ( " name: %s \n " , app_metadata [ title_idx ] - > lang_entry . name ) ;
consolePrint ( " publisher: %s \n " , app_metadata [ title_idx ] - > lang_entry . author ) ;
consolePrint ( " title id: %016lX \n " , app_metadata [ title_idx ] - > title_id ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( menu = = 2 )
{
2021-02-16 12:22:14 +00:00
consolePrint ( " ______________________________ \n \n " ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( title_info - > previous | | title_info - > next )
{
2021-02-16 12:22:14 +00:00
consolePrint ( " press zl/l and/or zr/r to change the selected title \n " ) ;
consolePrint ( " title: %u / %u \n " , list_idx , list_count ) ;
consolePrint ( " ______________________________ \n \n " ) ;
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consolePrint ( " selected %s info: \n \n " , title_info - > meta_key . type = = NcmContentMetaType_Application ? " base application " : \
2022-12-04 10:29:47 +00:00
( title_info - > meta_key . type = = NcmContentMetaType_Patch ? " update " : \
( title_info - > meta_key . type = = NcmContentMetaType_AddOnContent ? " dlc " : " dlc update " ) ) ) ;
2022-07-12 01:27:03 +01:00
consolePrint ( " source storage: %s \n " , titleGetNcmStorageIdName ( title_info - > storage_id ) ) ;
2021-02-16 12:22:14 +00:00
if ( title_info - > meta_key . type ! = NcmContentMetaType_Application ) consolePrint ( " title id: %016lX \n " , title_info - > meta_key . id ) ;
2022-07-12 01:27:03 +01:00
consolePrint ( " version: %u (%u.%u.%u-%u.%u) \n " , title_info - > version . value , title_info - > version . system_version . major , title_info - > version . system_version . minor , \
title_info - > version . system_version . micro , title_info - > version . system_version . major_relstep , title_info - > version . system_version . minor_relstep ) ;
2021-02-16 12:22:14 +00:00
consolePrint ( " content count: %u \n " , title_info - > content_count ) ;
consolePrint ( " size: %s \n " , title_info - > size_str ) ;
2020-10-22 05:38:14 +01:00
}
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consolePrint ( " ______________________________ \n \n " ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
u32 max_val = ( menu = = 0 ? app_count : ( menu = = 1 ? dump_type_strings_count : ( 1 + options_count ) ) ) ;
for ( u32 i = scroll ; i < max_val ; i + + )
{
if ( i > = ( scroll + page_size ) ) break ;
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consolePrint ( " %s " , i = = selected_idx ? " -> " : " " ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( menu = = 0 )
{
2021-02-16 12:22:14 +00:00
consolePrint ( " %016lX - %s \n " , app_metadata [ i ] - > title_id , app_metadata [ i ] - > lang_entry . name ) ;
2020-10-22 05:38:14 +01:00
} else
if ( menu = = 1 )
{
2021-02-16 12:22:14 +00:00
consolePrint ( " %s \n " , dump_type_strings [ i ] ) ;
2020-10-22 05:38:14 +01:00
} else
if ( menu = = 2 )
{
if ( i = = 0 )
{
2021-02-16 12:22:14 +00:00
consolePrint ( " start nsp dump \n " ) ;
2022-06-27 03:58:03 +01:00
} else
if ( i < options_count )
{
2021-02-16 12:22:14 +00:00
consolePrint ( " %s: < %s > \n " , options [ i - 1 ] . str , options [ i - 1 ] . val ? " yes " : " no " ) ;
2022-06-27 03:58:03 +01:00
} else {
consolePrint ( " %s: " , options [ i - 1 ] . str ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
u32 output_device = options [ i - 1 ] . val ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( output_device ! = 1 )
{
if ( ! device_retrieved_size )
{
sprintf ( device_total_fs_size_str , " %s/ " , output_device = = 0 ? DEVOPTAB_SDMC_DEVICE : ums_devices [ output_device - 2 ] . name ) ;
utilsGetFileSystemStatsByPath ( device_total_fs_size_str , & device_total_fs_size , & device_free_fs_size ) ;
utilsGenerateFormattedSizeString ( device_total_fs_size , device_total_fs_size_str , sizeof ( device_total_fs_size_str ) ) ;
utilsGenerateFormattedSizeString ( device_free_fs_size , device_free_fs_size_str , sizeof ( device_free_fs_size_str ) ) ;
device_retrieved_size = true ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( output_device = = 0 )
{
consolePrint ( " < sdmc: (%s / %s) > \n " , device_free_fs_size_str , device_total_fs_size_str ) ;
} else {
UsbHsFsDevice * ums_device = & ( ums_devices [ output_device - 2 ] ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( ! device_retrieved_info )
{
if ( ums_device - > product_name [ 0 ] )
{
sprintf ( device_info , " %s, LUN %u, FS #%u, %s " , ums_device - > product_name , ums_device - > lun , ums_device - > fs_idx , LIBUSBHSFS_FS_TYPE_STR ( ums_device - > fs_type ) ) ;
} else {
sprintf ( device_info , " LUN %u, FS #%u, %s " , ums_device - > lun , ums_device - > fs_idx , LIBUSBHSFS_FS_TYPE_STR ( ums_device - > fs_type ) ) ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
device_retrieved_info = true ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
consolePrint ( " < %s (%s) (%s / %s) > " , ums_device - > name , device_info , device_free_fs_size_str , device_total_fs_size_str ) ;
}
} else {
consolePrint ( " < usb host (pc) > " ) ;
device_retrieved_size = device_retrieved_info = false ;
}
2020-10-22 05:38:14 +01:00
}
}
}
2022-07-05 02:04:28 +01:00
2021-02-16 12:22:14 +00:00
consolePrint ( " \n " ) ;
consoleRefresh ( ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
bool data_update = false ;
2020-10-22 05:38:14 +01:00
u64 btn_down = 0 , btn_held = 0 ;
2022-07-05 02:04:28 +01:00
2022-02-19 05:03:06 +00:00
while ( ( applet_status = appletMainLoop ( ) ) )
2020-10-22 05:38:14 +01:00
{
2020-12-04 06:38:44 +00:00
utilsScanPads ( ) ;
btn_down = utilsGetButtonsDown ( ) ;
btn_held = utilsGetButtonsHeld ( ) ;
2020-10-22 05:38:14 +01:00
if ( btn_down | | btn_held ) break ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( titleIsGameCardInfoUpdated ( ) )
{
free ( app_metadata ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
app_metadata = titleGetApplicationMetadataEntries ( false , & app_count ) ;
if ( ! app_metadata )
{
consolePrint ( " \n app metadata failed \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
menu = selected_idx = scroll = 0 ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
title_idx = title_scroll = 0 ;
type_idx = type_scroll = 0 ;
list_count = list_idx = 0 ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
data_update = true ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
break ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( umsIsDeviceInfoUpdated ( ) )
{
free ( ums_devices ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
ums_devices = umsGetDevices ( & ums_device_count ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
options [ options_count - 1 ] . val = 0 ;
device_retrieved_size = device_retrieved_info = false ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
data_update = true ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
break ;
}
}
2022-07-05 02:04:28 +01:00
2022-02-19 05:03:06 +00:00
if ( ! applet_status ) break ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( data_update ) continue ;
2022-07-05 02:04:28 +01:00
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_A )
2020-10-22 05:38:14 +01:00
{
bool error = false ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( menu = = 0 )
{
title_idx = selected_idx ;
title_scroll = scroll ;
} else
if ( menu = = 1 )
{
type_idx = selected_idx ;
type_scroll = scroll ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
menu + + ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( menu = = 3 & & selected_idx ! = 0 )
{
menu - - ;
continue ;
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( menu = = 1 )
{
if ( ! titleGetUserApplicationData ( app_metadata [ title_idx ] - > title_id , & user_app_data ) )
{
consolePrint ( " \n get user application data failed! \n " ) ;
error = true ;
}
} else
if ( menu = = 2 )
{
2022-12-04 10:29:47 +00:00
if ( ( type_idx = = 0 & & ! user_app_data . app_info ) | | ( type_idx = = 1 & & ! user_app_data . patch_info ) | | ( type_idx = = 2 & & ! user_app_data . aoc_info ) | | ( type_idx = = 3 & & ! user_app_data . aoc_patch_info ) )
2020-10-22 05:38:14 +01:00
{
2022-12-04 10:29:47 +00:00
consolePrint ( " \n the selected title doesn't have available %s data \n " , type_idx = = 0 ? " base application " : ( type_idx = = 1 ? " update " : ( type_idx = = 2 ? " dlc " : " dlc update " ) ) ) ;
2020-10-22 05:38:14 +01:00
error = true ;
} else {
2022-12-04 10:29:47 +00:00
title_info = ( type_idx = = 0 ? user_app_data . app_info : ( type_idx = = 1 ? user_app_data . patch_info : ( type_idx = = 2 ? user_app_data . aoc_info : user_app_data . aoc_patch_info ) ) ) ;
2020-10-22 05:38:14 +01:00
list_count = titleGetCountFromInfoBlock ( title_info ) ;
list_idx = 1 ;
}
} else
if ( menu = = 3 )
{
2021-08-03 07:37:04 +01:00
utilsSetLongRunningProcessState ( true ) ;
2020-10-22 05:38:14 +01:00
nspDump ( title_info ) ;
2021-08-03 07:37:04 +01:00
utilsSetLongRunningProcessState ( false ) ;
2022-07-27 23:53:52 +01:00
device_retrieved_size = false ;
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( error | | menu > = 3 )
{
consolePrint ( " press any button to continue \n " ) ;
2021-02-16 12:22:14 +00:00
consoleRefresh ( ) ;
2020-12-04 06:38:44 +00:00
utilsWaitForButtonPress ( 0 ) ;
2020-10-22 05:38:14 +01:00
menu - - ;
} else {
selected_idx = scroll = 0 ;
}
} else
2020-12-04 06:38:44 +00:00
if ( ( btn_down & HidNpadButton_Down ) | | ( btn_held & ( HidNpadButton_StickLDown | HidNpadButton_StickRDown ) ) )
2020-10-22 05:38:14 +01:00
{
selected_idx + + ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( selected_idx > = max_val )
{
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_Down )
2020-10-22 05:38:14 +01:00
{
selected_idx = scroll = 0 ;
} else {
selected_idx = ( max_val - 1 ) ;
}
} else
if ( selected_idx > = ( scroll + ( page_size / 2 ) ) & & max_val > ( scroll + page_size ) )
{
scroll + + ;
}
} else
2020-12-04 06:38:44 +00:00
if ( ( btn_down & HidNpadButton_Up ) | | ( btn_held & ( HidNpadButton_StickLUp | HidNpadButton_StickRUp ) ) )
2020-10-22 05:38:14 +01:00
{
selected_idx - - ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( selected_idx = = UINT32_MAX )
{
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_Up )
2020-10-22 05:38:14 +01:00
{
selected_idx = ( max_val - 1 ) ;
scroll = ( max_val > = page_size ? ( max_val - page_size ) : 0 ) ;
} else {
selected_idx = 0 ;
}
} else
if ( selected_idx < ( scroll + ( page_size / 2 ) ) & & scroll > 0 )
{
scroll - - ;
}
} else
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_B )
2020-10-22 05:38:14 +01:00
{
menu - - ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( menu = = UINT32_MAX )
{
break ;
} else {
selected_idx = ( menu = = 0 ? title_idx : type_idx ) ;
scroll = ( menu = = 0 ? title_scroll : type_scroll ) ;
2021-06-01 02:12:15 +01:00
if ( menu = = 0 ) titleFreeUserApplicationData ( & user_app_data ) ;
2020-10-22 05:38:14 +01:00
}
} else
2020-12-04 06:38:44 +00:00
if ( ( btn_down & ( HidNpadButton_Left | HidNpadButton_Right ) ) & & menu = = 2 & & selected_idx ! = 0 )
2020-10-22 05:38:14 +01:00
{
2022-06-27 03:58:03 +01:00
if ( selected_idx < options_count )
{
options [ selected_idx - 1 ] . val ^ = 1 ;
} else {
bool left = ( btn_down & HidNpadButton_Left ) ;
u32 * output_device = & ( options [ selected_idx - 1 ] . val ) , orig_output_device = * output_device ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( left )
{
( * output_device ) - - ;
if ( * output_device = = UINT32_MAX ) * output_device = ( ums_device_count + 1 ) ;
} else {
( * output_device ) + + ;
if ( * output_device > ( ums_device_count + 1 ) ) * output_device = 0 ;
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( * output_device ! = orig_output_device ) device_retrieved_size = device_retrieved_info = false ;
}
2020-10-22 05:38:14 +01:00
} else
2020-12-04 06:38:44 +00:00
if ( ( btn_down & ( HidNpadButton_L | HidNpadButton_ZL ) ) & & menu = = 2 & & title_info - > previous )
2020-10-22 05:38:14 +01:00
{
title_info = title_info - > previous ;
list_idx - - ;
} else
2020-12-04 06:38:44 +00:00
if ( ( btn_down & ( HidNpadButton_R | HidNpadButton_ZR ) ) & & menu = = 2 & & title_info - > next )
2020-10-22 05:38:14 +01:00
{
title_info = title_info - > next ;
list_idx + + ;
2022-06-27 03:58:03 +01:00
} else
if ( ( btn_down & HidNpadButton_X ) & & ums_device_count )
{
for ( u32 i = 0 ; i < ums_device_count ; i + + ) usbHsFsUnmountDevice ( & ( ums_devices [ i ] ) , false ) ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
options [ options_count - 1 ] . val = 0 ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
free ( ums_devices ) ;
ums_devices = NULL ;
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
ums_device_count = 0 ;
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2020-12-04 06:38:44 +00:00
if ( btn_held & ( HidNpadButton_StickLDown | HidNpadButton_StickRDown | HidNpadButton_StickLUp | HidNpadButton_StickRUp ) ) svcSleepThread ( 50000000 ) ; // 50 ms
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2022-02-19 05:03:06 +00:00
if ( ! applet_status ) menu = UINT32_MAX ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
out2 :
2021-02-16 12:22:14 +00:00
consoleRefresh ( ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( menu ! = UINT32_MAX )
{
consolePrint ( " press any button to exit \n " ) ;
2020-12-04 06:38:44 +00:00
utilsWaitForButtonPress ( 0 ) ;
2020-10-22 05:38:14 +01:00
}
2022-07-05 02:04:28 +01:00
2022-06-27 03:58:03 +01:00
if ( ums_devices ) free ( ums_devices ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
if ( app_metadata ) free ( app_metadata ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
out :
utilsCloseResources ( ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
consoleExit ( NULL ) ;
2022-07-05 02:04:28 +01:00
2020-10-22 05:38:14 +01:00
return ret ;
}