2020-04-11 06:28:26 +01:00
# include <stdio.h>
# include <string.h>
# include <stdlib.h>
2020-05-18 06:37:36 +01:00
# include <errno.h>
2020-04-11 06:28:26 +01:00
# include <math.h>
# include <dirent.h>
# include <unistd.h>
# include <ctype.h>
# include <sys/stat.h>
# include <sys/statvfs.h>
# include <sys/socket.h>
# include <switch/services/ncm.h>
# include <switch/services/ns.h>
# include <libxml2/libxml/globals.h>
# include <libxml2/libxml/xpath.h>
# include <curl/curl.h>
# include <json-c/json.h>
# include <pthread.h>
# include "dumper.h"
# include "fs_ext.h"
# include "keys.h"
# include "ui.h"
# include "util.h"
# include "fatfs/ff.h"
/* Extern variables */
extern bool highlight ;
extern int breaks ;
extern int font_height ;
extern int cursor ;
extern int scroll ;
extern curMenuType menuType ;
extern u8 * fileNormalIconBuf ;
extern u8 * fileHighlightIconBuf ;
extern nca_keyset_t nca_keyset ;
/* Statically allocated variables */
static bool initNcm = false , initNs = false , initCsrng = false , initSpl = false , initPmdmnt = false , initPl = false , initNet = false ;
static bool openFsDevOp = false , openGcEvtNotifier = false , loadGcKernEvt = false , gcThreadInit = false , homeBtnBlocked = false ;
dumpOptions dumpCfg ;
bool keysFileAvailable = false ;
static pthread_t gameCardDetectionThread ;
static UEvent exitEvent ;
static AppletType programAppletType ;
char cfwDirStr [ 32 ] = { ' \0 ' } ;
gamecard_ctx_t gameCardInfo ;
u32 titleAppCount = 0 , titlePatchCount = 0 , titleAddOnCount = 0 ;
u32 sdCardTitleAppCount = 0 , sdCardTitlePatchCount = 0 , sdCardTitleAddOnCount = 0 ;
u32 emmcTitleAppCount = 0 , emmcTitlePatchCount = 0 , emmcTitleAddOnCount = 0 ;
u32 gameCardSdCardEmmcPatchCount = 0 , gameCardSdCardEmmcAddOnCount = 0 ;
base_app_ctx_t * baseAppEntries = NULL ;
patch_addon_ctx_t * patchEntries = NULL , * addOnEntries = NULL ;
static volatile bool gameCardInfoLoaded = false ;
static bool sdCardAndEmmcTitleInfoLoaded = false ;
exefs_ctx_t exeFsContext ;
romfs_ctx_t romFsContext ;
bktr_ctx_t bktrContext ;
char curRomFsPath [ NAME_BUF_LEN ] = { ' \0 ' } ;
u32 curRomFsDirOffset = 0 ;
romfs_browser_entry * romFsBrowserEntries = NULL ;
static char * result_buf = NULL ;
static size_t result_sz = 0 ;
static size_t result_written = 0 ;
char * * filenameBuffer = NULL ;
int filenameCount = 0 , filenameIndex = 0 ;
u8 * dumpBuf = NULL ;
u8 * gcReadBuf = NULL ;
u8 * ncaCtrBuf = NULL ;
orphan_patch_addon_entry * orphanEntries = NULL ;
u32 orphanEntriesCnt = 0 ;
char strbuf [ NAME_BUF_LEN ] = { ' \0 ' } ;
static const char * appLaunchPath = NULL ;
2020-05-18 06:37:36 +01:00
FsStorage fatFsStorage = { 0 } ;
static FATFS * fatFsObj = NULL ;
2020-04-11 06:28:26 +01:00
u64 freeSpace = 0 ;
char freeSpaceStr [ 32 ] = { ' \0 ' } ;
browser_entry_size_info * hfs0ExeFsEntriesSizes = NULL ;
void loadConfig ( )
{
// Set default configuration values
memset ( & dumpCfg , 0x00 , sizeof ( dumpOptions ) ) ;
dumpCfg . xciDumpCfg . isFat32 = true ;
dumpCfg . xciDumpCfg . calcCrc = true ;
dumpCfg . nspDumpCfg . isFat32 = true ;
dumpCfg . nspDumpCfg . useNoIntroLookup = true ;
dumpCfg . nspDumpCfg . npdmAcidRsaPatch = true ;
dumpCfg . batchDumpCfg . isFat32 = true ;
dumpCfg . batchDumpCfg . npdmAcidRsaPatch = true ;
dumpCfg . batchDumpCfg . skipDumpedTitles = true ;
dumpCfg . batchDumpCfg . haltOnErrors = true ;
dumpCfg . batchDumpCfg . batchModeSrc = BATCH_SOURCE_ALL ;
dumpCfg . exeFsDumpCfg . isFat32 = true ;
dumpCfg . romFsDumpCfg . isFat32 = true ;
FILE * configFile = fopen ( CONFIG_PATH , " rb " ) ;
if ( ! configFile ) return ;
fseek ( configFile , 0 , SEEK_END ) ;
size_t configFileSize = ftell ( configFile ) ;
rewind ( configFile ) ;
if ( configFileSize ! = sizeof ( dumpOptions ) )
{
fclose ( configFile ) ;
remove ( CONFIG_PATH ) ;
return ;
}
dumpOptions tmpCfg ;
size_t read_res = fread ( & tmpCfg , 1 , sizeof ( dumpOptions ) , configFile ) ;
fclose ( configFile ) ;
if ( read_res ! = sizeof ( dumpOptions ) )
{
remove ( CONFIG_PATH ) ;
return ;
}
memcpy ( & dumpCfg , & tmpCfg , sizeof ( dumpOptions ) ) ;
// Check if the configuration is correct
if ( dumpCfg . xciDumpCfg . setXciArchiveBit & & ! dumpCfg . xciDumpCfg . isFat32 ) dumpCfg . xciDumpCfg . setXciArchiveBit = false ;
if ( dumpCfg . nspDumpCfg . tiklessDump & & ! dumpCfg . nspDumpCfg . removeConsoleData ) dumpCfg . nspDumpCfg . tiklessDump = false ;
if ( dumpCfg . batchDumpCfg . tiklessDump & & ! dumpCfg . batchDumpCfg . removeConsoleData ) dumpCfg . batchDumpCfg . tiklessDump = false ;
if ( dumpCfg . batchDumpCfg . batchModeSrc > = BATCH_SOURCE_CNT ) dumpCfg . batchDumpCfg . batchModeSrc = BATCH_SOURCE_ALL ;
}
void saveConfig ( )
{
FILE * configFile = fopen ( CONFIG_PATH , " wb " ) ;
if ( ! configFile ) return ;
size_t write_res = fwrite ( & dumpCfg , 1 , sizeof ( dumpOptions ) , configFile ) ;
fclose ( configFile ) ;
if ( write_res ! = sizeof ( dumpOptions ) ) remove ( CONFIG_PATH ) ;
}
static void retrieveRunningCfwDir ( )
{
bool txService = isServiceRunning ( " tx " ) ;
bool rnxService = isServiceRunning ( " rnx " ) ;
if ( ! txService & & ! rnxService )
{
// Atmosphere
snprintf ( cfwDirStr , MAX_CHARACTERS ( cfwDirStr ) , CFW_PATH_ATMOSPHERE ) ;
} else
if ( txService & & ! rnxService )
{
// SX OS
snprintf ( cfwDirStr , MAX_CHARACTERS ( cfwDirStr ) , CFW_PATH_SXOS ) ;
} else {
// ReiNX
snprintf ( cfwDirStr , MAX_CHARACTERS ( cfwDirStr ) , CFW_PATH_REINX ) ;
}
}
static void createOutputDirectories ( )
{
mkdir ( HBLOADER_BASE_PATH , 0744 ) ;
mkdir ( APP_BASE_PATH , 0744 ) ;
mkdir ( XCI_DUMP_PATH , 0744 ) ;
mkdir ( NSP_DUMP_PATH , 0744 ) ;
mkdir ( HFS0_DUMP_PATH , 0744 ) ;
mkdir ( EXEFS_DUMP_PATH , 0744 ) ;
mkdir ( ROMFS_DUMP_PATH , 0744 ) ;
mkdir ( CERT_DUMP_PATH , 0744 ) ;
mkdir ( BATCH_OVERRIDES_PATH , 0744 ) ;
mkdir ( TICKET_PATH , 0744 ) ;
}
void freeFilenameBuffer ( void )
{
if ( ! filenameBuffer ) return ;
for ( int i = 0 ; i < filenameCount ; i + + )
{
if ( filenameBuffer [ i ] ) free ( filenameBuffer [ i ] ) ;
}
filenameCount = filenameIndex = 0 ;
free ( filenameBuffer ) ;
filenameBuffer = NULL ;
}
static bool allocateFilenameBuffer ( u32 cnt )
{
if ( ! cnt ) return false ;
freeFilenameBuffer ( ) ;
filenameBuffer = calloc ( cnt , sizeof ( char * ) ) ;
if ( ! filenameBuffer ) return false ;
filenameCount = ( int ) cnt ;
return true ;
}
static bool addStringToFilenameBuffer ( const char * str )
{
if ( ! str | | ! strlen ( str ) | | ( filenameIndex + 1 ) > filenameCount ) return false ;
filenameBuffer [ filenameIndex ] = strdup ( str ) ;
if ( ! filenameBuffer [ filenameIndex ] )
{
freeFilenameBuffer ( ) ;
return false ;
}
filenameIndex + + ;
return true ;
}
2020-10-13 12:26:29 +01:00
void freeRomFsBrowserEntries ( )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
if ( romFsBrowserEntries ! = NULL )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
free ( romFsBrowserEntries ) ;
romFsBrowserEntries = NULL ;
2020-04-11 06:28:26 +01:00
}
}
2020-10-13 12:26:29 +01:00
void freeHfs0ExeFsEntriesSizes ( )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
if ( hfs0ExeFsEntriesSizes )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
free ( hfs0ExeFsEntriesSizes ) ;
hfs0ExeFsEntriesSizes = NULL ;
2020-04-11 06:28:26 +01:00
}
}
2020-10-13 12:26:29 +01:00
void consoleErrorScreen ( const char * fmt , . . . )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
consoleInit ( NULL ) ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
va_list va ;
va_start ( va , fmt ) ;
vprintf ( fmt , va ) ;
va_end ( va ) ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
printf ( " \n Press any button to exit. \n " ) ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
while ( appletMainLoop ( ) )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
hidScanInput ( ) ;
u64 keysDown = hidKeysAllDown ( CONTROLLER_P1_AUTO ) ;
if ( keysDown & & ! ( ( keysDown & KEY_TOUCH ) | | ( keysDown & KEY_LSTICK_LEFT ) | | ( keysDown & KEY_LSTICK_RIGHT ) | | ( keysDown & KEY_LSTICK_UP ) | | ( keysDown & KEY_LSTICK_DOWN ) | | \
( keysDown & KEY_RSTICK_LEFT ) | | ( keysDown & KEY_RSTICK_RIGHT ) | | ( keysDown & KEY_RSTICK_UP ) | | ( keysDown & KEY_RSTICK_DOWN ) ) ) break ;
consoleUpdate ( NULL ) ;
2020-04-11 06:28:26 +01:00
}
2020-09-26 10:59:01 +01:00
2020-10-13 12:26:29 +01:00
consoleExit ( NULL ) ;
2020-04-11 06:28:26 +01:00
}
2020-10-13 12:26:29 +01:00
bool initApplicationResources ( int argc , char * * argv )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
Result result = 0 ;
bool success = false ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
/* Copy launch path */
if ( argc > 0 & & argv & & ! envIsNso ( ) )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
for ( int i = 0 ; i < argc ; i + + )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
if ( argv [ i ] & & strlen ( argv [ i ] ) > 10 & & ! strncasecmp ( argv [ i ] , " sdmc:/ " , 6 ) & & ! strncasecmp ( argv [ i ] + strlen ( argv [ i ] ) - 4 , " .nro " , 4 ) )
{
appLaunchPath = ( const char * ) argv [ i ] ;
break ;
}
2020-04-11 06:28:26 +01:00
}
}
2020-10-13 12:26:29 +01:00
/* Initialize services */
if ( ! initServices ( ) ) return false ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
/* Initialize UI */
if ( ! uiInit ( ) ) return false ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
/* Zero out gamecard info struct */
memset ( & gameCardInfo , 0 , sizeof ( gamecard_ctx_t ) ) ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
/* Zero out NCA keyset */
memset ( & nca_keyset , 0 , sizeof ( nca_keyset_t ) ) ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
/* Init ExeFS context */
initExeFsContext ( ) ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
/* Init RomFS context */
initRomFsContext ( ) ;
2020-04-11 06:28:26 +01:00
/* Init BKTR context */
initBktrContext ( ) ;
/* Make sure output directories exist */
createOutputDirectories ( ) ;
/* Check if the Lockpick_RCM keys file is available */
keysFileAvailable = checkIfFileExists ( KEYS_FILE_PATH ) ;
/* Retrieve running CFW directory */
retrieveRunningCfwDir ( ) ;
/* Get applet type */
programAppletType = appletGetAppletType ( ) ;
/* Disable screen dimming and auto sleep */
appletSetMediaPlaybackState ( true ) ;
/* Enable CPU boost mode */
appletSetCpuBoostMode ( ApmCpuBoostMode_Type1 ) ;
/* Mount eMMC BIS System partition */
if ( ! mountSysEmmcPartition ( ) ) goto out ;
/* Allocate memory for the general purpose dump buffer */
dumpBuf = calloc ( DUMP_BUFFER_SIZE , sizeof ( u8 ) ) ;
if ( ! dumpBuf )
{
uiDrawString ( STRING_DEFAULT_POS , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for the dump buffer! " , __func__ ) ;
goto out ;
}
/* Allocate memory for the gamecard read buffer */
gcReadBuf = calloc ( GAMECARD_READ_BUFFER_SIZE , sizeof ( u8 ) ) ;
if ( ! gcReadBuf )
{
uiDrawString ( STRING_DEFAULT_POS , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for the gamecard read buffer! " , __func__ ) ;
goto out ;
}
/* Allocate memory for the NCA AES-CTR operation buffer */
ncaCtrBuf = calloc ( NCA_CTR_BUFFER_SIZE , sizeof ( u8 ) ) ;
if ( ! ncaCtrBuf )
{
uiDrawString ( STRING_DEFAULT_POS , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for the NCA AES-CTR operation buffer! " , __func__ ) ;
goto out ;
}
/* Open device operator */
result = fsOpenDeviceOperator ( & ( gameCardInfo . fsOperatorInstance ) ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_DEFAULT_POS , FONT_COLOR_ERROR_RGB , " %s: failed to open device operator! (0x%08X) " , __func__ , result ) ;
goto out ;
}
openFsDevOp = true ;
/* Open gamecard detection event notifier */
result = fsOpenGameCardDetectionEventNotifier ( & ( gameCardInfo . fsGameCardEventNotifier ) ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_DEFAULT_POS , FONT_COLOR_ERROR_RGB , " %s: failed to open gamecard detection event notifier! (0x%08X) " , __func__ , result ) ;
goto out ;
}
openGcEvtNotifier = true ;
/* Retrieve gamecard detection kernel event */
result = fsEventNotifierGetEventHandle ( & ( gameCardInfo . fsGameCardEventNotifier ) , & ( gameCardInfo . fsGameCardKernelEvent ) , true ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_DEFAULT_POS , FONT_COLOR_ERROR_RGB , " %s: failed to retrieve gamecard detection event handle! (0x%08X) " , __func__ , result ) ;
goto out ;
}
loadGcKernEvt = true ;
/* Create usermode exit event */
ueventCreate ( & exitEvent , false ) ;
/* Create and start gamecard detection thread */
if ( ! createGameCardDetectionThread ( ) ) goto out ;
gcThreadInit = true ;
/* Load settings from configuration file */
loadConfig ( ) ;
/* Update free space */
updateFreeSpace ( ) ;
/* Set output status */
success = true ;
out :
if ( ! success )
{
uiRefreshDisplay ( ) ;
delay ( 5 ) ;
}
return success ;
}
void deinitApplicationResources ( )
{
/* Free global resources */
freeGlobalData ( ) ;
/* Save current settings to configuration file */
saveConfig ( ) ;
if ( gcThreadInit )
{
/* Signal the exit event to terminate the gamecard detection thread */
ueventSignal ( & exitEvent ) ;
/* Wait for the gamecard detection thread to exit */
pthread_join ( gameCardDetectionThread , NULL ) ;
}
/* Close gamecard detection kernel event */
if ( loadGcKernEvt ) eventClose ( & ( gameCardInfo . fsGameCardKernelEvent ) ) ;
/* Close gamecard detection event notifier */
if ( openGcEvtNotifier ) fsEventNotifierClose ( & ( gameCardInfo . fsGameCardEventNotifier ) ) ;
/* Close device operator */
if ( openFsDevOp ) fsDeviceOperatorClose ( & ( gameCardInfo . fsOperatorInstance ) ) ;
/* Free NCA AES-CTR operation buffer */
if ( ncaCtrBuf ) free ( ncaCtrBuf ) ;
/* Free gamecard read buffer */
if ( gcReadBuf ) free ( gcReadBuf ) ;
/* Free general purpose dump buffer */
if ( dumpBuf ) free ( dumpBuf ) ;
/* Unmount eMMC BIS System partition */
unmountSysEmmcPartition ( ) ;
/* Disable CPU boost mode */
appletSetCpuBoostMode ( ApmCpuBoostMode_Disabled ) ;
/* Enable screen dimming and auto sleep */
appletSetMediaPlaybackState ( false ) ;
/* Deinitialize UI */
uiDeinit ( ) ;
/* Deinitialize services */
deinitServices ( ) ;
}
void appletModeOperationWarning ( )
{
if ( ! appletModeCheck ( ) ) return ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " Do not press the " NINTENDO_FONT_HOME " button. Doing so could corrupt the SD card filesystem. " ) ;
breaks + + ;
}
void formatETAString ( u64 curTime , char * out , size_t outSize )
{
if ( ! out | | ! outSize ) return ;
u64 i , hour = 0 , min = 0 , sec = 0 ;
for ( i = 0 ; i < curTime ; i + + )
{
sec + + ;
if ( sec = = 60 )
{
sec = 0 ;
min + + ;
if ( min = = 60 )
{
min = 0 ;
hour + + ;
}
}
}
snprintf ( out , outSize , " %02luH%02luM%02luS " , hour , min , sec ) ;
}
void generateSdCardEmmcTitleList ( )
{
if ( ! titleAppCount | | ! baseAppEntries ) return ;
if ( ! allocateFilenameBuffer ( titleAppCount ) ) return ;
for ( u32 i = 0 ; i < titleAppCount ; i + + )
{
if ( ! addStringToFilenameBuffer ( baseAppEntries [ i ] . name ) ) return ;
}
}
2020-10-13 12:26:29 +01:00
void truncateBrowserEntryName ( char * str )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
if ( ! str | | ! strlen ( str ) ) return ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
u32 strWidth = uiGetStrWidth ( str ) ;
u32 limit = ( u32 ) ( FB_WIDTH - ( font_height * 8 ) ) ;
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
if ( ( BROWSER_ICON_DIMENSION + 16 + strWidth ) > = limit )
{
while ( ( BROWSER_ICON_DIMENSION + 16 + strWidth ) > = limit )
{
str [ strlen ( str ) - 1 ] = ' \0 ' ;
strWidth = uiGetStrWidth ( str ) ;
}
strcat ( str , " ... " ) ;
}
2020-04-11 06:28:26 +01:00
}
2020-10-13 12:26:29 +01:00
bool getHfs0FileList ( u32 partition )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
if ( partition > = gameCardInfo . hfs0PartitionCnt )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid HFS0 partition index! " , __func__ ) ;
breaks + = 2 ;
2020-04-11 06:28:26 +01:00
return false ;
}
2020-10-13 12:26:29 +01:00
if ( ! gameCardInfo . hfs0Partitions | | ! gameCardInfo . hfs0Partitions [ partition ] . header | | ! gameCardInfo . hfs0Partitions [ partition ] . header_size )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: HFS0 partition header information unavailable! " , __func__ ) ;
breaks + = 2 ;
return false ;
2020-04-11 06:28:26 +01:00
}
2020-10-13 12:26:29 +01:00
if ( ! gameCardInfo . hfs0Partitions [ partition ] . file_cnt | | ! gameCardInfo . hfs0Partitions [ partition ] . str_table_size )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: the selected HFS0 partition is empty! " , __func__ ) ;
breaks + = 2 ;
return false ;
2020-04-11 06:28:26 +01:00
}
2020-10-13 12:26:29 +01:00
u32 i ;
hfs0_file_entry entry ;
char curName [ NAME_BUF_LEN ] = { ' \0 ' } ;
freeHfs0ExeFsEntriesSizes ( ) ;
hfs0ExeFsEntriesSizes = calloc ( gameCardInfo . hfs0Partitions [ partition ] . file_cnt , sizeof ( browser_entry_size_info ) ) ;
if ( ! hfs0ExeFsEntriesSizes )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to allocate memory for HFS0 entries size info! " , __func__ ) ;
breaks + = 2 ;
return false ;
2020-04-11 06:28:26 +01:00
}
2020-10-13 12:26:29 +01:00
if ( ! allocateFilenameBuffer ( gameCardInfo . hfs0Partitions [ partition ] . file_cnt ) )
2020-04-11 06:28:26 +01:00
{
2020-10-13 12:26:29 +01:00
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for the filename buffer! " , __func__ ) ;
breaks + = 2 ;
return false ;
2020-04-11 06:28:26 +01:00
}
2020-10-13 12:26:29 +01:00
for ( i = 0 ; i < gameCardInfo . hfs0Partitions [ partition ] . file_cnt ; i + + )
2020-04-11 06:28:26 +01:00
{
memcpy ( & entry , gameCardInfo . hfs0Partitions [ partition ] . header + sizeof ( hfs0_header ) + ( i * sizeof ( hfs0_file_entry ) ) , sizeof ( hfs0_file_entry ) ) ;
char * cur_filename = ( char * ) ( gameCardInfo . hfs0Partitions [ partition ] . header + sizeof ( hfs0_header ) + ( gameCardInfo . hfs0Partitions [ partition ] . file_cnt * sizeof ( hfs0_file_entry ) ) + entry . filename_offset ) ;
snprintf ( curName , MAX_CHARACTERS ( curName ) , cur_filename ) ;
// Fix entry name length
truncateBrowserEntryName ( curName ) ;
if ( ! addStringToFilenameBuffer ( curName ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for filename entry in filename buffer! " , __func__ ) ;
breaks + = 2 ;
freeHfs0ExeFsEntriesSizes ( ) ;
return false ;
}
// Save entry size
2020-10-13 12:26:29 +01:00
hfs0ExeFsEntriesSizes [ i ] . size = entry . file_size ;
convertSize ( hfs0ExeFsEntriesSizes [ i ] . size , hfs0ExeFsEntriesSizes [ i ] . sizeStr , MAX_CHARACTERS ( hfs0ExeFsEntriesSizes [ i ] . sizeStr ) ) ;
}
2020-04-11 06:28:26 +01:00
2020-10-13 12:26:29 +01:00
return true ;
2020-04-11 06:28:26 +01:00
}
bool listDesiredNcaType ( NcmContentInfo * titleContentInfos , u32 titleContentInfoCnt , u8 type , int desiredIdOffset , u32 * outIndex , u32 * outCount )
{
if ( ! titleContentInfos | | ! titleContentInfoCnt | | type > NcmContentType_DeltaFragment | | ! outIndex | | ! outCount )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters. " , __func__ ) ;
return false ;
}
int idx = - 1 ;
u32 i , cnt = 0 ;
bool success = false ;
u32 * indexes = NULL , * tmpIndexes = NULL ;
int cur_breaks , initial_breaks = breaks ;
u32 selectedContent = 0 ;
u64 keysDown = 0 , keysHeld = 0 ;
char nca_id [ SHA256_HASH_SIZE + 1 ] = { ' \0 ' } ;
for ( i = 0 ; i < titleContentInfoCnt ; i + + )
{
if ( titleContentInfos [ i ] . content_type = = type )
{
if ( desiredIdOffset > = 0 )
{
if ( titleContentInfos [ i ] . id_offset = = ( u8 ) desiredIdOffset )
{
// Save the index for the content with the desired ID offset
idx = ( int ) i ;
}
} else {
tmpIndexes = realloc ( indexes , ( cnt + 1 ) * sizeof ( u32 ) ) ;
if ( ! tmpIndexes )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to reallocate indexes buffer! " , __func__ ) ;
goto out ;
}
indexes = tmpIndexes ;
tmpIndexes = NULL ;
indexes [ cnt ] = i ;
}
cnt + + ;
}
}
if ( ! cnt )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to find any %s NCAs! " , __func__ , getContentType ( type ) ) ;
goto out ;
}
if ( desiredIdOffset > = 0 )
{
if ( idx < 0 )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to find %s NCA with ID offset %d! " , __func__ , getContentType ( type ) , desiredIdOffset ) ;
goto out ;
}
} else {
// If only a single NCA with the desired content type was detected, save its index right away
if ( cnt = = 1 ) idx = ( int ) indexes [ 0 ] ;
}
// Return immediately if necessary
if ( idx > = 0 )
{
success = true ;
goto out ;
}
// Display a selection list
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Select one of the available %s NCAs from the list below: " , getContentType ( type ) ) ;
breaks + = 2 ;
while ( true )
{
cur_breaks = breaks ;
uiFill ( 0 , 8 + ( cur_breaks * LINE_HEIGHT ) , FB_WIDTH , FB_HEIGHT - ( 8 + ( cur_breaks * LINE_HEIGHT ) ) , BG_COLOR_RGB ) ;
for ( i = 0 ; i < cnt ; i + + )
{
u32 xpos = STRING_X_POS ;
u32 ypos = ( 8 + ( cur_breaks * LINE_HEIGHT ) + ( i * ( font_height + 12 ) ) + 6 ) ;
if ( i = = selectedContent )
{
highlight = true ;
uiFill ( 0 , ypos - 6 , FB_WIDTH , font_height + 12 , HIGHLIGHT_BG_COLOR_RGB ) ;
}
uiDrawIcon ( ( highlight ? fileHighlightIconBuf : fileNormalIconBuf ) , BROWSER_ICON_DIMENSION , BROWSER_ICON_DIMENSION , xpos , ypos ) ;
xpos + = ( BROWSER_ICON_DIMENSION + 8 ) ;
convertDataToHexString ( titleContentInfos [ indexes [ i ] ] . content_id . c , SHA256_HASH_SIZE / 2 , nca_id , SHA256_HASH_SIZE + 1 ) ;
snprintf ( strbuf , MAX_CHARACTERS ( strbuf ) , " %s.nca (ID Offset: %u) " , nca_id , titleContentInfos [ indexes [ i ] ] . id_offset ) ;
if ( highlight )
{
uiDrawString ( xpos , ypos , HIGHLIGHT_FONT_COLOR_RGB , strbuf ) ;
} else {
uiDrawString ( xpos , ypos , FONT_COLOR_RGB , strbuf ) ;
}
if ( i = = selectedContent ) highlight = false ;
}
while ( true )
{
uiUpdateStatusMsg ( ) ;
uiRefreshDisplay ( ) ;
hidScanInput ( ) ;
keysDown = hidKeysAllDown ( CONTROLLER_P1_AUTO ) ;
keysHeld = hidKeysAllHeld ( CONTROLLER_P1_AUTO ) ;
if ( ( keysDown & & ! ( keysDown & KEY_TOUCH ) ) | | ( keysHeld & & ! ( keysHeld & KEY_TOUCH ) ) ) break ;
}
if ( keysDown & KEY_A )
{
idx = ( int ) indexes [ selectedContent ] ;
break ;
}
if ( ( keysDown & KEY_DUP ) | | ( keysDown & KEY_LSTICK_UP ) | | ( keysHeld & KEY_RSTICK_UP ) )
{
if ( selectedContent > 0 ) selectedContent - - ;
}
if ( ( keysDown & KEY_DDOWN ) | | ( keysDown & KEY_LSTICK_DOWN ) | | ( keysHeld & KEY_RSTICK_DOWN ) )
{
if ( selectedContent < ( cnt - 1 ) ) selectedContent + + ;
}
}
breaks = initial_breaks ;
uiFill ( 0 , 8 + ( breaks * LINE_HEIGHT ) , FB_WIDTH , FB_HEIGHT - ( 8 + ( breaks * LINE_HEIGHT ) ) , BG_COLOR_RGB ) ;
uiRefreshDisplay ( ) ;
success = true ;
out :
if ( indexes ) free ( indexes ) ;
if ( success )
{
* outIndex = ( u32 ) idx ;
* outCount = cnt ;
}
return success ;
}
bool readNcaExeFsSection ( u32 titleIndex , bool usePatch )
{
u32 i = 0 ;
Result result ;
NcmStorageId curStorageId = NcmStorageId_None ;
NcmContentMetaType metaType ;
u32 titleCount = 0 , ncmTitleIndex = 0 ;
NcmContentInfo * titleContentInfos = NULL ;
u32 titleContentInfoCnt = 0 ;
u32 contentIndex = 0 , desiredNcaTypeCount = 0 ;
NcmContentId ncaId ;
char ncaIdStr [ SHA256_HASH_SIZE + 1 ] = { ' \0 ' } ;
NcmContentStorage ncmStorage ;
memset ( & ncmStorage , 0 , sizeof ( NcmContentStorage ) ) ;
u8 ncaHeader [ NCA_FULL_HEADER_LENGTH ] = { 0 } ;
nca_header_t dec_nca_header ;
title_rights_ctx rights_info ;
memset ( & rights_info , 0 , sizeof ( title_rights_ctx ) ) ;
u8 decrypted_nca_keys [ NCA_KEY_AREA_SIZE ] ;
bool success = false ;
if ( ( ! usePatch & & ! baseAppEntries ) | | ( usePatch & & ! patchEntries ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: title storage ID unavailable! " , __func__ ) ;
goto out ;
}
if ( ( ! usePatch & & titleIndex > = titleAppCount ) | | ( usePatch & & titleIndex > = titlePatchCount ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid title index! " , __func__ ) ;
goto out ;
}
curStorageId = ( ! usePatch ? baseAppEntries [ titleIndex ] . storageId : patchEntries [ titleIndex ] . storageId ) ;
ncmTitleIndex = ( ! usePatch ? baseAppEntries [ titleIndex ] . ncmIndex : patchEntries [ titleIndex ] . ncmIndex ) ;
metaType = ( ! usePatch ? NcmContentMetaType_Application : NcmContentMetaType_Patch ) ;
switch ( curStorageId )
{
case NcmStorageId_GameCard :
titleCount = ( ! usePatch ? titleAppCount : titlePatchCount ) ;
break ;
case NcmStorageId_SdCard :
titleCount = ( ! usePatch ? sdCardTitleAppCount : sdCardTitlePatchCount ) ;
break ;
case NcmStorageId_BuiltInUser :
titleCount = ( ! usePatch ? emmcTitleAppCount : emmcTitlePatchCount ) ;
break ;
default :
break ;
}
// If we're dealing with a gamecard, open the Secure HFS0 partition (IStorage partition #1)
if ( curStorageId = = NcmStorageId_GameCard )
{
result = openGameCardStoragePartition ( ISTORAGE_PARTITION_SECURE ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to open IStorage partition #1! (0x%08X) " , __func__ , result ) ;
goto out ;
}
}
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Looking for the Program NCA (%s)... " , ( ! usePatch ? " base application " : " update " ) ) ;
uiRefreshDisplay ( ) ;
breaks + + ;
if ( ! retrieveContentInfosFromTitle ( curStorageId , metaType , titleCount , ncmTitleIndex , & titleContentInfos , & titleContentInfoCnt ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , strbuf ) ;
goto out ;
}
if ( ! listDesiredNcaType ( titleContentInfos , titleContentInfoCnt , NcmContentType_Program , - 1 , & contentIndex , & desiredNcaTypeCount ) ) goto out ;
memcpy ( & ncaId , & ( titleContentInfos [ contentIndex ] . content_id ) , sizeof ( NcmContentId ) ) ;
convertDataToHexString ( titleContentInfos [ contentIndex ] . content_id . c , SHA256_HASH_SIZE / 2 , ncaIdStr , SHA256_HASH_SIZE + 1 ) ;
if ( desiredNcaTypeCount = = 1 )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Found Program NCA: \" %s.nca \" . " , ncaIdStr ) ;
uiRefreshDisplay ( ) ;
breaks + = 2 ;
}
/*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Retrieving ExeFS entries...");
uiRefreshDisplay ( ) ;
breaks + + ; */
result = ncmOpenContentStorage ( & ncmStorage , curStorageId ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: ncmOpenContentStorage failed! (0x%08X) " , __func__ , result ) ;
goto out ;
}
if ( ! readNcaDataByContentId ( & ncmStorage , & ncaId , 0 , ncaHeader , NCA_FULL_HEADER_LENGTH ) )
{
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to read header from Program NCA! " , __func__ ) ;
goto out ;
}
// Decrypt the NCA header
if ( ! decryptNcaHeader ( ncaHeader , NCA_FULL_HEADER_LENGTH , & dec_nca_header , & rights_info , decrypted_nca_keys , ( curStorageId ! = NcmStorageId_GameCard | | ( curStorageId = = NcmStorageId_GameCard & & usePatch ) ) ) ) goto out ;
if ( curStorageId = = NcmStorageId_GameCard )
{
bool has_rights_id = false ;
for ( i = 0 ; i < 0x10 ; i + + )
{
if ( dec_nca_header . rights_id [ i ] ! = 0 )
{
has_rights_id = true ;
break ;
}
}
if ( has_rights_id )
{
if ( usePatch )
{
// Retrieve the ticket from the HFS0 partition in the gamecard
if ( ! retrieveTitleKeyFromGameCardTicket ( & rights_info , decrypted_nca_keys ) ) goto out ;
} else {
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: Rights ID field in Program NCA header not empty! " , __func__ ) ;
goto out ;
}
}
}
// Read file entries from the ExeFS section
success = parseExeFsEntryFromNca ( & ncmStorage , & ncaId , & dec_nca_header , decrypted_nca_keys ) ;
if ( success )
{
exeFsContext . storageId = curStorageId ;
exeFsContext . idOffset = titleContentInfos [ contentIndex ] . id_offset ;
}
out :
if ( ! success )
{
ncmContentStorageClose ( & ncmStorage ) ;
if ( curStorageId = = NcmStorageId_GameCard ) closeGameCardStoragePartition ( ) ;
}
if ( titleContentInfos ) free ( titleContentInfos ) ;
return success ;
}
2020-09-26 10:59:01 +01:00
int readNcaRomFsSection ( u32 titleIndex , selectedRomFsType curRomFsType , int desiredIdOffset )
2020-04-11 06:28:26 +01:00
{
u32 i = 0 ;
Result result ;
NcmStorageId curStorageId = NcmStorageId_None ;
NcmContentMetaType metaType ;
u32 titleCount = 0 , ncmTitleIndex = 0 ;
NcmContentInfo * titleContentInfos = NULL ;
u32 titleContentInfoCnt = 0 ;
u32 contentIndex = 0 , desiredNcaTypeCount = 0 ;
NcmContentId ncaId ;
char ncaIdStr [ SHA256_HASH_SIZE + 1 ] = { ' \0 ' } ;
NcmContentStorage ncmStorage ;
memset ( & ncmStorage , 0 , sizeof ( NcmContentStorage ) ) ;
u8 ncaHeader [ NCA_FULL_HEADER_LENGTH ] = { 0 } ;
nca_header_t dec_nca_header ;
title_rights_ctx rights_info ;
memset ( & rights_info , 0 , sizeof ( title_rights_ctx ) ) ;
u8 decrypted_nca_keys [ NCA_KEY_AREA_SIZE ] ;
2020-09-26 10:59:01 +01:00
int ret = - 1 ;
2020-04-11 06:28:26 +01:00
if ( curRomFsType ! = ROMFS_TYPE_APP & & curRomFsType ! = ROMFS_TYPE_PATCH & & curRomFsType ! = ROMFS_TYPE_ADDON )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid RomFS title type! " , __func__ ) ;
goto out ;
}
if ( ( curRomFsType = = ROMFS_TYPE_APP & & ! baseAppEntries ) | | ( curRomFsType = = ROMFS_TYPE_PATCH & & ! patchEntries ) | | ( curRomFsType = = ROMFS_TYPE_ADDON & & ! addOnEntries ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: title storage ID unavailable! " , __func__ ) ;
goto out ;
}
if ( ( curRomFsType = = ROMFS_TYPE_APP & & titleIndex > = titleAppCount ) | | ( curRomFsType = = ROMFS_TYPE_PATCH & & titleIndex > = titlePatchCount ) | | ( curRomFsType = = ROMFS_TYPE_ADDON & & titleIndex > = titleAddOnCount ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid title index! " , __func__ ) ;
goto out ;
}
curStorageId = ( curRomFsType = = ROMFS_TYPE_APP ? baseAppEntries [ titleIndex ] . storageId : ( curRomFsType = = ROMFS_TYPE_PATCH ? patchEntries [ titleIndex ] . storageId : addOnEntries [ titleIndex ] . storageId ) ) ;
ncmTitleIndex = ( curRomFsType = = ROMFS_TYPE_APP ? baseAppEntries [ titleIndex ] . ncmIndex : ( curRomFsType = = ROMFS_TYPE_PATCH ? patchEntries [ titleIndex ] . ncmIndex : addOnEntries [ titleIndex ] . ncmIndex ) ) ;
metaType = ( curRomFsType = = ROMFS_TYPE_APP ? NcmContentMetaType_Application : ( curRomFsType = = ROMFS_TYPE_PATCH ? NcmContentMetaType_Patch : NcmContentMetaType_AddOnContent ) ) ;
switch ( curStorageId )
{
case NcmStorageId_GameCard :
titleCount = ( curRomFsType = = ROMFS_TYPE_APP ? titleAppCount : ( curRomFsType = = ROMFS_TYPE_PATCH ? titlePatchCount : titleAddOnCount ) ) ;
break ;
case NcmStorageId_SdCard :
titleCount = ( curRomFsType = = ROMFS_TYPE_APP ? sdCardTitleAppCount : ( curRomFsType = = ROMFS_TYPE_PATCH ? sdCardTitlePatchCount : sdCardTitleAddOnCount ) ) ;
break ;
case NcmStorageId_BuiltInUser :
titleCount = ( curRomFsType = = ROMFS_TYPE_APP ? emmcTitleAppCount : ( curRomFsType = = ROMFS_TYPE_PATCH ? emmcTitlePatchCount : emmcTitleAddOnCount ) ) ;
break ;
default :
break ;
}
// If we're dealing with a gamecard, open the Secure HFS0 partition (IStorage partition #1)
if ( curStorageId = = NcmStorageId_GameCard )
{
result = openGameCardStoragePartition ( ISTORAGE_PARTITION_SECURE ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to open IStorage partition #1! (0x%08X) " , __func__ , result ) ;
goto out ;
}
}
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Looking for the %s NCA (%s)... " , ( curRomFsType = = ROMFS_TYPE_ADDON ? " Data " : " Program " ) , ( curRomFsType = = ROMFS_TYPE_APP ? " base application " : ( curRomFsType = = ROMFS_TYPE_PATCH ? " update " : " DLC " ) ) ) ;
uiRefreshDisplay ( ) ;
breaks + + ;
if ( ! retrieveContentInfosFromTitle ( curStorageId , metaType , titleCount , ncmTitleIndex , & titleContentInfos , & titleContentInfoCnt ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , strbuf ) ;
goto out ;
}
if ( ! listDesiredNcaType ( titleContentInfos , titleContentInfoCnt , ( curRomFsType = = ROMFS_TYPE_ADDON ? NcmContentType_Data : NcmContentType_Program ) , desiredIdOffset , & contentIndex , & desiredNcaTypeCount ) ) goto out ;
memcpy ( & ncaId , & ( titleContentInfos [ contentIndex ] . content_id ) , sizeof ( NcmContentId ) ) ;
convertDataToHexString ( titleContentInfos [ contentIndex ] . content_id . c , SHA256_HASH_SIZE / 2 , ncaIdStr , SHA256_HASH_SIZE + 1 ) ;
if ( desiredNcaTypeCount = = 1 )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Found %s NCA: \" %s.nca \" . " , ( curRomFsType = = ROMFS_TYPE_ADDON ? " Data " : " Program " ) , ncaIdStr ) ;
uiRefreshDisplay ( ) ;
breaks + = 2 ;
}
/*uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_RGB, "Retrieving RomFS entry tables...");
uiRefreshDisplay ( ) ;
breaks + + ; */
result = ncmOpenContentStorage ( & ncmStorage , curStorageId ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: ncmOpenContentStorage failed! (0x%08X) " , __func__ , result ) ;
goto out ;
}
if ( ! readNcaDataByContentId ( & ncmStorage , & ncaId , 0 , ncaHeader , NCA_FULL_HEADER_LENGTH ) )
{
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to read header from %s NCA! " , __func__ , ( curRomFsType = = ROMFS_TYPE_ADDON ? " Data " : " Program " ) ) ;
goto out ;
}
// Decrypt the NCA header
if ( ! decryptNcaHeader ( ncaHeader , NCA_FULL_HEADER_LENGTH , & dec_nca_header , & rights_info , decrypted_nca_keys , ( curStorageId ! = NcmStorageId_GameCard | | ( curStorageId = = NcmStorageId_GameCard & & curRomFsType = = ROMFS_TYPE_PATCH ) ) ) ) goto out ;
if ( curStorageId = = NcmStorageId_GameCard )
{
bool has_rights_id = false ;
for ( i = 0 ; i < 0x10 ; i + + )
{
if ( dec_nca_header . rights_id [ i ] ! = 0 )
{
has_rights_id = true ;
break ;
}
}
if ( has_rights_id )
{
if ( curRomFsType = = ROMFS_TYPE_PATCH )
{
// Retrieve the ticket from the HFS0 partition in the gamecard
if ( ! retrieveTitleKeyFromGameCardTicket ( & rights_info , decrypted_nca_keys ) ) goto out ;
} else {
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: Rights ID field in %s NCA header not empty! " , __func__ , ( curRomFsType = = ROMFS_TYPE_ADDON ? " Data " : " Program " ) ) ;
goto out ;
}
}
}
if ( curRomFsType ! = ROMFS_TYPE_PATCH )
{
// Read directory and file tables from the RomFS section
2020-09-26 10:59:01 +01:00
ret = parseRomFsEntryFromNca ( & ncmStorage , & ncaId , & dec_nca_header , decrypted_nca_keys ) ;
if ( ret = = 0 )
2020-04-11 06:28:26 +01:00
{
romFsContext . storageId = curStorageId ;
romFsContext . idOffset = titleContentInfos [ contentIndex ] . id_offset ;
}
} else {
// Look for the base application title index
u32 appIndex ;
for ( i = 0 ; i < titleAppCount ; i + + )
{
if ( checkIfPatchOrAddOnBelongsToBaseApplication ( titleIndex , i , false ) )
{
appIndex = i ;
break ;
}
}
if ( i > = titleAppCount )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to find base application title index for the selected update! " , __func__ ) ;
goto out ;
}
// Read directory and file tables from the RomFS section in the Program NCA from the base application
2020-09-26 10:59:01 +01:00
// We'll proceed even if the Program NCA from the base application doesn't hold a RomFS section (ret == -2)
ret = readNcaRomFsSection ( appIndex , ROMFS_TYPE_APP , ( int ) titleContentInfos [ contentIndex ] . id_offset ) ;
if ( ret = = - 1 ) goto out ;
// Remove missing base RomFS error message if needed
if ( ret = = - 2 ) uiFill ( 0 , STRING_Y_POS ( breaks ) , FB_WIDTH , FB_HEIGHT - STRING_Y_POS ( breaks ) , BG_COLOR_RGB ) ;
// Update BKTR context to use the base RomFS if available
bktrContext . use_base_romfs = ( ret = = 0 ) ;
2020-04-11 06:28:26 +01:00
// Read BKTR entry data in the Program NCA from the update
2020-09-26 10:59:01 +01:00
ret = ( parseBktrEntryFromNca ( & ncmStorage , & ncaId , & dec_nca_header , decrypted_nca_keys ) ? 0 : - 1 ) ;
if ( ret = = 0 )
2020-04-11 06:28:26 +01:00
{
bktrContext . storageId = curStorageId ;
bktrContext . idOffset = titleContentInfos [ contentIndex ] . id_offset ;
}
}
out :
2020-09-26 10:59:01 +01:00
if ( ret ! = 0 )
2020-04-11 06:28:26 +01:00
{
ncmContentStorageClose ( & ncmStorage ) ;
if ( curStorageId = = NcmStorageId_GameCard ) closeGameCardStoragePartition ( ) ;
}
if ( titleContentInfos ) free ( titleContentInfos ) ;
2020-09-26 10:59:01 +01:00
return ret ;
2020-04-11 06:28:26 +01:00
}
bool getExeFsFileList ( )
{
if ( ! exeFsContext . exefs_header . file_cnt | | ! exeFsContext . exefs_entries | | ! exeFsContext . exefs_str_table )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to retrieve ExeFS section filelist! " , __func__ ) ;
return false ;
}
u32 i ;
char curName [ NAME_BUF_LEN ] = { ' \0 ' } ;
freeHfs0ExeFsEntriesSizes ( ) ;
hfs0ExeFsEntriesSizes = calloc ( exeFsContext . exefs_header . file_cnt , sizeof ( browser_entry_size_info ) ) ;
if ( ! hfs0ExeFsEntriesSizes )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to allocate memory for ExeFS entries size info! " , __func__ ) ;
return false ;
}
if ( ! allocateFilenameBuffer ( exeFsContext . exefs_header . file_cnt ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for the filename buffer! " , __func__ ) ;
return false ;
}
for ( i = 0 ; i < exeFsContext . exefs_header . file_cnt ; i + + )
{
char * cur_filename = ( exeFsContext . exefs_str_table + exeFsContext . exefs_entries [ i ] . filename_offset ) ;
snprintf ( curName , MAX_CHARACTERS ( curName ) , cur_filename ) ;
// Fix entry name length
truncateBrowserEntryName ( curName ) ;
if ( ! addStringToFilenameBuffer ( curName ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for filename entry in filename buffer! " , __func__ ) ;
freeHfs0ExeFsEntriesSizes ( ) ;
return false ;
}
// Save entry size
hfs0ExeFsEntriesSizes [ i ] . size = exeFsContext . exefs_entries [ i ] . file_size ;
convertSize ( hfs0ExeFsEntriesSizes [ i ] . size , hfs0ExeFsEntriesSizes [ i ] . sizeStr , MAX_CHARACTERS ( hfs0ExeFsEntriesSizes [ i ] . sizeStr ) ) ;
}
return true ;
}
bool getRomFsParentDir ( u32 dir_offset , bool usePatch , u32 * out )
{
if ( ( ! usePatch & & ( ! romFsContext . romfs_dirtable_size | | dir_offset > romFsContext . romfs_dirtable_size | | ! romFsContext . romfs_dir_entries ) ) | | ( usePatch & & ( ! bktrContext . romfs_dirtable_size | | dir_offset > bktrContext . romfs_dirtable_size | | ! bktrContext . romfs_dir_entries ) ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to retrieve parent RomFS section directory! " , __func__ ) ;
return false ;
}
romfs_dir * entry = ( ! usePatch ? ( romfs_dir * ) ( ( u8 * ) romFsContext . romfs_dir_entries + dir_offset ) : ( romfs_dir * ) ( ( u8 * ) bktrContext . romfs_dir_entries + dir_offset ) ) ;
* out = entry - > parent ;
return true ;
}
bool generateCurrentRomFsPath ( u32 dir_offset , bool usePatch )
{
if ( ( ! usePatch & & ( ! romFsContext . romfs_dirtable_size | | dir_offset > romFsContext . romfs_dirtable_size | | ! romFsContext . romfs_dir_entries ) ) | | ( usePatch & & ( ! bktrContext . romfs_dirtable_size | | dir_offset > bktrContext . romfs_dirtable_size | | ! bktrContext . romfs_dir_entries ) ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to generate current RomFS section path! " , __func__ ) ;
return false ;
}
// Generate current path if we're not dealing with the root directory
if ( dir_offset )
{
romfs_dir * entry = ( ! usePatch ? ( romfs_dir * ) ( ( u8 * ) romFsContext . romfs_dir_entries + dir_offset ) : ( romfs_dir * ) ( ( u8 * ) bktrContext . romfs_dir_entries + dir_offset ) ) ;
if ( ! entry - > nameLen )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: directory entry without name in RomFS section! " , __func__ ) ;
return false ;
}
// Check if we're not a root dir child
if ( entry - > parent )
{
if ( ! generateCurrentRomFsPath ( entry - > parent , usePatch ) ) return false ;
}
// Concatenate entry name
strcat ( curRomFsPath , " / " ) ;
strncat ( curRomFsPath , ( char * ) entry - > name , entry - > nameLen ) ;
} else {
strcat ( curRomFsPath , " / " ) ;
}
return true ;
}
bool getRomFsFileList ( u32 dir_offset , bool usePatch )
{
u64 entryOffset = 0 ;
u32 dirEntryCnt = 1 ; // Always add the parent directory entry ("..")
u32 fileEntryCnt = 0 ;
u32 totalEntryCnt = 0 ;
u32 i = 1 ;
u32 romFsParentDir = 0 ;
u64 dirTableSize ;
u64 fileTableSize ;
freeRomFsBrowserEntries ( ) ;
memset ( curRomFsPath , 0 , NAME_BUF_LEN ) ;
if ( ( ! usePatch & & ( ! romFsContext . romfs_dirtable_size | | dir_offset > romFsContext . romfs_dirtable_size | | ! romFsContext . romfs_dir_entries | | ! romFsContext . romfs_filetable_size | | ! romFsContext . romfs_file_entries ) ) | | ( usePatch & & ( ! bktrContext . romfs_dirtable_size | | dir_offset > bktrContext . romfs_dirtable_size | | ! bktrContext . romfs_dir_entries | | ! bktrContext . romfs_filetable_size | | ! bktrContext . romfs_file_entries ) ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to retrieve RomFS section filelist! " , __func__ ) ;
return false ;
}
if ( ! getRomFsParentDir ( dir_offset , usePatch , & romFsParentDir ) ) return false ;
if ( ! generateCurrentRomFsPath ( dir_offset , usePatch ) ) return false ;
dirTableSize = ( ! usePatch ? romFsContext . romfs_dirtable_size : bktrContext . romfs_dirtable_size ) ;
fileTableSize = ( ! usePatch ? romFsContext . romfs_filetable_size : bktrContext . romfs_filetable_size ) ;
// First count the directory entries
entryOffset = ROMFS_NONAME_DIRENTRY_SIZE ; // Always skip the first entry (root directory)
while ( entryOffset < dirTableSize )
{
romfs_dir * entry = ( ! usePatch ? ( romfs_dir * ) ( ( u8 * ) romFsContext . romfs_dir_entries + entryOffset ) : ( romfs_dir * ) ( ( u8 * ) bktrContext . romfs_dir_entries + entryOffset ) ) ;
if ( ! entry - > nameLen )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: directory entry without name in RomFS section! " , __func__ ) ;
return false ;
}
// Only add entries inside the directory we're looking in
if ( entry - > parent = = dir_offset ) dirEntryCnt + + ;
entryOffset + = round_up ( ROMFS_NONAME_DIRENTRY_SIZE + entry - > nameLen , 4 ) ;
}
// Now count the file entries
entryOffset = 0 ;
while ( entryOffset < fileTableSize )
{
romfs_file * entry = ( ! usePatch ? ( romfs_file * ) ( ( u8 * ) romFsContext . romfs_file_entries + entryOffset ) : ( romfs_file * ) ( ( u8 * ) bktrContext . romfs_file_entries + entryOffset ) ) ;
if ( ! entry - > nameLen )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: file entry without name in RomFS section! " , __func__ ) ;
return false ;
}
// Only add entries inside the directory we're looking in
if ( entry - > parent = = dir_offset ) fileEntryCnt + + ;
entryOffset + = round_up ( ROMFS_NONAME_FILEENTRY_SIZE + entry - > nameLen , 4 ) ;
}
totalEntryCnt = ( dirEntryCnt + fileEntryCnt ) ;
char curName [ NAME_BUF_LEN ] = { ' \0 ' } ;
// Silently return true if we're dealing with an empty directory
if ( ! totalEntryCnt ) goto out ;
// Allocate memory for our entries
romFsBrowserEntries = calloc ( totalEntryCnt , sizeof ( romfs_browser_entry ) ) ;
if ( ! romFsBrowserEntries )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to allocate memory for file/dir attributes in RomFS section! " , __func__ ) ;
return false ;
}
if ( ! allocateFilenameBuffer ( totalEntryCnt ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for the filename buffer! " , __func__ ) ;
freeRomFsBrowserEntries ( ) ;
return false ;
}
if ( ! addStringToFilenameBuffer ( " .. " ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for parent dir entry in filename buffer! " , __func__ ) ;
freeRomFsBrowserEntries ( ) ;
return false ;
}
// Add parent directory entry ("..")
romFsBrowserEntries [ 0 ] . type = ROMFS_ENTRY_DIR ;
romFsBrowserEntries [ 0 ] . offset = romFsParentDir ;
// First add the directory entries
if ( ( ! romFsParentDir & & dirEntryCnt > 1 ) | | ( romFsParentDir & & dirEntryCnt > 0 ) )
{
entryOffset = ROMFS_NONAME_DIRENTRY_SIZE ; // Always skip the first entry (root directory)
while ( entryOffset < dirTableSize )
{
romfs_dir * entry = ( ! usePatch ? ( romfs_dir * ) ( ( u8 * ) romFsContext . romfs_dir_entries + entryOffset ) : ( romfs_dir * ) ( ( u8 * ) bktrContext . romfs_dir_entries + entryOffset ) ) ;
// Only add entries inside the directory we're looking in
if ( entry - > parent = = dir_offset )
{
romFsBrowserEntries [ i ] . type = ROMFS_ENTRY_DIR ;
romFsBrowserEntries [ i ] . offset = entryOffset ;
snprintf ( curName , entry - > nameLen + 1 , ( char * ) entry - > name ) ;
// Fix entry name length
truncateBrowserEntryName ( curName ) ;
if ( ! addStringToFilenameBuffer ( curName ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for filename entry in filename buffer! " , __func__ ) ;
freeRomFsBrowserEntries ( ) ;
return false ;
}
i + + ;
}
entryOffset + = round_up ( ROMFS_NONAME_DIRENTRY_SIZE + entry - > nameLen , 4 ) ;
}
}
// Now add the file entries
if ( fileEntryCnt > 0 )
{
entryOffset = 0 ;
while ( entryOffset < fileTableSize )
{
romfs_file * entry = ( ! usePatch ? ( romfs_file * ) ( ( u8 * ) romFsContext . romfs_file_entries + entryOffset ) : ( romfs_file * ) ( ( u8 * ) bktrContext . romfs_file_entries + entryOffset ) ) ;
// Only add entries inside the directory we're looking in
if ( entry - > parent = = dir_offset )
{
romFsBrowserEntries [ i ] . type = ROMFS_ENTRY_FILE ;
romFsBrowserEntries [ i ] . offset = entryOffset ;
romFsBrowserEntries [ i ] . sizeInfo . size = entry - > dataSize ;
convertSize ( entry - > dataSize , romFsBrowserEntries [ i ] . sizeInfo . sizeStr , MAX_CHARACTERS ( romFsBrowserEntries [ i ] . sizeInfo . sizeStr ) ) ;
snprintf ( curName , entry - > nameLen + 1 , ( char * ) entry - > name ) ;
// Fix entry name length
truncateBrowserEntryName ( curName ) ;
if ( ! addStringToFilenameBuffer ( curName ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to allocate memory for filename entry in filename buffer! " , __func__ ) ;
freeRomFsBrowserEntries ( ) ;
return false ;
}
i + + ;
}
entryOffset + = round_up ( ROMFS_NONAME_FILEENTRY_SIZE + entry - > nameLen , 4 ) ;
}
}
out :
// Update current RomFS directory offset
curRomFsDirOffset = dir_offset ;
return true ;
}
void printProgressBar ( progress_ctx_t * progressCtx , bool calcData , u64 chunkSize )
{
if ( ! progressCtx ) return ;
if ( calcData )
{
timeGetCurrentTime ( TimeType_LocalSystemClock , & ( progressCtx - > now ) ) ;
// Workaround to properly calculate speed for sequential dumps
u64 speedCurOffset = ( progressCtx - > seqDumpCurOffset ? progressCtx - > seqDumpCurOffset : progressCtx - > curOffset ) ;
progressCtx - > lastSpeed = ( ( ( double ) ( speedCurOffset + chunkSize ) / ( double ) MiB ) / ( double ) ( progressCtx - > now - progressCtx - > start ) ) ;
progressCtx - > averageSpeed = ( ( SMOOTHING_FACTOR * progressCtx - > lastSpeed ) + ( ( 1 - SMOOTHING_FACTOR ) * progressCtx - > averageSpeed ) ) ;
if ( ! isnormal ( progressCtx - > averageSpeed ) ) progressCtx - > averageSpeed = SMOOTHING_FACTOR ; // Very low values
progressCtx - > remainingTime = ( u64 ) ( ( ( double ) ( progressCtx - > totalSize - ( progressCtx - > curOffset + chunkSize ) ) / ( double ) MiB ) / progressCtx - > averageSpeed ) ;
progressCtx - > progress = ( u8 ) ( ( ( progressCtx - > curOffset + chunkSize ) * 100 ) / progressCtx - > totalSize ) ;
}
formatETAString ( progressCtx - > remainingTime , progressCtx - > etaInfo , MAX_CHARACTERS ( progressCtx - > etaInfo ) ) ;
convertSize ( progressCtx - > curOffset + chunkSize , progressCtx - > curOffsetStr , MAX_CHARACTERS ( progressCtx - > curOffsetStr ) ) ;
uiFill ( 0 , ( progressCtx - > line_offset * LINE_HEIGHT ) + 8 , FB_WIDTH / 4 , LINE_HEIGHT * 2 , BG_COLOR_RGB ) ;
uiDrawString ( font_height * 2 , STRING_Y_POS ( progressCtx - > line_offset ) , FONT_COLOR_RGB , " %.2lf MiB/s [ETA: %s] " , progressCtx - > averageSpeed , progressCtx - > etaInfo ) ;
if ( progressCtx - > totalSize & & ( progressCtx - > curOffset + chunkSize ) < progressCtx - > totalSize )
{
uiFill ( FB_WIDTH / 4 , ( progressCtx - > line_offset * LINE_HEIGHT ) + 10 , FB_WIDTH / 2 , LINE_HEIGHT , EMPTY_BAR_COLOR_RGB ) ;
uiFill ( FB_WIDTH / 4 , ( progressCtx - > line_offset * LINE_HEIGHT ) + 10 , ( ( ( progressCtx - > curOffset + chunkSize ) * ( u64 ) ( FB_WIDTH / 2 ) ) / progressCtx - > totalSize ) , LINE_HEIGHT , FONT_COLOR_SUCCESS_RGB ) ;
} else {
uiFill ( FB_WIDTH / 4 , ( progressCtx - > line_offset * LINE_HEIGHT ) + 10 , FB_WIDTH / 2 , LINE_HEIGHT , FONT_COLOR_SUCCESS_RGB ) ;
}
uiFill ( FB_WIDTH - ( FB_WIDTH / 4 ) , ( progressCtx - > line_offset * LINE_HEIGHT ) + 8 , FB_WIDTH / 4 , LINE_HEIGHT * 2 , BG_COLOR_RGB ) ;
uiDrawString ( FB_WIDTH - ( FB_WIDTH / 4 ) + ( font_height * 2 ) , STRING_Y_POS ( progressCtx - > line_offset ) , FONT_COLOR_RGB , " %u%% [%s / %s] " , progressCtx - > progress , progressCtx - > curOffsetStr , progressCtx - > totalSizeStr ) ;
uiRefreshDisplay ( ) ;
uiUpdateStatusMsg ( ) ;
}
void setProgressBarError ( progress_ctx_t * progressCtx )
{
if ( ! progressCtx ) return ;
if ( progressCtx - > totalSize & & progressCtx - > curOffset < progressCtx - > totalSize )
{
uiFill ( FB_WIDTH / 4 , ( progressCtx - > line_offset * LINE_HEIGHT ) + 10 , FB_WIDTH / 2 , LINE_HEIGHT , EMPTY_BAR_COLOR_RGB ) ;
uiFill ( FB_WIDTH / 4 , ( progressCtx - > line_offset * LINE_HEIGHT ) + 10 , ( ( progressCtx - > curOffset * ( u64 ) ( FB_WIDTH / 2 ) ) / progressCtx - > totalSize ) , LINE_HEIGHT , FONT_COLOR_ERROR_RGB ) ;
} else {
uiFill ( FB_WIDTH / 4 , ( progressCtx - > line_offset * LINE_HEIGHT ) + 10 , FB_WIDTH / 2 , LINE_HEIGHT , FONT_COLOR_ERROR_RGB ) ;
}
}
bool cancelProcessCheck ( progress_ctx_t * progressCtx )
{
if ( ! progressCtx ) return false ;
hidScanInput ( ) ;
progressCtx - > cancelBtnState = ( hidKeysAllHeld ( CONTROLLER_P1_AUTO ) & KEY_B ) ;
if ( progressCtx - > cancelBtnState & & progressCtx - > cancelBtnState ! = progressCtx - > cancelBtnStatePrev )
{
// Cancel button has just been pressed
timeGetCurrentTime ( TimeType_LocalSystemClock , & ( progressCtx - > cancelStartTmr ) ) ;
} else
if ( progressCtx - > cancelBtnState & & progressCtx - > cancelBtnState = = progressCtx - > cancelBtnStatePrev & & progressCtx - > cancelStartTmr )
{
// If the cancel button has been held up to this point, check if at least CANCEL_BTN_SEC_HOLD seconds have passed
// Only perform this check if cancelStartTmr has already been set to a value greater than zero
timeGetCurrentTime ( TimeType_LocalSystemClock , & ( progressCtx - > cancelEndTmr ) ) ;
if ( ( progressCtx - > cancelEndTmr - progressCtx - > cancelStartTmr ) > = CANCEL_BTN_SEC_HOLD ) return true ;
} else {
progressCtx - > cancelStartTmr = progressCtx - > cancelEndTmr = 0 ;
}
progressCtx - > cancelBtnStatePrev = progressCtx - > cancelBtnState ;
return false ;
}
bool yesNoPrompt ( const char * message )
{
if ( message & & strlen ( message ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , message ) ;
breaks + + ;
}
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " [ %s ] Yes | [ %s ] No " , NINTENDO_FONT_A , NINTENDO_FONT_B ) ;
breaks + = 2 ;
bool ret = false ;
while ( true )
{
uiUpdateStatusMsg ( ) ;
uiRefreshDisplay ( ) ;
hidScanInput ( ) ;
u64 keysDown = hidKeysAllDown ( CONTROLLER_P1_AUTO ) ;
if ( keysDown & KEY_A )
{
ret = true ;
break ;
} else
if ( keysDown & KEY_B )
{
ret = false ;
break ;
}
}
return ret ;
}
bool checkIfDumpedXciContainsCertificate ( const char * xciPath )
{
if ( ! xciPath | | ! strlen ( xciPath ) ) return false ;
FILE * xciFile = NULL ;
u64 xciSize = 0 ;
size_t read_bytes ;
u8 xci_cert [ CERT_SIZE ] ;
u8 xci_cert_wiped [ CERT_SIZE ] ;
memset ( xci_cert_wiped , 0xFF , CERT_SIZE ) ;
xciFile = fopen ( xciPath , " rb " ) ;
if ( ! xciFile ) return false ;
fseek ( xciFile , 0 , SEEK_END ) ;
xciSize = ftell ( xciFile ) ;
rewind ( xciFile ) ;
if ( xciSize < ( size_t ) ( CERT_OFFSET + CERT_SIZE ) )
{
fclose ( xciFile ) ;
return false ;
}
fseek ( xciFile , CERT_OFFSET , SEEK_SET ) ;
read_bytes = fread ( xci_cert , 1 , CERT_SIZE , xciFile ) ;
fclose ( xciFile ) ;
if ( read_bytes ! = ( size_t ) CERT_SIZE ) return false ;
if ( memcmp ( xci_cert , xci_cert_wiped , CERT_SIZE ) ! = 0 ) return true ;
return false ;
}
bool checkIfDumpedNspContainsConsoleData ( const char * nspPath )
{
if ( ! nspPath | | ! strlen ( nspPath ) ) return false ;
FILE * nspFile = NULL ;
u64 nspSize = 0 ;
size_t read_bytes ;
pfs0_header nspHeader ;
pfs0_file_entry * nspEntries = NULL ;
char * nspStrTable = NULL ;
u32 i ;
bool foundTik = false ;
u64 tikOffset = 0 , tikSize = 0 ;
rsa2048_sha256_ticket tikData ;
const u8 titlekey_block_0x190_empty_hash [ 0x20 ] = {
0x2D , 0xFB , 0xA6 , 0x33 , 0x81 , 0x70 , 0x46 , 0xC7 , 0xF5 , 0x59 , 0xED , 0x4B , 0x93 , 0x07 , 0x60 , 0x48 ,
0x43 , 0x5F , 0x7E , 0x1A , 0x90 , 0xF1 , 0x4E , 0xB8 , 0x03 , 0x5C , 0x04 , 0xB9 , 0xEB , 0xAE , 0x25 , 0x37
} ;
u8 titlekey_block_0x190_hash [ 0x20 ] ;
nspFile = fopen ( nspPath , " rb " ) ;
if ( ! nspFile ) return false ;
fseek ( nspFile , 0 , SEEK_END ) ;
nspSize = ftell ( nspFile ) ;
rewind ( nspFile ) ;
if ( nspSize < sizeof ( pfs0_header ) )
{
fclose ( nspFile ) ;
return false ;
}
read_bytes = fread ( & nspHeader , 1 , sizeof ( pfs0_header ) , nspFile ) ;
if ( read_bytes ! = sizeof ( pfs0_header ) | | __builtin_bswap32 ( nspHeader . magic ) ! = PFS0_MAGIC | | nspSize < ( sizeof ( pfs0_header ) + ( sizeof ( pfs0_file_entry ) * ( u64 ) nspHeader . file_cnt ) + ( u64 ) nspHeader . str_table_size ) )
{
fclose ( nspFile ) ;
return false ;
}
nspEntries = calloc ( ( u64 ) nspHeader . file_cnt , sizeof ( pfs0_file_entry ) ) ;
if ( ! nspEntries )
{
fclose ( nspFile ) ;
return false ;
}
read_bytes = fread ( nspEntries , 1 , sizeof ( pfs0_file_entry ) * ( u64 ) nspHeader . file_cnt , nspFile ) ;
if ( read_bytes ! = ( sizeof ( pfs0_file_entry ) * ( u64 ) nspHeader . file_cnt ) )
{
free ( nspEntries ) ;
fclose ( nspFile ) ;
return false ;
}
nspStrTable = calloc ( ( u64 ) nspHeader . str_table_size , sizeof ( char ) ) ;
if ( ! nspStrTable )
{
free ( nspEntries ) ;
fclose ( nspFile ) ;
return false ;
}
read_bytes = fread ( nspStrTable , 1 , ( u64 ) nspHeader . str_table_size , nspFile ) ;
if ( read_bytes ! = ( u64 ) nspHeader . str_table_size )
{
free ( nspStrTable ) ;
free ( nspEntries ) ;
fclose ( nspFile ) ;
return false ;
}
for ( i = 0 ; i < nspHeader . file_cnt ; i + + )
{
char * curFilename = ( nspStrTable + nspEntries [ i ] . filename_offset ) ;
if ( ! strncasecmp ( curFilename + strlen ( curFilename ) - 4 , " .tik " , 4 ) )
{
tikOffset = ( sizeof ( pfs0_header ) + ( sizeof ( pfs0_file_entry ) * ( u64 ) nspHeader . file_cnt ) + ( u64 ) nspHeader . str_table_size + nspEntries [ i ] . file_offset ) ;
tikSize = nspEntries [ i ] . file_size ;
foundTik = true ;
break ;
}
}
free ( nspStrTable ) ;
free ( nspEntries ) ;
if ( ! foundTik | | tikSize ! = ETICKET_TIK_FILE_SIZE | | nspSize < ( tikOffset + tikSize ) )
{
fclose ( nspFile ) ;
return false ;
}
fseek ( nspFile , tikOffset , SEEK_SET ) ;
read_bytes = fread ( & tikData , 1 , ETICKET_TIK_FILE_SIZE , nspFile ) ;
fclose ( nspFile ) ;
if ( read_bytes ! = ETICKET_TIK_FILE_SIZE ) return false ;
sha256CalculateHash ( titlekey_block_0x190_hash , tikData . titlekey_block + 0x10 , 0xF0 ) ;
if ( strncmp ( tikData . sig_issuer , " Root-CA00000003-XS00000020 " , 26 ) ! = 0 | | memcmp ( titlekey_block_0x190_hash , titlekey_block_0x190_empty_hash , 0x20 ) ! = 0 | | tikData . titlekey_type ! = ETICKET_TITLEKEY_COMMON | | tikData . ticket_id ! = 0 | | tikData . device_id ! = 0 | | tikData . account_id ! = 0 ) return true ;
return false ;
}
void removeDirectoryWithVerbose ( const char * path , const char * msg )
{
if ( ! path | | ! strlen ( path ) | | ! msg | | ! strlen ( msg ) ) return ;
int initial_breaks = breaks ;
breaks + = 2 ;
if ( yesNoPrompt ( " Do you wish to delete the data dumped up to this point? This may take a while. " ) )
{
breaks = initial_breaks ;
uiFill ( 0 , STRING_Y_POS ( breaks ) , FB_WIDTH , FB_HEIGHT - STRING_Y_POS ( breaks ) , BG_COLOR_RGB ) ;
breaks + = 2 ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , msg ) ;
uiRefreshDisplay ( ) ;
fsdevDeleteDirectoryRecursively ( path ) ;
}
breaks = initial_breaks ;
uiFill ( 0 , STRING_Y_POS ( breaks ) , FB_WIDTH , FB_HEIGHT - STRING_Y_POS ( breaks ) , BG_COLOR_RGB ) ;
uiRefreshDisplay ( ) ;
}
static bool parseNSWDBRelease ( xmlDocPtr doc , xmlNodePtr cur , u32 crc )
{
if ( ! doc | | ! cur ) return false ;
xmlChar * key = NULL ;
xmlNodePtr node = cur ;
u32 xmlCrc = 0 ;
char xmlReleaseName [ 256 ] = { ' \0 ' } ;
bool found = false ;
while ( node )
{
if ( ( ! xmlStrcmp ( node - > name , ( const xmlChar * ) NSWDB_XML_CHILD_IMGCRC ) ) )
{
key = xmlNodeListGetString ( doc , node - > xmlChildrenNode , 1 ) ;
if ( key ) xmlCrc = strtoul ( ( const char * ) key , NULL , 16 ) ;
} else
if ( ( ! xmlStrcmp ( node - > name , ( const xmlChar * ) NSWDB_XML_CHILD_RELEASENAME ) ) )
{
key = xmlNodeListGetString ( doc , node - > xmlChildrenNode , 1 ) ;
if ( key ) snprintf ( xmlReleaseName , MAX_CHARACTERS ( xmlReleaseName ) , " %s " , ( const char * ) key ) ;
}
if ( key )
{
xmlFree ( key ) ;
key = NULL ;
}
node = node - > next ;
}
if ( strlen ( xmlReleaseName ) & & xmlCrc = = crc )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_SUCCESS_RGB , " Found matching Scene release: \" %s \" (CRC32: %08X). This is likely a good dump! " , xmlReleaseName , xmlCrc ) ;
found = true ;
}
return found ;
}
static xmlXPathObjectPtr getXPathNodeSet ( xmlDocPtr doc , char * xpathExpr )
{
if ( ! doc | | ! xpathExpr | | ! strlen ( xpathExpr ) ) return NULL ;
xmlXPathContextPtr context = NULL ;
xmlXPathObjectPtr result = NULL ;
context = xmlXPathNewContext ( doc ) ;
if ( ! context ) return NULL ;
result = xmlXPathEvalExpression ( ( xmlChar * ) xpathExpr , context ) ;
xmlXPathFreeContext ( context ) ;
if ( ! result ) return NULL ;
if ( xmlXPathNodeSetIsEmpty ( result - > nodesetval ) )
{
xmlXPathFreeObject ( result ) ;
return NULL ;
}
return result ;
}
void gameCardDumpNSWDBCheck ( u32 crc )
{
if ( menuType ! = MENUTYPE_GAMECARD | | ! titleAppCount | | ! baseAppEntries | | ! gameCardInfo . hfs0PartitionCnt ) return ;
u32 i , j ;
xmlDocPtr doc = NULL ;
bool found = false ;
doc = xmlParseFile ( NSWDB_XML_PATH ) ;
if ( ! doc )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to open and/or parse \" %s \" ! " , __func__ , NSWDB_XML_PATH ) ;
return ;
}
for ( i = 0 ; i < titleAppCount ; i + + )
{
snprintf ( strbuf , MAX_CHARACTERS ( strbuf ) , " //%s/%s[.//%s[contains(.,'%016lX')]] " , NSWDB_XML_ROOT , NSWDB_XML_CHILD , NSWDB_XML_CHILD_TITLEID , baseAppEntries [ i ] . titleId ) ;
xmlXPathObjectPtr nodeSet = getXPathNodeSet ( doc , strbuf ) ;
if ( ! nodeSet ) continue ;
for ( j = 0 ; j < ( u32 ) nodeSet - > nodesetval - > nodeNr ; j + + )
{
xmlNodePtr node = nodeSet - > nodesetval - > nodeTab [ j ] - > xmlChildrenNode ;
found = parseNSWDBRelease ( doc , node , crc ) ;
if ( found ) break ;
}
xmlXPathFreeObject ( nodeSet ) ;
if ( found ) break ;
}
xmlFreeDoc ( doc ) ;
if ( ! found ) uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " No match found in NSWDB.COM XML database! This could either be a bad dump or an undumped gamecard. " ) ;
}
static Result networkInit ( )
{
if ( initNet ) return 0 ;
Result result = socketInitializeDefault ( ) ;
if ( R_SUCCEEDED ( result ) )
{
curl_global_init ( CURL_GLOBAL_ALL ) ;
initNet = true ;
}
return result ;
}
static void networkExit ( )
{
if ( ! initNet ) return ;
curl_global_cleanup ( ) ;
socketExit ( ) ;
initNet = false ;
}
static size_t writeCurlFile ( char * buffer , size_t size , size_t number_of_items , void * input_stream )
{
size_t total_size = ( size * number_of_items ) ;
if ( fwrite ( buffer , 1 , total_size , input_stream ) ! = total_size ) return 0 ;
return total_size ;
}
static size_t writeCurlBuffer ( char * buffer , size_t size , size_t number_of_items , void * input_stream )
{
( void ) input_stream ;
const size_t bsz = ( size * number_of_items ) ;
if ( result_sz = = 0 | | ! result_buf )
{
result_sz = 0x1000 ;
result_buf = malloc ( result_sz ) ;
if ( ! result_buf ) return 0 ;
}
bool need_realloc = false ;
while ( ( result_written + bsz ) > result_sz )
{
result_sz < < = 1 ;
need_realloc = true ;
}
if ( need_realloc )
{
char * new_buf = realloc ( result_buf , result_sz ) ;
if ( ! new_buf ) return 0 ;
result_buf = new_buf ;
}
memcpy ( result_buf + result_written , buffer , bsz ) ;
result_written + = bsz ;
return bsz ;
}
static bool performCurlRequest ( CURL * curl , const char * url , FILE * filePtr , bool forceHttps , bool verbose )
{
if ( ! curl | | ! url | | ! strlen ( url ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to perform CURL request! " , __func__ ) ;
return false ;
}
curl_easy_setopt ( curl , CURLOPT_BUFFERSIZE , 102400L ) ;
curl_easy_setopt ( curl , CURLOPT_URL , url ) ;
curl_easy_setopt ( curl , CURLOPT_USERAGENT , HTTP_USER_AGENT ) ;
curl_easy_setopt ( curl , CURLOPT_FAILONERROR , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_NOPROGRESS , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_NOBODY , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_HEADER , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_FOLLOWLOCATION , 1L ) ;
curl_easy_setopt ( curl , CURLOPT_SSL_VERIFYPEER , 0L ) ;
curl_easy_setopt ( curl , CURLOPT_MAXREDIRS , 50L ) ;
if ( forceHttps ) curl_easy_setopt ( curl , CURLOPT_HTTP_VERSION , ( long ) CURL_HTTP_VERSION_2TLS ) ;
if ( filePtr )
{
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , writeCurlFile ) ;
curl_easy_setopt ( curl , CURLOPT_WRITEDATA , filePtr ) ;
} else {
curl_easy_setopt ( curl , CURLOPT_WRITEFUNCTION , writeCurlBuffer ) ;
}
CURLcode res ;
long http_code = 0 ;
double size = 0.0 ;
bool success = false ;
res = curl_easy_perform ( curl ) ;
result_sz = result_written = 0 ;
curl_easy_getinfo ( curl , CURLINFO_RESPONSE_CODE , & http_code ) ;
curl_easy_getinfo ( curl , CURLINFO_SIZE_DOWNLOAD , & size ) ;
if ( res = = CURLE_OK & & http_code > = 200 & & http_code < = 299 & & size > 0 )
{
if ( verbose ) uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_SUCCESS_RGB , " Successfully downloaded %.0lf bytes! " , size ) ;
success = true ;
} else {
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: CURL request failed for \" %s \" endpoint! \n HTTP status code: %ld " , __func__ , url , http_code ) ;
}
return success ;
}
void noIntroDumpCheck ( bool isDigital , u32 crc )
{
Result result ;
CURL * curl = NULL ;
char noIntroUrl [ 128 ] = { ' \0 ' } ;
// Build URL
// f = "cart" (XCI) or "dlc" (NSP)
// c = search by code (Title ID or serial)
// crc = search by CRC32 checksum
snprintf ( noIntroUrl , MAX_CHARACTERS ( noIntroUrl ) , " %s?f=%s&crc=%08X " , NOINTRO_DOM_CHECK_URL , ( isDigital ? " dlc " : " cart " ) , crc ) ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Performing CRC32 checksum lookup against No-Intro, please wait... " ) ;
uiRefreshDisplay ( ) ;
breaks + + ;
result = networkInit ( ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to initialize socket! (%08X) " , __func__ , result ) ;
goto out ;
}
curl = curl_easy_init ( ) ;
if ( ! curl )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to initialize CURL context! " , __func__ ) ;
goto out ;
}
if ( ! performCurlRequest ( curl , noIntroUrl , NULL , true , false ) ) goto out ;
if ( ! strlen ( result_buf ) | | ! strncmp ( result_buf , " unknown crc32 " , 13 ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " No match found in No-Intro database! This could either be a bad dump or an undumped %s. " , ( isDigital ? " digital title " : " gamecard " ) ) ;
goto out ;
}
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_SUCCESS_RGB , " Found matching No-Intro database entry: \" %s \" . This is likely a good dump! " , result_buf ) ;
out :
if ( result_buf )
{
free ( result_buf ) ;
result_buf = NULL ;
}
if ( curl ) curl_easy_cleanup ( curl ) ;
if ( R_SUCCEEDED ( result ) ) networkExit ( ) ;
}
void updateNSWDBXml ( )
{
Result result ;
CURL * curl = NULL ;
bool success = false ;
FILE * nswdbXml = NULL ;
result = networkInit ( ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to initialize socket! (%08X) " , __func__ , result ) ;
goto out ;
}
char xmlPath [ 256 ] = { ' \0 ' } ;
snprintf ( xmlPath , MAX_CHARACTERS ( xmlPath ) , " %s.tmp " , NSWDB_XML_PATH ) ;
curl = curl_easy_init ( ) ;
if ( ! curl )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to initialize CURL context! " , __func__ ) ;
goto out ;
}
nswdbXml = fopen ( xmlPath , " wb " ) ;
if ( ! nswdbXml )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to open \" %s \" in write mode! " , __func__ , NSWDB_XML_URL ) ;
goto out ;
}
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Downloading XML database from \" %s \" , please wait... " , NSWDB_XML_URL ) ;
breaks + + ;
appletModeOperationWarning ( ) ;
uiRefreshDisplay ( ) ;
breaks + + ;
changeHomeButtonBlockStatus ( true ) ;
success = performCurlRequest ( curl , NSWDB_XML_URL , nswdbXml , false , true ) ;
changeHomeButtonBlockStatus ( false ) ;
out :
if ( nswdbXml ) fclose ( nswdbXml ) ;
if ( success )
{
remove ( NSWDB_XML_PATH ) ;
rename ( xmlPath , NSWDB_XML_PATH ) ;
} else {
remove ( xmlPath ) ;
}
if ( curl ) curl_easy_cleanup ( curl ) ;
if ( R_SUCCEEDED ( result ) ) networkExit ( ) ;
breaks + = 2 ;
}
static int versionNumCmp ( char * ver1 , char * ver2 )
{
int i , curPart , res ;
char * token = NULL ;
// Define a struct for comparison purposes
typedef struct {
int major ;
int minor ;
int build ;
} version_t ;
version_t versionNum1 , versionNum2 ;
memset ( & versionNum1 , 0 , sizeof ( version_t ) ) ;
memset ( & versionNum2 , 0 , sizeof ( version_t ) ) ;
// Create copies of the version strings to avoid modifications by strtok()
char ver1tok [ 64 ] = { ' \0 ' } ;
snprintf ( ver1tok , 63 , ver1 ) ;
char ver2tok [ 64 ] = { ' \0 ' } ;
snprintf ( ver2tok , 63 , ver2 ) ;
// Parse version string 1
i = 0 ;
token = strtok ( ver1tok , " . " ) ;
while ( token ! = NULL & & i < 3 )
{
curPart = atoi ( token ) ;
switch ( i )
{
case 0 :
versionNum1 . major = curPart ;
break ;
case 1 :
versionNum1 . minor = curPart ;
break ;
case 2 :
versionNum1 . build = curPart ;
break ;
default :
break ;
}
token = strtok ( NULL , " . " ) ;
i + + ;
}
// Parse version string 2
i = 0 ;
token = strtok ( ver2tok , " . " ) ;
while ( token ! = NULL & & i < 3 )
{
curPart = atoi ( token ) ;
switch ( i )
{
case 0 :
versionNum2 . major = curPart ;
break ;
case 1 :
versionNum2 . minor = curPart ;
break ;
case 2 :
versionNum2 . build = curPart ;
break ;
default :
break ;
}
token = strtok ( NULL , " . " ) ;
i + + ;
}
// Compare version_t structs
if ( versionNum1 . major = = versionNum2 . major )
{
if ( versionNum1 . minor = = versionNum2 . minor )
{
if ( versionNum1 . build = = versionNum2 . build )
{
res = 0 ;
} else
if ( versionNum1 . build < versionNum2 . build )
{
res = - 1 ;
} else {
res = 1 ;
}
} else
if ( versionNum1 . minor < versionNum2 . minor )
{
res = - 1 ;
} else {
res = 1 ;
}
} else
if ( versionNum1 . major < versionNum2 . major )
{
res = - 1 ;
} else {
res = 1 ;
}
return res ;
}
static struct json_object * retrieveJsonObjMemberByNameAndType ( struct json_object * jobj , char * memberName , json_type memberType )
{
if ( ! jobj | | ! memberName | | ! strlen ( memberName ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to retrieve member by name and type from JSON object! " , __func__ ) ;
return NULL ;
}
struct json_object * memberObj = NULL ;
json_type memberObjType ;
if ( ! json_object_object_get_ex ( jobj , memberName , & memberObj ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to retrieve member \" %s \" from JSON object! " , __func__ , memberName ) ;
return NULL ;
}
memberObjType = json_object_get_type ( memberObj ) ;
if ( memberObjType ! = memberType )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid type for member \" %s \" in JSON object! (got \" %s \" , expected \" %s \" ) " , __func__ , memberName , json_type_to_name ( memberObjType ) , json_type_to_name ( memberType ) ) ;
return NULL ;
}
return memberObj ;
}
static const char * retrieveJsonObjStrMemberContentsByName ( struct json_object * jobj , char * memberName )
{
if ( ! jobj | | ! memberName | | ! strlen ( memberName ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to retrieve string member contents by name from JSON object! " , __func__ ) ;
return NULL ;
}
struct json_object * memberObj = retrieveJsonObjMemberByNameAndType ( jobj , memberName , json_type_string ) ;
if ( ! memberObj ) return NULL ;
const char * memberObjStr = json_object_get_string ( memberObj ) ;
if ( ! memberObjStr | | ! strlen ( memberObjStr ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: string member \" %s \" from JSON object is empty! " , __func__ , memberName ) ;
return NULL ;
}
return memberObjStr ;
}
static struct json_object * retrieveJsonObjArrayMemberByName ( struct json_object * jobj , char * memberName , size_t * outputArrayLength )
{
if ( ! jobj | | ! memberName | | ! strlen ( memberName ) | | ! outputArrayLength )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to retrieve array member by name from JSON object! " , __func__ ) ;
return NULL ;
}
struct json_object * memberObj = retrieveJsonObjMemberByNameAndType ( jobj , memberName , json_type_array ) ;
if ( memberObj ) * outputArrayLength = json_object_array_length ( memberObj ) ;
return memberObj ;
}
static struct json_object * retrieveJsonObjArrayElementByIndex ( struct json_object * jobj , size_t idx )
{
if ( ! jobj | | json_object_get_type ( jobj ) ! = json_type_array )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: invalid parameters to retrieve element by index from JSON array object! " , __func__ ) ;
return NULL ;
}
struct json_object * memberObjArrayElement = json_object_array_get_idx ( jobj , idx ) ;
if ( ! memberObjArrayElement ) uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to retrieve element at index %lu from JSON array object! " , __func__ , idx ) ;
return memberObjArrayElement ;
}
bool updateApplication ( )
{
if ( envIsNso ( ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to update application. Not running as a NRO. " , __func__ ) ;
breaks + = 2 ;
return false ;
}
Result result ;
CURL * curl = NULL ;
FILE * nxDumpToolNro = NULL ;
char releaseTag [ 32 ] = { ' \0 ' } ;
bool success = false ;
size_t i , assetsCnt = 0 ;
struct json_object * jobj = NULL , * assets = NULL ;
const char * releaseNameObjStr = NULL , * dlUrlObjStr = NULL ;
char nroPath [ NAME_BUF_LEN ] = { ' \0 ' } ;
snprintf ( nroPath , MAX_CHARACTERS ( nroPath ) , " %s.tmp " , ( appLaunchPath ? appLaunchPath : NRO_PATH ) ) ;
result = networkInit ( ) ;
if ( R_FAILED ( result ) )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to initialize socket! (%08X) " , __func__ , result ) ;
goto out ;
}
curl = curl_easy_init ( ) ;
if ( ! curl )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to initialize CURL context! " , __func__ ) ;
goto out ;
}
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Requesting latest release information from \" %s \" ... " , GITHUB_API_URL ) ;
breaks + + ;
uiRefreshDisplay ( ) ;
if ( ! performCurlRequest ( curl , GITHUB_API_URL , NULL , true , false ) ) goto out ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Parsing response JSON data... " ) ;
breaks + + ;
uiRefreshDisplay ( ) ;
jobj = json_tokener_parse ( result_buf ) ;
if ( ! jobj )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to parse JSON response! " , __func__ ) ;
goto out ;
}
releaseNameObjStr = retrieveJsonObjStrMemberContentsByName ( jobj , GITHUB_API_JSON_RELEASE_NAME ) ;
if ( ! releaseNameObjStr ) goto out ;
snprintf ( releaseTag , MAX_CHARACTERS ( releaseTag ) , releaseNameObjStr ) ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Latest release: %s. " , releaseTag ) ;
breaks + + ;
uiRefreshDisplay ( ) ;
// Remove the first character from the release name if it's v/V/r/R
if ( releaseTag [ 0 ] = = ' v ' | | releaseTag [ 0 ] = = ' V ' | | releaseTag [ 0 ] = = ' r ' | | releaseTag [ 0 ] = = ' R ' )
{
u32 releaseTagLen = strlen ( releaseTag ) ;
memmove ( releaseTag , releaseTag + 1 , releaseTagLen - 1 ) ;
releaseTag [ releaseTagLen - 1 ] = ' \0 ' ;
}
// Compare versions
if ( versionNumCmp ( releaseTag , APP_VERSION ) < = 0 )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " You already have the latest version! " ) ;
breaks + = 2 ;
// Ask the user if they want to perform a forced update
int cur_breaks = breaks ;
if ( yesNoPrompt ( " Do you want to perform a forced update? " ) )
{
// Remove the prompt from the screen
breaks = cur_breaks ;
uiFill ( 0 , STRING_Y_POS ( breaks ) , FB_WIDTH , FB_HEIGHT - STRING_Y_POS ( breaks ) , BG_COLOR_RGB ) ;
uiRefreshDisplay ( ) ;
} else {
breaks - = 2 ;
goto out ;
}
}
assets = retrieveJsonObjArrayMemberByName ( jobj , GITHUB_API_JSON_ASSETS , & assetsCnt ) ;
if ( ! assets ) goto out ;
// Cycle through the assets to find the right download URL
for ( i = 0 ; i < assetsCnt ; i + + )
{
struct json_object * assetElement = retrieveJsonObjArrayElementByIndex ( assets , i ) ;
if ( ! assetElement ) break ;
const char * assetName = retrieveJsonObjStrMemberContentsByName ( assetElement , GITHUB_API_JSON_ASSETS_NAME ) ;
if ( ! assetName ) break ;
if ( ! strncmp ( assetName , NRO_NAME , strlen ( assetName ) ) )
{
// Found it
dlUrlObjStr = retrieveJsonObjStrMemberContentsByName ( assetElement , GITHUB_API_JSON_ASSETS_DL_URL ) ;
break ;
}
}
if ( ! dlUrlObjStr )
{
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: unable to locate NRO download URL! " , __func__ ) ;
goto out ;
}
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Download URL: \" %s \" . " , dlUrlObjStr ) ;
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_RGB , " Please wait... " ) ;
breaks + + ;
appletModeOperationWarning ( ) ;
uiRefreshDisplay ( ) ;
breaks + + ;
changeHomeButtonBlockStatus ( true ) ;
nxDumpToolNro = fopen ( nroPath , " wb " ) ;
if ( ! nxDumpToolNro )
{
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_ERROR_RGB , " %s: failed to open \" %s \" in write mode! " , __func__ , nroPath ) ;
goto out ;
}
curl_easy_reset ( curl ) ;
success = performCurlRequest ( curl , dlUrlObjStr , nxDumpToolNro , true , true ) ;
if ( ! success ) goto out ;
breaks + + ;
uiDrawString ( STRING_X_POS , STRING_Y_POS ( breaks ) , FONT_COLOR_SUCCESS_RGB , " Please restart the application to reflect the changes. " ) ;
out :
if ( nxDumpToolNro ) fclose ( nxDumpToolNro ) ;
if ( strlen ( nroPath ) )
{
if ( success )
{
snprintf ( strbuf , MAX_CHARACTERS ( strbuf ) , nroPath ) ;
nroPath [ strlen ( nroPath ) - 4 ] = ' \0 ' ;
remove ( nroPath ) ;
rename ( strbuf , nroPath ) ;
} else {
remove ( nroPath ) ;
}
}
if ( jobj ) json_object_put ( jobj ) ;
if ( result_buf )
{
free ( result_buf ) ;
result_buf = NULL ;
}
if ( curl ) curl_easy_cleanup ( curl ) ;
if ( R_SUCCEEDED ( result ) ) networkExit ( ) ;
breaks + = 2 ;
changeHomeButtonBlockStatus ( false ) ;
return success ;
}