2020-11-08 19:08:30 +00: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-11-08 19:08:30 +00: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-11-08 19:08:30 +00: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-11-08 19:08:30 +00:00
*/
2021-03-26 04:35:14 +00:00
# include "nxdt_utils.h"
2022-07-05 00:25:28 +01:00
# include "romfs.h"
2020-11-08 19:08:30 +00:00
# include "gamecard.h"
# include "title.h"
# define BLOCK_SIZE 0x800000
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 } ;
2020-11-08 19:08:30 +00:00
static Mutex g_fileMutex = 0 ;
static CondVar g_readCondvar = 0 , g_writeCondvar = 0 ;
typedef struct
{
FILE * fd ;
RomFileSystemContext * romfs_ctx ;
void * data ;
size_t data_size ;
size_t data_written ;
size_t total_size ;
bool read_error ;
bool write_error ;
bool transfer_cancelled ;
} ThreadSharedData ;
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-11-08 19:08:30 +00:00
static void consolePrint ( const char * text , . . . )
{
va_list v ;
va_start ( v , text ) ;
vfprintf ( stdout , text , v ) ;
va_end ( v ) ;
consoleUpdate ( NULL ) ;
}
static void read_thread_func ( void * arg )
{
ThreadSharedData * shared_data = ( ThreadSharedData * ) arg ;
2022-07-05 00:25:28 +01:00
if ( ! shared_data | | shared_data - > fd | | ! shared_data - > data | | ! shared_data - > total_size | | ! shared_data - > romfs_ctx )
2020-11-08 19:08:30 +00:00
{
shared_data - > read_error = true ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
u8 * buf = malloc ( BLOCK_SIZE ) ;
if ( ! buf )
{
shared_data - > read_error = true ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
RomFileSystemFileEntry * file_entry = NULL ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
char path [ FS_MAX_PATH ] = { 0 } ;
sprintf ( path , " sdmc:/romfs " ) ;
2022-07-05 02:04:28 +01:00
2022-07-06 10:57:31 +01:00
/* Reset current file table offset. */
romfsResetFileTableOffset ( shared_data - > romfs_ctx ) ;
/* Loop through all file entries. */
while ( romfsCanMoveToNextFileEntry ( shared_data - > romfs_ctx ) )
2020-11-08 19:08:30 +00:00
{
/* Check if the transfer has been cancelled by the user. */
if ( shared_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_data - > data_size & & ! shared_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_data - > write_error ) break ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Close file. */
if ( shared_data - > fd )
{
fclose ( shared_data - > fd ) ;
shared_data - > fd = NULL ;
2022-07-05 05:01:07 +01:00
utilsCommitSdCardFileSystemChanges ( ) ;
2020-11-08 19:08:30 +00:00
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Retrieve RomFS file entry information. */
2022-07-06 10:57:31 +01:00
shared_data - > read_error = ( ! ( file_entry = romfsGetCurrentFileEntry ( shared_data - > romfs_ctx ) ) | | \
2022-07-05 00:25:28 +01:00
! romfsGeneratePathFromFileEntry ( shared_data - > romfs_ctx , file_entry , path + 11 , FS_MAX_PATH - 11 , RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly ) ) ;
2020-11-08 19:08:30 +00:00
if ( shared_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Create directory tree. */
utilsCreateDirectoryTree ( path , false ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Create file. */
2022-07-05 05:01:07 +01:00
if ( file_entry - > size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( path ) )
{
shared_data - > read_error = true ;
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2020-11-08 19:08:30 +00:00
shared_data - > read_error = ( ( shared_data - > fd = fopen ( path , " wb " ) ) = = NULL ) ;
if ( shared_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < file_entry - > size ; offset + = blksize )
{
if ( blksize > ( file_entry - > size - offset ) ) blksize = ( file_entry - > size - offset ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Check if the transfer has been cancelled by the user. */
if ( shared_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Read current file data chunk. */
2022-07-05 00:25:28 +01:00
shared_data - > read_error = ! romfsReadFileEntryData ( shared_data - > romfs_ctx , file_entry , buf , blksize , offset ) ;
2020-11-08 19:08:30 +00:00
if ( shared_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( shared_data - > data_size & & ! shared_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( shared_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Copy current file data chunk to the shared buffer. */
memcpy ( shared_data - > data , buf , blksize ) ;
shared_data - > data_size = blksize ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( shared_data - > read_error | | shared_data - > write_error | | shared_data - > transfer_cancelled ) break ;
2022-07-05 02:04:28 +01:00
2022-07-06 10:57:31 +01:00
/* Move to the next file entry. */
shared_data - > read_error = ! romfsMoveToNextFileEntry ( shared_data - > romfs_ctx ) ;
if ( shared_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2020-11-08 19:08:30 +00:00
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_data - > data_size & & ! shared_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( shared_data - > fd )
{
fclose ( shared_data - > fd ) ;
shared_data - > fd = NULL ;
2022-07-05 05:01:07 +01:00
if ( shared_data - > read_error | | shared_data - > write_error | | shared_data - > transfer_cancelled ) utilsRemoveConcatenationFile ( path ) ;
utilsCommitSdCardFileSystemChanges ( ) ;
2020-11-08 19:08:30 +00:00
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
free ( buf ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
end :
threadExit ( ) ;
}
static void write_thread_func ( void * arg )
{
ThreadSharedData * shared_data = ( ThreadSharedData * ) arg ;
if ( ! shared_data | | ! shared_data - > data )
{
shared_data - > write_error = true ;
goto end ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
while ( shared_data - > data_written < shared_data - > total_size )
{
/* Wait until the current file data chunk has been read */
mutexLock ( & g_fileMutex ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( ! shared_data - > data_size & & ! shared_data - > read_error ) condvarWait ( & g_writeCondvar , & g_fileMutex ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( shared_data - > read_error | | shared_data - > transfer_cancelled | | ! shared_data - > fd )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Write current file data chunk */
shared_data - > write_error = ( fwrite ( shared_data - > data , 1 , shared_data - > data_size , shared_data - > fd ) ! = shared_data - > data_size ) ;
if ( ! shared_data - > write_error )
{
shared_data - > data_written + = shared_data - > data_size ;
shared_data - > data_size = 0 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
/* Wake up the read thread to continue reading data */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_readCondvar ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( shared_data - > write_error ) break ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
end :
threadExit ( ) ;
}
u8 get_program_id_offset ( TitleInfo * info , u32 program_count )
{
if ( program_count < = 1 ) return 0 ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
u8 id_offset = 0 ;
u32 selected_idx = 0 , page_size = 30 , scroll = 0 ;
char nca_id_str [ 0x21 ] = { 0 } ;
2022-02-19 05:03:06 +00:00
bool applet_status = true ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
NcmContentInfo * * content_infos = calloc ( program_count , sizeof ( NcmContentInfo * ) ) ;
if ( ! content_infos ) return 0 ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
for ( u32 i = 0 , j = 0 ; i < info - > content_count & & j < program_count ; i + + )
{
if ( info - > content_infos [ i ] . content_type ! = NcmContentType_Program ) continue ;
content_infos [ j + + ] = & ( info - > content_infos [ i ] ) ;
}
2022-07-05 02:04:28 +01:00
2022-02-19 05:03:06 +00:00
while ( ( applet_status = appletMainLoop ( ) ) )
2020-11-08 19:08:30 +00:00
{
consoleClear ( ) ;
printf ( " select a program nca to dump the romfs from. \n \n " ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
for ( u32 i = scroll ; i < program_count ; i + + )
{
if ( i > = ( scroll + page_size ) ) break ;
2021-05-11 23:36:15 +01:00
utilsGenerateHexStringFromData ( nca_id_str , sizeof ( nca_id_str ) , content_infos [ i ] - > content_id . c , sizeof ( content_infos [ i ] - > content_id . c ) , false ) ;
2020-11-08 19:08:30 +00:00
printf ( " %s%s.nca (ID offset #%u) \n " , i = = selected_idx ? " -> " : " " , nca_id_str , content_infos [ i ] - > id_offset ) ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
printf ( " \n " ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consoleUpdate ( NULL ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
u64 btn_down = 0 , btn_held = 0 ;
2022-02-19 05:03:06 +00:00
while ( ( applet_status = appletMainLoop ( ) ) )
2020-11-08 19:08:30 +00:00
{
2020-12-04 06:38:44 +00:00
utilsScanPads ( ) ;
btn_down = utilsGetButtonsDown ( ) ;
btn_held = utilsGetButtonsHeld ( ) ;
2020-11-08 19:08:30 +00:00
if ( btn_down | | btn_held ) 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
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_A )
2020-11-08 19:08:30 +00:00
{
id_offset = content_infos [ selected_idx ] - > id_offset ;
break ;
} else
2020-12-04 06:38:44 +00:00
if ( ( btn_down & HidNpadButton_Down ) | | ( btn_held & ( HidNpadButton_StickLDown | HidNpadButton_StickRDown ) ) )
2020-11-08 19:08:30 +00:00
{
selected_idx + + ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( selected_idx > = program_count )
{
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_Down )
2020-11-08 19:08:30 +00:00
{
selected_idx = scroll = 0 ;
} else {
selected_idx = ( program_count - 1 ) ;
}
} else
if ( selected_idx > = ( scroll + ( page_size / 2 ) ) & & program_count > ( 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-11-08 19:08:30 +00:00
{
selected_idx - - ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( selected_idx = = UINT32_MAX )
{
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_Up )
2020-11-08 19:08:30 +00:00
{
selected_idx = ( program_count - 1 ) ;
scroll = ( program_count > = page_size ? ( program_count - page_size ) : 0 ) ;
} else {
selected_idx = 0 ;
}
} else
if ( selected_idx < ( scroll + ( page_size / 2 ) ) & & scroll > 0 )
{
scroll - - ;
}
}
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-11-08 19:08:30 +00:00
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
free ( content_infos ) ;
2022-07-05 02:04:28 +01:00
2022-02-19 05:03:06 +00:00
return ( applet_status ? id_offset : ( u8 ) program_count ) ;
2020-11-08 19:08:30 +00:00
}
2022-03-17 19:29:44 +00:00
static TitleInfo * get_latest_patch_info ( TitleInfo * patch_info )
{
if ( ! patch_info | | patch_info - > meta_key . type ! = NcmContentMetaType_Patch ) return NULL ;
2022-07-05 02:04:28 +01:00
2022-03-17 19:29:44 +00:00
TitleInfo * output = patch_info , * tmp = patch_info ;
u32 highest_version = patch_info - > version . value ;
2022-07-05 02:04:28 +01:00
2022-03-17 19:29:44 +00:00
while ( ( tmp = tmp - > next ) ! = NULL )
{
if ( tmp - > version . value > highest_version )
{
output = tmp ;
highest_version = output - > version . value ;
}
}
2022-07-05 02:04:28 +01:00
2022-03-17 19:29:44 +00:00
return output ;
}
2020-11-08 19:08:30 +00: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-11-08 19:08:30 +00: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-11-08 19:08:30 +00:00
u32 app_count = 0 ;
TitleApplicationMetadata * * app_metadata = NULL ;
TitleUserApplicationData user_app_data = { 0 } ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
u32 selected_idx = 0 , page_size = 30 , scroll = 0 ;
2022-02-19 05:03:06 +00:00
bool applet_status = true , exit_prompt = true ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
u8 * buf = NULL ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
NcaContext * base_nca_ctx = NULL , * update_nca_ctx = NULL ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
RomFileSystemContext romfs_ctx = { 0 } ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
ThreadSharedData shared_data = { 0 } ;
Thread read_thread = { 0 } , write_thread = { 0 } ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00: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-11-08 19:08:30 +00:00
consolePrint ( " app metadata succeeded \n " ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
buf = malloc ( BLOCK_SIZE ) ;
if ( ! buf )
{
consolePrint ( " buf failed \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consolePrint ( " buf succeeded \n " ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
base_nca_ctx = calloc ( 1 , sizeof ( NcaContext ) ) ;
if ( ! base_nca_ctx )
{
consolePrint ( " base nca ctx buf failed \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consolePrint ( " base nca ctx buf succeeded \n " ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
update_nca_ctx = calloc ( 1 , sizeof ( NcaContext ) ) ;
if ( ! update_nca_ctx )
{
consolePrint ( " update nca ctx buf failed \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consolePrint ( " update nca ctx buf succeeded \n " ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
utilsSleep ( 1 ) ;
2022-07-05 02:04:28 +01:00
2022-02-19 05:03:06 +00:00
while ( ( applet_status = appletMainLoop ( ) ) )
2020-11-08 19:08:30 +00:00
{
consoleClear ( ) ;
printf ( " select a user application to dump its romfs. \n if an update is available, patch romfs data will be dumped instead. \n data will be saved to \" sdmc:/romfs \" . \n press b to exit. \n \n " ) ;
printf ( " title: %u / %u \n " , selected_idx + 1 , app_count ) ;
printf ( " selected title: %016lX - %s \n \n " , app_metadata [ selected_idx ] - > title_id , app_metadata [ selected_idx ] - > lang_entry . name ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
for ( u32 i = scroll ; i < app_count ; i + + )
{
if ( i > = ( scroll + page_size ) ) break ;
printf ( " %s%016lX - %s \n " , i = = selected_idx ? " -> " : " " , app_metadata [ i ] - > title_id , app_metadata [ i ] - > lang_entry . name ) ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
printf ( " \n " ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consoleUpdate ( NULL ) ;
2022-07-05 02:04:28 +01:00
2021-02-20 18:52:07 +00:00
bool gc_update = false ;
2020-11-08 19:08:30 +00: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-11-08 19:08:30 +00:00
{
2020-12-04 06:38:44 +00:00
utilsScanPads ( ) ;
btn_down = utilsGetButtonsDown ( ) ;
btn_held = utilsGetButtonsHeld ( ) ;
2020-11-08 19:08:30 +00:00
if ( btn_down | | btn_held ) break ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( titleIsGameCardInfoUpdated ( ) )
{
free ( app_metadata ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00: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-11-08 19:08:30 +00:00
selected_idx = scroll = 0 ;
2021-02-20 18:52:07 +00:00
gc_update = true ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00: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
2021-02-20 18:52:07 +00:00
if ( gc_update ) continue ;
2022-07-05 02:04:28 +01:00
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_A )
2020-11-08 19:08:30 +00:00
{
if ( ! titleGetUserApplicationData ( app_metadata [ selected_idx ] - > title_id , & user_app_data ) | | ! user_app_data . app_info )
{
consolePrint ( " \n the selected title doesn't have available base content. \n " ) ;
utilsSleep ( 3 ) ;
2021-06-01 02:12:15 +01:00
titleFreeUserApplicationData ( & user_app_data ) ;
2020-11-08 19:08:30 +00:00
continue ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
break ;
} else
2020-12-04 06:38:44 +00:00
if ( ( btn_down & HidNpadButton_Down ) | | ( btn_held & ( HidNpadButton_StickLDown | HidNpadButton_StickRDown ) ) )
2020-11-08 19:08:30 +00:00
{
selected_idx + + ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( selected_idx > = app_count )
{
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_Down )
2020-11-08 19:08:30 +00:00
{
selected_idx = scroll = 0 ;
} else {
selected_idx = ( app_count - 1 ) ;
}
} else
if ( selected_idx > = ( scroll + ( page_size / 2 ) ) & & app_count > ( 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-11-08 19:08:30 +00:00
{
selected_idx - - ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( selected_idx = = UINT32_MAX )
{
2020-12-04 06:38:44 +00:00
if ( btn_down & HidNpadButton_Up )
2020-11-08 19:08:30 +00:00
{
selected_idx = ( app_count - 1 ) ;
scroll = ( app_count > = page_size ? ( app_count - 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-11-08 19:08:30 +00:00
{
exit_prompt = false ;
goto out2 ;
}
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-11-08 19:08:30 +00:00
}
2022-07-05 02:04:28 +01:00
2022-02-19 05:03:06 +00:00
if ( ! applet_status )
{
exit_prompt = false ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
u32 program_count = titleGetContentCountByType ( user_app_data . app_info , NcmContentType_Program ) ;
if ( ! program_count )
{
consolePrint ( " base app has no program ncas! \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
u8 program_id_offset = get_program_id_offset ( user_app_data . app_info , program_count ) ;
2022-02-19 05:03:06 +00:00
if ( program_id_offset > = program_count )
{
exit_prompt = false ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consoleClear ( ) ;
consolePrint ( " selected title: \n %s (%016lX) \n \n " , app_metadata [ selected_idx ] - > lang_entry . name , app_metadata [ selected_idx ] - > title_id + program_id_offset ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( ! ncaInitializeContext ( base_nca_ctx , user_app_data . app_info - > storage_id , ( user_app_data . app_info - > storage_id = = NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0 ) , \
2022-06-29 08:55:46 +01:00
titleGetContentInfoByTypeAndIdOffset ( user_app_data . app_info , NcmContentType_Program , program_id_offset ) , user_app_data . app_info - > version . value , NULL ) )
2020-11-08 19:08:30 +00:00
{
consolePrint ( " nca initialize base ctx failed \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
TitleInfo * latest_patch = NULL ;
if ( user_app_data . patch_info ) latest_patch = get_latest_patch_info ( user_app_data . patch_info ) ;
2022-07-05 02:04:28 +01:00
2022-07-05 05:01:07 +01:00
if ( ! latest_patch & & ( ! base_nca_ctx - > fs_ctx [ 1 ] . enabled | | ( base_nca_ctx - > fs_ctx [ 1 ] . section_type ! = NcaFsSectionType_RomFs & & base_nca_ctx - > fs_ctx [ 1 ] . section_type ! = NcaFsSectionType_Nca0RomFs ) ) )
{
consolePrint ( " base app has no valid romfs and no updates could be found \n " ) ;
goto out2 ;
}
2022-07-05 00:25:28 +01:00
if ( base_nca_ctx - > fs_ctx [ 1 ] . has_sparse_layer & & ( ! latest_patch | | latest_patch - > version . value < user_app_data . app_info - > version . value ) )
{
consolePrint ( " base app is a sparse title and no v%u or greater update could be found \n " , user_app_data . app_info - > version . value ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
if ( latest_patch )
2020-11-08 19:08:30 +00:00
{
2022-07-05 00:25:28 +01:00
consolePrint ( " using patch romfs with update v%u \n " , latest_patch - > version . value ) ;
2022-07-05 02:04:28 +01:00
2022-03-17 19:29:44 +00:00
if ( ! ncaInitializeContext ( update_nca_ctx , latest_patch - > storage_id , ( latest_patch - > storage_id = = NcmStorageId_GameCard ? GameCardHashFileSystemPartitionType_Secure : 0 ) , \
2022-06-29 08:55:46 +01:00
titleGetContentInfoByTypeAndIdOffset ( latest_patch , NcmContentType_Program , program_id_offset ) , latest_patch - > version . value , NULL ) )
2020-11-08 19:08:30 +00:00
{
consolePrint ( " nca initialize update ctx failed \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
if ( ! romfsInitializeContext ( & romfs_ctx , & ( base_nca_ctx - > fs_ctx [ 1 ] ) , & ( update_nca_ctx - > fs_ctx [ 1 ] ) ) )
2020-11-08 19:08:30 +00:00
{
2022-07-05 00:25:28 +01:00
consolePrint ( " romfs initialize ctx failed (update) \n " ) ;
2020-11-08 19:08:30 +00:00
goto out2 ;
}
} else {
2022-07-05 00:25:28 +01:00
consolePrint ( " using base romfs only \n " ) ;
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
if ( ! romfsInitializeContext ( & romfs_ctx , & ( base_nca_ctx - > fs_ctx [ 1 ] ) , NULL ) )
2020-11-08 19:08:30 +00:00
{
2022-07-05 00:25:28 +01:00
consolePrint ( " romfs initialize ctx failed (base) \n " ) ;
2020-11-08 19:08:30 +00:00
goto out2 ;
}
}
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
shared_data . romfs_ctx = & romfs_ctx ;
romfsGetTotalDataSize ( & romfs_ctx , & ( shared_data . total_size ) ) ;
2022-07-05 02:04:28 +01:00
2022-07-05 00:25:28 +01:00
consolePrint ( " romfs initialize ctx succeeded \n " ) ;
2022-07-05 02:04:28 +01:00
2022-07-05 05:01:07 +01:00
// check free space
u64 free_space = 0 ;
if ( ! utilsGetFileSystemStatsByPath ( " sdmc:/ " , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free sd card space \n " ) ;
goto out2 ;
}
if ( free_space < = shared_data . total_size )
{
consolePrint ( " extracted romfs size (0x%lX) exceeds free sd card space (0x%lX) \n " , shared_data . total_size , free_space ) ;
goto out2 ;
}
2020-11-08 19:08:30 +00:00
shared_data . fd = NULL ;
shared_data . data = buf ;
shared_data . data_size = 0 ;
shared_data . data_written = 0 ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consolePrint ( " creating threads \n " ) ;
utilsCreateThread ( & read_thread , read_thread_func , & shared_data , 2 ) ;
utilsCreateThread ( & write_thread , write_thread_func , & shared_data , 2 ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
u8 prev_time = 0 ;
u64 prev_size = 0 ;
u8 percent = 0 ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
time_t btn_cancel_start_tmr = 0 , btn_cancel_end_tmr = 0 ;
bool btn_cancel_cur_state = false , btn_cancel_prev_state = false ;
2022-07-05 02:04:28 +01:00
2021-08-03 07:37:04 +01:00
utilsSetLongRunningProcessState ( true ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consolePrint ( " hold b to cancel \n \n " ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
time_t start = time ( NULL ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
while ( shared_data . data_written < shared_data . total_size )
{
if ( shared_data . read_error | | shared_data . write_error ) break ;
2022-07-05 02:04:28 +01:00
2022-02-06 02:30:42 +00:00
struct tm ts = { 0 } ;
2020-11-08 19:08:30 +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
2020-11-08 19:08:30 +00:00
size_t size = shared_data . data_written ;
2022-07-05 02:04:28 +01:00
2020-12-04 06:38:44 +00:00
utilsScanPads ( ) ;
btn_cancel_cur_state = ( utilsGetButtonsHeld ( ) & HidNpadButton_B ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +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 )
{
mutexLock ( & g_fileMutex ) ;
shared_data . transfer_cancelled = true ;
mutexUnlock ( & g_fileMutex ) ;
break ;
}
} else {
btn_cancel_start_tmr = btn_cancel_end_tmr = 0 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +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
2020-11-08 19:08:30 +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 ;
2020-11-08 19:08:30 +00:00
prev_size = size ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
printf ( " %lu / %lu (%u%%) | Time elapsed: %lu \n " , size , shared_data . total_size , percent , ( now - start ) ) ;
consoleUpdate ( NULL ) ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
start = ( time ( NULL ) - start ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consolePrint ( " \n waiting for threads to join \n " ) ;
utilsJoinThread ( & read_thread ) ;
consolePrint ( " read_thread done: %lu \n " , time ( NULL ) ) ;
utilsJoinThread ( & write_thread ) ;
consolePrint ( " write_thread done: %lu \n " , time ( NULL ) ) ;
2022-07-05 02:04:28 +01:00
2021-08-03 07:37:04 +01:00
utilsSetLongRunningProcessState ( false ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( shared_data . read_error | | shared_data . write_error )
{
consolePrint ( " i/o error \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( shared_data . transfer_cancelled )
{
consolePrint ( " process cancelled \n " ) ;
goto out2 ;
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consolePrint ( " process completed in %lu seconds \n " , start ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
out2 :
if ( exit_prompt )
{
consolePrint ( " press any button to exit \n " ) ;
2020-12-04 06:38:44 +00:00
utilsWaitForButtonPress ( 0 ) ;
2020-11-08 19:08:30 +00:00
}
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
romfsFreeContext ( & romfs_ctx ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( update_nca_ctx ) free ( update_nca_ctx ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( base_nca_ctx ) free ( base_nca_ctx ) ;
2022-07-05 02:04:28 +01:00
2021-06-01 02:12:15 +01:00
titleFreeUserApplicationData ( & user_app_data ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( buf ) free ( buf ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
if ( app_metadata ) free ( app_metadata ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
out :
utilsCloseResources ( ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
consoleExit ( NULL ) ;
2022-07-05 02:04:28 +01:00
2020-11-08 19:08:30 +00:00
return ret ;
}