2023-05-24 20:05:34 +01:00
/*
* main . c
*
2024-04-12 10:47:36 +01:00
* Copyright ( c ) 2020 - 2024 , DarkMatterCore < pabloacurielz @ gmail . com > .
2023-05-24 20:05:34 +01:00
*
* This file is part of nxdumptool ( https : //github.com/DarkMatterCore/nxdumptool).
*
* nxdumptool is free software : you can redistribute it and / or modify
* it 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 .
*
* 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 .
*
* You should have received a copy of the GNU General Public License
* along with this program . If not , see < https : //www.gnu.org/licenses/>.
*/
2024-04-30 22:01:42 +01:00
# include <core/nxdt_utils.h>
# include <core/gamecard.h>
# include <core/title.h>
# include <core/cnmt.h>
# include <core/program_info.h>
# include <core/nacp.h>
# include <core/legal_info.h>
# include <core/cert.h>
# include <core/usb.h>
# include <core/devoptab/nxdt_devoptab.h>
2024-10-27 11:23:46 +00:00
# include <core/system_update.h>
2024-10-28 14:44:31 +00:00
# include <core/bis_storage.h>
2023-05-24 20:05:34 +01:00
# define BLOCK_SIZE USB_TRANSFER_BUFFER_SIZE
# define WAIT_TIME_LIMIT 30
2023-06-28 23:12:51 +01:00
# define OUTDIR APP_TITLE
2023-05-24 20:05:34 +01:00
/* Type definitions. */
typedef struct _Menu Menu ;
typedef u32 ( * MenuElementOptionGetterFunction ) ( void ) ;
typedef void ( * MenuElementOptionSetterFunction ) ( u32 idx ) ;
typedef bool ( * MenuElementFunction ) ( void * userdata ) ;
typedef struct {
u32 selected ; ///< Used to keep track of the selected option.
2023-11-26 21:54:25 +00:00
bool retrieved ; ///< Used to determine if the value for this option has already been retrieved from configuration.
2023-05-24 20:05:34 +01:00
MenuElementOptionGetterFunction getter_func ; ///< Pointer to a function to be called the first time an option value is loaded. Should be set to NULL if not used.
MenuElementOptionSetterFunction setter_func ; ///< Pointer to a function to be called each time a new option value is selected. Should be set to NULL if not used.
char * * options ; ///< Pointer to multiple char pointers with strings representing options. Last element must be set to NULL.
} MenuElementOption ;
typedef struct {
char * str ; ///< Pointer to a string to be printed for this menu element.
Menu * child_menu ; ///< Pointer to a child Menu element. Must be set to NULL if task_func != NULL.
MenuElementFunction task_func ; ///< Pointer to a function to be called by this element. Must be set to NULL if child_menu != NULL.
MenuElementOption * element_options ; ///< Options for this menu element. Should be set to NULL if not used.
void * userdata ; ///< Optional userdata pointer associated with this element. This is always passed to task_func as its only argument.
///< This may or may be not used by the menu handler. Should be set to NULL if not used.
} MenuElement ;
struct _Menu {
u32 id ; ///< Identifier.
struct _Menu * parent ; ///< Set to NULL in the root menu element.
u32 selected , scroll ; ///< Used to keep track of the selected element and scroll values.
MenuElement * * elements ; ///< Element info from this menu. Last element must be set to NULL.
} ;
typedef enum {
2023-05-26 19:23:23 +01:00
MenuId_Root = 0 ,
MenuId_GameCard = 1 ,
MenuId_XCI = 2 ,
2023-12-31 17:16:49 +00:00
MenuId_DumpHFS = 3 ,
MenuId_BrowseHFS = 4 ,
MenuId_UserTitles = 5 ,
MenuId_UserTitlesSubMenu = 6 ,
2024-10-27 11:23:46 +00:00
MenuId_NspTitleTypes = 7 ,
MenuId_Nsp = 8 ,
2023-12-31 17:16:49 +00:00
MenuId_TicketTitleTypes = 9 ,
MenuId_Ticket = 10 ,
MenuId_NcaTitleTypes = 11 ,
MenuId_Nca = 12 ,
MenuId_NcaFsSections = 13 ,
MenuId_NcaFsSectionsSubMenu = 14 ,
MenuId_SystemTitles = 15 ,
2024-10-27 11:23:46 +00:00
MenuId_SystemUpdate = 16 ,
2024-10-28 14:44:31 +00:00
MenuId_BrowseEmmc = 17 ,
MenuId_Count = 18
2023-05-24 20:05:34 +01:00
} MenuId ;
typedef struct
{
FILE * fp ;
void * data ;
size_t data_size ;
size_t data_written ;
size_t total_size ;
bool read_error ;
bool write_error ;
bool transfer_cancelled ;
} SharedThreadData ;
typedef struct {
SharedThreadData shared_thread_data ;
u32 xci_crc , full_xci_crc ;
} XciThreadData ;
typedef struct {
SharedThreadData shared_thread_data ;
HashFileSystemContext * hfs_ctx ;
} HfsThreadData ;
typedef struct {
void * data ;
size_t data_written ;
size_t total_size ;
bool error ;
bool transfer_cancelled ;
} NspThreadData ;
typedef struct {
TitleInfo * title_info ;
u32 content_idx ;
} NcaUserData ;
typedef struct {
SharedThreadData shared_thread_data ;
NcaContext * nca_ctx ;
} NcaThreadData ;
2023-05-30 00:22:12 +01:00
typedef struct {
SharedThreadData shared_thread_data ;
PartitionFileSystemContext * pfs_ctx ;
bool use_layeredfs_dir ;
} PfsThreadData ;
typedef struct {
SharedThreadData shared_thread_data ;
RomFileSystemContext * romfs_ctx ;
bool use_layeredfs_dir ;
} RomFsThreadData ;
2023-12-26 00:25:29 +00:00
typedef struct {
bool highlight ;
size_t size ;
char size_str [ 0x10 ] ;
struct dirent dt ;
} FsBrowserEntry ;
typedef struct {
SharedThreadData shared_thread_data ;
FILE * src ;
} FsBrowserFileThreadData ;
typedef struct {
SharedThreadData shared_thread_data ;
const char * dir_path ;
const FsBrowserEntry * entries ;
u32 entries_count ;
const char * base_out_path ;
} FsBrowserHighlightedEntriesThreadData ;
2024-10-27 11:23:46 +00:00
typedef struct {
SharedThreadData shared_thread_data ;
SystemUpdateDumpContext * sys_upd_dump_ctx ;
} SystemUpdateThreadData ;
2023-05-24 20:05:34 +01:00
/* Function prototypes. */
static void utilsScanPads ( void ) ;
static u64 utilsGetButtonsDown ( void ) ;
static u64 utilsGetButtonsHeld ( void ) ;
2023-11-03 18:03:50 +00:00
static u64 utilsWaitForButtonPress ( u64 flag ) ;
2023-05-24 20:05:34 +01:00
static void consolePrint ( const char * text , . . . ) ;
2023-11-03 01:22:47 +00:00
static void consolePrintReversedColors ( const char * text , . . . ) ;
2023-05-24 20:05:34 +01:00
static void consoleRefresh ( void ) ;
static u32 menuGetElementCount ( const Menu * menu ) ;
2023-11-26 21:54:25 +00:00
static void menuResetAttributes ( Menu * cur_menu , u32 element_count ) ;
2023-05-24 20:05:34 +01:00
void freeStorageList ( void ) ;
void updateStorageList ( void ) ;
2023-07-21 22:56:50 +01:00
void freeTitleList ( Menu * menu ) ;
void updateTitleList ( Menu * menu , Menu * submenu , bool is_system ) ;
2023-05-24 20:05:34 +01:00
2023-11-03 01:22:47 +00:00
static TitleInfo * getLatestTitleInfo ( TitleInfo * title_info , u32 * out_idx , u32 * out_count ) ;
2023-05-24 20:05:34 +01:00
void freeNcaList ( void ) ;
2024-02-16 22:31:24 +00:00
void updateNcaList ( TitleInfo * title_info , u32 * element_count ) ;
static void switchNcaListTitle ( Menu * * cur_menu , u32 * element_count , TitleInfo * title_info ) ;
2023-05-26 16:59:03 +01:00
void freeNcaFsSectionsList ( void ) ;
void updateNcaFsSectionsList ( NcaUserData * nca_user_data ) ;
2023-05-24 20:05:34 +01:00
2023-05-27 19:10:35 +01:00
void freeNcaBasePatchList ( void ) ;
void updateNcaBasePatchList ( TitleUserApplicationData * user_app_data , TitleInfo * title_info , NcaFsSectionContext * nca_fs_ctx ) ;
2023-05-24 20:05:34 +01:00
NX_INLINE bool useUsbHost ( void ) ;
static bool waitForGameCard ( void ) ;
static bool waitForUsb ( void ) ;
static char * generateOutputGameCardFileName ( const char * subdir , const char * extension , bool use_nacp_name ) ;
static char * generateOutputTitleFileName ( TitleInfo * title_info , const char * subdir , const char * extension ) ;
2023-05-30 00:22:12 +01:00
static char * generateOutputLayeredFsFileName ( u64 title_id , const char * subdir , const char * extension ) ;
2023-05-24 20:05:34 +01:00
static bool dumpGameCardSecurityInformation ( GameCardSecurityInformation * out ) ;
static bool saveGameCardImage ( void * userdata ) ;
static bool saveGameCardHeader ( void * userdata ) ;
static bool saveGameCardCardInfo ( void * userdata ) ;
static bool saveGameCardCertificate ( void * userdata ) ;
static bool saveGameCardInitialData ( void * userdata ) ;
static bool saveGameCardSpecificData ( void * userdata ) ;
static bool saveGameCardIdSet ( void * userdata ) ;
2023-12-05 18:21:45 +00:00
static bool saveGameCardUid ( void * userdata ) ;
2023-05-24 20:05:34 +01:00
static bool saveGameCardHfsPartition ( void * userdata ) ;
static bool saveGameCardRawHfsPartition ( HashFileSystemContext * hfs_ctx ) ;
static bool saveGameCardExtractedHfsPartition ( HashFileSystemContext * hfs_ctx ) ;
2023-12-31 17:16:49 +00:00
static bool browseGameCardHfsPartition ( void * userdata ) ;
2023-05-24 20:05:34 +01:00
static bool saveConsoleLafwBlob ( void * userdata ) ;
static bool saveNintendoSubmissionPackage ( void * userdata ) ;
static bool saveTicket ( void * userdata ) ;
static bool saveNintendoContentArchive ( void * userdata ) ;
2023-05-30 00:22:12 +01:00
static bool saveNintendoContentArchiveFsSection ( void * userdata ) ;
2023-12-26 00:25:29 +00:00
static bool browseNintendoContentArchiveFsSection ( void * userdata ) ;
2024-10-27 11:23:46 +00:00
static bool saveSystemUpdateDump ( void * userdata ) ;
2024-10-28 14:44:31 +00:00
static bool browseEmmcPartition ( void * userdata ) ;
2023-12-26 00:25:29 +00:00
static bool fsBrowser ( const char * mount_name , const char * base_out_path ) ;
static bool fsBrowserGetDirEntries ( const char * dir_path , FsBrowserEntry * * out_entries , u32 * out_entry_count ) ;
2024-10-28 14:44:31 +00:00
static int fsBrowserDirEntrySortFunction ( const void * a , const void * b ) ;
2023-12-26 00:25:29 +00:00
static bool fsBrowserDumpFile ( const char * dir_path , const FsBrowserEntry * entry , const char * base_out_path ) ;
static bool fsBrowserDumpHighlightedEntries ( const char * dir_path , const FsBrowserEntry * entries , u32 entries_count , const char * base_out_path ) ;
static bool initializeNcaFsContext ( void * userdata , u8 * out_section_type , bool * out_use_layeredfs_dir , NcaContext * * out_base_patch_nca_ctx , void * * out_fs_ctx ) ;
2023-05-30 00:22:12 +01:00
static bool saveRawPartitionFsSection ( PartitionFileSystemContext * pfs_ctx , bool use_layeredfs_dir ) ;
static bool saveExtractedPartitionFsSection ( PartitionFileSystemContext * pfs_ctx , bool use_layeredfs_dir ) ;
2023-05-30 22:34:34 +01:00
static bool saveRawRomFsSection ( RomFileSystemContext * romfs_ctx , bool use_layeredfs_dir ) ;
static bool saveExtractedRomFsSection ( RomFileSystemContext * romfs_ctx , bool use_layeredfs_dir ) ;
2023-05-24 20:05:34 +01:00
static void xciReadThreadFunc ( void * arg ) ;
static void rawHfsReadThreadFunc ( void * arg ) ;
static void extractedHfsReadThreadFunc ( void * arg ) ;
static void ncaReadThreadFunc ( void * arg ) ;
2023-05-30 00:22:12 +01:00
static void rawPartitionFsReadThreadFunc ( void * arg ) ;
static void extractedPartitionFsReadThreadFunc ( void * arg ) ;
2023-05-30 22:34:34 +01:00
static void rawRomFsReadThreadFunc ( void * arg ) ;
static void extractedRomFsReadThreadFunc ( void * arg ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
static void fsBrowserFileReadThreadFunc ( void * arg ) ;
static void fsBrowserHighlightedEntriesReadThreadFunc ( void * arg ) ;
static bool fsBrowserHighlightedEntriesReadThreadLoop ( SharedThreadData * shared_thread_data , const char * dir_path , const FsBrowserEntry * entries , u32 entries_count , const char * base_out_path , void * buf1 , void * buf2 ) ;
2024-10-27 11:23:46 +00:00
static void systemUpdateReadThreadFunc ( void * arg ) ;
2023-05-24 20:05:34 +01:00
static void genericWriteThreadFunc ( void * arg ) ;
static bool spanDumpThreads ( ThreadFunc read_func , ThreadFunc write_func , void * arg ) ;
static void nspThreadFunc ( void * arg ) ;
static u32 getOutputStorageOption ( void ) ;
static void setOutputStorageOption ( u32 idx ) ;
static u32 getGameCardPrependKeyAreaOption ( void ) ;
static void setGameCardPrependKeyAreaOption ( u32 idx ) ;
static u32 getGameCardKeepCertificateOption ( void ) ;
static void setGameCardKeepCertificateOption ( u32 idx ) ;
static u32 getGameCardTrimDumpOption ( void ) ;
static void setGameCardTrimDumpOption ( u32 idx ) ;
static u32 getGameCardCalculateChecksumOption ( void ) ;
static void setGameCardCalculateChecksumOption ( u32 idx ) ;
static u32 getGameCardWriteRawHfsPartitionOption ( void ) ;
static void setGameCardWriteRawHfsPartitionOption ( u32 idx ) ;
static u32 getNspSetDownloadDistributionOption ( void ) ;
static void setNspSetDownloadDistributionOption ( u32 idx ) ;
static u32 getNspRemoveConsoleDataOption ( void ) ;
static void setNspRemoveConsoleDataOption ( u32 idx ) ;
static u32 getNspRemoveTitlekeyCryptoOption ( void ) ;
static void setNspRemoveTitlekeyCryptoOption ( u32 idx ) ;
static u32 getNspDisableLinkedAccountRequirementOption ( void ) ;
static void setNspDisableLinkedAccountRequirementOption ( u32 idx ) ;
static u32 getNspEnableScreenshotsOption ( void ) ;
static void setNspEnableScreenshotsOption ( u32 idx ) ;
static u32 getNspEnableVideoCaptureOption ( void ) ;
static void setNspEnableVideoCaptureOption ( u32 idx ) ;
static u32 getNspDisableHdcpOption ( void ) ;
static void setNspDisableHdcpOption ( u32 idx ) ;
2023-07-17 00:03:05 +01:00
static u32 getNspGenerateAuthoringToolDataOption ( void ) ;
static void setNspGenerateAuthoringToolDataOption ( u32 idx ) ;
2023-05-24 20:05:34 +01:00
static u32 getTicketRemoveConsoleDataOption ( void ) ;
static void setTicketRemoveConsoleDataOption ( u32 idx ) ;
2023-05-30 00:22:12 +01:00
static u32 getNcaFsWriteRawSectionOption ( void ) ;
static void setNcaFsWriteRawSectionOption ( u32 idx ) ;
2023-05-26 19:23:23 +01:00
static u32 getNcaFsUseLayeredFsDirOption ( void ) ;
static void setNcaFsUseLayeredFsDirOption ( u32 idx ) ;
2024-08-26 08:45:52 +01:00
static bool resetSettings ( void * userdata ) ;
2023-05-24 20:05:34 +01:00
/* Global variables. */
bool g_borealisInitialized = false ;
static PadState g_padState = { 0 } ;
static char * g_noYesStrings [ ] = { " no " , " yes " , NULL } ;
static bool g_appletStatus = true ;
static UsbHsFsDevice * g_umsDevices = NULL ;
static u32 g_umsDeviceCount = 0 ;
static char * * g_storageOptions = NULL ;
static MenuElementOption g_storageMenuElementOption = {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getOutputStorageOption ,
. setter_func = & setOutputStorageOption ,
2023-05-27 19:10:35 +01:00
. options = NULL // Dynamically set
2023-05-24 20:05:34 +01:00
} ;
2023-05-26 16:59:03 +01:00
static MenuElement g_storageMenuElement = {
. str = " output storage " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & g_storageMenuElementOption ,
. userdata = NULL
} ;
2023-05-24 20:05:34 +01:00
static MenuElement * g_xciMenuElements [ ] = {
& ( MenuElement ) {
. str = " start xci dump " ,
. child_menu = NULL ,
. task_func = & saveGameCardImage ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " prepend key area " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getGameCardPrependKeyAreaOption ,
. setter_func = & setGameCardPrependKeyAreaOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " keep certificate " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getGameCardKeepCertificateOption ,
. setter_func = & setGameCardKeepCertificateOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " trim dump " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getGameCardTrimDumpOption ,
. setter_func = & setGameCardTrimDumpOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " calculate checksum " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 1 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getGameCardCalculateChecksumOption ,
. setter_func = & setGameCardCalculateChecksumOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
2023-05-26 16:59:03 +01:00
& g_storageMenuElement ,
2023-05-24 20:05:34 +01:00
NULL
} ;
static u32 g_hfsRootPartition = HashFileSystemPartitionType_Root ;
static u32 g_hfsUpdatePartition = HashFileSystemPartitionType_Update ;
static u32 g_hfsLogoPartition = HashFileSystemPartitionType_Logo ;
static u32 g_hfsNormalPartition = HashFileSystemPartitionType_Normal ;
static u32 g_hfsSecurePartition = HashFileSystemPartitionType_Secure ;
2023-12-31 17:16:49 +00:00
static MenuElement * g_gameCardHfsDumpMenuElements [ ] = {
2023-05-24 20:05:34 +01:00
& ( MenuElement ) {
. str = " dump root hfs partition " ,
. child_menu = NULL ,
. task_func = & saveGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsRootPartition
} ,
& ( MenuElement ) {
. str = " dump update hfs partition " ,
. child_menu = NULL ,
. task_func = & saveGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsUpdatePartition
} ,
& ( MenuElement ) {
. str = " dump logo hfs partition " ,
. child_menu = NULL ,
. task_func = & saveGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsLogoPartition
} ,
& ( MenuElement ) {
. str = " dump normal hfs partition " ,
. child_menu = NULL ,
. task_func = & saveGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsNormalPartition
} ,
& ( MenuElement ) {
. str = " dump secure hfs partition " ,
. child_menu = NULL ,
. task_func = & saveGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsSecurePartition
} ,
& ( MenuElement ) {
. str = " write raw hfs partition " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getGameCardWriteRawHfsPartitionOption ,
. setter_func = & setGameCardWriteRawHfsPartitionOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
2023-05-26 16:59:03 +01:00
& g_storageMenuElement ,
2023-05-24 20:05:34 +01:00
NULL
} ;
2023-12-31 17:16:49 +00:00
static MenuElement * g_gameCardHfsBrowseMenuElements [ ] = {
& ( MenuElement ) {
. str = " browse root hfs partition " ,
. child_menu = NULL ,
. task_func = & browseGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsRootPartition
} ,
& ( MenuElement ) {
. str = " browse update hfs partition " ,
. child_menu = NULL ,
. task_func = & browseGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsUpdatePartition
} ,
& ( MenuElement ) {
. str = " browse logo hfs partition " ,
. child_menu = NULL ,
. task_func = & browseGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsLogoPartition
} ,
& ( MenuElement ) {
. str = " browse normal hfs partition " ,
. child_menu = NULL ,
. task_func = & browseGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsNormalPartition
} ,
& ( MenuElement ) {
. str = " browse secure hfs partition " ,
. child_menu = NULL ,
. task_func = & browseGameCardHfsPartition ,
. element_options = NULL ,
. userdata = & g_hfsSecurePartition
} ,
& g_storageMenuElement ,
NULL
} ;
2023-05-24 20:05:34 +01:00
static MenuElement * g_gameCardMenuElements [ ] = {
& ( MenuElement ) {
. str = " dump gamecard image (xci) " ,
. child_menu = & ( Menu ) {
. id = MenuId_XCI ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_xciMenuElements
} ,
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-12-05 18:21:45 +00:00
. str = " dump gamecard initial data " ,
2023-05-24 20:05:34 +01:00
. child_menu = NULL ,
2023-12-05 18:21:45 +00:00
. task_func = & saveGameCardInitialData ,
2023-05-24 20:05:34 +01:00
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-12-05 18:21:45 +00:00
. str = " dump gamecard certificate " ,
2023-05-24 20:05:34 +01:00
. child_menu = NULL ,
2023-12-05 18:21:45 +00:00
. task_func = & saveGameCardCertificate ,
2023-05-24 20:05:34 +01:00
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-12-05 18:21:45 +00:00
. str = " dump gamecard id set " ,
2023-05-24 20:05:34 +01:00
. child_menu = NULL ,
2023-12-05 18:21:45 +00:00
. task_func = & saveGameCardIdSet ,
2023-05-24 20:05:34 +01:00
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-12-05 18:21:45 +00:00
. str = " dump gamecard uid " ,
2023-05-24 20:05:34 +01:00
. child_menu = NULL ,
2023-12-05 18:21:45 +00:00
. task_func = & saveGameCardUid ,
2023-05-24 20:05:34 +01:00
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-12-05 18:21:45 +00:00
. str = " dump gamecard header (optional) " ,
2023-05-24 20:05:34 +01:00
. child_menu = NULL ,
2023-12-05 18:21:45 +00:00
. task_func = & saveGameCardHeader ,
2023-05-24 20:05:34 +01:00
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-12-05 18:21:45 +00:00
. str = " dump gamecard cardinfo (optional) " ,
2023-05-24 20:05:34 +01:00
. child_menu = NULL ,
2023-12-05 18:21:45 +00:00
. task_func = & saveGameCardCardInfo ,
2023-05-24 20:05:34 +01:00
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-12-05 18:21:45 +00:00
. str = " dump gamecard specific data (optional) " ,
. child_menu = NULL ,
. task_func = & saveGameCardSpecificData ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " dump hfs partitions (optional) " ,
2023-05-24 20:05:34 +01:00
. child_menu = & ( Menu ) {
2023-12-31 17:16:49 +00:00
. id = MenuId_DumpHFS ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_gameCardHfsDumpMenuElements
} ,
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " browse hfs partitions (optional) " ,
. child_menu = & ( Menu ) {
. id = MenuId_BrowseHFS ,
2023-05-24 20:05:34 +01:00
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
2023-12-31 17:16:49 +00:00
. elements = g_gameCardHfsBrowseMenuElements
2023-05-24 20:05:34 +01:00
} ,
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-12-05 18:21:45 +00:00
. str = " dump console lafw blob (optional) " ,
2023-05-24 20:05:34 +01:00
. child_menu = NULL ,
. task_func = & saveConsoleLafwBlob ,
. element_options = NULL ,
. userdata = NULL
} ,
2023-05-26 16:59:03 +01:00
& g_storageMenuElement ,
2023-05-24 20:05:34 +01:00
NULL
} ;
static MenuElement * g_nspMenuElements [ ] = {
& ( MenuElement ) {
. str = " start nsp dump " ,
. child_menu = NULL ,
. task_func = & saveNintendoSubmissionPackage ,
. element_options = NULL ,
. userdata = NULL // Dynamically set to the TitleInfo object from the title to dump
} ,
& ( MenuElement ) {
. str = " nca: set content distribution type to \" download \" " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getNspSetDownloadDistributionOption ,
. setter_func = & setNspSetDownloadDistributionOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " tik: remove console specific data " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getNspRemoveConsoleDataOption ,
. setter_func = & setNspRemoveConsoleDataOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " nca/tik: remove titlekey crypto (overrides previous option) " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getNspRemoveTitlekeyCryptoOption ,
. setter_func = & setNspRemoveTitlekeyCryptoOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " nacp: disable linked account requirement " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 1 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getNspDisableLinkedAccountRequirementOption ,
. setter_func = & setNspDisableLinkedAccountRequirementOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " nacp: enable screenshots " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 1 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getNspEnableScreenshotsOption ,
. setter_func = & setNspEnableScreenshotsOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " nacp: enable video capture " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 1 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getNspEnableVideoCaptureOption ,
. setter_func = & setNspEnableVideoCaptureOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " nacp: disable hdcp " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 1 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getNspDisableHdcpOption ,
. setter_func = & setNspDisableHdcpOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-07-17 00:03:05 +01:00
. str = " nsp: generate authoringtool data " ,
2023-05-24 20:05:34 +01:00
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 1 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-07-17 00:03:05 +01:00
. getter_func = & getNspGenerateAuthoringToolDataOption ,
. setter_func = & setNspGenerateAuthoringToolDataOption ,
2023-05-24 20:05:34 +01:00
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
2023-05-26 16:59:03 +01:00
& g_storageMenuElement ,
2023-05-24 20:05:34 +01:00
NULL
} ;
static Menu g_nspMenu = {
2024-10-27 11:23:46 +00:00
. id = MenuId_Nsp ,
2023-05-24 20:05:34 +01:00
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_nspMenuElements
} ;
static MenuElement * g_ticketMenuElements [ ] = {
& ( MenuElement ) {
. str = " start ticket dump " ,
. child_menu = NULL ,
. task_func = & saveTicket ,
. element_options = NULL ,
. userdata = NULL // Dynamically set to the TitleInfo object from the title to dump
} ,
& ( MenuElement ) {
. str = " remove console specific data " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-24 20:05:34 +01:00
. getter_func = & getTicketRemoveConsoleDataOption ,
. setter_func = & setTicketRemoveConsoleDataOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
2023-05-26 16:59:03 +01:00
& g_storageMenuElement ,
2023-05-24 20:05:34 +01:00
NULL
} ;
static Menu g_ticketMenu = {
. id = MenuId_Ticket ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_ticketMenuElements
} ;
2023-05-30 00:22:12 +01:00
static TitleInfo * g_ncaUserTitleInfo = NULL , * g_ncaBasePatchTitleInfo = NULL , * g_ncaBasePatchTitleInfoBkp = NULL ;
2023-05-27 19:10:35 +01:00
static char * * g_ncaBasePatchOptions = NULL ;
static MenuElementOption g_ncaFsSectionsSubMenuBasePatchElementOption = {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-27 19:10:35 +01:00
. getter_func = NULL ,
. setter_func = NULL ,
. options = NULL // Dynamically set
} ;
2023-05-26 16:59:03 +01:00
2023-05-26 19:23:23 +01:00
static MenuElement * g_ncaFsSectionsSubMenuElements [ ] = {
& ( MenuElement ) {
2023-12-26 00:25:29 +00:00
. str = " start nca fs section dump " ,
2023-05-26 19:23:23 +01:00
. child_menu = NULL ,
2023-05-30 00:22:12 +01:00
. task_func = & saveNintendoContentArchiveFsSection ,
2023-05-26 19:23:23 +01:00
. element_options = NULL ,
. userdata = NULL // Dynamically set
} ,
2023-12-26 00:25:29 +00:00
& ( MenuElement ) {
. str = " browse nca fs section " ,
. child_menu = NULL ,
. task_func = & browseNintendoContentArchiveFsSection ,
. element_options = NULL ,
. userdata = NULL // Dynamically set
} ,
2023-05-27 19:10:35 +01:00
& ( MenuElement ) {
. str = " use base/patch title " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & g_ncaFsSectionsSubMenuBasePatchElementOption ,
. userdata = NULL
} ,
2023-05-26 19:23:23 +01:00
& ( MenuElement ) {
2023-05-30 00:22:12 +01:00
. str = " write raw section " ,
2023-05-26 19:23:23 +01:00
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-30 00:22:12 +01:00
. getter_func = & getNcaFsWriteRawSectionOption ,
. setter_func = & setNcaFsWriteRawSectionOption ,
2023-05-26 19:23:23 +01:00
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " use layeredfs dir " ,
. child_menu = NULL ,
. task_func = NULL ,
. element_options = & ( MenuElementOption ) {
. selected = 0 ,
2023-11-26 21:54:25 +00:00
. retrieved = false ,
2023-05-26 19:23:23 +01:00
. getter_func = & getNcaFsUseLayeredFsDirOption ,
. setter_func = & setNcaFsUseLayeredFsDirOption ,
. options = g_noYesStrings
} ,
. userdata = NULL
} ,
& g_storageMenuElement ,
NULL
} ;
static Menu g_ncaFsSectionsSubMenu = {
. id = MenuId_NcaFsSectionsSubMenu ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_ncaFsSectionsSubMenuElements
} ;
2023-05-27 19:10:35 +01:00
static bool g_ncaMenuRawMode = false ;
static NcaContext * g_ncaFsSectionsMenuCtx = NULL ;
2023-05-26 16:59:03 +01:00
static MenuElement * * g_ncaFsSectionsMenuElements = NULL ;
// Dynamically populated using g_ncaFsSectionsMenuElements.
static Menu g_ncaFsSectionsMenu = {
2023-05-26 19:23:23 +01:00
. id = MenuId_NcaFsSections ,
2023-05-26 16:59:03 +01:00
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = NULL
} ;
2023-05-24 20:05:34 +01:00
static MenuElement * * g_ncaMenuElements = NULL ;
// Dynamically populated using g_ncaMenuElements.
static Menu g_ncaMenu = {
2023-05-26 19:23:23 +01:00
. id = MenuId_Nca ,
2023-05-24 20:05:34 +01:00
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = NULL
} ;
static u32 g_metaTypeApplication = NcmContentMetaType_Application ;
static u32 g_metaTypePatch = NcmContentMetaType_Patch ;
static u32 g_metaTypeAOC = NcmContentMetaType_AddOnContent ;
static u32 g_metaTypeAOCPatch = NcmContentMetaType_DataPatch ;
static MenuElement * g_titleTypesMenuElements [ ] = {
& ( MenuElement ) {
. str = " dump base application " ,
. child_menu = NULL , // Dynamically set
. task_func = NULL ,
. element_options = NULL ,
. userdata = & g_metaTypeApplication
} ,
& ( MenuElement ) {
. str = " dump update " ,
. child_menu = NULL , // Dynamically set
. task_func = NULL ,
. element_options = NULL ,
. userdata = & g_metaTypePatch
} ,
& ( MenuElement ) {
. str = " dump dlc " ,
. child_menu = NULL , // Dynamically set
. task_func = NULL ,
. element_options = NULL ,
. userdata = & g_metaTypeAOC
} ,
& ( MenuElement ) {
. str = " dump dlc update " ,
. child_menu = NULL , // Dynamically set
. task_func = NULL ,
. element_options = NULL ,
. userdata = & g_metaTypeAOCPatch
} ,
NULL
} ;
static MenuElement * g_userTitlesSubMenuElements [ ] = {
& ( MenuElement ) {
. str = " nsp dump options " ,
. child_menu = & ( Menu ) {
2024-10-27 11:23:46 +00:00
. id = MenuId_NspTitleTypes ,
2023-05-24 20:05:34 +01:00
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_titleTypesMenuElements
} ,
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " ticket dump options " ,
. child_menu = & ( Menu ) {
. id = MenuId_TicketTitleTypes ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_titleTypesMenuElements
} ,
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
2023-05-26 16:59:03 +01:00
. str = " nca / nca fs dump options " ,
2023-05-24 20:05:34 +01:00
. child_menu = & ( Menu ) {
2023-05-26 19:23:23 +01:00
. id = MenuId_NcaTitleTypes ,
2023-05-24 20:05:34 +01:00
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_titleTypesMenuElements
} ,
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
NULL
} ;
2023-07-21 22:56:50 +01:00
// Dynamically set as child_menu for all g_userTitlesMenu entries.
2023-05-24 20:05:34 +01:00
static Menu g_userTitlesSubMenu = {
. id = MenuId_UserTitlesSubMenu ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_userTitlesSubMenuElements
} ;
2023-07-21 22:56:50 +01:00
// Dynamically populated.
2023-05-24 20:05:34 +01:00
static Menu g_userTitlesMenu = {
. id = MenuId_UserTitles ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = NULL
} ;
2023-07-21 22:56:50 +01:00
// Dynamically populated.
static Menu g_systemTitlesMenu = {
. id = MenuId_SystemTitles ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = NULL
} ;
2024-10-27 11:23:46 +00:00
static MenuElement * g_dumpSystemUpdateMenuElements [ ] = {
& ( MenuElement ) {
. str = " start dump " ,
. child_menu = NULL ,
. task_func = & saveSystemUpdateDump ,
. element_options = NULL ,
. userdata = NULL
} ,
& g_storageMenuElement ,
NULL
} ;
2024-10-28 14:44:31 +00:00
static u8 g_emmcProdinfofPartition = FsBisPartitionId_CalibrationFile ;
static u8 g_emmcSafePartition = FsBisPartitionId_SafeMode ;
static u8 g_emmcUserPartition = FsBisPartitionId_User ;
static u8 g_emmcSystemPartition = FsBisPartitionId_System ;
static MenuElement * g_emmcBrowseMenuElements [ ] = {
& ( MenuElement ) {
. str = " browse prodinfof emmc partition " ,
. child_menu = NULL ,
. task_func = & browseEmmcPartition ,
. element_options = NULL ,
. userdata = & g_emmcProdinfofPartition
} ,
& ( MenuElement ) {
. str = " browse safe emmc partition " ,
. child_menu = NULL ,
. task_func = & browseEmmcPartition ,
. element_options = NULL ,
. userdata = & g_emmcSafePartition
} ,
& ( MenuElement ) {
. str = " browse user emmc partition " ,
. child_menu = NULL ,
. task_func = & browseEmmcPartition ,
. element_options = NULL ,
. userdata = & g_emmcUserPartition
} ,
& ( MenuElement ) {
. str = " browse system mmc partition " ,
. child_menu = NULL ,
. task_func = & browseEmmcPartition ,
. element_options = NULL ,
. userdata = & g_emmcSystemPartition
} ,
& g_storageMenuElement ,
NULL
2024-10-27 11:23:46 +00:00
} ;
2023-05-24 20:05:34 +01:00
static MenuElement * g_rootMenuElements [ ] = {
& ( MenuElement ) {
. str = " gamecard menu " ,
. child_menu = & ( Menu ) {
. id = MenuId_GameCard ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_gameCardMenuElements
} ,
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " user titles menu " ,
. child_menu = & g_userTitlesMenu ,
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " system titles menu " ,
2023-07-21 22:56:50 +01:00
. child_menu = & g_systemTitlesMenu ,
2023-05-24 20:05:34 +01:00
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
2024-10-27 11:23:46 +00:00
& ( MenuElement ) {
. str = " dump system update " ,
2024-10-28 14:44:31 +00:00
. child_menu = & ( Menu ) {
. id = MenuId_SystemUpdate ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_dumpSystemUpdateMenuElements
} ,
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
& ( MenuElement ) {
. str = " browse emmc partitions " ,
. child_menu = & ( Menu ) {
. id = MenuId_BrowseEmmc ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_emmcBrowseMenuElements
} ,
2024-10-27 11:23:46 +00:00
. task_func = NULL ,
. element_options = NULL ,
. userdata = NULL
} ,
2023-11-26 21:54:25 +00:00
& ( MenuElement ) {
. str = " reset settings " ,
. child_menu = NULL ,
. task_func = & resetSettings ,
. element_options = NULL ,
. userdata = NULL
} ,
2023-05-24 20:05:34 +01:00
NULL
} ;
static Menu g_rootMenu = {
. id = MenuId_Root ,
. parent = NULL ,
. selected = 0 ,
. scroll = 0 ,
. elements = g_rootMenuElements
} ;
static Mutex g_conMutex = 0 , g_fileMutex = 0 ;
static CondVar g_readCondvar = 0 , g_writeCondvar = 0 ;
2023-12-26 00:25:29 +00:00
static char path [ FS_MAX_PATH * 2 ] = { 0 } ;
2023-05-24 20:05:34 +01:00
int main ( int argc , char * argv [ ] )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( argc ) ;
NX_IGNORE_ARG ( argv ) ;
2023-07-17 00:03:05 +01:00
int ret = EXIT_SUCCESS ;
2023-05-24 20:05:34 +01:00
2023-12-20 19:32:48 +00:00
if ( ! utilsInitializeResources ( ) )
2023-05-24 20:05:34 +01:00
{
2023-07-17 00:03:05 +01:00
ret = EXIT_FAILURE ;
2023-05-24 20:05:34 +01:00
goto end ;
}
/* Configure input. */
/* Up to 8 different, full controller inputs. */
/* Individual Joy-Cons not supported. */
padConfigureInput ( 8 , HidNpadStyleSet_NpadFullCtrl ) ;
padInitializeWithMask ( & g_padState , 0x1000000FFUL ) ;
consoleInit ( NULL ) ;
updateStorageList ( ) ;
2023-07-21 22:56:50 +01:00
updateTitleList ( & g_userTitlesMenu , & g_userTitlesSubMenu , false ) ;
updateTitleList ( & g_systemTitlesMenu , & g_ncaMenu , true ) ;
2023-05-24 20:05:34 +01:00
Menu * cur_menu = & g_rootMenu ;
2024-04-09 01:05:44 +01:00
u32 element_count = menuGetElementCount ( cur_menu ) , page_size = 20 ;
2023-05-24 20:05:34 +01:00
TitleApplicationMetadata * app_metadata = NULL ;
TitleUserApplicationData user_app_data = { 0 } ;
TitleInfo * title_info = NULL ;
u32 title_info_idx = 0 , title_info_count = 0 ;
2023-07-21 22:56:50 +01:00
bool is_system = false ;
2023-05-24 20:05:34 +01:00
while ( appletMainLoop ( ) )
{
2024-05-10 23:09:49 +01:00
MenuElement * selected_element = ( ( cur_menu - > elements & & element_count & & cur_menu - > selected < element_count ) ? cur_menu - > elements [ cur_menu - > selected ] : NULL ) ;
MenuElementOption * selected_element_options = ( selected_element ? selected_element - > element_options : NULL ) ;
2023-05-24 20:05:34 +01:00
2024-05-10 23:09:49 +01:00
if ( cur_menu - > id = = MenuId_UserTitlesSubMenu & & selected_element & & selected_element - > child_menu )
2023-05-24 20:05:34 +01:00
{
/* Set title types child menu pointer if we're currently at the user titles submenu. */
u32 child_id = selected_element - > child_menu - > id ;
g_titleTypesMenuElements [ 0 ] - > child_menu = g_titleTypesMenuElements [ 1 ] - > child_menu = \
2024-10-27 11:23:46 +00:00
g_titleTypesMenuElements [ 2 ] - > child_menu = g_titleTypesMenuElements [ 3 ] - > child_menu = ( child_id = = MenuId_NspTitleTypes ? & g_nspMenu : \
2023-05-24 20:05:34 +01:00
( child_id = = MenuId_TicketTitleTypes ? & g_ticketMenu : \
2023-05-26 19:23:23 +01:00
( child_id = = MenuId_NcaTitleTypes ? & g_ncaMenu : NULL ) ) ) ;
2023-05-24 20:05:34 +01:00
}
consoleClear ( ) ;
2023-12-26 00:25:29 +00:00
2023-10-12 10:24:03 +01:00
consolePrint ( APP_TITLE " v " APP_VERSION " ( " GIT_REV " ). \n Built on " BUILD_TIMESTAMP " . \n " ) ;
2023-05-24 20:05:34 +01:00
consolePrint ( " ______________________________ \n \n " ) ;
2023-05-26 16:59:03 +01:00
if ( cur_menu - > parent ) consolePrint ( " press b to go back \n " ) ;
2023-05-24 20:05:34 +01:00
if ( g_umsDeviceCount ) consolePrint ( " press x to safely remove all ums devices \n " ) ;
2024-08-24 18:40:17 +01:00
if ( ( cur_menu - > id = = MenuId_UserTitles | | cur_menu - > id = = MenuId_SystemTitles ) & & element_count ) consolePrint ( " press y to dump csv with title info to the sd card \n " ) ;
2023-11-26 21:54:25 +00:00
consolePrint ( " use the sticks to scroll faster \n " ) ;
2023-05-26 16:59:03 +01:00
consolePrint ( " press + to exit \n " ) ;
2023-05-24 20:05:34 +01:00
consolePrint ( " ______________________________ \n \n " ) ;
2024-05-10 23:09:49 +01:00
if ( ( cur_menu - > id = = MenuId_UserTitles | | cur_menu - > id = = MenuId_SystemTitles ) & & selected_element )
2023-05-24 20:05:34 +01:00
{
app_metadata = ( TitleApplicationMetadata * ) selected_element - > userdata ;
consolePrint ( " title: %u / %u \n " , cur_menu - > selected + 1 , element_count ) ;
consolePrint ( " selected title: %016lX - %s \n " , app_metadata - > title_id , selected_element - > str ) ;
consolePrint ( " ______________________________ \n \n " ) ;
} else
2023-07-21 22:56:50 +01:00
if ( cur_menu - > id > = MenuId_UserTitlesSubMenu & & cur_menu - > id < MenuId_SystemTitles )
2023-05-24 20:05:34 +01:00
{
2023-07-21 22:56:50 +01:00
if ( ! is_system )
{
consolePrint ( " title info: \n \n " ) ;
consolePrint ( " name: %s \n " , app_metadata - > lang_entry . name ) ;
consolePrint ( " publisher: %s \n " , app_metadata - > lang_entry . author ) ;
2024-10-27 11:23:46 +00:00
if ( cur_menu - > id = = MenuId_UserTitlesSubMenu | | cur_menu - > id = = MenuId_NspTitleTypes | | cur_menu - > id = = MenuId_TicketTitleTypes | | \
2023-07-21 22:56:50 +01:00
cur_menu - > id = = MenuId_NcaTitleTypes ) consolePrint ( " title id: %016lX \n " , app_metadata - > title_id ) ;
consolePrint ( " ______________________________ \n \n " ) ;
}
2023-05-24 20:05:34 +01:00
2024-10-27 11:23:46 +00:00
if ( cur_menu - > id = = MenuId_Nsp | | cur_menu - > id = = MenuId_Ticket | | cur_menu - > id = = MenuId_Nca | | \
2023-05-26 19:23:23 +01:00
cur_menu - > id = = MenuId_NcaFsSections | | cur_menu - > id = = MenuId_NcaFsSectionsSubMenu )
2023-05-24 20:05:34 +01:00
{
2023-05-26 19:23:23 +01:00
if ( cur_menu - > id ! = MenuId_NcaFsSections & & cur_menu - > id ! = MenuId_NcaFsSectionsSubMenu & & ( title_info - > previous | | title_info - > next ) )
2023-05-24 20:05:34 +01:00
{
2023-11-03 01:22:47 +00:00
consolePrintReversedColors ( " press l/zl/r/zr to change the selected title \n " ) ;
consolePrintReversedColors ( " title: %u / %u \n " , title_info_idx + 1 , title_info_count ) ;
2023-05-24 20:05:34 +01:00
consolePrint ( " ______________________________ \n \n " ) ;
}
consolePrint ( " selected title info: \n \n " ) ;
2023-07-21 22:56:50 +01:00
if ( is_system ) consolePrint ( " name: %s \n " , app_metadata - > lang_entry . name ) ;
2023-05-24 20:05:34 +01:00
consolePrint ( " title id: %016lX \n " , title_info - > meta_key . id ) ;
consolePrint ( " type: %s \n " , titleGetNcmContentMetaTypeName ( title_info - > meta_key . type ) ) ;
consolePrint ( " source storage: %s \n " , titleGetNcmStorageIdName ( title_info - > storage_id ) ) ;
consolePrint ( " version: %u (%u.%u.%u-%u.%u) \n " , title_info - > version . value , title_info - > version . system_version . major , title_info - > version . system_version . minor , \
title_info - > version . system_version . micro , title_info - > version . system_version . major_relstep , title_info - > version . system_version . minor_relstep ) ;
consolePrint ( " content count: %u \n " , title_info - > content_count ) ;
consolePrint ( " size: %s \n " , title_info - > size_str ) ;
consolePrint ( " ______________________________ \n \n " ) ;
2024-10-27 11:23:46 +00:00
if ( cur_menu - > id = = MenuId_Nsp ) g_nspMenuElements [ 0 ] - > userdata = title_info ;
2023-05-24 20:05:34 +01:00
if ( cur_menu - > id = = MenuId_Ticket ) g_ticketMenuElements [ 0 ] - > userdata = title_info ;
2023-05-26 16:59:03 +01:00
2023-05-26 19:23:23 +01:00
if ( cur_menu - > id = = MenuId_Nca )
2023-05-26 16:59:03 +01:00
{
2023-11-03 01:22:47 +00:00
consolePrintReversedColors ( " current mode: %s \n " , g_ncaMenuRawMode ? " raw nca " : " nca fs section " ) ;
consolePrintReversedColors ( " press y to switch to %s mode \n " , g_ncaMenuRawMode ? " nca fs section " : " raw nca " ) ;
2023-05-26 16:59:03 +01:00
consolePrint ( " ______________________________ \n \n " ) ;
}
2023-05-26 19:23:23 +01:00
if ( cur_menu - > id = = MenuId_NcaFsSections | | cur_menu - > id = = MenuId_NcaFsSectionsSubMenu )
2023-05-26 16:59:03 +01:00
{
consolePrint ( " selected nca info: \n \n " ) ;
consolePrint ( " content id: %s \n " , g_ncaFsSectionsMenuCtx - > content_id_str ) ;
consolePrint ( " content type: %s \n " , titleGetNcmContentTypeName ( g_ncaFsSectionsMenuCtx - > content_type ) ) ;
consolePrint ( " id offset: %u \n " , g_ncaFsSectionsMenuCtx - > id_offset ) ;
consolePrint ( " size: %s \n " , g_ncaFsSectionsMenuCtx - > content_size_str ) ;
consolePrint ( " ______________________________ \n \n " ) ;
}
2023-05-26 19:23:23 +01:00
if ( cur_menu - > id = = MenuId_NcaFsSectionsSubMenu )
{
NcaFsSectionContext * nca_fs_ctx = ( NcaFsSectionContext * ) g_ncaFsSectionsSubMenuElements [ 0 ] - > userdata ;
consolePrint ( " selected nca fs section info: \n " ) ;
consolePrint ( " section index: %u \n " , nca_fs_ctx - > section_idx ) ;
consolePrint ( " section type: %s \n " , ncaGetFsSectionTypeName ( nca_fs_ctx ) ) ;
consolePrint ( " section size: %s \n " , nca_fs_ctx - > section_size_str ) ;
consolePrint ( " ______________________________ \n \n " ) ;
}
2023-05-24 20:05:34 +01:00
}
2023-12-05 18:21:45 +00:00
} else
if ( cur_menu - > id = = MenuId_GameCard ) {
consolePrint ( " For a full gamecard image: dump XCI, initial data, certificate, id set and uid. \n " ) ;
consolePrint ( " ______________________________ \n \n " ) ;
2023-05-24 20:05:34 +01:00
}
for ( u32 i = cur_menu - > scroll ; i < element_count ; i + + )
{
if ( i > = ( cur_menu - > scroll + page_size ) ) break ;
MenuElement * cur_element = cur_menu - > elements [ i ] ;
MenuElementOption * cur_options = cur_element - > element_options ;
2023-07-21 22:56:50 +01:00
TitleApplicationMetadata * cur_app_metadata = ( ( cur_menu - > id = = MenuId_UserTitles | | cur_menu - > id = = MenuId_SystemTitles ) ? ( TitleApplicationMetadata * ) cur_element - > userdata : NULL ) ;
2023-05-24 20:05:34 +01:00
consolePrint ( " %s " , i = = cur_menu - > selected ? " -> " : " " ) ;
if ( cur_app_metadata ) consolePrint ( " %016lX - " , cur_app_metadata - > title_id ) ;
consolePrint ( " %s " , cur_element - > str ) ;
if ( cur_options )
{
2023-11-26 21:54:25 +00:00
if ( cur_options - > getter_func & & ! cur_options - > retrieved )
2023-05-24 20:05:34 +01:00
{
cur_options - > selected = cur_options - > getter_func ( ) ;
2023-11-26 21:54:25 +00:00
cur_options - > retrieved = true ;
2023-05-24 20:05:34 +01:00
}
consolePrint ( " : " ) ;
if ( cur_options - > selected > 0 ) consolePrint ( " < " ) ;
consolePrint ( " %s " , cur_options - > options [ cur_options - > selected ] ) ;
if ( cur_options - > options [ cur_options - > selected + 1 ] ) consolePrint ( " > " ) ;
}
consolePrint ( " \n " ) ;
}
if ( ! element_count ) consolePrint ( " no elements available! press b to go back " ) ;
consolePrint ( " \n " ) ;
consoleRefresh ( ) ;
bool data_update = false ;
u64 btn_down = 0 , btn_held = 0 ;
while ( ( g_appletStatus = appletMainLoop ( ) ) )
{
utilsScanPads ( ) ;
btn_down = utilsGetButtonsDown ( ) ;
btn_held = utilsGetButtonsHeld ( ) ;
if ( btn_down | | btn_held ) break ;
if ( umsIsDeviceInfoUpdated ( ) )
{
updateStorageList ( ) ;
data_update = true ;
break ;
}
if ( titleIsGameCardInfoUpdated ( ) )
{
2023-07-21 22:56:50 +01:00
updateTitleList ( & g_userTitlesMenu , & g_userTitlesSubMenu , false ) ;
2024-05-10 23:09:49 +01:00
if ( cur_menu - > id = = MenuId_UserTitles ) element_count = menuGetElementCount ( cur_menu ) ;
g_userTitlesMenu . selected = g_userTitlesMenu . scroll = 0 ;
2023-05-24 20:05:34 +01:00
data_update = true ;
break ;
}
2023-05-26 13:41:49 +01:00
2023-10-22 23:44:40 +01:00
utilsAppletLoopDelay ( ) ;
2023-05-24 20:05:34 +01:00
}
if ( ! g_appletStatus ) break ;
if ( data_update ) continue ;
2024-05-10 23:09:49 +01:00
if ( ( btn_down & HidNpadButton_A ) & & selected_element )
2023-05-24 20:05:34 +01:00
{
Menu * child_menu = selected_element - > child_menu ;
if ( child_menu )
{
bool error = false ;
/* Only change menus if a valid ID was set. */
if ( child_menu - > id = = MenuId_Root | | child_menu - > id > = MenuId_Count | | child_menu - > id = = cur_menu - > id ) continue ;
/* Retrieve extra data based on the current menu ID. */
if ( child_menu - > id = = MenuId_UserTitlesSubMenu )
{
error = ! titleGetUserApplicationData ( app_metadata - > title_id , & user_app_data ) ;
if ( error ) consolePrint ( " \n failed to get user application data for %016lX! \n " , app_metadata - > title_id ) ;
} else
2024-10-27 11:23:46 +00:00
if ( child_menu - > id = = MenuId_Nsp | | child_menu - > id = = MenuId_Ticket | | child_menu - > id = = MenuId_Nca )
2023-05-24 20:05:34 +01:00
{
2023-07-21 22:56:50 +01:00
u32 title_type = ( cur_menu - > id ! = MenuId_SystemTitles ? * ( ( u32 * ) selected_element - > userdata ) : NcmContentMetaType_Unknown ) ;
2023-05-24 20:05:34 +01:00
switch ( title_type )
{
case NcmContentMetaType_Application :
title_info = user_app_data . app_info ;
break ;
case NcmContentMetaType_Patch :
title_info = user_app_data . patch_info ;
break ;
case NcmContentMetaType_AddOnContent :
title_info = user_app_data . aoc_info ;
break ;
case NcmContentMetaType_DataPatch :
title_info = user_app_data . aoc_patch_info ;
break ;
default :
2023-07-21 22:56:50 +01:00
/* Get TitleInfo element on demand. */
2024-08-18 14:29:57 +01:00
title_info = titleGetTitleInfoEntryFromStorageByTitleId ( NcmStorageId_BuiltInSystem , app_metadata - > title_id ) ;
2023-05-24 20:05:34 +01:00
break ;
}
if ( title_info )
{
2023-11-03 01:22:47 +00:00
title_info = getLatestTitleInfo ( title_info , & title_info_idx , & title_info_count ) ;
2023-05-26 19:23:23 +01:00
if ( child_menu - > id = = MenuId_Nca )
2023-05-24 20:05:34 +01:00
{
2024-02-16 22:31:24 +00:00
updateNcaList ( title_info , & element_count ) ;
2023-05-24 20:05:34 +01:00
if ( ! g_ncaMenuElements | | ! g_ncaMenuElements [ 0 ] )
{
consolePrint ( " failed to generate nca list \n " ) ;
error = true ;
}
2023-07-21 22:56:50 +01:00
if ( ! error & & cur_menu - > id = = MenuId_SystemTitles ) is_system = true ;
2023-05-24 20:05:34 +01:00
}
} else {
2023-07-21 22:56:50 +01:00
if ( cur_menu - > id = = MenuId_SystemTitles )
{
consolePrint ( " \n unable to retrieve data for system title %016lX \n " , app_metadata - > title_id ) ;
} else {
consolePrint ( " \n the selected title doesn't have available %s data \n " , \
title_type = = NcmContentMetaType_Application ? " base application " : \
( title_type = = NcmContentMetaType_Patch ? " update " : ( title_type = = NcmContentMetaType_AddOnContent ? " dlc " : " dlc update " ) ) ) ;
}
2023-05-24 20:05:34 +01:00
error = true ;
}
2023-05-26 16:59:03 +01:00
} else
2023-05-26 19:23:23 +01:00
if ( child_menu - > id = = MenuId_NcaFsSections )
2023-05-26 16:59:03 +01:00
{
updateNcaFsSectionsList ( ( NcaUserData * ) selected_element - > userdata ) ;
if ( ! g_ncaFsSectionsMenuElements | | ! g_ncaFsSectionsMenuElements [ 0 ] )
{
consolePrint ( " failed to generate nca fs sections list \n " ) ;
error = true ;
}
2023-05-26 19:23:23 +01:00
} else
if ( child_menu - > id = = MenuId_NcaFsSectionsSubMenu )
{
NcaFsSectionContext * nca_fs_ctx = selected_element - > userdata ;
if ( nca_fs_ctx - > enabled )
{
2023-05-27 19:10:35 +01:00
updateNcaBasePatchList ( & user_app_data , title_info , nca_fs_ctx ) ;
2023-05-26 19:23:23 +01:00
} else {
consolePrint ( " can't dump an invalid nca fs section! \n " ) ;
error = true ;
}
2023-05-24 20:05:34 +01:00
}
if ( ! error )
{
child_menu - > parent = cur_menu ;
cur_menu = child_menu ;
element_count = menuGetElementCount ( cur_menu ) ;
} else {
consolePrint ( " press any button to go back \n " ) ;
consoleRefresh ( ) ;
utilsWaitForButtonPress ( 0 ) ;
}
} else
if ( selected_element - > task_func )
{
2023-12-31 17:16:49 +00:00
bool show_button_prompt = true ;
2023-05-24 20:05:34 +01:00
consoleClear ( ) ;
/* Wait for gamecard (if needed). */
2023-12-31 17:16:49 +00:00
if ( ( ( cur_menu - > id > = MenuId_GameCard & & cur_menu - > id < = MenuId_BrowseHFS ) | | ( title_info & & title_info - > storage_id = = NcmStorageId_GameCard ) ) & & ! waitForGameCard ( ) )
2023-05-24 20:05:34 +01:00
{
if ( g_appletStatus ) continue ;
break ;
}
2024-10-28 14:44:31 +00:00
if ( ( cur_menu - > id = = MenuId_NcaFsSectionsSubMenu & & cur_menu - > selected = = 1 ) | | cur_menu - > id = = MenuId_BrowseHFS | | cur_menu - > id = = MenuId_BrowseEmmc )
2023-12-31 17:16:49 +00:00
{
show_button_prompt = false ;
/* Ignore result. */
selected_element - > task_func ( selected_element - > userdata ) ;
/* Update free space. */
if ( ! useUsbHost ( ) ) updateStorageList ( ) ;
} else
if ( cur_menu - > id > MenuId_Root )
2023-05-24 20:05:34 +01:00
{
2023-11-26 21:54:25 +00:00
/* Wait for USB session (if needed). */
if ( useUsbHost ( ) & & ! waitForUsb ( ) )
{
if ( g_appletStatus ) continue ;
break ;
}
2023-05-24 20:05:34 +01:00
2023-11-26 21:54:25 +00:00
/* Run task. */
utilsSetLongRunningProcessState ( true ) ;
2023-05-24 20:05:34 +01:00
2023-11-26 21:54:25 +00:00
if ( selected_element - > task_func ( selected_element - > userdata ) )
{
if ( ! useUsbHost ( ) ) updateStorageList ( ) ; // update free space
}
2023-05-24 20:05:34 +01:00
2023-11-26 21:54:25 +00:00
utilsSetLongRunningProcessState ( false ) ;
2024-08-26 08:45:52 +01:00
} else {
/* Ignore result. */
selected_element - > task_func ( selected_element - > userdata ) ;
2023-11-26 21:54:25 +00:00
}
2023-05-24 20:05:34 +01:00
2023-12-31 17:16:49 +00:00
if ( g_appletStatus & & show_button_prompt )
2023-12-26 00:25:29 +00:00
{
/* Display prompt. */
consolePrint ( " press any button to continue " ) ;
utilsWaitForButtonPress ( 0 ) ;
}
2023-05-24 20:05:34 +01:00
}
} else
if ( ( ( btn_down & HidNpadButton_Down ) | | ( btn_held & ( HidNpadButton_StickLDown | HidNpadButton_StickRDown ) ) ) & & element_count )
{
cur_menu - > selected + + ;
if ( ! cur_menu - > elements [ cur_menu - > selected ] )
{
if ( btn_down & HidNpadButton_Down )
{
cur_menu - > selected = 0 ;
cur_menu - > scroll = 0 ;
} else {
cur_menu - > selected - - ;
}
} else
if ( cur_menu - > selected > = ( cur_menu - > scroll + ( page_size / 2 ) ) & & element_count > ( cur_menu - > scroll + page_size ) )
{
cur_menu - > scroll + + ;
}
} else
if ( ( ( btn_down & HidNpadButton_Up ) | | ( btn_held & ( HidNpadButton_StickLUp | HidNpadButton_StickRUp ) ) ) & & element_count )
{
cur_menu - > selected - - ;
if ( cur_menu - > selected = = UINT32_MAX )
{
if ( btn_down & HidNpadButton_Up )
{
cur_menu - > selected = ( element_count - 1 ) ;
cur_menu - > scroll = ( element_count > = page_size ? ( element_count - page_size ) : 0 ) ;
} else {
cur_menu - > selected = 0 ;
}
} else
if ( cur_menu - > selected < ( cur_menu - > scroll + ( page_size / 2 ) ) & & cur_menu - > scroll > 0 )
{
cur_menu - > scroll - - ;
}
} else
if ( ( btn_down & ( HidNpadButton_Right | HidNpadButton_StickLRight | HidNpadButton_StickRRight ) ) & & selected_element_options )
{
2023-05-30 00:22:12 +01:00
/* Point to the next base/patch title. */
2023-12-26 00:25:29 +00:00
if ( cur_menu - > id = = MenuId_NcaFsSectionsSubMenu & & cur_menu - > selected = = 2 )
2023-05-30 00:22:12 +01:00
{
if ( selected_element_options - > selected = = 0 & & g_ncaBasePatchTitleInfoBkp )
{
g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfoBkp ;
g_ncaBasePatchTitleInfoBkp = NULL ;
} else
if ( selected_element_options - > selected > 0 & & g_ncaBasePatchTitleInfo & & g_ncaBasePatchTitleInfo - > next )
{
g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfo - > next ;
}
}
2023-05-24 20:05:34 +01:00
selected_element_options - > selected + + ;
if ( ! selected_element_options - > options [ selected_element_options - > selected ] ) selected_element_options - > selected - - ;
if ( selected_element_options - > setter_func ) selected_element_options - > setter_func ( selected_element_options - > selected ) ;
} else
if ( ( btn_down & ( HidNpadButton_Left | HidNpadButton_StickLLeft | HidNpadButton_StickRLeft ) ) & & selected_element_options )
{
selected_element_options - > selected - - ;
if ( selected_element_options - > selected = = UINT32_MAX ) selected_element_options - > selected = 0 ;
if ( selected_element_options - > setter_func ) selected_element_options - > setter_func ( selected_element_options - > selected ) ;
2023-05-27 19:10:35 +01:00
/* Point to the previous base/patch title. */
2023-12-26 00:25:29 +00:00
if ( cur_menu - > id = = MenuId_NcaFsSectionsSubMenu & & cur_menu - > selected = = 2 )
2023-05-30 00:22:12 +01:00
{
if ( selected_element_options - > selected = = 0 & & g_ncaBasePatchTitleInfo )
{
g_ncaBasePatchTitleInfoBkp = g_ncaBasePatchTitleInfo ;
g_ncaBasePatchTitleInfo = NULL ;
} else
if ( selected_element_options - > selected > 0 & & g_ncaBasePatchTitleInfo & & g_ncaBasePatchTitleInfo - > previous )
{
g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfo - > previous ;
}
}
2023-05-24 20:05:34 +01:00
} else
2023-05-27 19:10:35 +01:00
if ( ( btn_down & HidNpadButton_B ) & & cur_menu - > parent )
2023-05-24 20:05:34 +01:00
{
2023-11-26 21:54:25 +00:00
menuResetAttributes ( cur_menu , element_count ) ;
2023-07-21 22:56:50 +01:00
if ( cur_menu - > id = = MenuId_UserTitles | | cur_menu - > id = = MenuId_SystemTitles )
2023-05-24 20:05:34 +01:00
{
app_metadata = NULL ;
} else
if ( cur_menu - > id = = MenuId_UserTitlesSubMenu )
{
titleFreeUserApplicationData ( & user_app_data ) ;
g_titleTypesMenuElements [ 0 ] - > child_menu = g_titleTypesMenuElements [ 1 ] - > child_menu = \
g_titleTypesMenuElements [ 2 ] - > child_menu = g_titleTypesMenuElements [ 3 ] - > child_menu = NULL ;
} else
2024-10-27 11:23:46 +00:00
if ( cur_menu - > id = = MenuId_NspTitleTypes | | cur_menu - > id = = MenuId_TicketTitleTypes | | cur_menu - > id = = MenuId_NcaTitleTypes )
2023-05-24 20:05:34 +01:00
{
title_info = NULL ;
title_info_idx = title_info_count = 0 ;
} else
2024-10-27 11:23:46 +00:00
if ( cur_menu - > id = = MenuId_Nsp )
2023-05-24 20:05:34 +01:00
{
g_nspMenuElements [ 0 ] - > userdata = NULL ;
} else
if ( cur_menu - > id = = MenuId_Ticket )
{
g_ticketMenuElements [ 0 ] - > userdata = NULL ;
} else
2023-05-26 19:23:23 +01:00
if ( cur_menu - > id = = MenuId_Nca )
2023-05-24 20:05:34 +01:00
{
freeNcaList ( ) ;
2023-07-21 22:56:50 +01:00
if ( is_system )
{
titleFreeTitleInfo ( & title_info ) ;
is_system = false ;
}
2023-05-26 16:59:03 +01:00
} else
2023-05-26 19:23:23 +01:00
if ( cur_menu - > id = = MenuId_NcaFsSections )
2023-05-26 16:59:03 +01:00
{
freeNcaFsSectionsList ( ) ;
2023-05-26 19:23:23 +01:00
} else
if ( cur_menu - > id = = MenuId_NcaFsSectionsSubMenu )
{
2023-05-27 19:10:35 +01:00
freeNcaBasePatchList ( ) ;
2023-05-24 20:05:34 +01:00
}
cur_menu = cur_menu - > parent ;
element_count = menuGetElementCount ( cur_menu ) ;
} else
if ( ( btn_down & HidNpadButton_X ) & & g_umsDeviceCount )
{
2023-06-28 23:12:51 +01:00
for ( u32 i = 0 ; i < g_umsDeviceCount ; i + + ) umsUnmountDevice ( & ( g_umsDevices [ i ] ) ) ;
2023-05-24 20:05:34 +01:00
updateStorageList ( ) ;
} else
2024-08-24 18:40:17 +01:00
if ( ( btn_down & HidNpadButton_Y ) & & ( cur_menu - > id = = MenuId_UserTitles | | cur_menu - > id = = MenuId_SystemTitles ) & & element_count )
{
consoleClear ( ) ;
consolePrint ( " dumping title info to csv, please wait... \n " ) ;
consoleRefresh ( ) ;
sprintf ( path , DEVOPTAB_SDMC_DEVICE " / " OUTDIR " /%s_title_records.csv " , cur_menu - > id = = MenuId_UserTitles ? " user " : " system " ) ;
char * csv_buf = NULL ;
size_t csv_buf_size = 0 ;
u32 proc_title_cnt = 0 ;
csv_buf = titleGenerateTitleRecordsCsv ( & csv_buf_size , & proc_title_cnt , cur_menu - > id = = MenuId_SystemTitles , false ) ;
if ( csv_buf )
{
utilsCreateDirectoryTree ( path , false ) ;
FILE * csv_fd = fopen ( path , " wb " ) ;
if ( csv_fd )
{
fwrite ( UTF8_BOM , 1 , strlen ( UTF8_BOM ) , csv_fd ) ;
fwrite ( csv_buf , 1 , csv_buf_size , csv_fd ) ;
fclose ( csv_fd ) ;
consolePrint ( " title info dumped to \" %s \" . %u title record(s) processed. \n " , path , proc_title_cnt ) ;
} else {
consolePrint ( " failed to open \" %s \" for writing \n " , path ) ;
}
free ( csv_buf ) ;
} else {
consolePrint ( " failed to generate csv data \n " ) ;
}
consolePrint ( " press any button to go back " ) ;
utilsWaitForButtonPress ( 0 ) ;
} else
2024-10-27 11:23:46 +00:00
if ( ( ( btn_down & ( HidNpadButton_L ) ) | | ( btn_held & HidNpadButton_ZL ) ) & & ( cur_menu - > id = = MenuId_Nsp | | cur_menu - > id = = MenuId_Ticket | | cur_menu - > id = = MenuId_Nca ) & & title_info - > previous )
2023-05-24 20:05:34 +01:00
{
title_info = title_info - > previous ;
title_info_idx - - ;
2024-02-16 22:31:24 +00:00
switchNcaListTitle ( & cur_menu , & element_count , title_info ) ;
2023-05-24 20:05:34 +01:00
} else
2024-10-27 11:23:46 +00:00
if ( ( ( btn_down & ( HidNpadButton_R ) ) | | ( btn_held & HidNpadButton_ZR ) ) & & ( cur_menu - > id = = MenuId_Nsp | | cur_menu - > id = = MenuId_Ticket | | cur_menu - > id = = MenuId_Nca ) & & title_info - > next )
2023-05-24 20:05:34 +01:00
{
title_info = title_info - > next ;
title_info_idx + + ;
2024-02-16 22:31:24 +00:00
switchNcaListTitle ( & cur_menu , & element_count , title_info ) ;
2023-05-26 16:59:03 +01:00
} else
2023-05-26 19:23:23 +01:00
if ( ( btn_down & HidNpadButton_Y ) & & cur_menu - > id = = MenuId_Nca )
2023-05-26 16:59:03 +01:00
{
/* Change NCA menu element properties. */
g_ncaMenuRawMode ^ = 1 ;
2023-05-24 20:05:34 +01:00
2023-05-26 16:59:03 +01:00
for ( u32 i = 0 ; g_ncaMenuElements [ i ] ; i + + )
2023-05-24 20:05:34 +01:00
{
2023-05-26 16:59:03 +01:00
g_ncaMenuElements [ i ] - > child_menu = ( g_ncaMenuRawMode ? NULL : & g_ncaFsSectionsMenu ) ;
g_ncaMenuElements [ i ] - > task_func = ( g_ncaMenuRawMode ? & saveNintendoContentArchive : NULL ) ;
2023-05-24 20:05:34 +01:00
}
2023-05-26 16:59:03 +01:00
} else
if ( btn_down & HidNpadButton_Plus )
{
break ;
2023-05-24 20:05:34 +01:00
}
2023-12-26 00:25:29 +00:00
if ( ! g_appletStatus ) break ;
2023-10-22 23:44:40 +01:00
utilsAppletLoopDelay ( ) ;
2023-05-24 20:05:34 +01:00
}
2023-05-26 16:59:03 +01:00
freeNcaFsSectionsList ( ) ;
2023-05-24 20:05:34 +01:00
freeNcaList ( ) ;
2023-07-21 22:56:50 +01:00
freeTitleList ( & g_systemTitlesMenu ) ;
freeTitleList ( & g_userTitlesMenu ) ;
2023-05-24 20:05:34 +01:00
freeStorageList ( ) ;
2023-05-26 16:59:03 +01:00
titleFreeUserApplicationData ( & user_app_data ) ;
2023-07-17 00:03:05 +01:00
end :
2023-05-24 20:05:34 +01:00
utilsCloseResources ( ) ;
consoleExit ( NULL ) ;
return ret ;
}
static void utilsScanPads ( void )
{
padUpdate ( & g_padState ) ;
}
static u64 utilsGetButtonsDown ( void )
{
return padGetButtonsDown ( & g_padState ) ;
}
static u64 utilsGetButtonsHeld ( void )
{
return padGetButtons ( & g_padState ) ;
}
2023-11-03 18:03:50 +00:00
static u64 utilsWaitForButtonPress ( u64 flag )
2023-05-24 20:05:34 +01:00
{
/* 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 ) ;
consoleRefresh ( ) ;
2023-11-03 18:03:50 +00:00
u64 btn_down = 0 ;
2023-05-24 20:05:34 +01:00
while ( appletMainLoop ( ) )
{
utilsScanPads ( ) ;
2023-11-03 18:03:50 +00:00
if ( ( btn_down = utilsGetButtonsDown ( ) ) & flag ) break ;
2023-10-22 23:44:40 +01:00
utilsAppletLoopDelay ( ) ;
2023-05-24 20:05:34 +01:00
}
2023-11-03 18:03:50 +00:00
return btn_down ;
2023-05-24 20:05:34 +01:00
}
static void consolePrint ( const char * text , . . . )
{
mutexLock ( & g_conMutex ) ;
va_list v ;
va_start ( v , text ) ;
vfprintf ( stdout , text , v ) ;
va_end ( v ) ;
mutexUnlock ( & g_conMutex ) ;
}
2023-11-03 01:22:47 +00:00
static void consolePrintReversedColors ( const char * text , . . . )
{
mutexLock ( & g_conMutex ) ;
printf ( CONSOLE_ESC ( 7 m ) ) ;
va_list v ;
va_start ( v , text ) ;
vfprintf ( stdout , text , v ) ;
va_end ( v ) ;
printf ( CONSOLE_ESC ( 0 m ) ) ;
mutexUnlock ( & g_conMutex ) ;
}
2023-05-24 20:05:34 +01:00
static void consoleRefresh ( void )
{
mutexLock ( & g_conMutex ) ;
fflush ( stdout ) ;
consoleUpdate ( NULL ) ;
mutexUnlock ( & g_conMutex ) ;
}
static u32 menuGetElementCount ( const Menu * menu )
{
if ( ! menu | | ! menu - > elements | | ! menu - > elements [ 0 ] ) return 0 ;
u32 cnt ;
for ( cnt = 0 ; menu - > elements [ cnt ] ; cnt + + ) ;
return cnt ;
}
2023-11-26 21:54:25 +00:00
static void menuResetAttributes ( Menu * cur_menu , u32 element_count )
{
if ( ! cur_menu ) return ;
cur_menu - > selected = 0 ;
cur_menu - > scroll = 0 ;
for ( u32 i = 0 ; i < element_count ; i + + )
{
MenuElement * cur_element = cur_menu - > elements [ i ] ;
MenuElementOption * cur_options = cur_element - > element_options ;
if ( cur_options & & cur_options ! = & g_storageMenuElementOption ) cur_options - > retrieved = false ;
}
}
2023-05-24 20:05:34 +01:00
void freeStorageList ( void )
{
u32 elem_count = ( 2 + g_umsDeviceCount ) ; // sd card, usb host, ums devices
/* Free all previously allocated data. */
if ( g_storageOptions )
{
for ( u32 i = 0 ; i < elem_count & & g_storageOptions [ i ] ; i + + )
{
free ( g_storageOptions [ i ] ) ;
g_storageOptions [ i ] = NULL ;
}
free ( g_storageOptions ) ;
g_storageOptions = NULL ;
}
if ( g_umsDevices )
{
free ( g_umsDevices ) ;
g_umsDevices = NULL ;
}
g_umsDeviceCount = 0 ;
2023-05-27 19:10:35 +01:00
g_storageMenuElementOption . options = NULL ;
2023-05-24 20:05:34 +01:00
}
void updateStorageList ( void )
{
u32 elem_count = 0 , idx = 0 ;
/* Free all previously allocated data. */
freeStorageList ( ) ;
/* Get UMS devices. */
g_umsDevices = umsGetDevices ( & g_umsDeviceCount ) ;
elem_count = ( 2 + g_umsDeviceCount ) ; // sd card, usb host, ums devices
2023-07-21 22:56:50 +01:00
/* Allocate buffer. */
g_storageOptions = calloc ( elem_count + 1 , sizeof ( char * ) ) ; // NULL terminator
2023-05-24 20:05:34 +01:00
/* Generate UMS device strings. */
for ( u32 i = 0 ; i < elem_count ; i + + )
{
u64 total = 0 , free = 0 ;
char total_str [ 36 ] = { 0 } , free_str [ 32 ] = { 0 } ;
2023-05-27 19:10:35 +01:00
if ( ! g_storageOptions [ idx ] )
{
g_storageOptions [ idx ] = calloc ( sizeof ( char ) , 0x300 ) ;
if ( ! g_storageOptions [ idx ] ) continue ;
}
2023-05-24 20:05:34 +01:00
if ( i = = 1 )
{
sprintf ( g_storageOptions [ idx ] , " usb host (pc) " ) ;
} else {
UsbHsFsDevice * ums_device = ( i > = 2 ? & ( g_umsDevices [ i - 2 ] ) : NULL ) ;
sprintf ( total_str , " %s/ " , i = = 0 ? DEVOPTAB_SDMC_DEVICE : ums_device - > name ) ;
utilsGetFileSystemStatsByPath ( total_str , & total , & free ) ;
utilsGenerateFormattedSizeString ( total , total_str , sizeof ( total_str ) ) ;
utilsGenerateFormattedSizeString ( free , free_str , sizeof ( free_str ) ) ;
if ( i = = 0 )
{
sprintf ( g_storageOptions [ idx ] , DEVOPTAB_SDMC_DEVICE " (%s / %s) " , free_str , total_str ) ;
} else {
if ( ums_device - > product_name [ 0 ] )
{
sprintf ( g_storageOptions [ idx ] , " %s (%s, LUN %u, FS #%u, %s) " , ums_device - > name , ums_device - > product_name , ums_device - > lun , ums_device - > fs_idx , LIBUSBHSFS_FS_TYPE_STR ( ums_device - > fs_type ) ) ;
} else {
sprintf ( g_storageOptions [ idx ] , " %s (LUN %u, FS #%u, %s) " , ums_device - > name , ums_device - > lun , ums_device - > fs_idx , LIBUSBHSFS_FS_TYPE_STR ( ums_device - > fs_type ) ) ;
}
sprintf ( g_storageOptions [ idx ] + strlen ( g_storageOptions [ idx ] ) , " (%s / %s) " , free_str , total_str ) ;
}
}
idx + + ;
}
/* Update storage menu element options. */
if ( g_storageMenuElementOption . selected > = elem_count )
{
g_storageMenuElementOption . selected = 0 ;
setOutputStorageOption ( 0 ) ;
}
g_storageMenuElementOption . options = g_storageOptions ;
}
2023-07-21 22:56:50 +01:00
void freeTitleList ( Menu * menu )
2023-05-24 20:05:34 +01:00
{
2023-07-21 22:56:50 +01:00
if ( ! menu ) return ;
MenuElement * * elements = menu - > elements ;
2023-05-24 20:05:34 +01:00
/* Free all previously allocated data. */
2023-07-21 22:56:50 +01:00
if ( elements )
2023-05-24 20:05:34 +01:00
{
2023-07-21 22:56:50 +01:00
for ( u32 i = 0 ; elements [ i ] ; i + + ) free ( elements [ i ] ) ;
free ( elements ) ;
2023-05-24 20:05:34 +01:00
}
2023-07-21 22:56:50 +01:00
menu - > scroll = 0 ;
menu - > selected = 0 ;
menu - > elements = NULL ;
2023-05-24 20:05:34 +01:00
}
2023-07-21 22:56:50 +01:00
void updateTitleList ( Menu * menu , Menu * submenu , bool is_system )
2023-05-24 20:05:34 +01:00
{
2023-07-21 22:56:50 +01:00
if ( ! menu | | ! submenu ) return ;
2023-05-24 20:05:34 +01:00
u32 app_count = 0 , idx = 0 ;
TitleApplicationMetadata * * app_metadata = NULL ;
2023-07-21 22:56:50 +01:00
MenuElement * * elements = NULL ;
2023-05-24 20:05:34 +01:00
/* Free all previously allocated data. */
2023-07-21 22:56:50 +01:00
freeTitleList ( menu ) ;
2023-05-24 20:05:34 +01:00
/* Get application metadata entries. */
2023-07-21 22:56:50 +01:00
app_metadata = titleGetApplicationMetadataEntries ( is_system , & app_count ) ;
2023-05-24 20:05:34 +01:00
if ( ! app_metadata | | ! app_count ) goto end ;
2023-07-21 22:56:50 +01:00
/* Allocate buffer. */
elements = calloc ( app_count + 1 , sizeof ( MenuElement * ) ) ; // NULL terminator
2023-05-24 20:05:34 +01:00
/* Generate menu elements. */
for ( u32 i = 0 ; i < app_count ; i + + )
{
TitleApplicationMetadata * cur_app_metadata = app_metadata [ i ] ;
2023-07-21 22:56:50 +01:00
if ( ! elements [ idx ] )
2023-05-27 19:10:35 +01:00
{
2023-07-21 22:56:50 +01:00
elements [ idx ] = calloc ( 1 , sizeof ( MenuElement ) ) ;
if ( ! elements [ idx ] ) continue ;
2023-05-27 19:10:35 +01:00
}
2023-05-24 20:05:34 +01:00
2023-07-21 22:56:50 +01:00
elements [ idx ] - > str = cur_app_metadata - > lang_entry . name ;
elements [ idx ] - > child_menu = submenu ;
elements [ idx ] - > userdata = cur_app_metadata ;
2023-05-24 20:05:34 +01:00
idx + + ;
}
2023-07-21 22:56:50 +01:00
menu - > elements = elements ;
2023-05-24 20:05:34 +01:00
end :
if ( app_metadata ) free ( app_metadata ) ;
}
2023-11-03 01:22:47 +00:00
static TitleInfo * getLatestTitleInfo ( TitleInfo * title_info , u32 * out_idx , u32 * out_count )
{
2023-11-26 21:54:25 +00:00
if ( ! title_info | | ! out_idx | | ! out_count | | ( title_info - > meta_key . type ! = NcmContentMetaType_Patch & & title_info - > meta_key . type ! = NcmContentMetaType_DataPatch ) )
{
if ( out_idx ) * out_idx = 0 ;
if ( out_count ) * out_count = titleGetCountFromInfoBlock ( title_info ) ;
return title_info ;
}
2023-11-03 01:22:47 +00:00
u32 idx = 0 , count = 1 ;
TitleInfo * cur_info = title_info - > previous , * out = title_info ;
while ( cur_info )
{
count + + ;
if ( cur_info - > version . value > out - > version . value )
{
out = cur_info ;
idx = count ;
}
cur_info = cur_info - > previous ;
}
idx = ( out ! = title_info ? ( count - idx ) : ( count - 1 ) ) ;
cur_info = title_info - > next ;
while ( cur_info )
{
count + + ;
if ( cur_info - > version . value > out - > version . value )
{
out = cur_info ;
idx = ( count - 1 ) ;
}
cur_info = cur_info - > next ;
}
* out_idx = idx ;
* out_count = count ;
return out ;
}
2023-05-24 20:05:34 +01:00
void freeNcaList ( void )
{
/* Free all previously allocated data. */
if ( g_ncaMenuElements )
{
2023-05-26 16:59:03 +01:00
u32 count = 0 ;
for ( count = 0 ; g_ncaMenuElements [ count ] ; count + + ) ;
for ( u32 i = 0 ; count > 0 & & i < ( count - 1 ) ; i + + ) // Don't free output storage element
2023-05-24 20:05:34 +01:00
{
if ( g_ncaMenuElements [ i ] - > str ) free ( g_ncaMenuElements [ i ] - > str ) ;
if ( g_ncaMenuElements [ i ] - > userdata ) free ( g_ncaMenuElements [ i ] - > userdata ) ;
free ( g_ncaMenuElements [ i ] ) ;
}
free ( g_ncaMenuElements ) ;
g_ncaMenuElements = NULL ;
}
g_ncaMenu . scroll = 0 ;
g_ncaMenu . selected = 0 ;
g_ncaMenu . elements = NULL ;
}
2024-02-16 22:31:24 +00:00
void updateNcaList ( TitleInfo * title_info , u32 * element_count )
2023-05-24 20:05:34 +01:00
{
u32 content_count = title_info - > content_count , idx = 0 ;
NcmContentInfo * content_infos = title_info - > content_infos ;
char nca_id_str [ 0x21 ] = { 0 } ;
/* Free all previously allocated data. */
freeNcaList ( ) ;
2023-07-21 22:56:50 +01:00
/* Allocate buffer. */
g_ncaMenuElements = calloc ( content_count + 2 , sizeof ( MenuElement * ) ) ; // Output storage, NULL terminator
2024-02-16 22:31:24 +00:00
if ( ! g_ncaMenuElements ) return ;
2023-05-24 20:05:34 +01:00
/* Generate menu elements. */
for ( u32 i = 0 ; i < content_count ; i + + )
{
NcmContentInfo * cur_content_info = & ( content_infos [ i ] ) ;
char * nca_info_str = NULL , nca_size_str [ 16 ] = { 0 } ;
u64 nca_size = 0 ;
NcaUserData * nca_user_data = NULL ;
2023-05-27 19:10:35 +01:00
if ( ! g_ncaMenuElements [ idx ] )
{
g_ncaMenuElements [ idx ] = calloc ( 1 , sizeof ( MenuElement ) ) ;
if ( ! g_ncaMenuElements [ idx ] ) continue ;
}
2023-05-24 20:05:34 +01:00
nca_info_str = calloc ( 128 , sizeof ( char ) ) ;
nca_user_data = calloc ( 1 , sizeof ( NcaUserData ) ) ;
if ( ! nca_info_str | | ! nca_user_data )
{
if ( nca_info_str ) free ( nca_info_str ) ;
if ( nca_user_data ) free ( nca_user_data ) ;
continue ;
}
2023-10-22 23:44:40 +01:00
utilsGenerateHexString ( nca_id_str , sizeof ( nca_id_str ) , cur_content_info - > content_id . c , sizeof ( cur_content_info - > content_id . c ) , false ) ;
2023-05-24 20:05:34 +01:00
ncmContentInfoSizeToU64 ( cur_content_info , & nca_size ) ;
utilsGenerateFormattedSizeString ( ( double ) nca_size , nca_size_str , sizeof ( nca_size_str ) ) ;
sprintf ( nca_info_str , " %s #%u: %s (%s) " , titleGetNcmContentTypeName ( cur_content_info - > content_type ) , cur_content_info - > id_offset , nca_id_str , nca_size_str ) ;
nca_user_data - > title_info = title_info ;
nca_user_data - > content_idx = i ;
g_ncaMenuElements [ idx ] - > str = nca_info_str ;
2023-05-26 16:59:03 +01:00
g_ncaMenuElements [ idx ] - > child_menu = ( g_ncaMenuRawMode ? NULL : & g_ncaFsSectionsMenu ) ;
g_ncaMenuElements [ idx ] - > task_func = ( g_ncaMenuRawMode ? & saveNintendoContentArchive : NULL ) ;
2023-05-24 20:05:34 +01:00
g_ncaMenuElements [ idx ] - > userdata = nca_user_data ;
idx + + ;
}
2024-02-16 22:31:24 +00:00
if ( idx > 0 )
{
g_ncaMenuElements [ idx ] = & g_storageMenuElement ;
g_ncaMenu . elements = g_ncaMenuElements ;
2023-05-26 16:59:03 +01:00
2024-02-16 22:31:24 +00:00
if ( element_count ) * element_count = ( idx + 1 ) ;
}
2023-05-24 20:05:34 +01:00
}
2024-02-16 22:31:24 +00:00
static void switchNcaListTitle ( Menu * * cur_menu , u32 * element_count , TitleInfo * title_info )
2023-05-26 16:59:03 +01:00
{
2024-02-16 22:31:24 +00:00
if ( ! cur_menu | | ! * cur_menu | | ( * cur_menu ) - > id ! = MenuId_Nca | | ! element_count | | ! title_info ) return ;
2023-05-26 16:59:03 +01:00
2024-02-16 22:31:24 +00:00
updateNcaList ( title_info , element_count ) ;
2023-05-26 16:59:03 +01:00
if ( ! g_ncaMenuElements | | ! g_ncaMenuElements [ 0 ] )
{
freeNcaList ( ) ;
consolePrint ( " \n failed to generate nca list for newly selected title \n press any button to go back \n " ) ;
consoleRefresh ( ) ;
utilsWaitForButtonPress ( 0 ) ;
2024-02-16 22:31:24 +00:00
( * cur_menu ) - > selected = 0 ;
( * cur_menu ) - > scroll = 0 ;
2023-05-26 16:59:03 +01:00
2024-02-16 22:31:24 +00:00
* cur_menu = ( * cur_menu ) - > parent ;
* element_count = menuGetElementCount ( * cur_menu ) ;
2023-05-26 16:59:03 +01:00
}
}
void freeNcaFsSectionsList ( void )
{
/* Free all previously allocated data. */
if ( g_ncaFsSectionsMenuCtx )
{
free ( g_ncaFsSectionsMenuCtx ) ;
g_ncaFsSectionsMenuCtx = NULL ;
}
if ( g_ncaFsSectionsMenuElements )
{
for ( u32 i = 0 ; g_ncaFsSectionsMenuElements [ i ] ! = NULL ; i + + )
{
if ( g_ncaFsSectionsMenuElements [ i ] - > str ) free ( g_ncaFsSectionsMenuElements [ i ] - > str ) ;
free ( g_ncaFsSectionsMenuElements [ i ] ) ;
}
free ( g_ncaFsSectionsMenuElements ) ;
g_ncaFsSectionsMenuElements = NULL ;
}
g_ncaFsSectionsMenu . scroll = 0 ;
g_ncaFsSectionsMenu . selected = 0 ;
g_ncaFsSectionsMenu . elements = NULL ;
}
void updateNcaFsSectionsList ( NcaUserData * nca_user_data )
{
TitleInfo * title_info = nca_user_data - > title_info ;
NcmContentInfo * content_info = & ( title_info - > content_infos [ nca_user_data - > content_idx ] ) ;
u32 idx = 0 ;
/* Free all previously allocated data. */
freeNcaFsSectionsList ( ) ;
2023-07-21 22:56:50 +01:00
/* Allocate buffer. */
g_ncaFsSectionsMenuElements = calloc ( NCA_FS_HEADER_COUNT + 1 , sizeof ( MenuElement * ) ) ; // NULL terminator
2023-05-26 16:59:03 +01:00
/* Initialize NCA context. */
g_ncaFsSectionsMenuCtx = calloc ( 1 , sizeof ( NcaContext ) ) ;
if ( ! ncaInitializeContext ( g_ncaFsSectionsMenuCtx , title_info - > storage_id , ( title_info - > storage_id = = NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0 ) , \
2023-05-30 00:22:12 +01:00
& ( title_info - > meta_key ) , content_info , NULL ) ) return ;
2023-05-26 16:59:03 +01:00
/* Generate menu elements. */
for ( u32 i = 0 ; i < NCA_FS_HEADER_COUNT ; i + + )
{
NcaFsSectionContext * cur_nca_fs_ctx = & ( g_ncaFsSectionsMenuCtx - > fs_ctx [ i ] ) ;
2023-05-26 19:23:23 +01:00
char * nca_fs_info_str = NULL ;
2023-05-26 16:59:03 +01:00
2023-05-27 19:10:35 +01:00
if ( ! g_ncaFsSectionsMenuElements [ idx ] )
{
g_ncaFsSectionsMenuElements [ idx ] = calloc ( 1 , sizeof ( MenuElement ) ) ;
if ( ! g_ncaFsSectionsMenuElements [ idx ] ) continue ;
}
2023-05-26 16:59:03 +01:00
nca_fs_info_str = calloc ( 128 , sizeof ( char ) ) ;
if ( ! nca_fs_info_str ) continue ;
2023-05-26 19:23:23 +01:00
if ( cur_nca_fs_ctx - > enabled )
{
sprintf ( nca_fs_info_str , " FS section #%u: %s (%s) " , i + 1 , ncaGetFsSectionTypeName ( cur_nca_fs_ctx ) , cur_nca_fs_ctx - > section_size_str ) ;
} else {
sprintf ( nca_fs_info_str , " FS section #%u: %s " , i + 1 , ncaGetFsSectionTypeName ( cur_nca_fs_ctx ) ) ;
}
2023-05-26 16:59:03 +01:00
g_ncaFsSectionsMenuElements [ idx ] - > str = nca_fs_info_str ;
2023-05-26 19:23:23 +01:00
g_ncaFsSectionsMenuElements [ idx ] - > child_menu = & g_ncaFsSectionsSubMenu ;
2023-05-26 16:59:03 +01:00
g_ncaFsSectionsMenuElements [ idx ] - > userdata = cur_nca_fs_ctx ;
idx + + ;
}
g_ncaFsSectionsMenu . elements = g_ncaFsSectionsMenuElements ;
}
2023-05-27 19:10:35 +01:00
void freeNcaBasePatchList ( void )
{
/* Free all previously allocated data. */
if ( g_ncaBasePatchOptions )
{
/* Skip the first option. */
for ( u32 i = 1 ; g_ncaBasePatchOptions [ i ] ; i + + )
{
free ( g_ncaBasePatchOptions [ i ] ) ;
g_ncaBasePatchOptions [ i ] = NULL ;
}
free ( g_ncaBasePatchOptions ) ;
g_ncaBasePatchOptions = NULL ;
}
g_ncaFsSectionsSubMenuBasePatchElementOption . selected = 0 ;
g_ncaFsSectionsSubMenuBasePatchElementOption . options = NULL ;
2023-12-26 00:25:29 +00:00
g_ncaFsSectionsSubMenuElements [ 0 ] - > userdata = g_ncaFsSectionsSubMenuElements [ 1 ] - > userdata = NULL ;
2023-05-27 19:10:35 +01:00
if ( g_ncaBasePatchTitleInfo & & ( g_ncaBasePatchTitleInfo - > meta_key . type = = NcmContentMetaType_AddOnContent | | g_ncaBasePatchTitleInfo - > meta_key . type = = NcmContentMetaType_DataPatch ) )
{
titleFreeTitleInfo ( & g_ncaBasePatchTitleInfo ) ;
}
2023-05-30 00:22:12 +01:00
g_ncaUserTitleInfo = g_ncaBasePatchTitleInfo = g_ncaBasePatchTitleInfoBkp = NULL ;
2023-05-27 19:10:35 +01:00
}
void updateNcaBasePatchList ( TitleUserApplicationData * user_app_data , TitleInfo * title_info , NcaFsSectionContext * nca_fs_ctx )
{
u32 elem_count = 1 , idx = 1 ; // "no" option
TitleInfo * cur_title_info = NULL ;
u8 title_type = title_info - > meta_key . type ;
u8 content_type = nca_fs_ctx - > nca_ctx - > content_type ;
u8 section_type = nca_fs_ctx - > section_type ;
bool unsupported = false ;
2023-05-30 00:22:12 +01:00
u32 selected_version = 0 ;
2023-05-27 19:10:35 +01:00
/* Free all previously allocated data. */
freeNcaBasePatchList ( ) ;
/* Only enable base/patch list if we're dealing with supported content types and/or FS section types. */
2023-12-17 18:03:08 +00:00
if ( ( content_type = = NcmContentType_Program | | content_type = = NcmContentType_Data | | content_type = = NcmContentType_HtmlDocument ) & & section_type < NcaFsSectionType_Nca0RomFs )
2023-05-27 19:10:35 +01:00
{
/* Retrieve corresponding TitleInfo linked list for the current title type. */
switch ( title_type )
{
case NcmContentMetaType_Application :
g_ncaBasePatchTitleInfo = user_app_data - > patch_info ;
break ;
case NcmContentMetaType_Patch :
g_ncaBasePatchTitleInfo = user_app_data - > app_info ;
break ;
case NcmContentMetaType_AddOnContent :
case NcmContentMetaType_DataPatch :
g_ncaBasePatchTitleInfo = titleGetAddOnContentBaseOrPatchList ( title_info ) ;
break ;
default :
2023-07-21 22:56:50 +01:00
unsupported = true ;
2023-05-27 19:10:35 +01:00
break ;
}
} else {
unsupported = true ;
}
/* Calculate element count. */
elem_count + = titleGetCountFromInfoBlock ( g_ncaBasePatchTitleInfo ) ;
2023-07-21 22:56:50 +01:00
/* Allocate buffer. */
g_ncaBasePatchOptions = calloc ( elem_count + 1 , sizeof ( char * ) ) ; // NULL terminator
2023-05-27 19:10:35 +01:00
/* Set first option. */
2023-05-30 00:22:12 +01:00
g_ncaBasePatchOptions [ 0 ] = ( unsupported ? " unsupported by this content/section type combo " : ( elem_count < 2 ? " none available " : " no " ) ) ;
2023-05-27 19:10:35 +01:00
/* Generate base/patch strings. */
cur_title_info = g_ncaBasePatchTitleInfo ;
while ( cur_title_info )
{
if ( ! g_ncaBasePatchOptions [ idx ] )
{
g_ncaBasePatchOptions [ idx ] = calloc ( sizeof ( char ) , 0x40 ) ;
if ( ! g_ncaBasePatchOptions [ idx ] )
{
cur_title_info = cur_title_info - > next ;
continue ;
}
}
snprintf ( g_ncaBasePatchOptions [ idx ] , 0x40 , " %s v%u (v%u.%u) (%s) " , titleGetNcmContentMetaTypeName ( cur_title_info - > meta_key . type ) , \
cur_title_info - > version . value , cur_title_info - > version . application_version . release_ver , cur_title_info - > version . application_version . private_ver , \
titleGetNcmStorageIdName ( cur_title_info - > storage_id ) ) ;
2023-05-30 00:22:12 +01:00
/* Make sure the highest available base/patch title is automatically selected. */
if ( cur_title_info - > version . value > = selected_version & & \
( ( ( title_type = = NcmContentMetaType_Application | | title_type = = NcmContentMetaType_AddOnContent ) & & ( ! nca_fs_ctx - > has_sparse_layer | | cur_title_info - > version . value > = title_info - > version . value ) ) | | \
( ( title_type = = NcmContentMetaType_Patch | | title_type = = NcmContentMetaType_DataPatch ) & & cur_title_info - > version . value < = title_info - > version . value ) ) )
{
g_ncaFsSectionsSubMenuBasePatchElementOption . selected = idx ;
selected_version = cur_title_info - > version . value ;
g_ncaBasePatchTitleInfo = cur_title_info ;
}
2023-05-27 19:10:35 +01:00
cur_title_info = cur_title_info - > next ;
idx + + ;
}
g_ncaFsSectionsSubMenuBasePatchElementOption . options = g_ncaBasePatchOptions ;
2023-12-26 00:25:29 +00:00
g_ncaFsSectionsSubMenuElements [ 0 ] - > userdata = g_ncaFsSectionsSubMenuElements [ 1 ] - > userdata = nca_fs_ctx ;
2023-05-30 00:22:12 +01:00
g_ncaUserTitleInfo = title_info ;
g_ncaBasePatchTitleInfoBkp = ( g_ncaFsSectionsSubMenuBasePatchElementOption . selected > 0 ? g_ncaBasePatchTitleInfo : NULL ) ;
2023-05-27 19:10:35 +01:00
}
2023-05-24 20:05:34 +01:00
NX_INLINE bool useUsbHost ( void )
{
return ( g_storageMenuElementOption . selected = = 1 ) ;
}
static bool waitForGameCard ( void )
{
consolePrint ( " waiting for gamecard... " ) ;
consoleRefresh ( ) ;
time_t start = time ( NULL ) ;
u8 status = GameCardStatus_NotInserted ;
while ( ( g_appletStatus = appletMainLoop ( ) ) )
{
if ( ( status = gamecardGetStatus ( ) ) > GameCardStatus_Processing ) break ;
time_t now = time ( NULL ) ;
time_t diff = ( now - start ) ;
if ( diff > = WAIT_TIME_LIMIT ) break ;
consolePrint ( " %lu " , diff ) ;
consoleRefresh ( ) ;
utilsSleep ( 1 ) ;
}
consolePrint ( " \n " ) ;
consoleRefresh ( ) ;
if ( ! g_appletStatus | | status = = GameCardStatus_NotInserted ) return false ;
switch ( status )
{
case GameCardStatus_NoGameCardPatchEnabled :
consolePrint ( " \" nogc \" patch enabled, please disable it and reboot your console \n " ) ;
break ;
case GameCardStatus_LotusAsicFirmwareUpdateRequired :
consolePrint ( " gamecard controller firmware update required, please update your console \n " ) ;
break ;
case GameCardStatus_InsertedAndInfoNotLoaded :
consolePrint ( " unexpected I/O error occurred, please check the logfile \n " ) ;
break ;
default :
break ;
}
if ( status ! = GameCardStatus_InsertedAndInfoLoaded )
{
2023-11-03 01:22:47 +00:00
consolePrint ( " press any button to go back \n " ) ;
2023-05-24 20:05:34 +01:00
utilsWaitForButtonPress ( 0 ) ;
return false ;
}
return true ;
}
static bool waitForUsb ( void )
{
consolePrint ( " waiting for usb session... " ) ;
consoleRefresh ( ) ;
time_t start = time ( NULL ) ;
u8 usb_host_speed = UsbHostSpeed_None ;
while ( ( g_appletStatus = appletMainLoop ( ) ) )
{
if ( ( usb_host_speed = usbIsReady ( ) ) ! = UsbHostSpeed_None ) break ;
time_t now = time ( NULL ) ;
time_t diff = ( now - start ) ;
if ( diff > = WAIT_TIME_LIMIT ) break ;
consolePrint ( " %lu " , diff ) ;
consoleRefresh ( ) ;
utilsSleep ( 1 ) ;
}
consolePrint ( " \n " ) ;
if ( usb_host_speed ! = UsbHostSpeed_None ) consolePrint ( " usb speed: %u.0 \n " , usb_host_speed ) ;
consoleRefresh ( ) ;
return ( g_appletStatus & & usb_host_speed ! = UsbHostSpeed_None ) ;
}
static bool saveFileData ( const char * filepath , void * data , size_t data_size )
{
if ( ! filepath | | ! * filepath | | ! data | | ! data_size )
{
consolePrint ( " invalid parameters to save file data! \n " ) ;
return false ;
}
if ( useUsbHost ( ) )
{
if ( ! usbSendFileProperties ( data_size , filepath ) )
{
consolePrint ( " failed to send file properties for \" %s \" ! \n " , filepath ) ;
return false ;
}
if ( ! usbSendFileData ( data , data_size ) )
{
consolePrint ( " failed to send file data for \" %s \" ! \n " , filepath ) ;
return false ;
}
} else {
utilsCreateDirectoryTree ( filepath , false ) ;
FILE * fp = fopen ( filepath , " wb " ) ;
if ( ! fp )
{
consolePrint ( " failed to open \" %s \" for writing! \n " , filepath ) ;
return false ;
}
ftruncate ( fileno ( fp ) , ( off_t ) data_size ) ;
size_t ret = fwrite ( data , 1 , data_size , fp ) ;
fclose ( fp ) ;
if ( g_storageMenuElementOption . selected = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
if ( ret ! = data_size )
{
consolePrint ( " failed to write 0x%lX byte(s) to \" %s \" ! (%d) \n " , data_size , filepath , errno ) ;
remove ( filepath ) ;
}
}
return true ;
}
static char * generateOutputGameCardFileName ( const char * subdir , const char * extension , bool use_nacp_name )
{
char * filename = NULL , * prefix = NULL , * output = NULL ;
u32 dev_idx = g_storageMenuElementOption . selected ;
if ( ( subdir & & ! * subdir ) | | ! extension | | ! * extension | | ( use_nacp_name & & ! ( filename = titleGenerateGameCardFileName ( TitleNamingConvention_Full , dev_idx > 0 ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly ) ) ) )
{
consolePrint ( " failed to generate gamecard filename! \n " ) ;
goto end ;
}
prefix = calloc ( sizeof ( char ) , FS_MAX_PATH ) ;
if ( ! prefix )
{
consolePrint ( " failed to generate prefix! \n " ) ;
goto end ;
}
2023-05-30 00:22:12 +01:00
if ( dev_idx ! = 1 ) sprintf ( prefix , " %s/ " OUTDIR , dev_idx = = 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices [ dev_idx - 2 ] . name ) ;
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
if ( subdir )
{
if ( subdir [ 0 ] ! = ' / ' ) strcat ( prefix , " / " ) ;
strcat ( prefix , subdir ) ;
2023-05-24 20:05:34 +01:00
}
output = ( use_nacp_name ? utilsGeneratePath ( prefix , filename , extension ) : utilsGeneratePath ( prefix , extension , NULL ) ) ;
if ( ! output ) consolePrint ( " failed to generate output filename! \n " ) ;
end :
if ( prefix ) free ( prefix ) ;
if ( filename ) free ( filename ) ;
return output ;
}
static char * generateOutputTitleFileName ( TitleInfo * title_info , const char * subdir , const char * extension )
{
char * filename = NULL , * prefix = NULL , * output = NULL ;
u32 dev_idx = g_storageMenuElementOption . selected ;
if ( ! title_info | | ( subdir & & ! * subdir ) | | ! extension | | ! * extension | | ! ( filename = titleGenerateFileName ( title_info , TitleNamingConvention_Full , dev_idx > 0 ? TitleFileNameIllegalCharReplaceType_IllegalFsChars : TitleFileNameIllegalCharReplaceType_KeepAsciiCharsOnly ) ) )
{
consolePrint ( " failed to generate title filename! \n " ) ;
goto end ;
}
prefix = calloc ( sizeof ( char ) , FS_MAX_PATH ) ;
if ( ! prefix )
{
consolePrint ( " failed to generate prefix! \n " ) ;
goto end ;
}
2023-05-30 00:22:12 +01:00
if ( dev_idx ! = 1 ) sprintf ( prefix , " %s/ " OUTDIR , dev_idx = = 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices [ dev_idx - 2 ] . name ) ;
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
if ( subdir )
{
if ( subdir [ 0 ] ! = ' / ' ) strcat ( prefix , " / " ) ;
strcat ( prefix , subdir ) ;
2023-05-24 20:05:34 +01:00
}
output = utilsGeneratePath ( prefix , filename , extension ) ;
if ( ! output ) consolePrint ( " failed to generate output filename! \n " ) ;
end :
if ( prefix ) free ( prefix ) ;
if ( filename ) free ( filename ) ;
return output ;
}
2023-05-30 00:22:12 +01:00
static char * generateOutputLayeredFsFileName ( u64 title_id , const char * subdir , const char * extension )
{
char * prefix = NULL , * output = NULL ;
u32 dev_idx = g_storageMenuElementOption . selected ;
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
if ( ( subdir & & ! * subdir ) | | ! extension | | ! * extension )
{
consolePrint ( " failed to generate title filename! \n " ) ;
goto end ;
}
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
prefix = calloc ( sizeof ( char ) , FS_MAX_PATH ) ;
if ( ! prefix )
{
consolePrint ( " failed to generate prefix! \n " ) ;
goto end ;
}
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
if ( dev_idx ! = 1 ) sprintf ( prefix , " %s " , dev_idx = = 0 ? DEVOPTAB_SDMC_DEVICE : g_umsDevices [ dev_idx - 2 ] . name ) ;
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
sprintf ( prefix + strlen ( prefix ) , " /atmosphere/contents/%016lX " , title_id ) ;
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
if ( subdir )
{
if ( subdir [ 0 ] ! = ' / ' ) strcat ( prefix , " / " ) ;
strcat ( prefix , subdir ) ;
}
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
output = utilsGeneratePath ( prefix , extension , NULL ) ;
if ( ! output ) consolePrint ( " failed to generate output filename! \n " ) ;
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
end :
if ( prefix ) free ( prefix ) ;
2023-05-24 20:05:34 +01:00
2023-05-30 00:22:12 +01:00
return output ;
}
2023-05-24 20:05:34 +01:00
static bool dumpGameCardSecurityInformation ( GameCardSecurityInformation * out )
{
if ( ! out )
{
consolePrint ( " invalid parameters to dump gamecard security information! \n " ) ;
return false ;
}
if ( ! gamecardGetSecurityInformation ( out ) )
{
consolePrint ( " failed to get gamecard security information \n " ) ;
return false ;
}
consolePrint ( " get gamecard security information ok \n " ) ;
return true ;
}
static bool saveGameCardImage ( void * userdata )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( userdata ) ;
2023-05-24 20:05:34 +01:00
u64 gc_size = 0 , free_space = 0 ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-05-24 20:05:34 +01:00
u32 key_area_crc = 0 ;
GameCardKeyArea gc_key_area = { 0 } ;
GameCardSecurityInformation gc_security_information = { 0 } ;
XciThreadData xci_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( xci_thread_data . shared_thread_data ) ;
char * filename = NULL ;
u32 dev_idx = g_storageMenuElementOption . selected ;
bool prepend_key_area = ( bool ) getGameCardPrependKeyAreaOption ( ) ;
bool keep_certificate = ( bool ) getGameCardKeepCertificateOption ( ) ;
bool trim_dump = ( bool ) getGameCardTrimDumpOption ( ) ;
bool calculate_checksum = ( bool ) getGameCardCalculateChecksumOption ( ) ;
bool success = false ;
consolePrint ( " gamecard image dump \n prepend key area: %s | keep certificate: %s | trim dump: %s | calculate checksum: %s \n \n " , prepend_key_area ? " yes " : " no " , keep_certificate ? " yes " : " no " , trim_dump ? " yes " : " no " , calculate_checksum ? " yes " : " no " ) ;
if ( ( ! trim_dump & & ! gamecardGetTotalSize ( & gc_size ) ) | | ( trim_dump & & ! gamecardGetTrimmedSize ( & gc_size ) ) | | ! gc_size )
{
consolePrint ( " failed to get gamecard size! \n " ) ;
goto end ;
}
shared_thread_data - > total_size = gc_size ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) gc_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " gamecard size: 0x%lX (%s) \n " , gc_size , size_str ) ;
2023-05-24 20:05:34 +01:00
if ( prepend_key_area )
{
gc_size + = sizeof ( GameCardKeyArea ) ;
if ( ! dumpGameCardSecurityInformation ( & gc_security_information ) ) goto end ;
memcpy ( & ( gc_key_area . initial_data ) , & ( gc_security_information . initial_data ) , sizeof ( GameCardInitialData ) ) ;
if ( calculate_checksum )
{
key_area_crc = crc32Calculate ( & gc_key_area , sizeof ( GameCardKeyArea ) ) ;
xci_thread_data . full_xci_crc = key_area_crc ;
}
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) gc_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " gamecard size (with key area): 0x%lX (%s) \n " , gc_size , size_str ) ;
2023-05-24 20:05:34 +01:00
}
2023-06-28 23:12:51 +01:00
snprintf ( path , MAX_ELEMENTS ( path ) , " [%s][%s][%s].xci " , prepend_key_area ? " KA " : " NKA " , keep_certificate ? " C " : " NC " , trim_dump ? " T " : " NT " ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( GAMECARD_SUBDIR , path , true ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( dev_idx = = 1 )
{
if ( ! usbSendFileProperties ( gc_size , filename ) )
{
consolePrint ( " failed to send file properties for \" %s \" ! \n " , filename ) ;
goto end ;
}
if ( prepend_key_area & & ! usbSendFileData ( & gc_key_area , sizeof ( GameCardKeyArea ) ) )
{
consolePrint ( " failed to send gamecard key area data! \n " ) ;
goto end ;
}
} else {
if ( ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
goto end ;
}
if ( gc_size > = free_space )
{
consolePrint ( " dump size exceeds free space \n " ) ;
goto end ;
}
utilsCreateDirectoryTree ( filename , false ) ;
if ( dev_idx = = 0 )
{
if ( gc_size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( filename ) )
{
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & gc_size > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
goto end ;
}
}
shared_thread_data - > fp = fopen ( filename , " wb " ) ;
if ( ! shared_thread_data - > fp )
{
consolePrint ( " failed to open \" %s \" for writing! \n " , filename ) ;
goto end ;
}
2023-12-20 23:39:56 +00:00
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
2023-05-24 20:05:34 +01:00
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) shared_thread_data - > total_size ) ;
if ( prepend_key_area & & fwrite ( & gc_key_area , 1 , sizeof ( GameCardKeyArea ) , shared_thread_data - > fp ) ! = sizeof ( GameCardKeyArea ) )
{
consolePrint ( " failed to write gamecard key area data! \n " ) ;
goto end ;
}
}
consoleRefresh ( ) ;
success = spanDumpThreads ( xciReadThreadFunc , genericWriteThreadFunc , & xci_thread_data ) ;
if ( success )
{
consolePrint ( " successfully saved xci as \" %s \" \n " , filename ) ;
if ( calculate_checksum )
{
if ( prepend_key_area ) consolePrint ( " key area crc: %08X | " , key_area_crc ) ;
consolePrint ( " xci crc: %08X " , xci_thread_data . xci_crc ) ;
if ( prepend_key_area ) consolePrint ( " | xci crc (with key area): %08X " , xci_thread_data . full_xci_crc ) ;
consolePrint ( " \n " ) ;
}
consoleRefresh ( ) ;
}
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( ! success & & dev_idx ! = 1 )
{
if ( dev_idx = = 0 )
{
utilsRemoveConcatenationFile ( filename ) ;
utilsCommitSdCardFileSystemChanges ( ) ;
} else {
remove ( filename ) ;
}
}
}
if ( filename ) free ( filename ) ;
return success ;
}
static bool saveGameCardHeader ( void * userdata )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( userdata ) ;
2023-05-24 20:05:34 +01:00
GameCardHeader gc_header = { 0 } ;
bool success = false ;
u32 crc = 0 ;
char * filename = NULL ;
if ( ! gamecardGetHeader ( & gc_header ) )
{
consolePrint ( " failed to get gamecard header \n " ) ;
goto end ;
}
consolePrint ( " get gamecard header ok \n " ) ;
crc = crc32Calculate ( & gc_header , sizeof ( GameCardHeader ) ) ;
snprintf ( path , MAX_ELEMENTS ( path ) , " (Header) (%08X).bin " , crc ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( GAMECARD_SUBDIR , path , true ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( ! saveFileData ( filename , & gc_header , sizeof ( GameCardHeader ) ) ) goto end ;
consolePrint ( " successfully saved header as \" %s \" \n " , filename ) ;
success = true ;
end :
if ( filename ) free ( filename ) ;
return success ;
}
static bool saveGameCardCardInfo ( void * userdata )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( userdata ) ;
2023-05-24 20:05:34 +01:00
GameCardInfo gc_cardinfo = { 0 } ;
bool success = false ;
u32 crc = 0 ;
char * filename = NULL ;
2024-04-12 12:40:50 +01:00
if ( ! gamecardGetPlaintextCardInfoArea ( & gc_cardinfo ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " failed to get gamecard cardinfo \n " ) ;
goto end ;
}
consolePrint ( " get gamecard cardinfo ok \n " ) ;
crc = crc32Calculate ( & gc_cardinfo , sizeof ( GameCardInfo ) ) ;
snprintf ( path , MAX_ELEMENTS ( path ) , " (CardInfo) (%08X).bin " , crc ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( GAMECARD_SUBDIR , path , true ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( ! saveFileData ( filename , & gc_cardinfo , sizeof ( GameCardInfo ) ) ) goto end ;
consolePrint ( " successfully saved cardinfo dump as \" %s \" \n " , filename ) ;
success = true ;
end :
if ( filename ) free ( filename ) ;
return success ;
}
static bool saveGameCardCertificate ( void * userdata )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( userdata ) ;
2023-05-24 20:05:34 +01:00
FsGameCardCertificate gc_cert = { 0 } ;
bool success = false ;
u32 crc = 0 ;
char * filename = NULL ;
if ( ! gamecardGetCertificate ( & gc_cert ) )
{
consolePrint ( " failed to get gamecard certificate \n " ) ;
goto end ;
}
consolePrint ( " get gamecard certificate ok \n " ) ;
crc = crc32Calculate ( & gc_cert , sizeof ( FsGameCardCertificate ) ) ;
snprintf ( path , MAX_ELEMENTS ( path ) , " (Certificate) (%08X).bin " , crc ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( GAMECARD_SUBDIR , path , true ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( ! saveFileData ( filename , & gc_cert , sizeof ( FsGameCardCertificate ) ) ) goto end ;
consolePrint ( " successfully saved certificate as \" %s \" \n " , filename ) ;
success = true ;
end :
if ( filename ) free ( filename ) ;
return success ;
}
static bool saveGameCardInitialData ( void * userdata )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( userdata ) ;
2023-05-24 20:05:34 +01:00
GameCardSecurityInformation gc_security_information = { 0 } ;
bool success = false ;
u32 crc = 0 ;
char * filename = NULL ;
if ( ! dumpGameCardSecurityInformation ( & gc_security_information ) ) goto end ;
crc = crc32Calculate ( & ( gc_security_information . initial_data ) , sizeof ( GameCardInitialData ) ) ;
snprintf ( path , MAX_ELEMENTS ( path ) , " (Initial Data) (%08X).bin " , crc ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( GAMECARD_SUBDIR , path , true ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( ! saveFileData ( filename , & ( gc_security_information . initial_data ) , sizeof ( GameCardInitialData ) ) ) goto end ;
consolePrint ( " successfully saved initial data as \" %s \" \n " , filename ) ;
success = true ;
end :
if ( filename ) free ( filename ) ;
return success ;
}
2023-12-05 18:21:45 +00:00
/* This will save the Gamecard Specific Data. Its format is specific and internal to the current LAFW firmware version and session of the GCBRG ASIC. */
/* Depending on which Switch system version the gamecard was dumped from, this data can change. */
/* Even re-inserting the gamecard will change parts of this data. */
/* For this reason the gamecard specific data is mostly uninteresting for gamecard preservation. */
/* Instead, take a look at saveGameCardIdSet and saveGameCardUid which is a more standardised format of the Gamecard ID data. */
2023-05-24 20:05:34 +01:00
static bool saveGameCardSpecificData ( void * userdata )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( userdata ) ;
2023-05-24 20:05:34 +01:00
GameCardSecurityInformation gc_security_information = { 0 } ;
bool success = false ;
u32 crc = 0 ;
char * filename = NULL ;
if ( ! dumpGameCardSecurityInformation ( & gc_security_information ) ) goto end ;
crc = crc32Calculate ( & ( gc_security_information . specific_data ) , sizeof ( GameCardSpecificData ) ) ;
snprintf ( path , MAX_ELEMENTS ( path ) , " (Specific Data) (%08X).bin " , crc ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( GAMECARD_SUBDIR , path , true ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( ! saveFileData ( filename , & ( gc_security_information . specific_data ) , sizeof ( GameCardSpecificData ) ) ) goto end ;
consolePrint ( " successfully saved specific data as \" %s \" \n " , filename ) ;
success = true ;
end :
if ( filename ) free ( filename ) ;
return success ;
}
static bool saveGameCardIdSet ( void * userdata )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( userdata ) ;
2023-05-24 20:05:34 +01:00
FsGameCardIdSet id_set = { 0 } ;
bool success = false ;
u32 crc = 0 ;
char * filename = NULL ;
2023-11-03 01:22:47 +00:00
if ( ! gamecardGetCardIdSet ( & id_set ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " failed to get gamecard id set \n " ) ;
goto end ;
}
crc = crc32Calculate ( & id_set , sizeof ( FsGameCardIdSet ) ) ;
snprintf ( path , MAX_ELEMENTS ( path ) , " (Card ID Set) (%08X).bin " , crc ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( GAMECARD_SUBDIR , path , true ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( ! saveFileData ( filename , & id_set , sizeof ( FsGameCardIdSet ) ) ) goto end ;
consolePrint ( " successfully saved gamecard id set as \" %s \" \n " , filename ) ;
success = true ;
end :
if ( filename ) free ( filename ) ;
return success ;
}
2023-12-05 18:21:45 +00:00
static bool saveGameCardUid ( void * userdata )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( userdata ) ;
2023-12-05 18:21:45 +00:00
GameCardSecurityInformation gc_security_information = { 0 } ;
bool success = false ;
u32 crc = 0 ;
char * filename = NULL ;
2023-12-17 18:03:08 +00:00
if ( ! gamecardGetSecurityInformation ( & gc_security_information ) )
2023-12-05 18:21:45 +00:00
{
consolePrint ( " failed to get gamecard security information \n " ) ;
goto end ;
}
2024-08-06 21:57:54 +01:00
crc = crc32Calculate ( & ( gc_security_information . specific_data . card_uid ) , sizeof ( gc_security_information . specific_data . card_uid ) ) ;
2023-12-05 18:21:45 +00:00
snprintf ( path , MAX_ELEMENTS ( path ) , " (Card UID) (%08X).bin " , crc ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( GAMECARD_SUBDIR , path , true ) ;
2023-12-05 18:21:45 +00:00
if ( ! filename ) goto end ;
2024-08-06 21:57:54 +01:00
if ( ! saveFileData ( filename , & ( gc_security_information . specific_data . card_uid ) , sizeof ( gc_security_information . specific_data . card_uid ) ) ) goto end ;
2023-12-05 18:21:45 +00:00
consolePrint ( " successfully saved gamecard uid as \" %s \" \n " , filename ) ;
success = true ;
end :
if ( filename ) free ( filename ) ;
return success ;
}
2023-05-24 20:05:34 +01:00
static bool saveGameCardHfsPartition ( void * userdata )
{
u32 hfs_partition_type = ( userdata ? * ( ( u32 * ) userdata ) : HashFileSystemPartitionType_None ) ;
bool write_raw_hfs_partition = ( bool ) getGameCardWriteRawHfsPartitionOption ( ) ;
HashFileSystemContext hfs_ctx = { 0 } ;
bool success = false ;
if ( hfs_partition_type < HashFileSystemPartitionType_Root | | hfs_partition_type > HashFileSystemPartitionType_Secure )
{
consolePrint ( " invalid hfs partition type! (%u) \n " , hfs_partition_type ) ;
goto end ;
}
if ( ! gamecardGetHashFileSystemContext ( hfs_partition_type , & hfs_ctx ) )
{
consolePrint ( " get hfs ctx failed! this partition type may not exist within the inserted gamecard \n " ) ;
goto end ;
}
success = ( write_raw_hfs_partition ? saveGameCardRawHfsPartition ( & hfs_ctx ) : saveGameCardExtractedHfsPartition ( & hfs_ctx ) ) ;
end :
hfsFreeContext ( & hfs_ctx ) ;
return success ;
}
static bool saveGameCardRawHfsPartition ( HashFileSystemContext * hfs_ctx )
{
u64 free_space = 0 ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-05-24 20:05:34 +01:00
HfsThreadData hfs_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( hfs_thread_data . shared_thread_data ) ;
char * filename = NULL ;
u32 dev_idx = g_storageMenuElementOption . selected ;
bool success = false ;
hfs_thread_data . hfs_ctx = hfs_ctx ;
shared_thread_data - > total_size = hfs_ctx - > size ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) hfs_ctx - > size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " raw %s hfs partition size: 0x%lX (%s) \n " , hfs_ctx - > name , hfs_ctx - > size , size_str ) ;
2023-05-24 20:05:34 +01:00
snprintf ( path , MAX_ELEMENTS ( path ) , " /%s.hfs0 " , hfs_ctx - > name ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( HFS_SUBDIR " /Raw " , path , true ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( dev_idx = = 1 )
{
if ( ! usbSendFileProperties ( shared_thread_data - > total_size , filename ) )
{
consolePrint ( " failed to send file properties for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
goto end ;
}
if ( shared_thread_data - > total_size > = free_space )
{
consolePrint ( " dump size exceeds free space \n " ) ;
goto end ;
}
utilsCreateDirectoryTree ( filename , false ) ;
if ( dev_idx = = 0 )
{
if ( shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( filename ) )
{
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
goto end ;
}
}
shared_thread_data - > fp = fopen ( filename , " wb " ) ;
if ( ! shared_thread_data - > fp )
{
consolePrint ( " failed to open \" %s \" for writing! \n " , filename ) ;
goto end ;
}
2023-12-20 23:39:56 +00:00
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
2023-05-24 20:05:34 +01:00
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) shared_thread_data - > total_size ) ;
}
consoleRefresh ( ) ;
success = spanDumpThreads ( rawHfsReadThreadFunc , genericWriteThreadFunc , & hfs_thread_data ) ;
if ( success )
{
consolePrint ( " successfully saved raw hfs partition as \" %s \" \n " , filename ) ;
consoleRefresh ( ) ;
}
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( ! success & & dev_idx ! = 1 )
{
if ( dev_idx = = 0 )
{
utilsRemoveConcatenationFile ( filename ) ;
utilsCommitSdCardFileSystemChanges ( ) ;
} else {
remove ( filename ) ;
}
}
}
if ( filename ) free ( filename ) ;
return success ;
}
static bool saveGameCardExtractedHfsPartition ( HashFileSystemContext * hfs_ctx )
{
u64 data_size = 0 ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-05-24 20:05:34 +01:00
HfsThreadData hfs_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( hfs_thread_data . shared_thread_data ) ;
bool success = false ;
if ( ! hfsGetTotalDataSize ( hfs_ctx , & data_size ) )
{
consolePrint ( " failed to calculate extracted %s hfs partition size! \n " , hfs_ctx - > name ) ;
goto end ;
}
if ( ! data_size )
{
consolePrint ( " %s hfs partition is empty! \n " , hfs_ctx - > name ) ;
goto end ;
}
hfs_thread_data . hfs_ctx = hfs_ctx ;
shared_thread_data - > total_size = data_size ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) data_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " extracted %s hfs partition size: 0x%lX (%s) \n " , hfs_ctx - > name , data_size , size_str ) ;
2023-05-24 20:05:34 +01:00
consoleRefresh ( ) ;
success = spanDumpThreads ( extractedHfsReadThreadFunc , genericWriteThreadFunc , & hfs_thread_data ) ;
end :
return success ;
}
2023-12-31 17:16:49 +00:00
static bool browseGameCardHfsPartition ( void * userdata )
{
u32 hfs_partition_type = ( userdata ? * ( ( u32 * ) userdata ) : HashFileSystemPartitionType_None ) ;
HashFileSystemContext hfs_ctx = { 0 } ;
char mount_name [ DEVOPTAB_MOUNT_NAME_LENGTH ] = { 0 } , subdir [ 0x20 ] = { 0 } , * base_out_path = NULL ;
bool success = false ;
if ( hfs_partition_type < HashFileSystemPartitionType_Root | | hfs_partition_type > HashFileSystemPartitionType_Secure )
{
consolePrint ( " invalid hfs partition type! (%u) \n " , hfs_partition_type ) ;
goto end ;
}
if ( ! gamecardGetHashFileSystemContext ( hfs_partition_type , & hfs_ctx ) )
{
consolePrint ( " get hfs ctx failed! this partition type may not exist within the inserted gamecard \n " ) ;
goto end ;
}
/* Mount devoptab device. */
snprintf ( mount_name , MAX_ELEMENTS ( mount_name ) , " hfs%s " , hfs_ctx . name ) ;
if ( ! devoptabMountHashFileSystemDevice ( & hfs_ctx , mount_name ) )
{
consolePrint ( " hfs ctx devoptab mount failed! \n " ) ;
goto end ;
}
/* Generate output base path. */
snprintf ( subdir , MAX_ELEMENTS ( subdir ) , " /%s " , hfs_ctx . name ) ;
2024-11-02 23:49:07 +00:00
base_out_path = generateOutputGameCardFileName ( HFS_SUBDIR " /Extracted " , subdir , true ) ;
2023-12-31 17:16:49 +00:00
if ( ! base_out_path ) goto end ;
/* Display file browser. */
success = fsBrowser ( mount_name , base_out_path ) ;
/* Unmount devoptab device. */
devoptabUnmountDevice ( mount_name ) ;
end :
/* Free data. */
if ( base_out_path ) free ( base_out_path ) ;
hfsFreeContext ( & hfs_ctx ) ;
if ( ! success & & g_appletStatus )
{
consolePrint ( " press any button to continue \n " ) ;
utilsWaitForButtonPress ( 0 ) ;
}
return success ;
}
2023-05-24 20:05:34 +01:00
static bool saveConsoleLafwBlob ( void * userdata )
{
2023-12-20 19:32:48 +00:00
NX_IGNORE_ARG ( userdata ) ;
2023-05-24 20:05:34 +01:00
u64 lafw_version = 0 ;
LotusAsicFirmwareBlob lafw_blob = { 0 } ;
bool success = false ;
u32 crc = 0 ;
char * filename = NULL ;
const char * fw_type_str = NULL , * dev_type_str = NULL ;
if ( ! gamecardGetLotusAsicFirmwareBlob ( & lafw_blob , & lafw_version ) )
{
consolePrint ( " failed to get console lafw blob \n " ) ;
goto end ;
}
fw_type_str = gamecardGetLafwTypeString ( lafw_blob . fw_type ) ;
if ( ! fw_type_str ) fw_type_str = " Unknown " ;
dev_type_str = gamecardGetLafwDeviceTypeString ( lafw_blob . device_type ) ;
if ( ! dev_type_str ) dev_type_str = " Unknown " ;
consolePrint ( " get console lafw blob ok \n " ) ;
crc = crc32Calculate ( & lafw_blob , sizeof ( LotusAsicFirmwareBlob ) ) ;
snprintf ( path , MAX_ELEMENTS ( path ) , " LAFW (%s) (%s) (v%lu) (%08X).bin " , fw_type_str , dev_type_str , lafw_version , crc ) ;
filename = generateOutputGameCardFileName ( NULL , path , false ) ;
if ( ! filename ) goto end ;
if ( ! saveFileData ( filename , & lafw_blob , sizeof ( LotusAsicFirmwareBlob ) ) ) goto end ;
consolePrint ( " successfully saved lafw blob as \" %s \" \n " , filename ) ;
success = true ;
end :
if ( filename ) free ( filename ) ;
return success ;
}
static bool saveNintendoSubmissionPackage ( void * userdata )
{
if ( ! userdata ) return false ;
TitleInfo * title_info = ( TitleInfo * ) userdata ;
TitleApplicationMetadata * app_metadata = title_info - > app_metadata ;
NspThreadData nsp_thread_data = { 0 } ;
Thread dump_thread = { 0 } ;
time_t start = 0 , btn_cancel_start_tmr = 0 , btn_cancel_end_tmr = 0 ;
bool btn_cancel_cur_state = false , btn_cancel_prev_state = false , success = false ;
u64 prev_size = 0 ;
u8 prev_time = 0 , percent = 0 ;
consolePrint ( " %s info: \n \n " , title_info - > meta_key . type = = NcmContentMetaType_Application ? " base application " : \
( title_info - > meta_key . type = = NcmContentMetaType_Patch ? " update " : \
( title_info - > meta_key . type = = NcmContentMetaType_AddOnContent ? " dlc " : " dlc update " ) ) ) ;
if ( app_metadata )
{
consolePrint ( " name: %s \n " , app_metadata - > lang_entry . name ) ;
consolePrint ( " publisher: %s \n " , app_metadata - > lang_entry . author ) ;
}
consolePrint ( " source storage: %s \n " , titleGetNcmStorageIdName ( title_info - > storage_id ) ) ;
consolePrint ( " title id: %016lX \n " , title_info - > meta_key . id ) ;
consolePrint ( " version: %u (%u.%u.%u-%u.%u) \n " , title_info - > version . value , title_info - > version . system_version . major , title_info - > version . system_version . minor , \
title_info - > version . system_version . micro , title_info - > version . system_version . major_relstep , \
title_info - > version . system_version . minor_relstep ) ;
consolePrint ( " content count: %u \n " , title_info - > content_count ) ;
consolePrint ( " size: %s \n " , title_info - > size_str ) ;
consolePrint ( " ______________________________ \n \n " ) ;
consoleRefresh ( ) ;
/* Create dump thread. */
nsp_thread_data . data = title_info ;
utilsCreateThread ( & dump_thread , nspThreadFunc , & nsp_thread_data , 2 ) ;
/* Wait until the background thread calculates the NSP size. */
2023-10-22 23:44:40 +01:00
while ( ! nsp_thread_data . total_size & & ! nsp_thread_data . error ) utilsAppletLoopDelay ( ) ;
2023-05-24 20:05:34 +01:00
if ( nsp_thread_data . error )
{
utilsJoinThread ( & dump_thread ) ;
return false ;
}
/* Start dump. */
start = time ( NULL ) ;
while ( nsp_thread_data . data_written < nsp_thread_data . total_size )
{
g_appletStatus = appletMainLoop ( ) ;
if ( ! g_appletStatus )
{
mutexLock ( & g_fileMutex ) ;
nsp_thread_data . transfer_cancelled = true ;
mutexUnlock ( & g_fileMutex ) ;
}
if ( nsp_thread_data . error | | nsp_thread_data . transfer_cancelled ) break ;
struct tm ts = { 0 } ;
time_t now = time ( NULL ) ;
localtime_r ( & now , & ts ) ;
size_t size = nsp_thread_data . data_written ;
utilsScanPads ( ) ;
btn_cancel_cur_state = ( utilsGetButtonsHeld ( ) & HidNpadButton_B ) ;
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 ) ;
nsp_thread_data . transfer_cancelled = true ;
mutexUnlock ( & g_fileMutex ) ;
break ;
}
} else {
btn_cancel_start_tmr = btn_cancel_end_tmr = 0 ;
}
btn_cancel_prev_state = btn_cancel_cur_state ;
if ( prev_time = = ts . tm_sec | | prev_size = = size ) continue ;
percent = ( u8 ) ( ( size * 100 ) / nsp_thread_data . total_size ) ;
prev_time = ts . tm_sec ;
prev_size = size ;
consolePrint ( " %lu / %lu (%u%%) | Time elapsed: %lu \n " , size , nsp_thread_data . total_size , percent , ( now - start ) ) ;
consoleRefresh ( ) ;
2023-05-26 13:41:49 +01:00
2023-10-22 23:44:40 +01:00
utilsAppletLoopDelay ( ) ;
2023-05-24 20:05:34 +01:00
}
consolePrint ( " \n waiting for thread to join \n " ) ;
consoleRefresh ( ) ;
utilsJoinThread ( & dump_thread ) ;
consolePrint ( " dump_thread done: %lu \n " , time ( NULL ) ) ;
if ( nsp_thread_data . error )
{
consolePrint ( " i/o error \n " ) ;
} else
if ( nsp_thread_data . transfer_cancelled )
{
consolePrint ( " process cancelled \n " ) ;
} else {
start = ( time ( NULL ) - start ) ;
consolePrint ( " process completed in %lu seconds \n " , start ) ;
success = true ;
}
consoleRefresh ( ) ;
return success ;
}
static bool saveTicket ( void * userdata )
{
TitleInfo * title_info = ( TitleInfo * ) userdata ;
u8 content_type = 0 ;
NcmContentInfo * content_info = NULL ;
NcaContext * nca_ctx = NULL ;
Ticket tik = { 0 } ;
u32 crc = 0 ;
char * filename = NULL ;
bool remove_console_data = ( bool ) getTicketRemoveConsoleDataOption ( ) ;
bool success = false ;
if ( ! title_info | | title_info - > meta_key . type < NcmContentMetaType_Application | | title_info - > meta_key . type = = NcmContentMetaType_Delta | | \
title_info - > meta_key . type > NcmContentMetaType_DataPatch )
{
consolePrint ( " invalid title info object \n " ) ;
return false ;
}
/* Get a NcmContentInfo entry for a potential NCA with a rights ID. */
content_type = ( ( title_info - > meta_key . type = = NcmContentMetaType_Application | | title_info - > meta_key . type = = NcmContentMetaType_Patch ) ? NcmContentType_Program : NcmContentType_Data ) ;
content_info = titleGetContentInfoByTypeAndIdOffset ( title_info , content_type , 0 ) ;
if ( ! content_info )
{
consolePrint ( " content info entry with type 0x%X unavailable \n " , content_type ) ;
return false ;
}
/* Allocate buffer for NCA context. */
if ( ! ( nca_ctx = calloc ( 1 , sizeof ( NcaContext ) ) ) )
{
consolePrint ( " nca ctx calloc failed \n " ) ;
goto end ;
}
/* Initialize NCA context. */
if ( ! ncaInitializeContext ( nca_ctx , title_info - > storage_id , ( title_info - > storage_id = = NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0 ) , \
2023-05-30 00:22:12 +01:00
& ( title_info - > meta_key ) , content_info , & tik ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " nca initialize ctx failed \n " ) ;
goto end ;
}
/* Check if a ticket was retrieved. */
if ( ! nca_ctx - > rights_id_available )
{
consolePrint ( " rights id unavailable in target title -- this title doesn't use titlekey crypto \n there's no ticket to be retrieved \n " ) ;
goto end ;
}
Rework signature/cert/tik interfaces.
* signature: add comments to SignatureType enum entries about the exact signing algorithms and padding schemes used.
* signature: rename signatureGetSigType() -> signatureGetTypeFromSignedBlob().
* signature: rename signatureIsValidSigType() -> signatureIsValidType().
* signature: rename signatureGetSigSize() -> signatureGetSigSizeByType().
* signature: rename signatureGetBlockSize() -> signatureGetBlockSizeByType().
* signature: rename signatureGetSig() -> signatureGetSigFromSignedBlob().
* signature: rename signatureGetPayload() -> signatureGetPayloadFromSignedBlob().
* signature: add signatureGetBlockSizeFromSignedBlob().
* cert: add more comments to the code.
* cert: update code to match signature interface changes.
* cert: add CERT_RSA_PUB_EXP_SIZE macro.
* cert: change public_exponent field in CertPublicKeyBlockRsa* structs from u32 to u8 array.
* cert: add size field to CertificateChain struct.
* cert: rename certGetCommonBlock() -> certGetCommonBlockFromSignedCertBlob.
* cert: rename certGetPublicKeySize() -> certGetPublicKeySizeByType().
* cert: rename certGetPublicKeyBlockSize() -> certGetPublicKeyBlockSizeByType().
* cert: rename certIsValidCertificate() -> certIsValidSignedCertBlob().
* cert: rename certGetSignedCertificateSize() -> certGetSignedCertBlobSize().
* cert: rename certGetSignedCertificateHashAreaSize() -> certGetSignedCertBlobHashAreaSize().
* cert: remove certGetPublicKey(), certGetPublicExponent() and certCalculateRawCertificateChainSize().
* cert: add certGetPublicKeyTypeFromCommonBlock(), certGetPublicKeyTypeFromSignedCertBlob(), certGetPublicKeySizeFromSignedCertBlob(), certGetPublicKeyBlockSizeFromSignedCertBlob(), certGetPublicKeyFromSignedCertBlob(), certGetPublicExponentFromSignedCertBlob(), certIsValidCertificate() (w/diff func sig), certGetCommonBlockFromCertificate(), certGetPublicKeyTypeFromCertificate(), certGetPublicKeySizeFromCertificate(), certGetPublicKeyBlockSizeFromCertificate(), certGetPublicKeyFromCertificate(), certGetPublicExponentFromCertificate() and certGetHashAreaSizeFromCertificate() functions.
* cert: avoid byteswapping the public key type value in multiple places -- it is now only being done in certGetPublicKeyTypeFromCommonBlock().
* cert: call certFreeCertificateChain() in _certRetrieveCertificateChainBySignatureIssuer() before attempting to retrieve the certificate chain.
* cert: other minor changes and corrections.
* tik: update code to match signature interface changes.
* tik: add missing comments to TikPropertyMask enum entries.
* tik: add key_generation, enc_titlekey_str and dec_titlekey_str fields to Ticket struct.
* tik: update tikRetrieveTicketByRightsId() to also take in a key_generation argument, instead of getting it from the rights ID (which could fail if it's using a key generation lower than HOS 3.0.1) or the key_generation field from the common ticket block (which could fail if the ticket has been tampered by certain tools).
* tik: rename tikGetCommonBlock() -> tikGetCommonBlockFromSignedTicketBlob().
* tik: change function signature for tikGetTicketSectionRecordsBlockSize().
* tik: rename tikIsValidTicket() -> tikIsValidSignedTicketBlob().
* tik: rename tikGetSignedTicketSize() -> tikGetSignedTicketBlobSize().
* tik: rename tikGetSignedTicketHashAreaSize() -> tikGetSignedTicketBlobHashAreaSize().
* tik: rename tikGetEncryptedTitleKeyFromTicket() -> tikGetEncryptedTitleKey().
* tik: add tikIsValidTicket() (w/diff func sig), tikGetCommonBlockFromTicket(), tikGetHashAreaSizeFromTicket(), tikFixTamperedCommonTicket(), tikVerifyRsa2048Sha256Signature() and tikDecryptVolatileTicket() functions. Ticket signature verification is only carried out for common tickets in tikFixTamperedCommonTicket().
* tik: change argument order in tikGetTicketEntryOffsetFromTicketList() and tikRetrieveTicketEntryFromTicketBin().
* tik: add TIK_COMMON_CERT_NAME and TIK_DEV_CERT_ISSUER macros.
* tik: use a scoped lock when calling tikRetrieveTicketFromEsSaveDataByRightsId().
* tik: simplify certificate chain retrieval steps in tikConvertPersonalizedTicketToCommonTicket() by always using the XS00000020 certificate.
* tik: wipe license_type and property_mask fields in tikConvertPersonalizedTicketToCommonTicket().
* tik: other minor changes and corrections.
Other changes include:
* keys: fix key generation checks in keysGetNcaKeyAreaKeyEncryptionKey() and keysGetTicketCommonKey().
* rsa: move core logic from rsa2048VerifySha256BasedPssSignature() into a new function: rsa2048VerifySha256BasedSignature().
* rsa: add rsa2048VerifySha256BasedPkcs1v15Signature() function.
2023-10-15 16:53:46 +01:00
if ( ! nca_ctx - > titlekey_retrieved )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " failed to retrieve ticket (unavailable?) \n try launching nxdumptool while overriding the title you wish to dump a ticket from \n " ) ;
goto end ;
}
/* Remove console-specific data, if needed. */
Rework signature/cert/tik interfaces.
* signature: add comments to SignatureType enum entries about the exact signing algorithms and padding schemes used.
* signature: rename signatureGetSigType() -> signatureGetTypeFromSignedBlob().
* signature: rename signatureIsValidSigType() -> signatureIsValidType().
* signature: rename signatureGetSigSize() -> signatureGetSigSizeByType().
* signature: rename signatureGetBlockSize() -> signatureGetBlockSizeByType().
* signature: rename signatureGetSig() -> signatureGetSigFromSignedBlob().
* signature: rename signatureGetPayload() -> signatureGetPayloadFromSignedBlob().
* signature: add signatureGetBlockSizeFromSignedBlob().
* cert: add more comments to the code.
* cert: update code to match signature interface changes.
* cert: add CERT_RSA_PUB_EXP_SIZE macro.
* cert: change public_exponent field in CertPublicKeyBlockRsa* structs from u32 to u8 array.
* cert: add size field to CertificateChain struct.
* cert: rename certGetCommonBlock() -> certGetCommonBlockFromSignedCertBlob.
* cert: rename certGetPublicKeySize() -> certGetPublicKeySizeByType().
* cert: rename certGetPublicKeyBlockSize() -> certGetPublicKeyBlockSizeByType().
* cert: rename certIsValidCertificate() -> certIsValidSignedCertBlob().
* cert: rename certGetSignedCertificateSize() -> certGetSignedCertBlobSize().
* cert: rename certGetSignedCertificateHashAreaSize() -> certGetSignedCertBlobHashAreaSize().
* cert: remove certGetPublicKey(), certGetPublicExponent() and certCalculateRawCertificateChainSize().
* cert: add certGetPublicKeyTypeFromCommonBlock(), certGetPublicKeyTypeFromSignedCertBlob(), certGetPublicKeySizeFromSignedCertBlob(), certGetPublicKeyBlockSizeFromSignedCertBlob(), certGetPublicKeyFromSignedCertBlob(), certGetPublicExponentFromSignedCertBlob(), certIsValidCertificate() (w/diff func sig), certGetCommonBlockFromCertificate(), certGetPublicKeyTypeFromCertificate(), certGetPublicKeySizeFromCertificate(), certGetPublicKeyBlockSizeFromCertificate(), certGetPublicKeyFromCertificate(), certGetPublicExponentFromCertificate() and certGetHashAreaSizeFromCertificate() functions.
* cert: avoid byteswapping the public key type value in multiple places -- it is now only being done in certGetPublicKeyTypeFromCommonBlock().
* cert: call certFreeCertificateChain() in _certRetrieveCertificateChainBySignatureIssuer() before attempting to retrieve the certificate chain.
* cert: other minor changes and corrections.
* tik: update code to match signature interface changes.
* tik: add missing comments to TikPropertyMask enum entries.
* tik: add key_generation, enc_titlekey_str and dec_titlekey_str fields to Ticket struct.
* tik: update tikRetrieveTicketByRightsId() to also take in a key_generation argument, instead of getting it from the rights ID (which could fail if it's using a key generation lower than HOS 3.0.1) or the key_generation field from the common ticket block (which could fail if the ticket has been tampered by certain tools).
* tik: rename tikGetCommonBlock() -> tikGetCommonBlockFromSignedTicketBlob().
* tik: change function signature for tikGetTicketSectionRecordsBlockSize().
* tik: rename tikIsValidTicket() -> tikIsValidSignedTicketBlob().
* tik: rename tikGetSignedTicketSize() -> tikGetSignedTicketBlobSize().
* tik: rename tikGetSignedTicketHashAreaSize() -> tikGetSignedTicketBlobHashAreaSize().
* tik: rename tikGetEncryptedTitleKeyFromTicket() -> tikGetEncryptedTitleKey().
* tik: add tikIsValidTicket() (w/diff func sig), tikGetCommonBlockFromTicket(), tikGetHashAreaSizeFromTicket(), tikFixTamperedCommonTicket(), tikVerifyRsa2048Sha256Signature() and tikDecryptVolatileTicket() functions. Ticket signature verification is only carried out for common tickets in tikFixTamperedCommonTicket().
* tik: change argument order in tikGetTicketEntryOffsetFromTicketList() and tikRetrieveTicketEntryFromTicketBin().
* tik: add TIK_COMMON_CERT_NAME and TIK_DEV_CERT_ISSUER macros.
* tik: use a scoped lock when calling tikRetrieveTicketFromEsSaveDataByRightsId().
* tik: simplify certificate chain retrieval steps in tikConvertPersonalizedTicketToCommonTicket() by always using the XS00000020 certificate.
* tik: wipe license_type and property_mask fields in tikConvertPersonalizedTicketToCommonTicket().
* tik: other minor changes and corrections.
Other changes include:
* keys: fix key generation checks in keysGetNcaKeyAreaKeyEncryptionKey() and keysGetTicketCommonKey().
* rsa: move core logic from rsa2048VerifySha256BasedPssSignature() into a new function: rsa2048VerifySha256BasedSignature().
* rsa: add rsa2048VerifySha256BasedPkcs1v15Signature() function.
2023-10-15 16:53:46 +01:00
if ( remove_console_data & & tikIsPersonalizedTicket ( & tik ) & & ! tikConvertPersonalizedTicketToCommonTicket ( & tik , NULL , NULL ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " failed to convert personalized ticket to common ticket \n " ) ;
goto end ;
}
/* Save ticket. */
crc = crc32Calculate ( tik . data , tik . size ) ;
snprintf ( path , MAX_ELEMENTS ( path ) , " (%08X).tik " , crc ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputTitleFileName ( title_info , TICKET_SUBDIR , path ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( ! saveFileData ( filename , tik . data , tik . size ) ) goto end ;
consolePrint ( " rights id: %s \n " , tik . rights_id_str ) ;
Rework signature/cert/tik interfaces.
* signature: add comments to SignatureType enum entries about the exact signing algorithms and padding schemes used.
* signature: rename signatureGetSigType() -> signatureGetTypeFromSignedBlob().
* signature: rename signatureIsValidSigType() -> signatureIsValidType().
* signature: rename signatureGetSigSize() -> signatureGetSigSizeByType().
* signature: rename signatureGetBlockSize() -> signatureGetBlockSizeByType().
* signature: rename signatureGetSig() -> signatureGetSigFromSignedBlob().
* signature: rename signatureGetPayload() -> signatureGetPayloadFromSignedBlob().
* signature: add signatureGetBlockSizeFromSignedBlob().
* cert: add more comments to the code.
* cert: update code to match signature interface changes.
* cert: add CERT_RSA_PUB_EXP_SIZE macro.
* cert: change public_exponent field in CertPublicKeyBlockRsa* structs from u32 to u8 array.
* cert: add size field to CertificateChain struct.
* cert: rename certGetCommonBlock() -> certGetCommonBlockFromSignedCertBlob.
* cert: rename certGetPublicKeySize() -> certGetPublicKeySizeByType().
* cert: rename certGetPublicKeyBlockSize() -> certGetPublicKeyBlockSizeByType().
* cert: rename certIsValidCertificate() -> certIsValidSignedCertBlob().
* cert: rename certGetSignedCertificateSize() -> certGetSignedCertBlobSize().
* cert: rename certGetSignedCertificateHashAreaSize() -> certGetSignedCertBlobHashAreaSize().
* cert: remove certGetPublicKey(), certGetPublicExponent() and certCalculateRawCertificateChainSize().
* cert: add certGetPublicKeyTypeFromCommonBlock(), certGetPublicKeyTypeFromSignedCertBlob(), certGetPublicKeySizeFromSignedCertBlob(), certGetPublicKeyBlockSizeFromSignedCertBlob(), certGetPublicKeyFromSignedCertBlob(), certGetPublicExponentFromSignedCertBlob(), certIsValidCertificate() (w/diff func sig), certGetCommonBlockFromCertificate(), certGetPublicKeyTypeFromCertificate(), certGetPublicKeySizeFromCertificate(), certGetPublicKeyBlockSizeFromCertificate(), certGetPublicKeyFromCertificate(), certGetPublicExponentFromCertificate() and certGetHashAreaSizeFromCertificate() functions.
* cert: avoid byteswapping the public key type value in multiple places -- it is now only being done in certGetPublicKeyTypeFromCommonBlock().
* cert: call certFreeCertificateChain() in _certRetrieveCertificateChainBySignatureIssuer() before attempting to retrieve the certificate chain.
* cert: other minor changes and corrections.
* tik: update code to match signature interface changes.
* tik: add missing comments to TikPropertyMask enum entries.
* tik: add key_generation, enc_titlekey_str and dec_titlekey_str fields to Ticket struct.
* tik: update tikRetrieveTicketByRightsId() to also take in a key_generation argument, instead of getting it from the rights ID (which could fail if it's using a key generation lower than HOS 3.0.1) or the key_generation field from the common ticket block (which could fail if the ticket has been tampered by certain tools).
* tik: rename tikGetCommonBlock() -> tikGetCommonBlockFromSignedTicketBlob().
* tik: change function signature for tikGetTicketSectionRecordsBlockSize().
* tik: rename tikIsValidTicket() -> tikIsValidSignedTicketBlob().
* tik: rename tikGetSignedTicketSize() -> tikGetSignedTicketBlobSize().
* tik: rename tikGetSignedTicketHashAreaSize() -> tikGetSignedTicketBlobHashAreaSize().
* tik: rename tikGetEncryptedTitleKeyFromTicket() -> tikGetEncryptedTitleKey().
* tik: add tikIsValidTicket() (w/diff func sig), tikGetCommonBlockFromTicket(), tikGetHashAreaSizeFromTicket(), tikFixTamperedCommonTicket(), tikVerifyRsa2048Sha256Signature() and tikDecryptVolatileTicket() functions. Ticket signature verification is only carried out for common tickets in tikFixTamperedCommonTicket().
* tik: change argument order in tikGetTicketEntryOffsetFromTicketList() and tikRetrieveTicketEntryFromTicketBin().
* tik: add TIK_COMMON_CERT_NAME and TIK_DEV_CERT_ISSUER macros.
* tik: use a scoped lock when calling tikRetrieveTicketFromEsSaveDataByRightsId().
* tik: simplify certificate chain retrieval steps in tikConvertPersonalizedTicketToCommonTicket() by always using the XS00000020 certificate.
* tik: wipe license_type and property_mask fields in tikConvertPersonalizedTicketToCommonTicket().
* tik: other minor changes and corrections.
Other changes include:
* keys: fix key generation checks in keysGetNcaKeyAreaKeyEncryptionKey() and keysGetTicketCommonKey().
* rsa: move core logic from rsa2048VerifySha256BasedPssSignature() into a new function: rsa2048VerifySha256BasedSignature().
* rsa: add rsa2048VerifySha256BasedPkcs1v15Signature() function.
2023-10-15 16:53:46 +01:00
consolePrint ( " encrypted titlekey: %s \n " , tik . enc_titlekey_str ) ;
consolePrint ( " decrypted titlekey: %s \n \n " , tik . dec_titlekey_str ) ;
2023-05-24 20:05:34 +01:00
consolePrint ( " successfully saved ticket as \" %s \" \n " , filename ) ;
success = true ;
end :
if ( filename ) free ( filename ) ;
if ( nca_ctx ) free ( nca_ctx ) ;
return success ;
}
static bool saveNintendoContentArchive ( void * userdata )
{
if ( ! userdata ) return false ;
NcaUserData * nca_user_data = ( NcaUserData * ) userdata ;
TitleInfo * title_info = nca_user_data - > title_info ;
NcmContentInfo * content_info = & ( title_info - > content_infos [ nca_user_data - > content_idx ] ) ;
NcaThreadData nca_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( nca_thread_data . shared_thread_data ) ;
u64 free_space = 0 ;
2023-05-31 14:36:21 +01:00
char * filename = NULL , subdir [ 0x20 ] = { 0 } ;
2023-05-24 20:05:34 +01:00
u32 dev_idx = g_storageMenuElementOption . selected ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-05-24 20:05:34 +01:00
bool success = false ;
/* Allocate buffer for NCA context. */
if ( ! ( nca_thread_data . nca_ctx = calloc ( 1 , sizeof ( NcaContext ) ) ) )
{
consolePrint ( " nca ctx calloc failed \n " ) ;
goto end ;
}
/* Initialize NCA context. */
if ( ! ncaInitializeContext ( nca_thread_data . nca_ctx , title_info - > storage_id , ( title_info - > storage_id = = NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0 ) , \
2023-05-30 00:22:12 +01:00
& ( title_info - > meta_key ) , content_info , NULL ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " nca initialize ctx failed \n " ) ;
goto end ;
}
shared_thread_data - > total_size = nca_thread_data . nca_ctx - > content_size ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) shared_thread_data - > total_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " nca size: 0x%lX (%s) \n " , shared_thread_data - > total_size , size_str ) ;
2023-05-24 20:05:34 +01:00
2024-11-02 23:49:07 +00:00
snprintf ( subdir , MAX_ELEMENTS ( subdir ) , NCA_SUBDIR " /%s " , nca_thread_data . nca_ctx - > storage_id = = NcmStorageId_BuiltInSystem ? " System " : " User " ) ;
2023-11-03 01:22:47 +00:00
snprintf ( path , MAX_ELEMENTS ( path ) , " /%s.%s " , nca_thread_data . nca_ctx - > content_id_str , content_info - > content_type = = NcmContentType_Meta ? " cnmt.nca " : " nca " ) ;
2023-05-31 14:36:21 +01:00
filename = generateOutputTitleFileName ( title_info , subdir , path ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
if ( dev_idx = = 1 )
{
if ( ! usbSendFileProperties ( shared_thread_data - > total_size , filename ) )
{
consolePrint ( " failed to send file properties for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
goto end ;
}
if ( shared_thread_data - > total_size > = free_space )
{
consolePrint ( " dump size exceeds free space \n " ) ;
goto end ;
}
utilsCreateDirectoryTree ( filename , false ) ;
if ( dev_idx = = 0 )
{
if ( shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( filename ) )
{
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
goto end ;
}
}
shared_thread_data - > fp = fopen ( filename , " wb " ) ;
if ( ! shared_thread_data - > fp )
{
consolePrint ( " failed to open \" %s \" for writing! \n " , filename ) ;
goto end ;
}
2023-12-20 23:39:56 +00:00
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
2023-05-24 20:05:34 +01:00
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) shared_thread_data - > total_size ) ;
}
consoleRefresh ( ) ;
success = spanDumpThreads ( ncaReadThreadFunc , genericWriteThreadFunc , & nca_thread_data ) ;
if ( success )
{
consolePrint ( " successfully saved nca as \" %s \" \n " , filename ) ;
consoleRefresh ( ) ;
}
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( ! success & & dev_idx ! = 1 )
{
if ( dev_idx = = 0 )
{
utilsRemoveConcatenationFile ( filename ) ;
utilsCommitSdCardFileSystemChanges ( ) ;
} else {
remove ( filename ) ;
}
}
}
if ( filename ) free ( filename ) ;
if ( nca_thread_data . nca_ctx ) free ( nca_thread_data . nca_ctx ) ;
return success ;
}
2023-05-30 00:22:12 +01:00
static bool saveNintendoContentArchiveFsSection ( void * userdata )
2023-05-24 20:05:34 +01:00
{
2023-12-26 00:25:29 +00:00
u8 section_type = 0 ;
bool use_layeredfs_dir = false ;
NcaContext * base_patch_nca_ctx = NULL ;
void * fs_ctx = NULL ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
bool write_raw_section = ( bool ) getNcaFsWriteRawSectionOption ( ) ;
bool success = false ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
/* Initialize NCA FS section context. */
if ( ! initializeNcaFsContext ( userdata , & section_type , & use_layeredfs_dir , & base_patch_nca_ctx , & fs_ctx ) ) return false ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
/* Perform requested operation. */
if ( section_type = = NcaFsSectionType_PartitionFs )
2023-05-24 20:05:34 +01:00
{
2023-12-26 00:25:29 +00:00
PartitionFileSystemContext * pfs_ctx = ( PartitionFileSystemContext * ) fs_ctx ;
success = ( write_raw_section ? saveRawPartitionFsSection ( pfs_ctx , use_layeredfs_dir ) : saveExtractedPartitionFsSection ( pfs_ctx , use_layeredfs_dir ) ) ;
pfsFreeContext ( pfs_ctx ) ;
} else {
RomFileSystemContext * romfs_ctx = ( RomFileSystemContext * ) fs_ctx ;
success = ( write_raw_section ? saveRawRomFsSection ( romfs_ctx , use_layeredfs_dir ) : saveExtractedRomFsSection ( romfs_ctx , use_layeredfs_dir ) ) ;
romfsFreeContext ( romfs_ctx ) ;
2023-05-30 00:22:12 +01:00
}
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
/* Free data. */
free ( fs_ctx ) ;
free ( base_patch_nca_ctx ) ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
return success ;
}
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
static bool browseNintendoContentArchiveFsSection ( void * userdata )
{
u8 section_type = 0 ;
bool use_layeredfs_dir = false ;
2023-05-30 00:22:12 +01:00
NcaContext * base_patch_nca_ctx = NULL ;
2023-12-26 00:25:29 +00:00
void * fs_ctx = NULL ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
PartitionFileSystemContext * pfs_ctx = NULL ;
RomFileSystemContext * romfs_ctx = NULL ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
NcaFsSectionContext * nca_fs_ctx = NULL ;
NcaContext * nca_ctx = NULL ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
u64 title_id = 0 ;
u8 title_type = 0 ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
char mount_name [ DEVOPTAB_MOUNT_NAME_LENGTH ] = { 0 } , subdir [ 0x20 ] = { 0 } , extension [ FS_MAX_PATH ] = { 0 } ;
char * base_out_path = NULL ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
bool success = false ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
/* Initialize NCA FS section context. */
if ( ! initializeNcaFsContext ( userdata , & section_type , & use_layeredfs_dir , & base_patch_nca_ctx , & fs_ctx ) ) goto end ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
/* Mount devoptab device. */
2023-05-30 00:22:12 +01:00
if ( section_type = = NcaFsSectionType_PartitionFs )
{
2023-12-26 00:25:29 +00:00
pfs_ctx = ( PartitionFileSystemContext * ) fs_ctx ;
nca_fs_ctx = pfs_ctx - > nca_fs_ctx ;
2023-05-24 20:05:34 +01:00
2024-04-12 12:40:50 +01:00
snprintf ( mount_name , MAX_ELEMENTS ( mount_name ) , " %s " , pfs_ctx - > is_exefs ? " ncaexefs " : " ncapfs " ) ;
2023-12-26 00:25:29 +00:00
if ( ! devoptabMountPartitionFileSystemDevice ( pfs_ctx , mount_name ) )
2023-05-24 20:05:34 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " pfs ctx devoptab mount failed! \n " ) ;
2023-05-30 00:22:12 +01:00
goto end ;
2023-05-24 20:05:34 +01:00
}
2023-05-30 00:22:12 +01:00
} else {
2023-12-26 00:25:29 +00:00
romfs_ctx = ( RomFileSystemContext * ) fs_ctx ;
nca_fs_ctx = romfs_ctx - > default_storage_ctx - > nca_fs_ctx ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
snprintf ( mount_name , MAX_ELEMENTS ( mount_name ) , " ncaromfs " ) ;
if ( ! devoptabMountRomFileSystemDevice ( romfs_ctx , mount_name ) )
2023-05-30 00:22:12 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " romfs ctx devoptab mount failed! \n " ) ;
2023-05-30 00:22:12 +01:00
goto end ;
}
2023-12-26 00:25:29 +00:00
}
/* Generate output base path. */
nca_ctx = nca_fs_ctx - > nca_ctx ;
title_id = nca_ctx - > title_id ;
title_type = nca_ctx - > title_type ;
if ( use_layeredfs_dir )
{
/* Only use base title IDs if we're dealing with patches. */
title_id = ( title_type = = NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId ( title_id ) : \
( title_type = = NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId ( title_id ) : title_id ) ) ;
2024-01-10 10:44:59 +00:00
base_out_path = generateOutputLayeredFsFileName ( title_id + nca_ctx - > id_offset , NULL , section_type = = NcaFsSectionType_PartitionFs ? " exefs " : " romfs " ) ;
2023-12-26 00:25:29 +00:00
} else {
2024-11-02 23:49:07 +00:00
snprintf ( subdir , MAX_ELEMENTS ( subdir ) , NCA_FS_SUBDIR " /%s/Extracted " , nca_ctx - > storage_id = = NcmStorageId_BuiltInSystem ? " System " : " User " ) ;
2023-12-26 00:25:29 +00:00
snprintf ( extension , MAX_ELEMENTS ( extension ) , " /%s #%u/%u " , titleGetNcmContentTypeName ( nca_ctx - > content_type ) , nca_ctx - > id_offset , nca_fs_ctx - > section_idx ) ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
TitleInfo * title_info = ( title_id = = g_ncaUserTitleInfo - > meta_key . id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo ) ;
base_out_path = generateOutputTitleFileName ( title_info , subdir , extension ) ;
2023-05-24 20:05:34 +01:00
}
2023-12-26 00:25:29 +00:00
if ( ! base_out_path ) goto end ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
/* Display file browser. */
success = fsBrowser ( mount_name , base_out_path ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
/* Unmount devoptab device. */
devoptabUnmountDevice ( mount_name ) ;
end :
/* Free data. */
if ( base_out_path ) free ( base_out_path ) ;
if ( pfs_ctx ) pfsFreeContext ( pfs_ctx ) ;
if ( romfs_ctx ) romfsFreeContext ( romfs_ctx ) ;
if ( fs_ctx ) free ( fs_ctx ) ;
2023-05-30 00:22:12 +01:00
if ( base_patch_nca_ctx ) free ( base_patch_nca_ctx ) ;
2023-12-26 00:25:29 +00:00
if ( ! success & & g_appletStatus )
{
consolePrint ( " press any button to continue \n " ) ;
utilsWaitForButtonPress ( 0 ) ;
}
2023-05-30 00:22:12 +01:00
return success ;
2023-05-24 20:05:34 +01:00
}
2024-10-27 11:23:46 +00:00
static bool saveSystemUpdateDump ( void * userdata )
{
NX_IGNORE_ARG ( userdata ) ;
SystemUpdateDumpContext sys_upd_dump_ctx = { 0 } ;
SystemUpdateThreadData sys_upd_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( sys_upd_thread_data . shared_thread_data ) ;
char size_str [ 16 ] = { 0 } ;
bool success = false ;
if ( ! systemUpdateInitializeDumpContext ( & sys_upd_dump_ctx ) )
{
consolePrint ( " system update dump ctx init failed! \n " ) ;
goto end ;
}
sys_upd_thread_data . sys_upd_dump_ctx = & sys_upd_dump_ctx ;
shared_thread_data - > total_size = sys_upd_dump_ctx . total_size ;
utilsGenerateFormattedSizeString ( ( double ) sys_upd_dump_ctx . total_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " sysupd description: %.*s \n sysupd size: 0x%lX (%s) \n sysupd content count: %u \n " , ( int ) sizeof ( sys_upd_dump_ctx . version_file . display_title ) , \
sys_upd_dump_ctx . version_file . display_title , sys_upd_dump_ctx . total_size , \
size_str , sys_upd_dump_ctx . content_count ) ;
consoleRefresh ( ) ;
success = spanDumpThreads ( systemUpdateReadThreadFunc , genericWriteThreadFunc , & sys_upd_thread_data ) ;
end :
systemUpdateFreeDumpContext ( & sys_upd_dump_ctx ) ;
return success ;
}
2024-10-28 14:44:31 +00:00
static bool browseEmmcPartition ( void * userdata )
{
u8 bis_partition_id = ( userdata ? * ( ( u8 * ) userdata ) : 0 ) ;
2024-11-02 23:49:07 +00:00
const char * gpt_name = NULL , * mount_name = NULL ;
2024-10-28 14:44:31 +00:00
char * base_out_path = NULL ;
bool success = false ;
2024-11-02 23:49:07 +00:00
if ( bis_partition_id < FsBisPartitionId_CalibrationFile | | bis_partition_id > FsBisPartitionId_System )
2024-10-28 14:44:31 +00:00
{
consolePrint ( " invalid bis partition id! (%u) \n " , bis_partition_id ) ;
goto end ;
}
2024-11-02 23:49:07 +00:00
if ( ! ( gpt_name = bisStorageGetGptPartitionNameByBisPartitionId ( bis_partition_id ) ) | | ! ( mount_name = bisStorageGetMountNameByBisPartitionId ( bis_partition_id ) ) )
{
consolePrint ( " failed to get names for bis partition id! (%u) \n " , bis_partition_id ) ;
goto end ;
}
2024-10-28 14:44:31 +00:00
/* Generate output base path. */
2024-11-02 23:49:07 +00:00
base_out_path = generateOutputGameCardFileName ( utilsGetAtmosphereEmummcStatus ( ) ? EMUMMC_SUBDIR : SYSMMC_SUBDIR , gpt_name , false ) ;
2024-10-28 14:44:31 +00:00
if ( ! base_out_path ) goto end ;
/* Display file browser. */
success = fsBrowser ( mount_name , base_out_path ) ;
end :
/* Free data. */
if ( base_out_path ) free ( base_out_path ) ;
if ( ! success & & g_appletStatus )
{
consolePrint ( " press any button to continue \n " ) ;
utilsWaitForButtonPress ( 0 ) ;
}
return success ;
}
2023-12-26 00:25:29 +00:00
static bool fsBrowser ( const char * mount_name , const char * base_out_path )
2023-05-24 20:05:34 +01:00
{
2023-12-26 00:25:29 +00:00
char dir_path [ FS_MAX_PATH ] = { 0 } ;
size_t dir_path_len = 0 ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
FsBrowserEntry * entries = NULL ;
u32 entries_count = 0 , depth = 0 ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
u32 scroll = 0 , selected = 0 , highlighted = 0 , page_size = 20 ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
bool success = true ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
/* Get root directory entries. */
snprintf ( dir_path , MAX_ELEMENTS ( dir_path ) , " %s:/ " , mount_name ) ;
dir_path_len = strlen ( dir_path ) ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
if ( ! ( success = fsBrowserGetDirEntries ( dir_path , & entries , & entries_count ) ) ) goto end ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
while ( ( g_appletStatus = appletMainLoop ( ) ) )
2023-05-24 20:05:34 +01:00
{
2023-12-26 00:25:29 +00:00
consoleClear ( ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
consolePrint ( " press a to enter a directory / dump a file \n " ) ;
consolePrint ( " press b to %s \n " , depth > 0 ? " move back to the parent dir " : " exit the fs browser " ) ;
consolePrint ( " press r to (un)highlight the selected entry \n " ) ;
consolePrint ( " press l to invert the current selection \n " ) ;
consolePrint ( " press zr to highlight all entries \n " ) ;
consolePrint ( " press zl to unhighlight all entries \n " ) ;
consolePrint ( " press y to dump the highlighted entries \n " ) ;
consolePrint ( " use the sticks to scroll faster \n " ) ;
consolePrint ( " press + to exit \n " ) ;
consolePrint ( " ______________________________ \n \n " ) ;
2023-05-24 20:05:34 +01:00
2024-10-28 14:44:31 +00:00
if ( entries_count )
{
consolePrint ( " entry: %u / %u \n " , selected + 1 , entries_count ) ;
consolePrint ( " highlighted: %u / %u \n " , highlighted , entries_count ) ;
consolePrint ( " selected: %s \n " , entries [ selected ] . dt . d_name ) ;
}
2023-12-26 00:25:29 +00:00
consolePrint ( " current path: %s \n " , dir_path ) ;
consolePrint ( " ______________________________ \n \n " ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
for ( u32 i = scroll ; i < entries_count ; i + + )
2023-05-24 20:05:34 +01:00
{
2023-12-26 00:25:29 +00:00
if ( i > = ( scroll + page_size ) ) break ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
FsBrowserEntry * cur_entry = & ( entries [ i ] ) ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
consolePrint ( " %s " , i = = selected ? " -> " : " " ) ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
if ( cur_entry - > highlight )
2023-05-30 00:22:12 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrintReversedColors ( " [%c] %s " , cur_entry - > dt . d_type = = DT_DIR ? ' D ' : ' F ' , cur_entry - > dt . d_name ) ;
if ( cur_entry - > dt . d_type = = DT_REG ) consolePrintReversedColors ( " (%s) " , cur_entry - > size_str ) ;
} else {
consolePrint ( " [%c] %s " , cur_entry - > dt . d_type = = DT_DIR ? ' D ' : ' F ' , cur_entry - > dt . d_name ) ;
if ( cur_entry - > dt . d_type = = DT_REG ) consolePrint ( " (%s) " , cur_entry - > size_str ) ;
2023-05-30 00:22:12 +01:00
}
2023-12-26 00:25:29 +00:00
consolePrint ( " \n " ) ;
2023-05-30 00:22:12 +01:00
}
2023-12-26 00:25:29 +00:00
if ( ! entries_count ) consolePrint ( " no elements available! " ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
consolePrint ( " \n " ) ;
2023-05-30 00:22:12 +01:00
consoleRefresh ( ) ;
2023-12-26 00:25:29 +00:00
u64 btn_down = 0 , btn_held = 0 ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
while ( ( g_appletStatus = appletMainLoop ( ) ) )
2023-05-30 00:22:12 +01:00
{
2023-12-26 00:25:29 +00:00
utilsScanPads ( ) ;
btn_down = utilsGetButtonsDown ( ) ;
btn_held = utilsGetButtonsHeld ( ) ;
if ( btn_down | | btn_held ) break ;
utilsAppletLoopDelay ( ) ;
2023-05-30 00:22:12 +01:00
}
2023-12-26 00:25:29 +00:00
if ( ! g_appletStatus ) break ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( ( btn_down & HidNpadButton_A ) & & entries_count )
{
FsBrowserEntry * selected_entry = & ( entries [ selected ] ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( selected_entry - > dt . d_type = = DT_DIR )
{
/* Change directory. */
snprintf ( dir_path + dir_path_len , MAX_ELEMENTS ( dir_path ) - dir_path_len , " %s%s " , depth > 0 ? " / " : " " , selected_entry - > dt . d_name ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( ! ( success = fsBrowserGetDirEntries ( dir_path , & entries , & entries_count ) ) ) break ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
/* Update variables. */
dir_path_len = strlen ( dir_path ) ;
scroll = selected = highlighted = 0 ;
depth + + ;
} else {
/* Dump file. */
2023-12-31 17:16:49 +00:00
utilsSetLongRunningProcessState ( true ) ;
2023-12-26 00:25:29 +00:00
fsBrowserDumpFile ( dir_path , selected_entry , base_out_path ) ;
2023-12-31 17:16:49 +00:00
utilsSetLongRunningProcessState ( false ) ;
2023-12-26 00:25:29 +00:00
}
} else
if ( btn_down & HidNpadButton_B )
{
if ( depth > 0 )
{
/* Go back to the parent directory. */
char * ptr = strrchr ( dir_path , ' / ' ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( depth > 1 )
{
* ptr = ' \0 ' ;
} else {
* ( + + ptr ) = ' \0 ' ;
}
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( ! ( success = fsBrowserGetDirEntries ( dir_path , & entries , & entries_count ) ) ) break ;
/* Update variables. */
dir_path_len = strlen ( dir_path ) ;
scroll = selected = highlighted = 0 ;
depth - - ;
} else {
break ;
}
} else
if ( ( btn_down & HidNpadButton_R ) & & entries_count )
{
/* (Un)highlight the selected entry. */
FsBrowserEntry * selected_entry = & ( entries [ selected ] ) ;
selected_entry - > highlight ^ = 1 ;
highlighted + = ( selected_entry - > highlight ? 1 : - 1 ) ;
} else
if ( ( btn_down & HidNpadButton_L ) & & entries_count )
{
/* Invert current selection. */
for ( u32 i = 0 ; i < entries_count ; i + + )
{
FsBrowserEntry * cur_entry = & ( entries [ i ] ) ;
cur_entry - > highlight ^ = 1 ;
highlighted + = ( cur_entry - > highlight ? 1 : - 1 ) ;
}
} else
if ( ( btn_down & HidNpadButton_ZR ) & & entries_count )
{
/* Highlight all entries. */
for ( u32 i = 0 ; i < entries_count ; i + + ) entries [ i ] . highlight = true ;
/* Update counter. */
highlighted = entries_count ;
} else
if ( ( btn_down & HidNpadButton_ZL ) & & entries_count )
{
/* Unhighlight all entries. */
for ( u32 i = 0 ; i < entries_count ; i + + ) entries [ i ] . highlight = false ;
/* Reset counter. */
highlighted = 0 ;
} else
if ( ( btn_down & HidNpadButton_Y ) & & entries_count & & highlighted )
{
/* Dump highlighted entries. */
2023-12-31 17:16:49 +00:00
utilsSetLongRunningProcessState ( true ) ;
2023-12-26 00:25:29 +00:00
fsBrowserDumpHighlightedEntries ( dir_path , entries , entries_count , base_out_path ) ;
2023-12-31 17:16:49 +00:00
utilsSetLongRunningProcessState ( false ) ;
2023-12-26 00:25:29 +00:00
/* Unhighlight all entries. */
for ( u32 i = 0 ; i < entries_count ; i + + ) entries [ i ] . highlight = false ;
/* Reset counter. */
highlighted = 0 ;
} else
if ( ( ( btn_down & HidNpadButton_Down ) | | ( btn_held & ( HidNpadButton_StickLDown | HidNpadButton_StickRDown ) ) ) & & entries_count )
{
selected + + ;
if ( selected > = entries_count )
{
if ( btn_down & HidNpadButton_Down )
{
scroll = 0 ;
selected = 0 ;
} else {
selected - - ;
}
} else
if ( selected > = ( scroll + ( page_size / 2 ) ) & & entries_count > ( scroll + page_size ) )
{
scroll + + ;
}
} else
if ( ( ( btn_down & HidNpadButton_Up ) | | ( btn_held & ( HidNpadButton_StickLUp | HidNpadButton_StickRUp ) ) ) & & entries_count )
{
selected - - ;
if ( selected = = UINT32_MAX )
{
if ( btn_down & HidNpadButton_Up )
{
selected = ( entries_count - 1 ) ;
scroll = ( entries_count > = page_size ? ( entries_count - page_size ) : 0 ) ;
} else {
selected = 0 ;
}
} else
if ( selected < ( scroll + ( page_size / 2 ) ) & & scroll > 0 )
{
scroll - - ;
}
} else
if ( btn_down & HidNpadButton_Plus )
{
g_appletStatus = false ;
break ;
}
utilsAppletLoopDelay ( ) ;
}
end :
if ( entries ) free ( entries ) ;
return success ;
}
static bool fsBrowserGetDirEntries ( const char * dir_path , FsBrowserEntry * * out_entries , u32 * out_entry_count )
{
DIR * dp = NULL ;
struct dirent * dt = NULL ;
struct stat st = { 0 } ;
FsBrowserEntry * entries = NULL , * entries_tmp = NULL ;
char tmp_path [ FS_MAX_PATH ] = { 0 } ;
u32 count = 0 ;
bool append_path_sep = ( dir_path [ strlen ( dir_path ) - 1 ] ! = ' / ' ) ;
bool success = false ;
/* Free input pointer, if needed. */
if ( * out_entries )
2023-05-30 00:22:12 +01:00
{
2023-12-26 00:25:29 +00:00
free ( * out_entries ) ;
* out_entries = NULL ;
}
/* Open directory. */
dp = opendir ( dir_path ) ;
if ( ! dp )
{
consolePrint ( " failed to open dir \" %s \" \n " , dir_path ) ;
2023-05-30 00:22:12 +01:00
goto end ;
}
2023-12-26 00:25:29 +00:00
/* Get entry count. */
while ( ( dt = readdir ( dp ) ) )
{
/* Skip "." and ".." entries. */
2023-12-31 17:16:49 +00:00
if ( ! strcmp ( dt - > d_name , " . " ) | | ! strcmp ( dt - > d_name , " .. " ) ) continue ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
/* Reallocate directory entries buffer. */
if ( ! ( entries_tmp = realloc ( entries , ( count + 1 ) * sizeof ( FsBrowserEntry ) ) ) )
{
consolePrint ( " failed to allocate memory for dir entries in \" %s \" \n " , dir_path ) ;
goto end ;
}
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
entries = entries_tmp ;
entries_tmp = NULL ;
/* Store entry data. */
FsBrowserEntry * cur_entry = & ( entries [ count + + ] ) ;
memset ( cur_entry , 0 , sizeof ( FsBrowserEntry ) ) ;
if ( dt - > d_type = = DT_REG )
{
/* Get file size. */
snprintf ( tmp_path , MAX_ELEMENTS ( tmp_path ) , " %s%s%s " , dir_path , append_path_sep ? " / " : " " , dt - > d_name ) ;
stat ( tmp_path , & st ) ;
cur_entry - > size = st . st_size ;
utilsGenerateFormattedSizeString ( ( double ) st . st_size , cur_entry - > size_str , sizeof ( cur_entry - > size_str ) ) ;
}
memcpy ( & ( cur_entry - > dt ) , dt , sizeof ( struct dirent ) ) ;
}
/* Short-circuit: handle empty directories. */
if ( ! entries )
{
* out_entry_count = 0 ;
success = true ;
goto end ;
}
2024-10-28 14:44:31 +00:00
/* Sort entries. */
if ( count > 1 ) qsort ( entries , count , sizeof ( FsBrowserEntry ) , & fsBrowserDirEntrySortFunction ) ;
2023-12-26 00:25:29 +00:00
/* Update output pointers. */
* out_entries = entries ;
* out_entry_count = count ;
/* Update return value. */
success = true ;
2023-05-30 00:22:12 +01:00
end :
2023-12-26 00:25:29 +00:00
if ( dp ) closedir ( dp ) ;
if ( ! success & & entries ) free ( entries ) ;
2023-05-30 00:22:12 +01:00
return success ;
}
2024-10-28 14:44:31 +00:00
static int fsBrowserDirEntrySortFunction ( const void * a , const void * b )
{
const FsBrowserEntry * entry_1 = ( const FsBrowserEntry * ) a ;
const FsBrowserEntry * entry_2 = ( const FsBrowserEntry * ) b ;
if ( entry_1 - > dt . d_type < entry_2 - > dt . d_type )
{
return - 1 ;
} else
if ( entry_1 - > dt . d_type > entry_2 - > dt . d_type )
{
return 1 ;
}
return strcasecmp ( entry_1 - > dt . d_name , entry_2 - > dt . d_name ) ;
}
2023-12-26 00:25:29 +00:00
static bool fsBrowserDumpFile ( const char * dir_path , const FsBrowserEntry * entry , const char * base_out_path )
2023-05-30 22:34:34 +01:00
{
u64 free_space = 0 ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
FsBrowserFileThreadData fs_browser_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( fs_browser_thread_data . shared_thread_data ) ;
2023-05-30 00:22:12 +01:00
2023-05-30 22:34:34 +01:00
u32 dev_idx = g_storageMenuElementOption . selected ;
2023-05-30 00:22:12 +01:00
2023-05-30 22:34:34 +01:00
bool success = false ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
shared_thread_data - > total_size = entry - > size ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
snprintf ( path , MAX_ELEMENTS ( path ) , " %s%s%s " , dir_path , dir_path [ strlen ( dir_path ) - 1 ] ! = ' / ' ? " / " : " " , entry - > dt . d_name ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
consoleClear ( ) ;
consolePrint ( " file path: %s \n " , path ) ;
2024-08-26 08:45:52 +01:00
consolePrint ( " file size: 0x%lX (%s) \n \n " , entry - > size , entry - > size_str ) ;
2023-12-26 00:25:29 +00:00
/* Open input file. */
fs_browser_thread_data . src = fopen ( path , " rb " ) ;
if ( ! fs_browser_thread_data . src )
2023-05-30 22:34:34 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to open input file! \n " ) ;
goto end ;
}
2023-07-17 19:20:49 +01:00
2023-12-26 00:25:29 +00:00
setvbuf ( fs_browser_thread_data . src , NULL , _IONBF , 0 ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
const char * dir_path_start = ( strchr ( dir_path , ' / ' ) + 1 ) ;
if ( * dir_path_start )
{
snprintf ( path , MAX_ELEMENTS ( path ) , " %s/%s/%s " , base_out_path , dir_path_start , entry - > dt . d_name ) ;
} else {
snprintf ( path , MAX_ELEMENTS ( path ) , " %s/%s " , base_out_path , entry - > dt . d_name ) ;
2023-05-30 22:34:34 +01:00
}
2023-05-30 00:22:12 +01:00
2023-05-30 22:34:34 +01:00
if ( dev_idx = = 1 )
{
2023-12-26 00:25:29 +00:00
if ( ! waitForUsb ( ) ) goto end ;
if ( ! usbSendFileProperties ( shared_thread_data - > total_size , path ) )
2023-05-30 22:34:34 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to send file properties for \" %s \" ! \n " , path ) ;
2023-05-30 22:34:34 +01:00
goto end ;
}
} else {
2023-12-26 00:25:29 +00:00
if ( ! utilsGetFileSystemStatsByPath ( path , NULL , & free_space ) )
2023-05-30 22:34:34 +01:00
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
goto end ;
}
2023-05-30 00:22:12 +01:00
2023-05-30 22:34:34 +01:00
if ( shared_thread_data - > total_size > = free_space )
{
consolePrint ( " dump size exceeds free space \n " ) ;
goto end ;
}
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
utilsCreateDirectoryTree ( path , false ) ;
2023-05-30 00:22:12 +01:00
2023-05-30 22:34:34 +01:00
if ( dev_idx = = 0 )
{
2023-12-26 00:25:29 +00:00
if ( shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( path ) )
2023-05-30 22:34:34 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , path ) ;
2023-05-30 22:34:34 +01:00
goto end ;
}
} else {
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
goto end ;
}
}
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
shared_thread_data - > fp = fopen ( path , " wb " ) ;
if ( ! shared_thread_data - > fp )
{
consolePrint ( " failed to open \" %s \" for writing! \n " , path ) ;
goto end ;
}
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) shared_thread_data - > total_size ) ;
}
consoleRefresh ( ) ;
success = spanDumpThreads ( fsBrowserFileReadThreadFunc , genericWriteThreadFunc , & fs_browser_thread_data ) ;
if ( success )
{
consolePrint ( " successfully saved file to \" %s \" \n " , path ) ;
consoleRefresh ( ) ;
}
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( ! success & & dev_idx ! = 1 )
{
if ( dev_idx = = 0 )
{
utilsRemoveConcatenationFile ( path ) ;
utilsCommitSdCardFileSystemChanges ( ) ;
} else {
remove ( path ) ;
}
}
}
if ( fs_browser_thread_data . src ) fclose ( fs_browser_thread_data . src ) ;
consolePrint ( " press any button to continue \n " ) ;
utilsWaitForButtonPress ( 0 ) ;
return success ;
}
static bool fsBrowserDumpHighlightedEntries ( const char * dir_path , const FsBrowserEntry * entries , u32 entries_count , const char * base_out_path )
{
bool append_path_sep = ( dir_path [ strlen ( dir_path ) - 1 ] ! = ' / ' ) ;
u64 data_size = 0 ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-12-26 00:25:29 +00:00
FsBrowserHighlightedEntriesThreadData fs_browser_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( fs_browser_thread_data . shared_thread_data ) ;
bool success = false ;
consoleClear ( ) ;
consolePrint ( " calculating dump size... \n " ) ;
consoleRefresh ( ) ;
/* Calculate dump size. */
for ( u32 i = 0 ; i < entries_count ; i + + )
{
const FsBrowserEntry * cur_entry = & ( entries [ i ] ) ;
if ( ! cur_entry - > highlight ) continue ;
if ( cur_entry - > dt . d_type = = DT_DIR )
{
/* Get directory size. */
u64 dir_size = 0 ;
snprintf ( path , MAX_ELEMENTS ( path ) , " %s%s%s " , dir_path , append_path_sep ? " / " : " " , cur_entry - > dt . d_name ) ;
if ( ! utilsGetDirectorySize ( path , & dir_size ) )
{
consolePrint ( " failed to calculate size for dir \" %s \" \n " , path ) ;
goto end ;
}
/* Update dump size. */
data_size + = dir_size ;
} else {
/* Update dump size. */
data_size + = cur_entry - > size ;
}
}
fs_browser_thread_data . dir_path = dir_path ;
fs_browser_thread_data . entries = entries ;
fs_browser_thread_data . entries_count = entries_count ;
fs_browser_thread_data . base_out_path = base_out_path ;
shared_thread_data - > total_size = data_size ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) data_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " dump size: 0x%lX (%s) \n " , data_size , size_str ) ;
2023-12-26 00:25:29 +00:00
consoleRefresh ( ) ;
success = spanDumpThreads ( fsBrowserHighlightedEntriesReadThreadFunc , genericWriteThreadFunc , & fs_browser_thread_data ) ;
end :
consolePrint ( " press any button to continue \n " ) ;
utilsWaitForButtonPress ( 0 ) ;
return success ;
}
static bool initializeNcaFsContext ( void * userdata , u8 * out_section_type , bool * out_use_layeredfs_dir , NcaContext * * out_base_patch_nca_ctx , void * * out_fs_ctx )
{
NcaFsSectionContext * nca_fs_ctx = ( NcaFsSectionContext * ) userdata ;
NcaContext * nca_ctx = ( nca_fs_ctx ? nca_fs_ctx - > nca_ctx : NULL ) ;
/* Sanity checks. */
if ( ! g_ncaUserTitleInfo | | ! nca_fs_ctx | | ! nca_ctx | | ! nca_fs_ctx - > enabled | | nca_fs_ctx - > section_type > NcaFsSectionType_Nca0RomFs | | \
( nca_fs_ctx - > section_type = = NcaFsSectionType_Nca0RomFs & & g_ncaBasePatchTitleInfo ) )
{
consolePrint ( " invalid nca fs parameters! \n " ) ;
return false ;
}
if ( nca_fs_ctx - > has_sparse_layer )
{
if ( ! g_ncaBasePatchTitleInfo )
{
consolePrint ( " the selected nca fs section holds a sparse storage \n a matching patch of at least v%u must be selected \n " , nca_ctx - > title_version . value ) ;
return false ;
} else
if ( g_ncaBasePatchTitleInfo - > version . value < nca_ctx - > title_version . value )
{
consolePrint ( " the selected patch doesn't meet the sparse storage version requirement! \n v%u < v%u \n " , g_ncaBasePatchTitleInfo - > version . value , nca_ctx - > title_version . value ) ;
return false ;
}
}
if ( nca_fs_ctx - > section_type = = NcaFsSectionType_PatchRomFs & & ! g_ncaBasePatchTitleInfo )
{
consolePrint ( " patch romfs section selected but no base app provided \n " ) ;
return false ;
}
u8 title_type = nca_ctx - > title_type ;
u8 content_type = nca_ctx - > content_type ;
u8 section_type = nca_fs_ctx - > section_type ;
NcmContentInfo * base_patch_content_info = ( g_ncaBasePatchTitleInfo ? titleGetContentInfoByTypeAndIdOffset ( g_ncaBasePatchTitleInfo , content_type , nca_ctx - > id_offset ) : NULL ) ;
NcaContext * base_patch_nca_ctx = NULL ;
NcaFsSectionContext * base_patch_nca_fs_ctx = NULL ;
bool use_layeredfs_dir = ( bool ) getNcaFsUseLayeredFsDirOption ( ) ;
bool success = false ;
/* Override LayeredFS flag, if needed. */
if ( use_layeredfs_dir & & \
( title_type = = NcmContentMetaType_Unknown | | ( title_type > NcmContentMetaType_SystemData & & title_type < NcmContentMetaType_Application ) | | \
( title_type = = NcmContentMetaType_SystemProgram & & ( content_type ! = NcmContentType_Program | | nca_fs_ctx - > section_idx ! = 0 ) ) | | \
( title_type = = NcmContentMetaType_SystemData & & ( content_type ! = NcmContentType_Data | | nca_fs_ctx - > section_idx ! = 0 ) ) | | \
( ( title_type = = NcmContentMetaType_Application | | title_type = = NcmContentMetaType_Patch ) & & ( content_type ! = NcmContentType_Program | | nca_fs_ctx - > section_idx > 1 ) ) | | \
( ( title_type = = NcmContentMetaType_AddOnContent | | title_type = = NcmContentMetaType_DataPatch ) & & ( content_type ! = NcmContentType_Data | | nca_fs_ctx - > section_idx ! = 0 ) ) ) )
{
consolePrint ( " layeredfs setting disabled (unsupported by current content/section type combo) \n " ) ;
use_layeredfs_dir = false ;
}
/* Initialize base/patch NCA context, if needed. */
if ( base_patch_content_info )
{
base_patch_nca_ctx = calloc ( 1 , sizeof ( NcaContext ) ) ;
if ( ! base_patch_nca_ctx )
{
consolePrint ( " failed to allocate memory for base/patch nca ctx! \n " ) ;
goto end ;
}
if ( ! ncaInitializeContext ( base_patch_nca_ctx , g_ncaBasePatchTitleInfo - > storage_id , ( g_ncaBasePatchTitleInfo - > storage_id = = NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0 ) , \
& ( g_ncaBasePatchTitleInfo - > meta_key ) , base_patch_content_info , NULL ) )
{
consolePrint ( " failed to initialize base/patch nca ctx! \n " ) ;
goto end ;
}
/* Use a matching NCA FS section entry. */
base_patch_nca_fs_ctx = & ( base_patch_nca_ctx - > fs_ctx [ nca_fs_ctx - > section_idx ] ) ;
}
if ( section_type = = NcaFsSectionType_PartitionFs )
{
/* Select the right NCA FS section context, depending on the sparse layer flag. */
NcaFsSectionContext * pfs_nca_fs_ctx = ( ( title_type = = NcmContentMetaType_Application & & base_patch_nca_fs_ctx & & base_patch_nca_fs_ctx - > enabled ) ? base_patch_nca_fs_ctx : nca_fs_ctx ) ;
/* Initialize PartitionFS context. */
PartitionFileSystemContext * pfs_ctx = calloc ( 1 , sizeof ( PartitionFileSystemContext ) ) ;
if ( ! pfs_ctx )
{
consolePrint ( " pfs ctx alloc failed! \n " ) ;
goto end ;
}
if ( ! pfsInitializeContext ( pfs_ctx , pfs_nca_fs_ctx ) )
{
consolePrint ( " pfs initialize ctx failed! \n " ) ;
free ( pfs_ctx ) ;
goto end ;
}
* out_fs_ctx = pfs_ctx ;
} else {
/* Select the right base/patch NCA FS section contexts. */
NcaFsSectionContext * base_nca_fs_ctx = ( section_type = = NcaFsSectionType_PatchRomFs ? base_patch_nca_fs_ctx : nca_fs_ctx ) ;
NcaFsSectionContext * patch_nca_fs_ctx = ( section_type = = NcaFsSectionType_PatchRomFs ? nca_fs_ctx : base_patch_nca_fs_ctx ) ;
/* Initialize RomFS context. */
RomFileSystemContext * romfs_ctx = calloc ( 1 , sizeof ( RomFileSystemContext ) ) ;
if ( ! romfs_ctx )
{
consolePrint ( " romfs ctx alloc failed! \n " ) ;
goto end ;
}
if ( ! romfsInitializeContext ( romfs_ctx , base_nca_fs_ctx , patch_nca_fs_ctx ) )
{
consolePrint ( " romfs initialize ctx failed! \n " ) ;
free ( romfs_ctx ) ;
goto end ;
}
* out_fs_ctx = romfs_ctx ;
}
/* Update output pointers. */
* out_section_type = section_type ;
* out_use_layeredfs_dir = use_layeredfs_dir ;
* out_base_patch_nca_ctx = base_patch_nca_ctx ;
success = true ;
end :
if ( ! success & & base_patch_nca_ctx ) free ( base_patch_nca_ctx ) ;
return success ;
}
static bool saveRawPartitionFsSection ( PartitionFileSystemContext * pfs_ctx , bool use_layeredfs_dir )
{
u64 free_space = 0 ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-12-26 00:25:29 +00:00
PfsThreadData pfs_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( pfs_thread_data . shared_thread_data ) ;
NcaFsSectionContext * nca_fs_ctx = pfs_ctx - > nca_fs_ctx ;
NcaContext * nca_ctx = nca_fs_ctx - > nca_ctx ;
u64 title_id = nca_ctx - > title_id ;
u8 title_type = nca_ctx - > title_type ;
char subdir [ 0x20 ] = { 0 } , * filename = NULL ;
u32 dev_idx = g_storageMenuElementOption . selected ;
bool success = false ;
pfs_thread_data . pfs_ctx = pfs_ctx ;
pfs_thread_data . use_layeredfs_dir = use_layeredfs_dir ;
shared_thread_data - > total_size = pfs_ctx - > size ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) pfs_ctx - > size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " raw partitionfs section size: 0x%lX (%s) \n " , pfs_ctx - > size , size_str ) ;
2023-12-26 00:25:29 +00:00
if ( use_layeredfs_dir )
{
/* Only use base title IDs if we're dealing with patches. */
title_id = ( title_type = = NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId ( title_id ) : \
( title_type = = NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId ( title_id ) : title_id ) ) ;
filename = generateOutputLayeredFsFileName ( title_id + nca_ctx - > id_offset , NULL , " exefs.nsp " ) ;
} else {
2024-11-02 23:49:07 +00:00
snprintf ( subdir , MAX_ELEMENTS ( subdir ) , NCA_FS_SUBDIR " /%s/Raw " , nca_ctx - > storage_id = = NcmStorageId_BuiltInSystem ? " System " : " User " ) ;
2023-12-26 00:25:29 +00:00
snprintf ( path , MAX_ELEMENTS ( path ) , " /%s #%u/%u.nsp " , titleGetNcmContentTypeName ( nca_ctx - > content_type ) , nca_ctx - > id_offset , nca_fs_ctx - > section_idx ) ;
TitleInfo * title_info = ( title_id = = g_ncaUserTitleInfo - > meta_key . id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo ) ;
filename = generateOutputTitleFileName ( title_info , subdir , path ) ;
}
if ( ! filename ) goto end ;
if ( dev_idx = = 1 )
{
if ( ! usbSendFileProperties ( shared_thread_data - > total_size , filename ) )
{
consolePrint ( " failed to send file properties for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
goto end ;
}
if ( shared_thread_data - > total_size > = free_space )
{
consolePrint ( " dump size exceeds free space \n " ) ;
goto end ;
}
utilsCreateDirectoryTree ( filename , false ) ;
if ( dev_idx = = 0 )
{
if ( shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( filename ) )
{
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
goto end ;
}
}
shared_thread_data - > fp = fopen ( filename , " wb " ) ;
if ( ! shared_thread_data - > fp )
{
consolePrint ( " failed to open \" %s \" for writing! \n " , filename ) ;
goto end ;
}
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) shared_thread_data - > total_size ) ;
}
consoleRefresh ( ) ;
success = spanDumpThreads ( rawPartitionFsReadThreadFunc , genericWriteThreadFunc , & pfs_thread_data ) ;
if ( success )
{
consolePrint ( " successfully saved raw partitionfs section as \" %s \" \n " , filename ) ;
consoleRefresh ( ) ;
}
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( ! success & & dev_idx ! = 1 )
{
if ( dev_idx = = 0 )
{
utilsRemoveConcatenationFile ( filename ) ;
utilsCommitSdCardFileSystemChanges ( ) ;
} else {
remove ( filename ) ;
}
}
}
if ( filename ) free ( filename ) ;
return success ;
}
static bool saveExtractedPartitionFsSection ( PartitionFileSystemContext * pfs_ctx , bool use_layeredfs_dir )
{
u64 data_size = 0 ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-12-26 00:25:29 +00:00
PfsThreadData pfs_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( pfs_thread_data . shared_thread_data ) ;
bool success = false ;
if ( ! pfsGetTotalDataSize ( pfs_ctx , & data_size ) )
{
consolePrint ( " failed to calculate extracted partitionfs section size! \n " ) ;
goto end ;
}
if ( ! data_size )
{
consolePrint ( " partitionfs section is empty! \n " ) ;
goto end ;
}
pfs_thread_data . pfs_ctx = pfs_ctx ;
pfs_thread_data . use_layeredfs_dir = use_layeredfs_dir ;
shared_thread_data - > total_size = data_size ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) data_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " extracted partitionfs section size: 0x%lX (%s) \n " , data_size , size_str ) ;
2023-12-26 00:25:29 +00:00
consoleRefresh ( ) ;
success = spanDumpThreads ( extractedPartitionFsReadThreadFunc , genericWriteThreadFunc , & pfs_thread_data ) ;
end :
return success ;
}
static bool saveRawRomFsSection ( RomFileSystemContext * romfs_ctx , bool use_layeredfs_dir )
{
u64 free_space = 0 ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-12-26 00:25:29 +00:00
RomFsThreadData romfs_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( romfs_thread_data . shared_thread_data ) ;
NcaFsSectionContext * nca_fs_ctx = romfs_ctx - > default_storage_ctx - > nca_fs_ctx ;
NcaContext * nca_ctx = nca_fs_ctx - > nca_ctx ;
u64 title_id = nca_ctx - > title_id ;
u8 title_type = nca_ctx - > title_type ;
char subdir [ 0x20 ] = { 0 } , * filename = NULL ;
u32 dev_idx = g_storageMenuElementOption . selected ;
bool success = false ;
romfs_thread_data . romfs_ctx = romfs_ctx ;
romfs_thread_data . use_layeredfs_dir = use_layeredfs_dir ;
shared_thread_data - > total_size = romfs_ctx - > size ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) romfs_ctx - > size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " raw romfs section size: 0x%lX (%s) \n " , romfs_ctx - > size , size_str ) ;
2023-12-26 00:25:29 +00:00
if ( use_layeredfs_dir )
{
/* Only use base title IDs if we're dealing with patches. */
title_id = ( title_type = = NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId ( title_id ) : \
( title_type = = NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId ( title_id ) : title_id ) ) ;
filename = generateOutputLayeredFsFileName ( title_id + nca_ctx - > id_offset , NULL , " romfs.bin " ) ;
} else {
2024-11-02 23:49:07 +00:00
snprintf ( subdir , MAX_ELEMENTS ( subdir ) , NCA_FS_SUBDIR " /%s/Raw " , nca_ctx - > storage_id = = NcmStorageId_BuiltInSystem ? " System " : " User " ) ;
2023-12-26 00:25:29 +00:00
snprintf ( path , MAX_ELEMENTS ( path ) , " /%s #%u/%u.bin " , titleGetNcmContentTypeName ( nca_ctx - > content_type ) , nca_ctx - > id_offset , nca_fs_ctx - > section_idx ) ;
TitleInfo * title_info = ( title_id = = g_ncaUserTitleInfo - > meta_key . id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo ) ;
filename = generateOutputTitleFileName ( title_info , subdir , path ) ;
}
if ( ! filename ) goto end ;
if ( dev_idx = = 1 )
{
if ( ! usbSendFileProperties ( shared_thread_data - > total_size , filename ) )
{
consolePrint ( " failed to send file properties for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
goto end ;
}
if ( shared_thread_data - > total_size > = free_space )
{
consolePrint ( " dump size exceeds free space \n " ) ;
goto end ;
}
utilsCreateDirectoryTree ( filename , false ) ;
if ( dev_idx = = 0 )
{
if ( shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( filename ) )
{
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & shared_thread_data - > total_size > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
goto end ;
}
}
shared_thread_data - > fp = fopen ( filename , " wb " ) ;
if ( ! shared_thread_data - > fp )
{
consolePrint ( " failed to open \" %s \" for writing! \n " , filename ) ;
goto end ;
}
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) shared_thread_data - > total_size ) ;
}
consoleRefresh ( ) ;
success = spanDumpThreads ( rawRomFsReadThreadFunc , genericWriteThreadFunc , & romfs_thread_data ) ;
if ( success )
{
consolePrint ( " successfully saved raw romfs section as \" %s \" \n " , filename ) ;
consoleRefresh ( ) ;
}
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( ! success & & dev_idx ! = 1 )
{
if ( dev_idx = = 0 )
{
utilsRemoveConcatenationFile ( filename ) ;
utilsCommitSdCardFileSystemChanges ( ) ;
} else {
remove ( filename ) ;
}
}
}
if ( filename ) free ( filename ) ;
return success ;
}
static bool saveExtractedRomFsSection ( RomFileSystemContext * romfs_ctx , bool use_layeredfs_dir )
{
u64 data_size = 0 ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-12-26 00:25:29 +00:00
RomFsThreadData romfs_thread_data = { 0 } ;
SharedThreadData * shared_thread_data = & ( romfs_thread_data . shared_thread_data ) ;
bool success = false ;
if ( ! romfsGetTotalDataSize ( romfs_ctx , false , & data_size ) )
{
consolePrint ( " failed to calculate extracted romfs section size! \n " ) ;
goto end ;
}
if ( ! data_size )
{
consolePrint ( " romfs section is empty! \n " ) ;
goto end ;
}
romfs_thread_data . romfs_ctx = romfs_ctx ;
romfs_thread_data . use_layeredfs_dir = use_layeredfs_dir ;
shared_thread_data - > total_size = data_size ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) data_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " extracted romfs section size: 0x%lX (%s) \n " , data_size , size_str ) ;
2023-12-26 00:25:29 +00:00
consoleRefresh ( ) ;
success = spanDumpThreads ( extractedRomFsReadThreadFunc , genericWriteThreadFunc , & romfs_thread_data ) ;
end :
return success ;
}
static void xciReadThreadFunc ( void * arg )
{
void * buf1 = NULL , * buf2 = NULL ;
XciThreadData * xci_thread_data = ( XciThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( xci_thread_data - > shared_thread_data ) ;
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
if ( ! shared_thread_data - > total_size | | ! buf1 | | ! buf2 )
{
shared_thread_data - > read_error = true ;
goto end ;
}
shared_thread_data - > data = NULL ;
shared_thread_data - > data_size = 0 ;
bool prepend_key_area = ( bool ) getGameCardPrependKeyAreaOption ( ) ;
bool keep_certificate = ( bool ) getGameCardKeepCertificateOption ( ) ;
bool calculate_checksum = ( bool ) getGameCardCalculateChecksumOption ( ) ;
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < shared_thread_data - > total_size ; offset + = blksize )
{
if ( blksize > ( shared_thread_data - > total_size - offset ) ) blksize = ( shared_thread_data - > total_size - offset ) ;
/* Check if the transfer has been cancelled by the user */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current data chunk */
shared_thread_data - > read_error = ! gamecardReadStorage ( buf1 , blksize , offset ) ;
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Remove certificate */
2024-04-19 14:58:16 +01:00
if ( ! keep_certificate & & offset = = 0 ) memset ( ( u8 * ) buf1 + GAMECARD_CERT_OFFSET , 0xFF , sizeof ( FsGameCardCertificate ) ) ;
2023-12-26 00:25:29 +00:00
/* Update checksum */
if ( calculate_checksum )
{
xci_thread_data - > xci_crc = crc32CalculateWithSeed ( xci_thread_data - > xci_crc , buf1 , blksize ) ;
if ( prepend_key_area ) xci_thread_data - > full_xci_crc = crc32CalculateWithSeed ( xci_thread_data - > full_xci_crc , buf1 , blksize ) ;
}
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
end :
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
static void rawHfsReadThreadFunc ( void * arg )
{
void * buf1 = NULL , * buf2 = NULL ;
HfsThreadData * hfs_thread_data = ( HfsThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( hfs_thread_data - > shared_thread_data ) ;
HashFileSystemContext * hfs_ctx = hfs_thread_data - > hfs_ctx ;
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
if ( ! shared_thread_data - > total_size | | ! hfs_ctx | | ! buf1 | | ! buf2 )
{
shared_thread_data - > read_error = true ;
goto end ;
}
shared_thread_data - > data = NULL ;
shared_thread_data - > data_size = 0 ;
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < shared_thread_data - > total_size ; offset + = blksize )
{
if ( blksize > ( shared_thread_data - > total_size - offset ) ) blksize = ( shared_thread_data - > total_size - offset ) ;
/* Check if the transfer has been cancelled by the user */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current data chunk */
shared_thread_data - > read_error = ! hfsReadPartitionData ( hfs_ctx , buf1 , blksize , offset ) ;
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
end :
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
static void extractedHfsReadThreadFunc ( void * arg )
{
void * buf1 = NULL , * buf2 = NULL ;
HfsThreadData * hfs_thread_data = ( HfsThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( hfs_thread_data - > shared_thread_data ) ;
HashFileSystemContext * hfs_ctx = hfs_thread_data - > hfs_ctx ;
u32 hfs_entry_count = hfsGetEntryCount ( hfs_ctx ) ;
char hfs_path [ FS_MAX_PATH ] = { 0 } , * filename = NULL ;
size_t filename_len = 0 ;
HashFileSystemEntry * hfs_entry = NULL ;
char * hfs_entry_name = NULL ;
u64 free_space = 0 ;
u32 dev_idx = g_storageMenuElementOption . selected ;
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
snprintf ( hfs_path , MAX_ELEMENTS ( hfs_path ) , " /%s " , hfs_ctx - > name ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( HFS_SUBDIR " /Extracted " , hfs_path , true ) ;
2023-12-26 00:25:29 +00:00
filename_len = ( filename ? strlen ( filename ) : 0 ) ;
if ( ! shared_thread_data - > total_size | | ! hfs_entry_count | | ! buf1 | | ! buf2 | | ! filename )
{
shared_thread_data - > read_error = true ;
goto end ;
}
if ( dev_idx ! = 1 )
{
if ( ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
shared_thread_data - > read_error = true ;
}
if ( ! shared_thread_data - > read_error & & shared_thread_data - > total_size > = free_space )
{
consolePrint ( " dump size exceeds free space \n " ) ;
shared_thread_data - > read_error = true ;
}
} else {
if ( ! usbStartExtractedFsDump ( shared_thread_data - > total_size , filename ) )
{
consolePrint ( " failed to send extracted fs info to host \n " ) ;
shared_thread_data - > read_error = true ;
}
}
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
goto end ;
}
/* Loop through all file entries. */
for ( u32 i = 0 ; i < hfs_entry_count ; i + + )
{
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
if ( dev_idx ! = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
/* Close file. */
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
}
}
/* Retrieve Hash FS file entry information. */
shared_thread_data - > read_error = ( ( hfs_entry = hfsGetEntryByIndex ( hfs_ctx , i ) ) = = NULL | | ( hfs_entry_name = hfsGetEntryName ( hfs_ctx , hfs_entry ) ) = = NULL ) ;
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Generate output path. */
snprintf ( hfs_path , MAX_ELEMENTS ( hfs_path ) , " %s/%s " , filename , hfs_entry_name ) ;
utilsReplaceIllegalCharacters ( hfs_path + filename_len + 1 , dev_idx = = 0 ) ;
if ( dev_idx = = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
/* Send current file properties */
shared_thread_data - > read_error = ! usbSendFileProperties ( hfs_entry - > size , hfs_path ) ;
} else {
/* Create directory tree. */
utilsCreateDirectoryTree ( hfs_path , false ) ;
if ( dev_idx = = 0 )
{
/* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */
if ( hfs_entry - > size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( hfs_path ) )
{
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , hfs_path ) ;
shared_thread_data - > read_error = true ;
}
} else {
/* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & hfs_entry - > size > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
shared_thread_data - > read_error = true ;
}
}
if ( ! shared_thread_data - > read_error )
{
/* Open output file. */
shared_thread_data - > read_error = ( ( shared_thread_data - > fp = fopen ( hfs_path , " wb " ) ) = = NULL ) ;
if ( ! shared_thread_data - > read_error )
{
/* Set file size. */
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) hfs_entry - > size ) ;
} else {
consolePrint ( " failed to open \" %s \" for writing! \n " , hfs_path ) ;
}
}
}
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < hfs_entry - > size ; offset + = blksize )
{
if ( blksize > ( hfs_entry - > size - offset ) ) blksize = ( hfs_entry - > size - offset ) ;
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current file data chunk. */
shared_thread_data - > read_error = ! hfsReadEntryData ( hfs_ctx , hfs_entry , buf1 , blksize , offset ) ;
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
2023-05-30 22:34:34 +01:00
}
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) break ;
2023-05-30 22:34:34 +01:00
}
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > read_error & & ! shared_thread_data - > write_error & & ! shared_thread_data - > transfer_cancelled )
{
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( dev_idx = = 1 ) usbEndExtractedFsDump ( ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
consolePrint ( " successfully saved extracted hfs partition data to \" %s \" \n " , filename ) ;
2023-05-30 22:34:34 +01:00
consoleRefresh ( ) ;
}
2023-05-30 00:22:12 +01:00
2023-05-30 22:34:34 +01:00
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) & & dev_idx ! = 1 )
2023-05-30 22:34:34 +01:00
{
2023-12-26 00:25:29 +00:00
utilsDeleteDirectoryRecursively ( filename ) ;
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
2023-05-30 22:34:34 +01:00
}
}
2023-05-30 00:22:12 +01:00
2023-05-30 22:34:34 +01:00
if ( filename ) free ( filename ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
threadExit ( ) ;
2023-05-30 22:34:34 +01:00
}
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
static void ncaReadThreadFunc ( void * arg )
2023-05-30 00:22:12 +01:00
{
void * buf1 = NULL , * buf2 = NULL ;
2023-12-26 00:25:29 +00:00
NcaThreadData * nca_thread_data = ( NcaThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( nca_thread_data - > shared_thread_data ) ;
NcaContext * nca_ctx = nca_thread_data - > nca_ctx ;
2023-05-30 00:22:12 +01:00
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > total_size | | ! nca_ctx | | ! buf1 | | ! buf2 )
2023-05-30 00:22:12 +01:00
{
shared_thread_data - > read_error = true ;
goto end ;
}
shared_thread_data - > data = NULL ;
shared_thread_data - > data_size = 0 ;
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < shared_thread_data - > total_size ; offset + = blksize )
{
if ( blksize > ( shared_thread_data - > total_size - offset ) ) blksize = ( shared_thread_data - > total_size - offset ) ;
/* Check if the transfer has been cancelled by the user */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current data chunk */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ! ncaReadContentFile ( nca_ctx , buf1 , blksize , offset ) ;
2023-05-30 00:22:12 +01:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
end :
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
2023-12-26 00:25:29 +00:00
static void rawPartitionFsReadThreadFunc ( void * arg )
2023-05-30 00:22:12 +01:00
{
void * buf1 = NULL , * buf2 = NULL ;
2023-12-26 00:25:29 +00:00
PfsThreadData * pfs_thread_data = ( PfsThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( pfs_thread_data - > shared_thread_data ) ;
PartitionFileSystemContext * pfs_ctx = pfs_thread_data - > pfs_ctx ;
2023-05-30 00:22:12 +01:00
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > total_size | | ! pfs_ctx | | ! buf1 | | ! buf2 )
2023-05-30 00:22:12 +01:00
{
shared_thread_data - > read_error = true ;
goto end ;
}
shared_thread_data - > data = NULL ;
shared_thread_data - > data_size = 0 ;
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < shared_thread_data - > total_size ; offset + = blksize )
{
if ( blksize > ( shared_thread_data - > total_size - offset ) ) blksize = ( shared_thread_data - > total_size - offset ) ;
/* Check if the transfer has been cancelled by the user */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current data chunk */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ! pfsReadPartitionData ( pfs_ctx , buf1 , blksize , offset ) ;
2023-05-30 00:22:12 +01:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
end :
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
2023-12-26 00:25:29 +00:00
static void extractedPartitionFsReadThreadFunc ( void * arg )
2023-05-30 00:22:12 +01:00
{
void * buf1 = NULL , * buf2 = NULL ;
2023-12-26 00:25:29 +00:00
PfsThreadData * pfs_thread_data = ( PfsThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( pfs_thread_data - > shared_thread_data ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
PartitionFileSystemContext * pfs_ctx = pfs_thread_data - > pfs_ctx ;
u32 pfs_entry_count = pfsGetEntryCount ( pfs_ctx ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
char pfs_path [ FS_MAX_PATH ] = { 0 } , subdir [ 0x20 ] = { 0 } , * filename = NULL ;
2023-05-30 00:22:12 +01:00
size_t filename_len = 0 ;
2023-12-26 00:25:29 +00:00
PartitionFileSystemEntry * pfs_entry = NULL ;
char * pfs_entry_name = NULL ;
NcaFsSectionContext * nca_fs_ctx = pfs_ctx - > nca_fs_ctx ;
NcaContext * nca_ctx = nca_fs_ctx - > nca_ctx ;
u64 title_id = nca_ctx - > title_id ;
u8 title_type = nca_ctx - > title_type ;
2023-05-30 00:22:12 +01:00
u64 free_space = 0 ;
u32 dev_idx = g_storageMenuElementOption . selected ;
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
2023-12-26 00:25:29 +00:00
if ( pfs_thread_data - > use_layeredfs_dir )
{
/* Only use base title IDs if we're dealing with patches. */
title_id = ( title_type = = NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId ( title_id ) : \
( title_type = = NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId ( title_id ) : title_id ) ) ;
filename = generateOutputLayeredFsFileName ( title_id + nca_ctx - > id_offset , NULL , " exefs " ) ;
} else {
2024-11-02 23:49:07 +00:00
snprintf ( subdir , MAX_ELEMENTS ( subdir ) , NCA_FS_SUBDIR " /%s/Extracted " , nca_ctx - > storage_id = = NcmStorageId_BuiltInSystem ? " System " : " User " ) ;
2023-12-26 00:25:29 +00:00
snprintf ( pfs_path , MAX_ELEMENTS ( pfs_path ) , " /%s #%u/%u " , titleGetNcmContentTypeName ( nca_ctx - > content_type ) , nca_ctx - > id_offset , nca_fs_ctx - > section_idx ) ;
TitleInfo * title_info = ( title_id = = g_ncaUserTitleInfo - > meta_key . id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo ) ;
filename = generateOutputTitleFileName ( title_info , subdir , pfs_path ) ;
}
2023-05-30 00:22:12 +01:00
filename_len = ( filename ? strlen ( filename ) : 0 ) ;
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > total_size | | ! pfs_entry_count | | ! buf1 | | ! buf2 | | ! filename )
2023-05-30 00:22:12 +01:00
{
shared_thread_data - > read_error = true ;
goto end ;
}
if ( dev_idx ! = 1 )
{
if ( ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
shared_thread_data - > read_error = true ;
}
2023-11-11 20:39:41 +00:00
if ( ! shared_thread_data - > read_error & & shared_thread_data - > total_size > = free_space )
2023-05-30 00:22:12 +01:00
{
consolePrint ( " dump size exceeds free space \n " ) ;
shared_thread_data - > read_error = true ;
2023-11-11 20:39:41 +00:00
}
} else {
if ( ! usbStartExtractedFsDump ( shared_thread_data - > total_size , filename ) )
{
consolePrint ( " failed to send extracted fs info to host \n " ) ;
shared_thread_data - > read_error = true ;
2023-05-30 00:22:12 +01:00
}
}
2023-11-11 20:39:41 +00:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
goto end ;
}
2023-05-30 00:22:12 +01:00
/* Loop through all file entries. */
2023-12-26 00:25:29 +00:00
for ( u32 i = 0 ; i < pfs_entry_count ; i + + )
2023-05-30 00:22:12 +01:00
{
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
if ( dev_idx ! = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
/* Close file. */
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
2023-12-26 00:25:29 +00:00
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
2023-05-30 00:22:12 +01:00
}
}
2023-12-26 00:25:29 +00:00
/* Retrieve Partition FS file entry information. */
shared_thread_data - > read_error = ( ( pfs_entry = pfsGetEntryByIndex ( pfs_ctx , i ) ) = = NULL | | ( pfs_entry_name = pfsGetEntryName ( pfs_ctx , pfs_entry ) ) = = NULL ) ;
2023-05-30 00:22:12 +01:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Generate output path. */
2023-12-26 00:25:29 +00:00
snprintf ( pfs_path , MAX_ELEMENTS ( pfs_path ) , " %s/%s " , filename , pfs_entry_name ) ;
utilsReplaceIllegalCharacters ( pfs_path + filename_len + 1 , dev_idx = = 0 ) ;
2023-05-30 00:22:12 +01:00
if ( dev_idx = = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
/* Send current file properties */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ! usbSendFileProperties ( pfs_entry - > size , pfs_path ) ;
2023-05-30 00:22:12 +01:00
} else {
/* Create directory tree. */
2023-12-26 00:25:29 +00:00
utilsCreateDirectoryTree ( pfs_path , false ) ;
2023-05-30 00:22:12 +01:00
if ( dev_idx = = 0 )
{
/* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */
2023-12-26 00:25:29 +00:00
if ( pfs_entry - > size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( pfs_path ) )
2023-05-30 00:22:12 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , pfs_path ) ;
2023-05-30 00:22:12 +01:00
shared_thread_data - > read_error = true ;
}
} else {
/* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */
2023-12-26 00:25:29 +00:00
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & pfs_entry - > size > FAT32_FILESIZE_LIMIT )
2023-05-30 00:22:12 +01:00
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
shared_thread_data - > read_error = true ;
}
}
if ( ! shared_thread_data - > read_error )
{
/* Open output file. */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ( ( shared_thread_data - > fp = fopen ( pfs_path , " wb " ) ) = = NULL ) ;
2023-05-30 00:22:12 +01:00
if ( ! shared_thread_data - > read_error )
{
/* Set file size. */
2023-12-20 23:39:56 +00:00
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
2023-12-26 00:25:29 +00:00
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) pfs_entry - > size ) ;
2023-05-30 00:22:12 +01:00
} else {
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to open \" %s \" for writing! \n " , pfs_path ) ;
2023-05-30 00:22:12 +01:00
}
}
}
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2023-12-26 00:25:29 +00:00
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < pfs_entry - > size ; offset + = blksize )
2023-05-30 00:22:12 +01:00
{
2023-12-26 00:25:29 +00:00
if ( blksize > ( pfs_entry - > size - offset ) ) blksize = ( pfs_entry - > size - offset ) ;
2023-05-30 00:22:12 +01:00
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current file data chunk. */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ! pfsReadEntryData ( pfs_ctx , pfs_entry , buf1 , blksize , offset ) ;
2023-05-30 00:22:12 +01:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
2023-12-26 00:25:29 +00:00
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
2023-05-30 00:22:12 +01:00
condvarWakeAll ( & g_writeCondvar ) ;
}
2023-12-26 00:25:29 +00:00
if ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) break ;
}
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > read_error & & ! shared_thread_data - > write_error & & ! shared_thread_data - > transfer_cancelled )
{
/* Wait until the previous file data chunk has been written. */
2023-05-30 00:22:12 +01:00
mutexLock ( & g_fileMutex ) ;
2023-12-26 00:25:29 +00:00
if ( shared_thread_data - > data_size ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( dev_idx = = 1 ) usbEndExtractedFsDump ( ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
consolePrint ( " successfully saved extracted partitionfs section data to \" %s \" \n " , filename ) ;
consoleRefresh ( ) ;
}
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
if ( ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) & & dev_idx ! = 1 )
{
utilsDeleteDirectoryRecursively ( filename ) ;
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
}
2023-05-30 00:22:12 +01:00
}
2023-12-26 00:25:29 +00:00
if ( filename ) free ( filename ) ;
2023-05-30 00:22:12 +01:00
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
2023-12-26 00:25:29 +00:00
static void rawRomFsReadThreadFunc ( void * arg )
2023-05-30 00:22:12 +01:00
{
void * buf1 = NULL , * buf2 = NULL ;
2023-12-26 00:25:29 +00:00
RomFsThreadData * romfs_thread_data = ( RomFsThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( romfs_thread_data - > shared_thread_data ) ;
RomFileSystemContext * romfs_ctx = romfs_thread_data - > romfs_ctx ;
2023-05-30 00:22:12 +01:00
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > total_size | | ! romfs_ctx | | ! buf1 | | ! buf2 )
2023-05-30 00:22:12 +01:00
{
shared_thread_data - > read_error = true ;
goto end ;
}
shared_thread_data - > data = NULL ;
shared_thread_data - > data_size = 0 ;
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < shared_thread_data - > total_size ; offset + = blksize )
{
if ( blksize > ( shared_thread_data - > total_size - offset ) ) blksize = ( shared_thread_data - > total_size - offset ) ;
/* Check if the transfer has been cancelled by the user */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current data chunk */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ! romfsReadFileSystemData ( romfs_ctx , buf1 , blksize , offset ) ;
2023-05-30 00:22:12 +01:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
end :
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
2023-12-26 00:25:29 +00:00
static void extractedRomFsReadThreadFunc ( void * arg )
2023-05-30 00:22:12 +01:00
{
void * buf1 = NULL , * buf2 = NULL ;
2023-12-26 00:25:29 +00:00
RomFsThreadData * romfs_thread_data = ( RomFsThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( romfs_thread_data - > shared_thread_data ) ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
RomFileSystemContext * romfs_ctx = romfs_thread_data - > romfs_ctx ;
RomFileSystemFileEntry * romfs_file_entry = NULL ;
u64 cur_entry_offset = 0 ;
2023-05-30 00:22:12 +01:00
2023-12-26 00:25:29 +00:00
char romfs_path [ FS_MAX_PATH ] = { 0 } , subdir [ 0x20 ] = { 0 } , * filename = NULL ;
2023-05-30 00:22:12 +01:00
size_t filename_len = 0 ;
2023-12-26 00:25:29 +00:00
NcaFsSectionContext * nca_fs_ctx = romfs_ctx - > default_storage_ctx - > nca_fs_ctx ;
2023-07-17 19:20:49 +01:00
NcaContext * nca_ctx = nca_fs_ctx - > nca_ctx ;
2023-05-30 00:22:12 +01:00
u64 title_id = nca_ctx - > title_id ;
u8 title_type = nca_ctx - > title_type ;
u64 free_space = 0 ;
u32 dev_idx = g_storageMenuElementOption . selected ;
2023-12-26 00:25:29 +00:00
u8 romfs_illegal_char_replace_type = ( dev_idx ! = 0 ? RomFileSystemPathIllegalCharReplaceType_IllegalFsChars : RomFileSystemPathIllegalCharReplaceType_KeepAsciiCharsOnly ) ;
2023-05-30 00:22:12 +01:00
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
2023-12-26 00:25:29 +00:00
if ( romfs_thread_data - > use_layeredfs_dir )
2023-05-30 00:22:12 +01:00
{
/* Only use base title IDs if we're dealing with patches. */
2023-07-17 19:20:49 +01:00
title_id = ( title_type = = NcmContentMetaType_Patch ? titleGetApplicationIdByPatchId ( title_id ) : \
( title_type = = NcmContentMetaType_DataPatch ? titleGetAddOnContentIdByDataPatchId ( title_id ) : title_id ) ) ;
2023-12-26 00:25:29 +00:00
filename = generateOutputLayeredFsFileName ( title_id + nca_ctx - > id_offset , NULL , " romfs " ) ;
2023-05-30 00:22:12 +01:00
} else {
2024-11-02 23:49:07 +00:00
snprintf ( subdir , MAX_ELEMENTS ( subdir ) , NCA_FS_SUBDIR " /%s/Extracted " , nca_ctx - > storage_id = = NcmStorageId_BuiltInSystem ? " System " : " User " ) ;
2023-12-26 00:25:29 +00:00
snprintf ( romfs_path , MAX_ELEMENTS ( romfs_path ) , " /%s #%u/%u " , titleGetNcmContentTypeName ( nca_ctx - > content_type ) , nca_ctx - > id_offset , nca_fs_ctx - > section_idx ) ;
2023-05-30 00:22:12 +01:00
TitleInfo * title_info = ( title_id = = g_ncaUserTitleInfo - > meta_key . id ? g_ncaUserTitleInfo : g_ncaBasePatchTitleInfo ) ;
2023-12-26 00:25:29 +00:00
filename = generateOutputTitleFileName ( title_info , subdir , romfs_path ) ;
2023-05-30 00:22:12 +01:00
}
filename_len = ( filename ? strlen ( filename ) : 0 ) ;
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > total_size | | ! buf1 | | ! buf2 | | ! filename )
2023-05-30 00:22:12 +01:00
{
shared_thread_data - > read_error = true ;
goto end ;
}
2023-12-26 00:25:29 +00:00
snprintf ( romfs_path , MAX_ELEMENTS ( romfs_path ) , " %s " , filename ) ;
2023-05-30 00:22:12 +01:00
if ( dev_idx ! = 1 )
{
if ( ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
shared_thread_data - > read_error = true ;
}
2023-11-11 20:39:41 +00:00
if ( ! shared_thread_data - > read_error & & shared_thread_data - > total_size > = free_space )
2023-05-30 00:22:12 +01:00
{
consolePrint ( " dump size exceeds free space \n " ) ;
shared_thread_data - > read_error = true ;
}
2023-11-11 20:39:41 +00:00
} else {
if ( ! usbStartExtractedFsDump ( shared_thread_data - > total_size , filename ) )
{
consolePrint ( " failed to send extracted fs info to host \n " ) ;
shared_thread_data - > read_error = true ;
}
}
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
goto end ;
2023-05-30 00:22:12 +01:00
}
/* Loop through all file entries. */
2023-12-26 00:25:29 +00:00
while ( shared_thread_data - > data_written < shared_thread_data - > total_size & & cur_entry_offset < romfs_ctx - > file_table_size )
2023-05-30 00:22:12 +01:00
{
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
if ( dev_idx ! = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
2023-05-24 20:05:34 +01:00
/* Close file. */
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
2023-12-26 00:25:29 +00:00
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
2023-05-24 20:05:34 +01:00
}
}
2023-12-26 00:25:29 +00:00
/* Retrieve RomFS file entry information and generate output path. */
shared_thread_data - > read_error = ( ! ( romfs_file_entry = romfsGetFileEntryByOffset ( romfs_ctx , cur_entry_offset ) ) | | \
! romfsGeneratePathFromFileEntry ( romfs_ctx , romfs_file_entry , romfs_path + filename_len , sizeof ( romfs_path ) - filename_len , romfs_illegal_char_replace_type ) ) ;
2023-05-24 20:05:34 +01:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
if ( dev_idx = = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
/* Send current file properties */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ! usbSendFileProperties ( romfs_file_entry - > size , romfs_path ) ;
2023-05-24 20:05:34 +01:00
} else {
/* Create directory tree. */
2023-12-26 00:25:29 +00:00
utilsCreateDirectoryTree ( romfs_path , false ) ;
2023-05-24 20:05:34 +01:00
if ( dev_idx = = 0 )
{
/* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */
2023-12-26 00:25:29 +00:00
if ( romfs_file_entry - > size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( romfs_path ) )
2023-05-24 20:05:34 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , romfs_path ) ;
2023-05-24 20:05:34 +01:00
shared_thread_data - > read_error = true ;
}
} else {
/* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */
2023-12-26 00:25:29 +00:00
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & romfs_file_entry - > size > FAT32_FILESIZE_LIMIT )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
shared_thread_data - > read_error = true ;
}
}
if ( ! shared_thread_data - > read_error )
{
/* Open output file. */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ( ( shared_thread_data - > fp = fopen ( romfs_path , " wb " ) ) = = NULL ) ;
2023-05-24 20:05:34 +01:00
if ( ! shared_thread_data - > read_error )
{
/* Set file size. */
2023-12-20 23:39:56 +00:00
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
2023-12-26 00:25:29 +00:00
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) romfs_file_entry - > size ) ;
2023-05-24 20:05:34 +01:00
} else {
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to open \" %s \" for writing! \n " , romfs_path ) ;
2023-05-24 20:05:34 +01:00
}
}
}
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2023-12-26 00:25:29 +00:00
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < romfs_file_entry - > size ; offset + = blksize )
2023-05-24 20:05:34 +01:00
{
2023-12-26 00:25:29 +00:00
if ( blksize > ( romfs_file_entry - > size - offset ) ) blksize = ( romfs_file_entry - > size - offset ) ;
2023-05-24 20:05:34 +01:00
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current file data chunk. */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ! romfsReadFileEntryData ( romfs_ctx , romfs_file_entry , buf1 , blksize , offset ) ;
2023-05-24 20:05:34 +01:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
if ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) break ;
2023-12-26 00:25:29 +00:00
/* Get the offset for the next file entry. */
cur_entry_offset + = ALIGN_UP ( sizeof ( RomFileSystemFileEntry ) + romfs_file_entry - > name_length , ROMFS_TABLE_ENTRY_ALIGNMENT ) ;
2023-05-24 20:05:34 +01:00
}
if ( ! shared_thread_data - > read_error & & ! shared_thread_data - > write_error & & ! shared_thread_data - > transfer_cancelled )
{
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
2023-11-11 20:39:41 +00:00
if ( dev_idx = = 1 ) usbEndExtractedFsDump ( ) ;
2023-12-26 00:25:29 +00:00
consolePrint ( " successfully saved extracted romfs section data to \" %s \" \n " , filename ) ;
2023-05-24 20:05:34 +01:00
consoleRefresh ( ) ;
2023-05-30 22:34:34 +01:00
}
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) & & dev_idx ! = 1 )
{
utilsDeleteDirectoryRecursively ( filename ) ;
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
}
}
if ( filename ) free ( filename ) ;
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
2023-12-26 00:25:29 +00:00
static void fsBrowserFileReadThreadFunc ( void * arg )
2023-05-30 22:34:34 +01:00
{
void * buf1 = NULL , * buf2 = NULL ;
2023-12-26 00:25:29 +00:00
FsBrowserFileThreadData * fs_browser_thread_data = ( FsBrowserFileThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( fs_browser_thread_data - > shared_thread_data ) ;
FILE * src = fs_browser_thread_data - > src ;
2023-05-30 22:34:34 +01:00
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > total_size | | ! src | | ! buf1 | | ! buf2 )
2023-05-30 22:34:34 +01:00
{
shared_thread_data - > read_error = true ;
goto end ;
}
shared_thread_data - > data = NULL ;
shared_thread_data - > data_size = 0 ;
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < shared_thread_data - > total_size ; offset + = blksize )
{
if ( blksize > ( shared_thread_data - > total_size - offset ) ) blksize = ( shared_thread_data - > total_size - offset ) ;
/* Check if the transfer has been cancelled by the user */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current data chunk */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ( fread ( buf1 , 1 , blksize , src ) ! = blksize ) ;
2023-05-30 22:34:34 +01:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
end :
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
2023-12-26 00:25:29 +00:00
static void fsBrowserHighlightedEntriesReadThreadFunc ( void * arg )
2023-05-30 22:34:34 +01:00
{
void * buf1 = NULL , * buf2 = NULL ;
2023-12-26 00:25:29 +00:00
FsBrowserHighlightedEntriesThreadData * fs_browser_thread_data = ( FsBrowserHighlightedEntriesThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( fs_browser_thread_data - > shared_thread_data ) ;
2023-05-30 22:34:34 +01:00
2023-12-26 00:25:29 +00:00
const char * dir_path = fs_browser_thread_data - > dir_path ;
const FsBrowserEntry * entries = fs_browser_thread_data - > entries ;
u32 entries_count = fs_browser_thread_data - > entries_count ;
const char * base_out_path = fs_browser_thread_data - > base_out_path ;
2023-05-30 22:34:34 +01:00
u32 dev_idx = g_storageMenuElementOption . selected ;
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > total_size | | ! dir_path | | ! * dir_path | | ! entries | | ! entries_count | | ! base_out_path | | ! * base_out_path | | ! buf1 | | ! buf2 )
2023-05-30 22:34:34 +01:00
{
shared_thread_data - > read_error = true ;
goto end ;
}
if ( dev_idx ! = 1 )
{
2023-12-26 00:25:29 +00:00
u64 free_space = 0 ;
if ( ! utilsGetFileSystemStatsByPath ( base_out_path , NULL , & free_space ) )
2023-05-30 22:34:34 +01:00
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
shared_thread_data - > read_error = true ;
}
2023-11-11 20:39:41 +00:00
if ( ! shared_thread_data - > read_error & & shared_thread_data - > total_size > = free_space )
2023-05-30 22:34:34 +01:00
{
consolePrint ( " dump size exceeds free space \n " ) ;
shared_thread_data - > read_error = true ;
}
2023-11-11 20:39:41 +00:00
} else {
2023-12-26 00:25:29 +00:00
if ( ! usbStartExtractedFsDump ( shared_thread_data - > total_size , base_out_path ) )
2023-11-11 20:39:41 +00:00
{
consolePrint ( " failed to send extracted fs info to host \n " ) ;
shared_thread_data - > read_error = true ;
}
}
2023-12-26 00:25:29 +00:00
if ( ! shared_thread_data - > read_error )
{
/* Dump highlighted entries. */
fsBrowserHighlightedEntriesReadThreadLoop ( shared_thread_data , dir_path , entries , entries_count , base_out_path , buf1 , buf2 ) ;
if ( ! shared_thread_data - > read_error & & ! shared_thread_data - > write_error & & ! shared_thread_data - > transfer_cancelled )
{
if ( dev_idx = = 1 ) usbEndExtractedFsDump ( ) ;
consolePrint ( " successfully saved dumped data to \" %s \" \n " , base_out_path ) ;
consoleRefresh ( ) ;
}
} else {
condvarWakeAll ( & g_writeCondvar ) ;
}
end :
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
static bool fsBrowserHighlightedEntriesReadThreadLoop ( SharedThreadData * shared_thread_data , const char * dir_path , const FsBrowserEntry * entries , u32 entries_count , const char * base_out_path , void * buf1 , void * buf2 )
{
bool append_path_sep = ( dir_path [ strlen ( dir_path ) - 1 ] ! = ' / ' ) ;
u32 dev_idx = g_storageMenuElementOption . selected ;
bool is_topmost = ( entries & & entries_count ) ; /* If entry data is provided, it means we're dealing with the topmost directory. */
const char * dir_path_start = ( strchr ( dir_path , ' / ' ) + 1 ) ;
char * tmp_path = NULL ;
FILE * src = NULL ;
/* Allocate memory for our temporary path. */
tmp_path = calloc ( sizeof ( char ) , FS_MAX_PATH ) ;
if ( ( shared_thread_data - > read_error = ( tmp_path = = NULL ) ) )
2023-11-11 20:39:41 +00:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to allocate memory for path! \n " ) ;
2023-11-11 20:39:41 +00:00
condvarWakeAll ( & g_writeCondvar ) ;
goto end ;
2023-05-30 22:34:34 +01:00
}
2023-12-26 00:25:29 +00:00
/* Get directory entries, if needed. */
if ( ! is_topmost & & ( shared_thread_data - > read_error = ! fsBrowserGetDirEntries ( dir_path , ( FsBrowserEntry * * ) & entries , & entries_count ) ) )
2023-05-30 22:34:34 +01:00
{
2023-12-26 00:25:29 +00:00
condvarWakeAll ( & g_writeCondvar ) ;
goto end ;
}
/* Loop through all highlighted entries. */
for ( u32 i = 0 ; i < entries_count ; i + + )
{
/* Get current entry. */
const FsBrowserEntry * entry = & ( entries [ i ] ) ;
if ( is_topmost & & ! entry - > highlight ) continue ;
2023-05-30 22:34:34 +01:00
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
if ( dev_idx ! = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
/* Close file. */
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
2023-12-26 00:25:29 +00:00
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
2023-05-30 22:34:34 +01:00
}
}
2023-12-26 00:25:29 +00:00
/* Generate input path. */
snprintf ( tmp_path , FS_MAX_PATH , " %s%s%s " , dir_path , append_path_sep ? " / " : " " , entry - > dt . d_name ) ;
if ( entry - > dt . d_type = = DT_DIR )
{
/* Dump directory. */
if ( ! fsBrowserHighlightedEntriesReadThreadLoop ( shared_thread_data , tmp_path , NULL , 0 , base_out_path , buf1 , buf2 ) ) break ;
continue ;
}
/* Open input file. */
src = fopen ( tmp_path , " rb " ) ;
if ( ( shared_thread_data - > read_error = ( src = = NULL ) ) )
2023-05-30 22:34:34 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to open file \" %s \" for reading! \n " , tmp_path ) ;
2023-05-30 22:34:34 +01:00
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2023-12-26 00:25:29 +00:00
setvbuf ( src , NULL , _IONBF , 0 ) ;
/* Generate output path. */
if ( * dir_path_start )
{
snprintf ( tmp_path , FS_MAX_PATH , " %s/%s/%s " , base_out_path , dir_path_start , entry - > dt . d_name ) ;
} else {
snprintf ( tmp_path , FS_MAX_PATH , " %s/%s " , base_out_path , entry - > dt . d_name ) ;
}
2023-05-30 22:34:34 +01:00
if ( dev_idx = = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
/* Send current file properties */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ! usbSendFileProperties ( entry - > size , tmp_path ) ;
2023-05-30 22:34:34 +01:00
} else {
/* Create directory tree. */
2023-12-26 00:25:29 +00:00
utilsCreateDirectoryTree ( tmp_path , false ) ;
2023-05-30 22:34:34 +01:00
if ( dev_idx = = 0 )
{
/* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */
2023-12-26 00:25:29 +00:00
if ( entry - > size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( tmp_path ) )
2023-05-30 22:34:34 +01:00
{
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , tmp_path ) ;
2023-05-30 22:34:34 +01:00
shared_thread_data - > read_error = true ;
}
} else {
/* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */
2023-12-26 00:25:29 +00:00
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & entry - > size > FAT32_FILESIZE_LIMIT )
2023-05-30 22:34:34 +01:00
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
shared_thread_data - > read_error = true ;
}
}
if ( ! shared_thread_data - > read_error )
{
/* Open output file. */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ( ( shared_thread_data - > fp = fopen ( tmp_path , " wb " ) ) = = NULL ) ;
2023-05-30 22:34:34 +01:00
if ( ! shared_thread_data - > read_error )
{
/* Set file size. */
2023-12-20 23:39:56 +00:00
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
2023-12-26 00:25:29 +00:00
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) entry - > size ) ;
2023-05-30 22:34:34 +01:00
} else {
2023-12-26 00:25:29 +00:00
consolePrint ( " failed to open \" %s \" for writing! \n " , tmp_path ) ;
2023-05-30 22:34:34 +01:00
}
}
}
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
2023-12-26 00:25:29 +00:00
/* Dump file. */
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < entry - > size ; offset + = blksize )
2023-05-30 22:34:34 +01:00
{
2023-12-26 00:25:29 +00:00
if ( blksize > ( entry - > size - offset ) ) blksize = ( entry - > size - offset ) ;
2023-05-30 22:34:34 +01:00
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current file data chunk. */
2023-12-26 00:25:29 +00:00
shared_thread_data - > read_error = ( fread ( buf1 , 1 , blksize , src ) ! = blksize ) ;
2023-05-30 22:34:34 +01:00
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
2023-12-26 00:25:29 +00:00
/* Close input file. */
fclose ( src ) ;
src = NULL ;
2023-05-30 22:34:34 +01:00
2023-12-26 00:25:29 +00:00
if ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) break ;
2023-05-30 22:34:34 +01:00
}
if ( ! shared_thread_data - > read_error & & ! shared_thread_data - > write_error & & ! shared_thread_data - > transfer_cancelled )
{
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
2023-05-24 20:05:34 +01:00
}
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) & & dev_idx ! = 1 )
{
2023-12-26 00:25:29 +00:00
utilsDeleteDirectoryRecursively ( base_out_path ) ;
2023-05-24 20:05:34 +01:00
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
}
}
2023-12-26 00:25:29 +00:00
if ( src ) fclose ( src ) ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
if ( ! is_topmost & & entries ) free ( ( FsBrowserEntry * ) entries ) ;
2023-05-24 20:05:34 +01:00
2023-12-26 00:25:29 +00:00
if ( tmp_path ) free ( tmp_path ) ;
return ! shared_thread_data - > read_error ;
2023-05-24 20:05:34 +01:00
}
2024-10-27 11:23:46 +00:00
static void systemUpdateReadThreadFunc ( void * arg )
{
void * buf1 = NULL , * buf2 = NULL ;
SystemUpdateThreadData * sys_upd_thread_data = ( SystemUpdateThreadData * ) arg ;
SharedThreadData * shared_thread_data = & ( sys_upd_thread_data - > shared_thread_data ) ;
SystemUpdateDumpContext * sys_upd_dump_ctx = sys_upd_thread_data - > sys_upd_dump_ctx ;
char sys_upd_path [ FS_MAX_PATH ] = { 0 } , * filename = NULL ;
size_t filename_len = 0 ;
u64 free_space = 0 ;
u32 dev_idx = g_storageMenuElementOption . selected ;
u64 nca_filesize = 0 ;
char * nca_filename = NULL ;
buf1 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
buf2 = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ;
snprintf ( sys_upd_path , MAX_ELEMENTS ( sys_upd_path ) , " /%.*s (%s) " , ( int ) sizeof ( sys_upd_dump_ctx - > version_file . display_title ) ,
sys_upd_dump_ctx - > version_file . display_title , utilsIsDevelopmentUnit ( ) ? " Dev " : " Prod " ) ;
2024-11-02 23:49:07 +00:00
filename = generateOutputGameCardFileName ( SYSTEM_UPDATE_SUBDIR , sys_upd_path , false ) ;
2024-10-27 11:23:46 +00:00
filename_len = ( filename ? strlen ( filename ) : 0 ) ;
if ( ! shared_thread_data - > total_size | | ! buf1 | | ! buf2 | | ! filename )
{
shared_thread_data - > read_error = true ;
goto end ;
}
utilsReplaceIllegalCharacters ( strrchr ( filename , ' / ' ) + 1 , dev_idx = = 0 ) ;
if ( dev_idx ! = 1 )
{
if ( ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
shared_thread_data - > read_error = true ;
}
if ( ! shared_thread_data - > read_error & & shared_thread_data - > total_size > = free_space )
{
consolePrint ( " dump size exceeds free space \n " ) ;
shared_thread_data - > read_error = true ;
}
} else {
if ( ! usbStartExtractedFsDump ( shared_thread_data - > total_size , filename ) )
{
consolePrint ( " failed to send extracted fs info to host \n " ) ;
shared_thread_data - > read_error = true ;
}
}
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
goto end ;
}
/* Loop through all file entries. */
while ( sys_upd_dump_ctx - > content_idx < sys_upd_dump_ctx - > content_count )
{
if ( nca_filename )
{
free ( nca_filename ) ;
nca_filename = NULL ;
}
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
if ( dev_idx ! = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
/* Close file. */
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
}
}
/* Retrieve system update content file information. */
shared_thread_data - > read_error = ( ! systemUpdateGetCurrentContentFileSizeFromDumpContext ( sys_upd_dump_ctx , & nca_filesize ) | | ! nca_filesize | | \
! ( nca_filename = systemUpdateGetCurrentContentFileNameFromDumpContext ( sys_upd_dump_ctx ) ) ) ;
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Generate output path. */
snprintf ( sys_upd_path , MAX_ELEMENTS ( sys_upd_path ) , " %s/%s " , filename , nca_filename ) ;
utilsReplaceIllegalCharacters ( sys_upd_path + filename_len + 1 , dev_idx = = 0 ) ;
if ( dev_idx = = 1 )
{
/* Wait until the previous data chunk has been written */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( shared_thread_data - > write_error ) break ;
/* Send current file properties */
shared_thread_data - > read_error = ! usbSendFileProperties ( nca_filesize , sys_upd_path ) ;
} else {
/* Create directory tree. */
utilsCreateDirectoryTree ( sys_upd_path , false ) ;
if ( dev_idx = = 0 )
{
/* Create ConcatenationFile if we're dealing with a big file + SD card as the output storage. */
if ( nca_filesize > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( sys_upd_path ) )
{
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , sys_upd_path ) ;
shared_thread_data - > read_error = true ;
}
} else {
/* Don't handle file chunks on FAT12/FAT16/FAT32 formatted UMS devices. */
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & nca_filesize > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
shared_thread_data - > read_error = true ;
}
}
if ( ! shared_thread_data - > read_error )
{
/* Open output file. */
shared_thread_data - > read_error = ( ( shared_thread_data - > fp = fopen ( sys_upd_path , " wb " ) ) = = NULL ) ;
if ( ! shared_thread_data - > read_error )
{
/* Set file size. */
setvbuf ( shared_thread_data - > fp , NULL , _IONBF , 0 ) ;
ftruncate ( fileno ( shared_thread_data - > fp ) , ( off_t ) nca_filesize ) ;
} else {
consolePrint ( " failed to open \" %s \" for writing! \n " , sys_upd_path ) ;
}
}
}
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
for ( u64 offset = 0 , blksize = BLOCK_SIZE ; offset < nca_filesize ; offset + = blksize )
{
if ( blksize > ( nca_filesize - offset ) ) blksize = ( nca_filesize - offset ) ;
/* Check if the transfer has been cancelled by the user. */
if ( shared_thread_data - > transfer_cancelled )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Read current file data chunk. */
shared_thread_data - > read_error = ! systemUpdateReadCurrentContentFileFromDumpContext ( sys_upd_dump_ctx , buf1 , blksize ) ;
if ( shared_thread_data - > read_error )
{
condvarWakeAll ( & g_writeCondvar ) ;
break ;
}
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size & & ! shared_thread_data - > write_error ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > write_error )
{
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Update shared object. */
shared_thread_data - > data = buf1 ;
shared_thread_data - > data_size = blksize ;
/* Swap buffers. */
buf1 = buf2 ;
buf2 = shared_thread_data - > data ;
/* Wake up the write thread to continue writing data. */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_writeCondvar ) ;
}
if ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) break ;
}
if ( ! shared_thread_data - > read_error & & ! shared_thread_data - > write_error & & ! shared_thread_data - > transfer_cancelled )
{
/* Wait until the previous file data chunk has been written. */
mutexLock ( & g_fileMutex ) ;
if ( shared_thread_data - > data_size ) condvarWait ( & g_readCondvar , & g_fileMutex ) ;
mutexUnlock ( & g_fileMutex ) ;
if ( dev_idx = = 1 ) usbEndExtractedFsDump ( ) ;
shared_thread_data - > read_error = ! systemUpdateIsDumpContextFinished ( sys_upd_dump_ctx ) ;
if ( ! shared_thread_data - > read_error )
{
consolePrint ( " successfully saved system update data to \" %s \" \n " , filename ) ;
} else {
consolePrint ( " unexpected sys upd dump ctx error \n " ) ;
}
consoleRefresh ( ) ;
}
end :
if ( shared_thread_data - > fp )
{
fclose ( shared_thread_data - > fp ) ;
shared_thread_data - > fp = NULL ;
if ( ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) & & dev_idx ! = 1 )
{
utilsDeleteDirectoryRecursively ( filename ) ;
if ( dev_idx = = 0 ) utilsCommitSdCardFileSystemChanges ( ) ;
}
}
if ( nca_filename ) free ( nca_filename ) ;
if ( filename ) free ( filename ) ;
if ( buf2 ) free ( buf2 ) ;
if ( buf1 ) free ( buf1 ) ;
threadExit ( ) ;
}
2023-05-24 20:05:34 +01:00
static void genericWriteThreadFunc ( void * arg )
{
SharedThreadData * shared_thread_data = ( SharedThreadData * ) arg ; // UB but we don't care
while ( shared_thread_data - > data_written < shared_thread_data - > total_size )
{
/* Wait until the current file data chunk has been read */
mutexLock ( & g_fileMutex ) ;
if ( ! shared_thread_data - > data_size & & ! shared_thread_data - > read_error ) condvarWait ( & g_writeCondvar , & g_fileMutex ) ;
if ( shared_thread_data - > read_error | | shared_thread_data - > transfer_cancelled | | ( ! useUsbHost ( ) & & ! shared_thread_data - > fp ) )
{
if ( useUsbHost ( ) & & shared_thread_data - > transfer_cancelled ) usbCancelFileTransfer ( ) ;
mutexUnlock ( & g_fileMutex ) ;
break ;
}
/* Write current file data chunk */
if ( useUsbHost ( ) )
{
shared_thread_data - > write_error = ! usbSendFileData ( shared_thread_data - > data , shared_thread_data - > data_size ) ;
} else {
shared_thread_data - > write_error = ( fwrite ( shared_thread_data - > data , 1 , shared_thread_data - > data_size , shared_thread_data - > fp ) ! = shared_thread_data - > data_size ) ;
}
if ( ! shared_thread_data - > write_error )
{
shared_thread_data - > data_written + = shared_thread_data - > data_size ;
shared_thread_data - > data_size = 0 ;
}
/* Wake up the read thread to continue reading data */
mutexUnlock ( & g_fileMutex ) ;
condvarWakeAll ( & g_readCondvar ) ;
if ( shared_thread_data - > write_error ) break ;
}
threadExit ( ) ;
}
static bool spanDumpThreads ( ThreadFunc read_func , ThreadFunc write_func , void * arg )
{
SharedThreadData * shared_thread_data = ( SharedThreadData * ) arg ; // UB but we don't care
Thread read_thread = { 0 } , write_thread = { 0 } ;
time_t start = 0 , btn_cancel_start_tmr = 0 , btn_cancel_end_tmr = 0 ;
bool btn_cancel_cur_state = false , btn_cancel_prev_state = false , success = false ;
u64 prev_size = 0 ;
u8 prev_time = 0 , percent = 0 ;
consolePrint ( " creating threads \n " ) ;
utilsCreateThread ( & read_thread , read_func , arg , 2 ) ;
utilsCreateThread ( & write_thread , write_func , arg , 2 ) ;
consolePrint ( " hold b to cancel \n \n " ) ;
consoleRefresh ( ) ;
start = time ( NULL ) ;
while ( shared_thread_data - > data_written < shared_thread_data - > total_size )
{
g_appletStatus = appletMainLoop ( ) ;
if ( ! g_appletStatus )
{
mutexLock ( & g_fileMutex ) ;
shared_thread_data - > transfer_cancelled = true ;
mutexUnlock ( & g_fileMutex ) ;
}
if ( shared_thread_data - > read_error | | shared_thread_data - > write_error | | shared_thread_data - > transfer_cancelled ) break ;
struct tm ts = { 0 } ;
time_t now = time ( NULL ) ;
localtime_r ( & now , & ts ) ;
size_t size = shared_thread_data - > data_written ;
utilsScanPads ( ) ;
btn_cancel_cur_state = ( utilsGetButtonsHeld ( ) & HidNpadButton_B ) ;
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_thread_data - > transfer_cancelled = true ;
mutexUnlock ( & g_fileMutex ) ;
break ;
}
} else {
btn_cancel_start_tmr = btn_cancel_end_tmr = 0 ;
}
btn_cancel_prev_state = btn_cancel_cur_state ;
if ( prev_time = = ts . tm_sec | | prev_size = = size ) continue ;
percent = ( u8 ) ( ( size * 100 ) / shared_thread_data - > total_size ) ;
prev_time = ts . tm_sec ;
prev_size = size ;
consolePrint ( " %lu / %lu (%u%%) | Time elapsed: %lu \n " , size , shared_thread_data - > total_size , percent , ( now - start ) ) ;
consoleRefresh ( ) ;
2023-05-26 13:41:49 +01:00
2023-10-22 23:44:40 +01:00
utilsAppletLoopDelay ( ) ;
2023-05-24 20:05:34 +01:00
}
consolePrint ( " \n waiting for threads to join \n " ) ;
consoleRefresh ( ) ;
utilsJoinThread ( & read_thread ) ;
consolePrint ( " read_thread done: %lu \n " , time ( NULL ) ) ;
utilsJoinThread ( & write_thread ) ;
consolePrint ( " write_thread done: %lu \n " , time ( NULL ) ) ;
if ( shared_thread_data - > read_error | | shared_thread_data - > write_error )
{
consolePrint ( " i/o error \n " ) ;
} else
if ( shared_thread_data - > transfer_cancelled )
{
consolePrint ( " process cancelled \n " ) ;
} else {
start = ( time ( NULL ) - start ) ;
consolePrint ( " process completed in %lu seconds \n " , start ) ;
success = true ;
}
consoleRefresh ( ) ;
return success ;
}
static void nspThreadFunc ( void * arg )
{
NspThreadData * nsp_thread_data = ( NspThreadData * ) arg ;
TitleInfo * title_info = NULL ;
bool set_download_type = ( bool ) getNspSetDownloadDistributionOption ( ) ;
bool remove_console_data = ( bool ) getNspRemoveConsoleDataOption ( ) ;
bool remove_titlekey_crypto = ( bool ) getNspRemoveTitlekeyCryptoOption ( ) ;
bool patch_sua = ( bool ) getNspDisableLinkedAccountRequirementOption ( ) ;
bool patch_screenshot = ( bool ) getNspEnableScreenshotsOption ( ) ;
bool patch_video_capture = ( bool ) getNspEnableVideoCaptureOption ( ) ;
bool patch_hdcp = ( bool ) getNspDisableHdcpOption ( ) ;
2023-07-17 00:03:05 +01:00
bool generate_authoringtool_data = ( bool ) getNspGenerateAuthoringToolDataOption ( ) ;
2023-11-03 18:03:50 +00:00
bool success = false , no_titlekey_confirmation = false ;
2023-05-24 20:05:34 +01:00
u64 free_space = 0 ;
u32 dev_idx = g_storageMenuElementOption . selected ;
u8 * buf = NULL ;
char * filename = NULL ;
2023-12-20 23:39:56 +00:00
FILE * fp = NULL ;
2023-05-24 20:05:34 +01:00
NcaContext * nca_ctx = NULL ;
NcaContext * meta_nca_ctx = NULL ;
ContentMetaContext cnmt_ctx = { 0 } ;
ProgramInfoContext * program_info_ctx = NULL ;
u32 program_idx = 0 , program_count = 0 ;
NacpContext * nacp_ctx = NULL ;
u32 control_idx = 0 , control_count = 0 ;
LegalInfoContext * legal_info_ctx = NULL ;
u32 legal_info_idx = 0 , legal_info_count = 0 ;
Ticket tik = { 0 } ;
TikCommonBlock * tik_common_block = NULL ;
u8 * raw_cert_chain = NULL ;
u64 raw_cert_chain_size = 0 ;
2023-11-03 01:22:47 +00:00
PartitionFileSystemImageContext pfs_img_ctx = { 0 } ;
pfsInitializeImageContext ( & pfs_img_ctx ) ;
2023-05-24 20:05:34 +01:00
char entry_name [ 64 ] = { 0 } ;
u64 nsp_header_size = 0 , nsp_size = 0 , nsp_offset = 0 ;
2024-08-26 08:45:52 +01:00
char size_str [ 16 ] = { 0 } ;
2023-05-24 20:05:34 +01:00
char * tmp_name = NULL ;
2023-11-03 01:22:47 +00:00
Sha256Context clean_sha256_ctx = { 0 } , dirty_sha256_ctx = { 0 } ;
u8 clean_sha256_hash [ SHA256_HASH_SIZE ] = { 0 } , dirty_sha256_hash [ SHA256_HASH_SIZE ] = { 0 } ;
2023-05-24 20:05:34 +01:00
if ( ! nsp_thread_data | | ! ( title_info = ( TitleInfo * ) nsp_thread_data - > data ) | | ! title_info - > content_count | | ! title_info - > content_infos ) goto end ;
/* Allocate memory for the dump process. */
if ( ! ( buf = usbAllocatePageAlignedBuffer ( BLOCK_SIZE ) ) )
{
consolePrint ( " buf alloc failed \n " ) ;
goto end ;
}
/* Generate output path. */
2024-11-02 23:49:07 +00:00
filename = generateOutputTitleFileName ( title_info , NSP_SUBDIR , " .nsp " ) ;
2023-05-24 20:05:34 +01:00
if ( ! filename ) goto end ;
/* Get free space on output storage. */
if ( dev_idx ! = 1 & & ! utilsGetFileSystemStatsByPath ( filename , NULL , & free_space ) )
{
consolePrint ( " failed to retrieve free space from selected device \n " ) ;
goto end ;
}
if ( ! ( nca_ctx = calloc ( title_info - > content_count , sizeof ( NcaContext ) ) ) )
{
consolePrint ( " nca ctx calloc failed \n " ) ;
goto end ;
}
// determine if we should initialize programinfo ctx
2023-07-17 00:03:05 +01:00
if ( generate_authoringtool_data )
2023-05-24 20:05:34 +01:00
{
program_count = titleGetContentCountByType ( title_info , NcmContentType_Program ) ;
if ( program_count & & ! ( program_info_ctx = calloc ( program_count , sizeof ( ProgramInfoContext ) ) ) )
{
consolePrint ( " program info ctx calloc failed \n " ) ;
goto end ;
}
}
// determine if we should initialize nacp ctx
2023-07-17 00:03:05 +01:00
if ( patch_sua | | patch_screenshot | | patch_video_capture | | patch_hdcp | | generate_authoringtool_data )
2023-05-24 20:05:34 +01:00
{
control_count = titleGetContentCountByType ( title_info , NcmContentType_Control ) ;
if ( control_count & & ! ( nacp_ctx = calloc ( control_count , sizeof ( NacpContext ) ) ) )
{
consolePrint ( " nacp ctx calloc failed \n " ) ;
goto end ;
}
}
// determine if we should initialize legalinfo ctx
2023-07-17 00:03:05 +01:00
if ( generate_authoringtool_data )
2023-05-24 20:05:34 +01:00
{
legal_info_count = titleGetContentCountByType ( title_info , NcmContentType_LegalInformation ) ;
if ( legal_info_count & & ! ( legal_info_ctx = calloc ( legal_info_count , sizeof ( LegalInfoContext ) ) ) )
{
consolePrint ( " legal info ctx calloc failed \n " ) ;
goto end ;
}
}
// set meta nca as the last nca
meta_nca_ctx = & ( nca_ctx [ title_info - > content_count - 1 ] ) ;
if ( ! ncaInitializeContext ( meta_nca_ctx , title_info - > storage_id , ( title_info - > storage_id = = NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0 ) , \
2023-05-30 00:22:12 +01:00
& ( title_info - > meta_key ) , titleGetContentInfoByTypeAndIdOffset ( title_info , NcmContentType_Meta , 0 ) , & tik ) )
2023-05-24 20:05:34 +01:00
{
2023-05-26 19:23:23 +01:00
consolePrint ( " meta nca initialize ctx failed \n " ) ;
2023-05-24 20:05:34 +01:00
goto end ;
}
2023-05-26 19:23:23 +01:00
consolePrint ( " meta nca initialize ctx succeeded \n " ) ;
2023-05-24 20:05:34 +01:00
if ( ! cnmtInitializeContext ( & cnmt_ctx , meta_nca_ctx ) )
{
consolePrint ( " cnmt initialize ctx failed \n " ) ;
goto end ;
}
consolePrint ( " cnmt initialize ctx succeeded (%s) \n " , meta_nca_ctx - > content_id_str ) ;
// initialize nca context
// initialize content type context
// generate nca patches (if needed)
// generate content type xml
for ( u32 i = 0 , j = 0 ; i < title_info - > content_count ; i + + )
{
// skip meta nca since we already initialized it
NcmContentInfo * content_info = & ( title_info - > content_infos [ i ] ) ;
if ( content_info - > content_type = = NcmContentType_Meta ) continue ;
NcaContext * cur_nca_ctx = & ( nca_ctx [ j ] ) ;
2023-05-30 00:22:12 +01:00
if ( ! ncaInitializeContext ( cur_nca_ctx , title_info - > storage_id , ( title_info - > storage_id = = NcmStorageId_GameCard ? HashFileSystemPartitionType_Secure : 0 ) , \
& ( title_info - > meta_key ) , content_info , & tik ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " %s #%u initialize nca ctx failed \n " , titleGetNcmContentTypeName ( content_info - > content_type ) , content_info - > id_offset ) ;
goto end ;
}
2024-04-09 01:05:44 +01:00
consolePrint ( " %s #%u initialize nca ctx succeeded \n " , titleGetNcmContentTypeName ( cur_nca_ctx - > content_type ) , cur_nca_ctx - > id_offset ) ;
2023-05-24 20:05:34 +01:00
// don't go any further with this nca if we can't access its fs data because it's pointless
2023-11-03 18:03:50 +00:00
if ( cur_nca_ctx - > rights_id_available & & ! cur_nca_ctx - > titlekey_retrieved & & ! no_titlekey_confirmation )
2023-05-24 20:05:34 +01:00
{
2023-11-03 18:03:50 +00:00
consolePrintReversedColors ( " \n unable to retrieve titlekey for the selected title " ) ;
consolePrintReversedColors ( " \n if you proceed, nca modifications will be disabled, and content decryption " ) ;
consolePrintReversedColors ( " \n will not be possible for external tools (e.g. emulators, etc.) \n " ) ;
2023-11-26 21:54:25 +00:00
consolePrintReversedColors ( " \n this may occur because of different reasons: \n " ) ;
consolePrintReversedColors ( " \n 1. you haven't launched this game/dlc at least once since you downloaded it " ) ;
consolePrintReversedColors ( " \n 2. this is a shared game/dlc across different switch consoles using the " ) ;
consolePrintReversedColors ( " \n same nintendo account and you're using the secondary console " ) ;
consolePrintReversedColors ( " \n 3. you downloaded this game/dlc onto your sd card using your sysmmc, then " ) ;
consolePrintReversedColors ( " \n copied the 'nintendo' folder data into the 'emummc' folder (or viceversa) \n " ) ;
consolePrintReversedColors ( " \n cases 1 and 2 can be fixed by exiting nxdumptool, launching the game " ) ;
consolePrintReversedColors ( " \n and then running nxdumptool once again \n " ) ;
consolePrintReversedColors ( " \n case 3 can be fixed by running nxdumptool directly under the emmc that was " ) ;
consolePrintReversedColors ( " \n used to download the game/dlc \n " ) ;
2023-11-03 18:03:50 +00:00
consolePrintReversedColors ( " \n press a to proceed anyway, or b to cancel \n \n " ) ;
u64 btn_down = utilsWaitForButtonPress ( HidNpadButton_A | HidNpadButton_B ) ;
if ( btn_down & HidNpadButton_A )
{
j + + ;
no_titlekey_confirmation = true ;
continue ;
}
goto end ;
2023-05-24 20:05:34 +01:00
}
// set download distribution type
// has no effect if this nca uses NcaDistributionType_Download
if ( set_download_type ) ncaSetDownloadDistributionType ( cur_nca_ctx ) ;
// remove titlekey crypto
// has no effect if this nca doesn't use titlekey crypto
if ( remove_titlekey_crypto & & ! ncaRemoveTitleKeyCrypto ( cur_nca_ctx ) )
{
consolePrint ( " nca remove titlekey crypto failed \n " ) ;
goto end ;
}
if ( ! cur_nca_ctx - > fs_ctx [ 0 ] . has_sparse_layer )
{
switch ( content_info - > content_type )
{
case NcmContentType_Program :
{
2024-04-09 01:05:44 +01:00
// don't proceed if we didn't allocate programinfo ctx
2023-05-24 20:05:34 +01:00
if ( ! program_count | | ! program_info_ctx ) break ;
ProgramInfoContext * cur_program_info_ctx = & ( program_info_ctx [ program_idx ] ) ;
if ( ! programInfoInitializeContext ( cur_program_info_ctx , cur_nca_ctx ) )
{
consolePrint ( " initialize program info ctx failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
if ( ! programInfoGenerateAuthoringToolXml ( cur_program_info_ctx ) )
{
consolePrint ( " program info xml failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
program_idx + + ;
consolePrint ( " initialize program info ctx succeeded (%s) \n " , cur_nca_ctx - > content_id_str ) ;
break ;
}
case NcmContentType_Control :
{
// don't proceed if we didn't allocate nacp ctx
if ( ! control_count | | ! nacp_ctx ) break ;
NacpContext * cur_nacp_ctx = & ( nacp_ctx [ control_idx ] ) ;
if ( ! nacpInitializeContext ( cur_nacp_ctx , cur_nca_ctx ) )
{
consolePrint ( " initialize nacp ctx failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
if ( ! nacpGenerateNcaPatch ( cur_nacp_ctx , patch_sua , patch_screenshot , patch_video_capture , patch_hdcp ) )
{
consolePrint ( " nacp nca patch failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2023-07-17 00:03:05 +01:00
if ( generate_authoringtool_data & & ! nacpGenerateAuthoringToolXml ( cur_nacp_ctx , title_info - > version . value , cnmtGetRequiredTitleVersion ( & cnmt_ctx ) ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " nacp xml failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
control_idx + + ;
consolePrint ( " initialize nacp ctx succeeded (%s) \n " , cur_nca_ctx - > content_id_str ) ;
break ;
}
case NcmContentType_LegalInformation :
{
// don't proceed if we didn't allocate legalinfo ctx
if ( ! legal_info_count | | ! legal_info_ctx ) break ;
LegalInfoContext * cur_legal_info_ctx = & ( legal_info_ctx [ legal_info_idx ] ) ;
if ( ! legalInfoInitializeContext ( cur_legal_info_ctx , cur_nca_ctx ) )
{
consolePrint ( " initialize legal info ctx failed (%s) \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
legal_info_idx + + ;
consolePrint ( " initialize legal info ctx succeeded (%s) \n " , cur_nca_ctx - > content_id_str ) ;
break ;
}
default :
break ;
}
}
if ( ! ncaEncryptHeader ( cur_nca_ctx ) )
{
consolePrint ( " %s #%u encrypt nca header failed \n " , titleGetNcmContentTypeName ( content_info - > content_type ) , content_info - > id_offset ) ;
goto end ;
}
j + + ;
}
consoleRefresh ( ) ;
// generate cnmt xml right away even though we don't yet have all the data we need
// This is because we need its size to calculate the full nsp size
2023-07-17 00:03:05 +01:00
if ( generate_authoringtool_data & & ! cnmtGenerateAuthoringToolXml ( & cnmt_ctx , nca_ctx , title_info - > content_count ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " cnmt xml #1 failed \n " ) ;
goto end ;
}
2024-04-09 01:05:44 +01:00
bool retrieve_tik_cert = ( ! remove_titlekey_crypto & & tikIsValidTicket ( & tik ) ) ;
2023-05-24 20:05:34 +01:00
if ( retrieve_tik_cert )
{
Rework signature/cert/tik interfaces.
* signature: add comments to SignatureType enum entries about the exact signing algorithms and padding schemes used.
* signature: rename signatureGetSigType() -> signatureGetTypeFromSignedBlob().
* signature: rename signatureIsValidSigType() -> signatureIsValidType().
* signature: rename signatureGetSigSize() -> signatureGetSigSizeByType().
* signature: rename signatureGetBlockSize() -> signatureGetBlockSizeByType().
* signature: rename signatureGetSig() -> signatureGetSigFromSignedBlob().
* signature: rename signatureGetPayload() -> signatureGetPayloadFromSignedBlob().
* signature: add signatureGetBlockSizeFromSignedBlob().
* cert: add more comments to the code.
* cert: update code to match signature interface changes.
* cert: add CERT_RSA_PUB_EXP_SIZE macro.
* cert: change public_exponent field in CertPublicKeyBlockRsa* structs from u32 to u8 array.
* cert: add size field to CertificateChain struct.
* cert: rename certGetCommonBlock() -> certGetCommonBlockFromSignedCertBlob.
* cert: rename certGetPublicKeySize() -> certGetPublicKeySizeByType().
* cert: rename certGetPublicKeyBlockSize() -> certGetPublicKeyBlockSizeByType().
* cert: rename certIsValidCertificate() -> certIsValidSignedCertBlob().
* cert: rename certGetSignedCertificateSize() -> certGetSignedCertBlobSize().
* cert: rename certGetSignedCertificateHashAreaSize() -> certGetSignedCertBlobHashAreaSize().
* cert: remove certGetPublicKey(), certGetPublicExponent() and certCalculateRawCertificateChainSize().
* cert: add certGetPublicKeyTypeFromCommonBlock(), certGetPublicKeyTypeFromSignedCertBlob(), certGetPublicKeySizeFromSignedCertBlob(), certGetPublicKeyBlockSizeFromSignedCertBlob(), certGetPublicKeyFromSignedCertBlob(), certGetPublicExponentFromSignedCertBlob(), certIsValidCertificate() (w/diff func sig), certGetCommonBlockFromCertificate(), certGetPublicKeyTypeFromCertificate(), certGetPublicKeySizeFromCertificate(), certGetPublicKeyBlockSizeFromCertificate(), certGetPublicKeyFromCertificate(), certGetPublicExponentFromCertificate() and certGetHashAreaSizeFromCertificate() functions.
* cert: avoid byteswapping the public key type value in multiple places -- it is now only being done in certGetPublicKeyTypeFromCommonBlock().
* cert: call certFreeCertificateChain() in _certRetrieveCertificateChainBySignatureIssuer() before attempting to retrieve the certificate chain.
* cert: other minor changes and corrections.
* tik: update code to match signature interface changes.
* tik: add missing comments to TikPropertyMask enum entries.
* tik: add key_generation, enc_titlekey_str and dec_titlekey_str fields to Ticket struct.
* tik: update tikRetrieveTicketByRightsId() to also take in a key_generation argument, instead of getting it from the rights ID (which could fail if it's using a key generation lower than HOS 3.0.1) or the key_generation field from the common ticket block (which could fail if the ticket has been tampered by certain tools).
* tik: rename tikGetCommonBlock() -> tikGetCommonBlockFromSignedTicketBlob().
* tik: change function signature for tikGetTicketSectionRecordsBlockSize().
* tik: rename tikIsValidTicket() -> tikIsValidSignedTicketBlob().
* tik: rename tikGetSignedTicketSize() -> tikGetSignedTicketBlobSize().
* tik: rename tikGetSignedTicketHashAreaSize() -> tikGetSignedTicketBlobHashAreaSize().
* tik: rename tikGetEncryptedTitleKeyFromTicket() -> tikGetEncryptedTitleKey().
* tik: add tikIsValidTicket() (w/diff func sig), tikGetCommonBlockFromTicket(), tikGetHashAreaSizeFromTicket(), tikFixTamperedCommonTicket(), tikVerifyRsa2048Sha256Signature() and tikDecryptVolatileTicket() functions. Ticket signature verification is only carried out for common tickets in tikFixTamperedCommonTicket().
* tik: change argument order in tikGetTicketEntryOffsetFromTicketList() and tikRetrieveTicketEntryFromTicketBin().
* tik: add TIK_COMMON_CERT_NAME and TIK_DEV_CERT_ISSUER macros.
* tik: use a scoped lock when calling tikRetrieveTicketFromEsSaveDataByRightsId().
* tik: simplify certificate chain retrieval steps in tikConvertPersonalizedTicketToCommonTicket() by always using the XS00000020 certificate.
* tik: wipe license_type and property_mask fields in tikConvertPersonalizedTicketToCommonTicket().
* tik: other minor changes and corrections.
Other changes include:
* keys: fix key generation checks in keysGetNcaKeyAreaKeyEncryptionKey() and keysGetTicketCommonKey().
* rsa: move core logic from rsa2048VerifySha256BasedPssSignature() into a new function: rsa2048VerifySha256BasedSignature().
* rsa: add rsa2048VerifySha256BasedPkcs1v15Signature() function.
2023-10-15 16:53:46 +01:00
if ( ! ( tik_common_block = tikGetCommonBlockFromTicket ( & tik ) ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " tik common block failed " ) ;
goto end ;
}
if ( remove_console_data & & tik_common_block - > titlekey_type = = TikTitleKeyType_Personalized )
{
if ( ! tikConvertPersonalizedTicketToCommonTicket ( & tik , & raw_cert_chain , & raw_cert_chain_size ) )
{
consolePrint ( " tik convert failed \n " ) ;
goto end ;
}
} else {
raw_cert_chain = ( title_info - > storage_id = = NcmStorageId_GameCard ? certRetrieveRawCertificateChainFromGameCardByRightsId ( & ( tik_common_block - > rights_id ) , & raw_cert_chain_size ) : \
certGenerateRawCertificateChainBySignatureIssuer ( tik_common_block - > issuer , & raw_cert_chain_size ) ) ;
if ( ! raw_cert_chain )
{
consolePrint ( " cert failed \n " ) ;
goto end ;
}
}
}
// add nca info
for ( u32 i = 0 ; i < title_info - > content_count ; i + + )
{
NcaContext * cur_nca_ctx = & ( nca_ctx [ i ] ) ;
sprintf ( entry_name , " %s.%s " , cur_nca_ctx - > content_id_str , cur_nca_ctx - > content_type = = NcmContentType_Meta ? " cnmt.nca " : " nca " ) ;
2023-11-03 01:22:47 +00:00
if ( ! pfsAddEntryInformationToImageContext ( & pfs_img_ctx , entry_name , cur_nca_ctx - > content_size , NULL ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
}
// add cnmt xml info
2023-07-17 00:03:05 +01:00
if ( generate_authoringtool_data )
2023-05-24 20:05:34 +01:00
{
sprintf ( entry_name , " %s.cnmt.xml " , meta_nca_ctx - > content_id_str ) ;
2023-11-03 01:22:47 +00:00
if ( ! pfsAddEntryInformationToImageContext ( & pfs_img_ctx , entry_name , cnmt_ctx . authoring_tool_xml_size , & ( meta_nca_ctx - > content_type_ctx_data_idx ) ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
}
// add content type ctx data info
2023-07-17 00:03:05 +01:00
u32 limit = generate_authoringtool_data ? ( title_info - > content_count - 1 ) : 0 ;
2023-05-24 20:05:34 +01:00
for ( u32 i = 0 ; i < limit ; i + + )
{
bool ret = false ;
NcaContext * cur_nca_ctx = & ( nca_ctx [ i ] ) ;
if ( ! cur_nca_ctx - > content_type_ctx ) continue ;
switch ( cur_nca_ctx - > content_type )
{
case NcmContentType_Program :
{
ProgramInfoContext * cur_program_info_ctx = ( ProgramInfoContext * ) cur_nca_ctx - > content_type_ctx ;
sprintf ( entry_name , " %s.programinfo.xml " , cur_nca_ctx - > content_id_str ) ;
2023-11-03 01:22:47 +00:00
ret = pfsAddEntryInformationToImageContext ( & pfs_img_ctx , entry_name , cur_program_info_ctx - > authoring_tool_xml_size , & ( cur_nca_ctx - > content_type_ctx_data_idx ) ) ;
2023-05-24 20:05:34 +01:00
break ;
}
case NcmContentType_Control :
{
NacpContext * cur_nacp_ctx = ( NacpContext * ) cur_nca_ctx - > content_type_ctx ;
for ( u8 j = 0 ; j < cur_nacp_ctx - > icon_count ; j + + )
{
NacpIconContext * icon_ctx = & ( cur_nacp_ctx - > icon_ctx [ j ] ) ;
sprintf ( entry_name , " %s.nx.%s.jpg " , cur_nca_ctx - > content_id_str , nacpGetLanguageString ( icon_ctx - > language ) ) ;
2023-11-03 01:22:47 +00:00
if ( ! pfsAddEntryInformationToImageContext ( & pfs_img_ctx , entry_name , icon_ctx - > icon_size , j = = 0 ? & ( cur_nca_ctx - > content_type_ctx_data_idx ) : NULL ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
}
sprintf ( entry_name , " %s.nacp.xml " , cur_nca_ctx - > content_id_str ) ;
2023-11-03 01:22:47 +00:00
ret = pfsAddEntryInformationToImageContext ( & pfs_img_ctx , entry_name , cur_nacp_ctx - > authoring_tool_xml_size , ! cur_nacp_ctx - > icon_count ? & ( cur_nca_ctx - > content_type_ctx_data_idx ) : NULL ) ;
2023-05-24 20:05:34 +01:00
break ;
}
case NcmContentType_LegalInformation :
{
LegalInfoContext * cur_legal_info_ctx = ( LegalInfoContext * ) cur_nca_ctx - > content_type_ctx ;
sprintf ( entry_name , " %s.legalinfo.xml " , cur_nca_ctx - > content_id_str ) ;
2023-11-03 01:22:47 +00:00
ret = pfsAddEntryInformationToImageContext ( & pfs_img_ctx , entry_name , cur_legal_info_ctx - > authoring_tool_xml_size , & ( cur_nca_ctx - > content_type_ctx_data_idx ) ) ;
2023-05-24 20:05:34 +01:00
break ;
}
default :
break ;
}
if ( ! ret )
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
}
// add ticket and cert info
if ( retrieve_tik_cert )
{
sprintf ( entry_name , " %s.tik " , tik . rights_id_str ) ;
2023-11-03 01:22:47 +00:00
if ( ! pfsAddEntryInformationToImageContext ( & pfs_img_ctx , entry_name , tik . size , NULL ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
sprintf ( entry_name , " %s.cert " , tik . rights_id_str ) ;
2023-11-03 01:22:47 +00:00
if ( ! pfsAddEntryInformationToImageContext ( & pfs_img_ctx , entry_name , raw_cert_chain_size , NULL ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs add entry failed: %s \n " , entry_name ) ;
goto end ;
}
}
2023-11-03 01:22:47 +00:00
// write pfs header to memory buffer
if ( ! pfsWriteImageContextHeaderToMemoryBuffer ( & pfs_img_ctx , buf , BLOCK_SIZE , & nsp_header_size ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs write header to mem #1 failed \n " ) ;
goto end ;
}
2023-11-03 01:22:47 +00:00
nsp_size = ( nsp_header_size + pfs_img_ctx . fs_size ) ;
2024-08-26 08:45:52 +01:00
utilsGenerateFormattedSizeString ( ( double ) nsp_header_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " nsp header size: 0x%lX (%s) \n " , nsp_header_size , size_str ) ;
utilsGenerateFormattedSizeString ( ( double ) nsp_size , size_str , sizeof ( size_str ) ) ;
consolePrint ( " nsp size: 0x%lX (%s) \n " , nsp_size , size_str ) ;
2023-05-24 20:05:34 +01:00
consoleRefresh ( ) ;
if ( dev_idx = = 1 )
{
if ( ! usbSendNspProperties ( nsp_size , filename , ( u32 ) nsp_header_size ) )
{
consolePrint ( " usb send nsp properties failed \n " ) ;
goto end ;
}
} else {
if ( nsp_size > = free_space )
{
consolePrint ( " nsp size exceeds free space \n " ) ;
goto end ;
}
utilsCreateDirectoryTree ( filename , false ) ;
if ( dev_idx = = 0 )
{
if ( nsp_size > FAT32_FILESIZE_LIMIT & & ! utilsCreateConcatenationFile ( filename ) )
{
consolePrint ( " failed to create concatenation file for \" %s \" ! \n " , filename ) ;
goto end ;
}
} else {
if ( g_umsDevices [ dev_idx - 2 ] . fs_type < UsbHsFsDeviceFileSystemType_exFAT & & nsp_size > FAT32_FILESIZE_LIMIT )
{
consolePrint ( " split dumps not supported for FAT12/16/32 volumes in UMS devices (yet) \n " ) ;
goto end ;
}
}
2023-12-20 23:39:56 +00:00
if ( ! ( fp = fopen ( filename , " wb " ) ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " fopen failed \n " ) ;
goto end ;
}
// set file size
2023-12-20 23:39:56 +00:00
setvbuf ( fp , NULL , _IONBF , 0 ) ;
ftruncate ( fileno ( fp ) , ( off_t ) nsp_size ) ;
2023-05-24 20:05:34 +01:00
// write placeholder header
memset ( buf , 0 , nsp_header_size ) ;
2023-12-20 23:39:56 +00:00
fwrite ( buf , 1 , nsp_header_size , fp ) ;
2023-05-24 20:05:34 +01:00
}
consolePrint ( " dump process started, please wait. hold b to cancel. \n " ) ;
consoleRefresh ( ) ;
nsp_offset + = nsp_header_size ;
// set nsp size
nsp_thread_data - > total_size = nsp_size ;
// write ncas
for ( u32 i = 0 ; i < title_info - > content_count ; i + + )
{
NcaContext * cur_nca_ctx = & ( nca_ctx [ i ] ) ;
u64 blksize = BLOCK_SIZE ;
2023-11-03 01:22:47 +00:00
sha256ContextCreate ( & clean_sha256_ctx ) ;
sha256ContextCreate ( & dirty_sha256_ctx ) ;
2023-05-24 20:05:34 +01:00
if ( cur_nca_ctx - > content_type = = NcmContentType_Meta & & ( ! cnmtGenerateNcaPatch ( & cnmt_ctx ) | | ! ncaEncryptHeader ( cur_nca_ctx ) ) )
{
consolePrint ( " cnmt generate patch failed \n " ) ;
goto end ;
}
bool dirty_header = ncaIsHeaderDirty ( cur_nca_ctx ) ;
if ( dev_idx = = 1 )
{
2023-11-03 01:22:47 +00:00
tmp_name = pfsGetEntryNameByIndexFromImageContext ( & pfs_img_ctx , i ) ;
2023-05-24 20:05:34 +01:00
if ( ! usbSendFileProperties ( cur_nca_ctx - > content_size , tmp_name ) )
{
consolePrint ( " usb send file properties \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
}
for ( u64 offset = 0 ; offset < cur_nca_ctx - > content_size ; offset + = blksize , nsp_offset + = blksize , nsp_thread_data - > data_written + = blksize )
{
mutexLock ( & g_fileMutex ) ;
bool cancelled = nsp_thread_data - > transfer_cancelled ;
mutexUnlock ( & g_fileMutex ) ;
2023-11-11 11:16:29 +00:00
if ( cancelled ) goto end ;
2023-05-24 20:05:34 +01:00
if ( ( cur_nca_ctx - > content_size - offset ) < blksize ) blksize = ( cur_nca_ctx - > content_size - offset ) ;
// read nca chunk
if ( ! ncaReadContentFile ( cur_nca_ctx , buf , blksize , offset ) )
{
consolePrint ( " nca read failed at 0x%lX for \" %s \" \n " , offset , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2023-11-03 01:22:47 +00:00
// update clean hash calculation
sha256ContextUpdate ( & clean_sha256_ctx , buf , blksize ) ;
2023-11-11 20:39:41 +00:00
if ( ( offset + blksize ) > = cur_nca_ctx - > content_size )
{
// get clean hash
sha256ContextGetHash ( & clean_sha256_ctx , clean_sha256_hash ) ;
// validate clean hash
if ( ! cnmtVerifyContentHash ( & cnmt_ctx , cur_nca_ctx , clean_sha256_hash ) )
{
2024-08-26 08:45:52 +01:00
consolePrint ( " sha256 checksum mismatch for nca \" %s \" \n please check for corrupted data using the data management menu \n " , cur_nca_ctx - > content_id_str ) ;
2023-11-11 20:39:41 +00:00
goto end ;
}
}
2023-05-24 20:05:34 +01:00
if ( dirty_header )
{
// write re-encrypted headers
if ( ! cur_nca_ctx - > header_written ) ncaWriteEncryptedHeaderDataToMemoryBuffer ( cur_nca_ctx , buf , blksize , offset ) ;
if ( cur_nca_ctx - > content_type_ctx_patch )
{
// write content type context patch
switch ( cur_nca_ctx - > content_type )
{
case NcmContentType_Meta :
cnmtWriteNcaPatch ( & cnmt_ctx , buf , blksize , offset ) ;
break ;
case NcmContentType_Control :
nacpWriteNcaPatch ( ( NacpContext * ) cur_nca_ctx - > content_type_ctx , buf , blksize , offset ) ;
break ;
default :
break ;
}
}
// update flag to avoid entering this code block if it's not needed anymore
dirty_header = ( ! cur_nca_ctx - > header_written | | cur_nca_ctx - > content_type_ctx_patch ) ;
}
2023-11-03 01:22:47 +00:00
// update dirty hash calculation
sha256ContextUpdate ( & dirty_sha256_ctx , buf , blksize ) ;
2023-05-24 20:05:34 +01:00
// write nca chunk
if ( dev_idx = = 1 )
{
if ( ! usbSendFileData ( buf , blksize ) )
{
consolePrint ( " send file data failed \n " ) ;
goto end ;
}
} else {
2023-12-20 23:39:56 +00:00
fwrite ( buf , 1 , blksize , fp ) ;
2023-05-24 20:05:34 +01:00
}
}
2023-11-11 20:39:41 +00:00
// get dirty hash
2023-11-03 01:22:47 +00:00
sha256ContextGetHash ( & dirty_sha256_ctx , dirty_sha256_hash ) ;
2023-05-24 20:05:34 +01:00
2023-11-03 01:22:47 +00:00
if ( memcmp ( clean_sha256_hash , dirty_sha256_hash , SHA256_HASH_SIZE ) ! = 0 )
2023-05-24 20:05:34 +01:00
{
2023-11-03 01:22:47 +00:00
// update content id and hash
ncaUpdateContentIdAndHash ( cur_nca_ctx , dirty_sha256_hash ) ;
// update cnmt
if ( ! cnmtUpdateContentInfo ( & cnmt_ctx , cur_nca_ctx ) )
{
consolePrint ( " cnmt update content info failed \n " ) ;
goto end ;
}
// update pfs entry name
if ( ! pfsUpdateEntryNameFromImageContext ( & pfs_img_ctx , i , cur_nca_ctx - > content_id_str ) )
{
consolePrint ( " pfs update entry name failed for nca \" %s \" \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
2023-05-24 20:05:34 +01:00
}
}
2023-07-17 00:03:05 +01:00
if ( generate_authoringtool_data )
2023-05-24 20:05:34 +01:00
{
// regenerate cnmt xml
if ( ! cnmtGenerateAuthoringToolXml ( & cnmt_ctx , nca_ctx , title_info - > content_count ) )
{
consolePrint ( " cnmt xml #2 failed \n " ) ;
goto end ;
}
// write cnmt xml
if ( dev_idx = = 1 )
{
2023-11-03 01:22:47 +00:00
tmp_name = pfsGetEntryNameByIndexFromImageContext ( & pfs_img_ctx , meta_nca_ctx - > content_type_ctx_data_idx ) ;
2023-05-24 20:05:34 +01:00
if ( ! usbSendFileProperties ( cnmt_ctx . authoring_tool_xml_size , tmp_name ) | | ! usbSendFileData ( cnmt_ctx . authoring_tool_xml , cnmt_ctx . authoring_tool_xml_size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
} else {
2023-12-20 23:39:56 +00:00
fwrite ( cnmt_ctx . authoring_tool_xml , 1 , cnmt_ctx . authoring_tool_xml_size , fp ) ;
2023-05-24 20:05:34 +01:00
}
nsp_offset + = cnmt_ctx . authoring_tool_xml_size ;
nsp_thread_data - > data_written + = cnmt_ctx . authoring_tool_xml_size ;
// update cnmt xml pfs entry name
2023-11-03 01:22:47 +00:00
if ( ! pfsUpdateEntryNameFromImageContext ( & pfs_img_ctx , meta_nca_ctx - > content_type_ctx_data_idx , meta_nca_ctx - > content_id_str ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs update entry name cnmt xml failed \n " ) ;
goto end ;
}
}
// write content type ctx data
for ( u32 i = 0 ; i < limit ; i + + )
{
NcaContext * cur_nca_ctx = & ( nca_ctx [ i ] ) ;
if ( ! cur_nca_ctx - > content_type_ctx ) continue ;
char * authoring_tool_xml = NULL ;
u64 authoring_tool_xml_size = 0 ;
u32 data_idx = cur_nca_ctx - > content_type_ctx_data_idx ;
switch ( cur_nca_ctx - > content_type )
{
case NcmContentType_Program :
{
ProgramInfoContext * cur_program_info_ctx = ( ProgramInfoContext * ) cur_nca_ctx - > content_type_ctx ;
authoring_tool_xml = cur_program_info_ctx - > authoring_tool_xml ;
authoring_tool_xml_size = cur_program_info_ctx - > authoring_tool_xml_size ;
break ;
}
case NcmContentType_Control :
{
NacpContext * cur_nacp_ctx = ( NacpContext * ) cur_nca_ctx - > content_type_ctx ;
authoring_tool_xml = cur_nacp_ctx - > authoring_tool_xml ;
authoring_tool_xml_size = cur_nacp_ctx - > authoring_tool_xml_size ;
// loop through available icons
for ( u8 j = 0 ; j < cur_nacp_ctx - > icon_count ; j + + )
{
NacpIconContext * icon_ctx = & ( cur_nacp_ctx - > icon_ctx [ j ] ) ;
// write icon
if ( dev_idx = = 1 )
{
2023-11-03 01:22:47 +00:00
tmp_name = pfsGetEntryNameByIndexFromImageContext ( & pfs_img_ctx , data_idx ) ;
2023-05-24 20:05:34 +01:00
if ( ! usbSendFileProperties ( icon_ctx - > icon_size , tmp_name ) | | ! usbSendFileData ( icon_ctx - > icon_data , icon_ctx - > icon_size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
} else {
2023-12-20 23:39:56 +00:00
fwrite ( icon_ctx - > icon_data , 1 , icon_ctx - > icon_size , fp ) ;
2023-05-24 20:05:34 +01:00
}
nsp_offset + = icon_ctx - > icon_size ;
nsp_thread_data - > data_written + = icon_ctx - > icon_size ;
// update pfs entry name
2023-11-03 01:22:47 +00:00
if ( ! pfsUpdateEntryNameFromImageContext ( & pfs_img_ctx , data_idx + + , cur_nca_ctx - > content_id_str ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs update entry name failed for icon \" %s \" (%u) \n " , cur_nca_ctx - > content_id_str , icon_ctx - > language ) ;
goto end ;
}
}
break ;
}
case NcmContentType_LegalInformation :
{
LegalInfoContext * cur_legal_info_ctx = ( LegalInfoContext * ) cur_nca_ctx - > content_type_ctx ;
authoring_tool_xml = cur_legal_info_ctx - > authoring_tool_xml ;
authoring_tool_xml_size = cur_legal_info_ctx - > authoring_tool_xml_size ;
break ;
}
default :
break ;
}
// write xml
if ( dev_idx = = 1 )
{
2023-11-03 01:22:47 +00:00
tmp_name = pfsGetEntryNameByIndexFromImageContext ( & pfs_img_ctx , data_idx ) ;
2023-05-24 20:05:34 +01:00
if ( ! usbSendFileProperties ( authoring_tool_xml_size , tmp_name ) | | ! usbSendFileData ( authoring_tool_xml , authoring_tool_xml_size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
} else {
2023-12-20 23:39:56 +00:00
fwrite ( authoring_tool_xml , 1 , authoring_tool_xml_size , fp ) ;
2023-05-24 20:05:34 +01:00
}
nsp_offset + = authoring_tool_xml_size ;
nsp_thread_data - > data_written + = authoring_tool_xml_size ;
// update pfs entry name
2023-11-03 01:22:47 +00:00
if ( ! pfsUpdateEntryNameFromImageContext ( & pfs_img_ctx , data_idx , cur_nca_ctx - > content_id_str ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs update entry name failed for xml \" %s \" \n " , cur_nca_ctx - > content_id_str ) ;
goto end ;
}
}
if ( retrieve_tik_cert )
{
// write ticket
if ( dev_idx = = 1 )
{
2023-11-03 01:22:47 +00:00
tmp_name = pfsGetEntryNameByIndexFromImageContext ( & pfs_img_ctx , pfs_img_ctx . header . entry_count - 2 ) ;
2023-05-24 20:05:34 +01:00
if ( ! usbSendFileProperties ( tik . size , tmp_name ) | | ! usbSendFileData ( tik . data , tik . size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
} else {
2023-12-20 23:39:56 +00:00
fwrite ( tik . data , 1 , tik . size , fp ) ;
2023-05-24 20:05:34 +01:00
}
nsp_offset + = tik . size ;
nsp_thread_data - > data_written + = tik . size ;
// write cert
if ( dev_idx = = 1 )
{
2023-11-03 01:22:47 +00:00
tmp_name = pfsGetEntryNameByIndexFromImageContext ( & pfs_img_ctx , pfs_img_ctx . header . entry_count - 1 ) ;
2023-05-24 20:05:34 +01:00
if ( ! usbSendFileProperties ( raw_cert_chain_size , tmp_name ) | | ! usbSendFileData ( raw_cert_chain , raw_cert_chain_size ) )
{
consolePrint ( " send \" %s \" failed \n " , tmp_name ) ;
goto end ;
}
} else {
2023-12-20 23:39:56 +00:00
fwrite ( raw_cert_chain , 1 , raw_cert_chain_size , fp ) ;
2023-05-24 20:05:34 +01:00
}
nsp_offset + = raw_cert_chain_size ;
nsp_thread_data - > data_written + = raw_cert_chain_size ;
}
// write new pfs0 header
2023-11-03 01:22:47 +00:00
if ( ! pfsWriteImageContextHeaderToMemoryBuffer ( & pfs_img_ctx , buf , BLOCK_SIZE , & nsp_header_size ) )
2023-05-24 20:05:34 +01:00
{
consolePrint ( " pfs write header to mem #2 failed \n " ) ;
goto end ;
}
if ( dev_idx = = 1 )
{
if ( ! usbSendNspHeader ( buf , ( u32 ) nsp_header_size ) )
{
consolePrint ( " send nsp header failed \n " ) ;
goto end ;
}
} else {
2023-12-20 23:39:56 +00:00
rewind ( fp ) ;
fwrite ( buf , 1 , nsp_header_size , fp ) ;
2023-05-24 20:05:34 +01:00
}
nsp_thread_data - > data_written + = nsp_header_size ;
success = true ;
end :
consoleRefresh ( ) ;
mutexLock ( & g_fileMutex ) ;
if ( ! success & & ! nsp_thread_data - > transfer_cancelled ) nsp_thread_data - > error = true ;
mutexUnlock ( & g_fileMutex ) ;
2023-12-20 23:39:56 +00:00
if ( fp )
2023-05-24 20:05:34 +01:00
{
2023-12-20 23:39:56 +00:00
fclose ( fp ) ;
2023-05-24 20:05:34 +01:00
2023-11-11 11:16:29 +00:00
if ( ! success )
2023-05-24 20:05:34 +01:00
{
if ( dev_idx = = 0 )
{
utilsRemoveConcatenationFile ( filename ) ;
utilsCommitSdCardFileSystemChanges ( ) ;
} else {
remove ( filename ) ;
}
}
}
2023-11-11 11:16:29 +00:00
if ( ! success & & dev_idx = = 1 ) usbCancelFileTransfer ( ) ;
2023-11-03 01:22:47 +00:00
pfsFreeImageContext ( & pfs_img_ctx ) ;
2023-05-24 20:05:34 +01:00
if ( raw_cert_chain ) free ( raw_cert_chain ) ;
if ( legal_info_ctx )
{
for ( u32 i = 0 ; i < legal_info_count ; i + + ) legalInfoFreeContext ( & ( legal_info_ctx [ i ] ) ) ;
free ( legal_info_ctx ) ;
}
if ( nacp_ctx )
{
for ( u32 i = 0 ; i < control_count ; i + + ) nacpFreeContext ( & ( nacp_ctx [ i ] ) ) ;
free ( nacp_ctx ) ;
}
if ( program_info_ctx )
{
for ( u32 i = 0 ; i < program_count ; i + + ) programInfoFreeContext ( & ( program_info_ctx [ i ] ) ) ;
free ( program_info_ctx ) ;
}
cnmtFreeContext ( & cnmt_ctx ) ;
if ( nca_ctx ) free ( nca_ctx ) ;
if ( filename ) free ( filename ) ;
if ( buf ) free ( buf ) ;
threadExit ( ) ;
}
static u32 getOutputStorageOption ( void )
{
return ( u32 ) configGetInteger ( " output_storage " ) ;
}
static void setOutputStorageOption ( u32 idx )
{
if ( idx < ConfigOutputStorage_Count ) configSetInteger ( " output_storage " , ( int ) idx ) ;
}
static u32 getGameCardPrependKeyAreaOption ( void )
{
return ( u32 ) configGetBoolean ( " gamecard/prepend_key_area " ) ;
}
static void setGameCardPrependKeyAreaOption ( u32 idx )
{
configSetBoolean ( " gamecard/prepend_key_area " , ( bool ) idx ) ;
}
static u32 getGameCardKeepCertificateOption ( void )
{
return ( u32 ) configGetBoolean ( " gamecard/keep_certificate " ) ;
}
static void setGameCardKeepCertificateOption ( u32 idx )
{
configSetBoolean ( " gamecard/keep_certificate " , ( bool ) idx ) ;
}
static u32 getGameCardTrimDumpOption ( void )
{
return ( u32 ) configGetBoolean ( " gamecard/trim_dump " ) ;
}
static void setGameCardTrimDumpOption ( u32 idx )
{
configSetBoolean ( " gamecard/trim_dump " , ( bool ) idx ) ;
}
static u32 getGameCardCalculateChecksumOption ( void )
{
return ( u32 ) configGetBoolean ( " gamecard/calculate_checksum " ) ;
}
static void setGameCardCalculateChecksumOption ( u32 idx )
{
configSetBoolean ( " gamecard/calculate_checksum " , ( bool ) idx ) ;
}
static u32 getGameCardWriteRawHfsPartitionOption ( void )
{
return ( u32 ) configGetBoolean ( " gamecard/write_raw_hfs_partition " ) ;
}
static void setGameCardWriteRawHfsPartitionOption ( u32 idx )
{
configSetBoolean ( " gamecard/write_raw_hfs_partition " , ( bool ) idx ) ;
}
static u32 getNspSetDownloadDistributionOption ( void )
{
return ( u32 ) configGetBoolean ( " nsp/set_download_distribution " ) ;
}
static void setNspSetDownloadDistributionOption ( u32 idx )
{
configSetBoolean ( " nsp/set_download_distribution " , ( bool ) idx ) ;
}
static u32 getNspRemoveConsoleDataOption ( void )
{
return ( u32 ) configGetBoolean ( " nsp/remove_console_data " ) ;
}
static void setNspRemoveConsoleDataOption ( u32 idx )
{
configSetBoolean ( " nsp/remove_console_data " , ( bool ) idx ) ;
}
static u32 getNspRemoveTitlekeyCryptoOption ( void )
{
return ( u32 ) configGetBoolean ( " nsp/remove_titlekey_crypto " ) ;
}
static void setNspRemoveTitlekeyCryptoOption ( u32 idx )
{
configSetBoolean ( " nsp/remove_titlekey_crypto " , ( bool ) idx ) ;
}
static u32 getNspDisableLinkedAccountRequirementOption ( void )
{
return ( u32 ) configGetBoolean ( " nsp/disable_linked_account_requirement " ) ;
}
static void setNspDisableLinkedAccountRequirementOption ( u32 idx )
{
configSetBoolean ( " nsp/disable_linked_account_requirement " , ( bool ) idx ) ;
}
static u32 getNspEnableScreenshotsOption ( void )
{
return ( u32 ) configGetBoolean ( " nsp/enable_screenshots " ) ;
}
static void setNspEnableScreenshotsOption ( u32 idx )
{
configSetBoolean ( " nsp/enable_screenshots " , ( bool ) idx ) ;
}
static u32 getNspEnableVideoCaptureOption ( void )
{
return ( u32 ) configGetBoolean ( " nsp/enable_video_capture " ) ;
}
static void setNspEnableVideoCaptureOption ( u32 idx )
{
configSetBoolean ( " nsp/enable_video_capture " , ( bool ) idx ) ;
}
static u32 getNspDisableHdcpOption ( void )
{
return ( u32 ) configGetBoolean ( " nsp/disable_hdcp " ) ;
}
static void setNspDisableHdcpOption ( u32 idx )
{
configSetBoolean ( " nsp/disable_hdcp " , ( bool ) idx ) ;
}
2023-07-17 00:03:05 +01:00
static u32 getNspGenerateAuthoringToolDataOption ( void )
2023-05-24 20:05:34 +01:00
{
2023-07-17 00:03:05 +01:00
return ( u32 ) configGetBoolean ( " nsp/generate_authoringtool_data " ) ;
2023-05-24 20:05:34 +01:00
}
2023-07-17 00:03:05 +01:00
static void setNspGenerateAuthoringToolDataOption ( u32 idx )
2023-05-24 20:05:34 +01:00
{
2023-07-17 00:03:05 +01:00
configSetBoolean ( " nsp/generate_authoringtool_data " , ( bool ) idx ) ;
2023-05-24 20:05:34 +01:00
}
static u32 getTicketRemoveConsoleDataOption ( void )
{
return ( u32 ) configGetBoolean ( " ticket/remove_console_data " ) ;
}
static void setTicketRemoveConsoleDataOption ( u32 idx )
{
configSetBoolean ( " ticket/remove_console_data " , ( bool ) idx ) ;
}
2023-05-26 19:23:23 +01:00
2023-05-30 00:22:12 +01:00
static u32 getNcaFsWriteRawSectionOption ( void )
2023-05-26 19:23:23 +01:00
{
2023-05-30 00:22:12 +01:00
return ( u32 ) configGetBoolean ( " nca_fs/write_raw_section " ) ;
2023-05-26 19:23:23 +01:00
}
2023-05-30 00:22:12 +01:00
static void setNcaFsWriteRawSectionOption ( u32 idx )
2023-05-26 19:23:23 +01:00
{
2023-05-30 00:22:12 +01:00
configSetBoolean ( " nca_fs/write_raw_section " , ( bool ) idx ) ;
2023-05-26 19:23:23 +01:00
}
static u32 getNcaFsUseLayeredFsDirOption ( void )
{
return ( u32 ) configGetBoolean ( " nca_fs/use_layeredfs_dir " ) ;
}
static void setNcaFsUseLayeredFsDirOption ( u32 idx )
{
configSetBoolean ( " nca_fs/use_layeredfs_dir " , ( bool ) idx ) ;
}
2024-08-26 08:45:52 +01:00
static bool resetSettings ( void * userdata )
{
NX_IGNORE_ARG ( userdata ) ;
consolePrint ( " are you sure you want to reset all settings to their default values? \n " ) ;
consolePrint ( " press a to proceed, or b to cancel \n \n " ) ;
u64 btn_down = utilsWaitForButtonPress ( HidNpadButton_A | HidNpadButton_B ) ;
if ( btn_down & HidNpadButton_A )
{
configResetSettings ( ) ;
MenuElement * * element_lists [ ] = {
g_xciMenuElements ,
g_gameCardHfsDumpMenuElements ,
g_nspMenuElements ,
g_ticketMenuElements ,
g_ncaFsSectionsSubMenuElements ,
NULL
} ;
for ( u32 i = 0 ; element_lists [ i ] ! = NULL ; i + + )
{
MenuElement * * cur_element_list = element_lists [ i ] ;
for ( u32 j = 0 ; cur_element_list [ j ] ! = NULL ; j + + )
{
MenuElement * cur_element = cur_element_list [ j ] ;
MenuElementOption * cur_options = cur_element - > element_options ;
if ( ! cur_options ) continue ;
cur_options - > retrieved = false ;
if ( cur_options - > getter_func )
{
cur_options - > selected = cur_options - > getter_func ( ) ;
cur_options - > retrieved = true ;
}
}
}
consolePrint ( " settings successfully reset \n " ) ;
}
return false ;
}