1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-11-22 18:26:39 +00:00

Update to v1.1.7.

This commit is contained in:
Pablo Curiel 2019-11-06 14:22:40 -04:00
parent bb450df38d
commit 3a16147c59
20 changed files with 5907 additions and 2514 deletions

View file

@ -33,7 +33,7 @@ include $(DEVKITPRO)/libnx/switch_rules
VERSION_MAJOR := 1 VERSION_MAJOR := 1
VERSION_MINOR := 1 VERSION_MINOR := 1
VERSION_MICRO := 6 VERSION_MICRO := 7
APP_TITLE := nxdumptool APP_TITLE := nxdumptool
APP_AUTHOR := MCMrARM, DarkMatterCore APP_AUTHOR := MCMrARM, DarkMatterCore

View file

@ -7,9 +7,13 @@ Main features
* Generates full Cartridge Image dumps (XCI) with optional certificate removal and/or trimming. * Generates full Cartridge Image dumps (XCI) with optional certificate removal and/or trimming.
* Generates installable Nintendo Submission Packages (NSP) from base applications, updates and DLCs stored in the inserted gamecard, SD card and eMMC storage devices. * Generates installable Nintendo Submission Packages (NSP) from base applications, updates and DLCs stored in the inserted gamecard, SD card and eMMC storage devices.
* The generated dumps follow the `AuditingTool` format from Scene releases. * The generated dumps follow the `AuditingTool` format from Scene releases.
* Capable of generating dumps without console specific information (common ticket).
* Capable of generating ticket-less (standard crypto) dumps. * Capable of generating ticket-less (standard crypto) dumps.
* Capable of generating dumps from installed updates/DLCs with missing base applications (orphan titles). * Capable of generating dumps from installed updates/DLCs with missing base applications (orphan titles).
* Batch mode available. * Compatible with game pre-installs.
* Batch mode available, with customizable dump settings.
* Manual gamecard certificate dump.
* Manual ticket dump from installed SD/eMMC titles + optional removal of console specific data.
* Compatible with multigame carts. * Compatible with multigame carts.
* CRC32 checksum calculation for XCI/NSP dumps. * CRC32 checksum calculation for XCI/NSP dumps.
* Full XCI dump verification using XML database from NSWDB.COM (NSWreleases.xml). * Full XCI dump verification using XML database from NSWDB.COM (NSWreleases.xml).
@ -19,15 +23,17 @@ Main features
* Program NCA ExeFS/RomFS section & Data NCA RomFS section file data dumping + browser with manual file dump support. * Program NCA ExeFS/RomFS section & Data NCA RomFS section file data dumping + browser with manual file dump support.
* Compatible with base applications, updates and DLCs (if available). * Compatible with base applications, updates and DLCs (if available).
* Supports manual RomFS directory dumping. * Supports manual RomFS directory dumping.
* Manual gamecard certificate dump.
* Free SD card space checks in place. * Free SD card space checks in place.
* File splitting support for all operations. * File splitting support for all operations.
* Capable of storing split XCI/NSP dumps in directories with the archive bit set. * Capable of storing split XCI/NSP dumps in directories with the archive bit set.
* Sequential (multi-session) dump support, in case there's not enough storage space available for a XCI/NSP full dump.
* Metadata retrieval using NCM and NS services. * Metadata retrieval using NCM and NS services.
* Dump speed calculation, ETA calculation and progress bar. * Dump speed calculation, ETA calculation and progress bar.
Operations related to installed SD/eMMC titles require a keys file located at "sdmc:/switch/prod.keys". Use [Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM) to generate it. Operations related to installed SD/eMMC titles require a keys file located at "sdmc:/switch/prod.keys". Use [Lockpick_RCM](https://github.com/shchmue/Lockpick_RCM) to generate it.
Please launch the application through title override (hold R while launching a game) whenever possible to avoid memory allocation problems.
Thanks to Thanks to
-------------- --------------
@ -46,9 +52,10 @@ Thanks to
* The [LZ4 project](http://www.lz4.org), for the LZ4 C-code implementation (licensed under [BSD 2-Clause](https://github.com/lz4/lz4/blob/master/lib/LICENSE)). * The [LZ4 project](http://www.lz4.org), for the LZ4 C-code implementation (licensed under [BSD 2-Clause](https://github.com/lz4/lz4/blob/master/lib/LICENSE)).
* [AnalogMan](https://github.com/AnalogMan151) and [0Liam](https://github.com/0Liam), for their constant support and ideas. * [AnalogMan](https://github.com/AnalogMan151) and [0Liam](https://github.com/0Liam), for their constant support and ideas.
* [RattletraPM](https://github.com/RattletraPM), for the awesome icon used in the application. * [RattletraPM](https://github.com/RattletraPM), for the awesome icon used in the application.
* The GNOME project, from which the high contrast directory/file icons for the filebrowser modes were retrieved. * The GNOME project, from which the [high contrast icons](https://commons.wikimedia.org/wiki/GNOME_High_contrast_icons) were retrieved.
* The folks from ReSwitched, for working towards the creation of a good homebrew ecosystem. * The folks from ReSwitched, for working towards the creation of a good homebrew ecosystem.
* The Comfy Boyes, for being both awesome and supportive. You know who you are. * The Comfy Boyes, for being both awesome and supportive. You know who you are.
* My girlfriend, for putting up with me even though I dedicate most of my free time to this little project, and for doing her best to cheer me up and keep me going. I love you.
Donate Donate
-------------- --------------
@ -60,6 +67,55 @@ If you like my work and you'd like to support me in any way, it's not necessary,
Changelog Changelog
-------------- --------------
**v1.1.7:**
* Tickets and RSA certificates are now properly parsed from their respective system savedata files, thanks to the efforts of [shchmue](https://github.com/shchmue)!
* Speeds up ticket / titlekey retrieval for NSP/ExeFS/RomFS operations.
* Removes the need to bundle RSA certificates inside the application. Yay!
* As a bonus, the new `XS00000024` personalized ticket certificate introduced in 9.0.0 is now supported. Thanks to [SimonTime](https://github.com/simontime) for providing insight on this matter!
* Added NSP dump support for pre-installed titles.
* If the selected title uses titlekey crypto and no ticket for it can be found, a prompt will be displayed, asking the user if they want to proceed anyway (even though content decryption won't be possible).
* This prompt will *not* appear in batch dump operations. The dump procedure will always go ahead.
* Sequential NSP dump operations will only display the prompt during their first run.
* Added a new Ticket submenu for SD/eMMC titles. It can be used to only dump the Ticket from a specific base application / update / DLC, without having to dump its entire NSP.
* Dumped tickets are stored in `sdmc:/switch/nxdumptool/Ticket`.
* A configurable option is also available to remove console specific data from dumped tickets.
* The encrypted + decrypted title key is displayed during the dumping process, along with the Rights ID for the title.
* Just so you know, if you want to dump tickets from base application updates bundled in gamecards, use the HFS0 browser.
* Added an option in NSP/batch dump menus to control the replacement of the NPDM RSA key/sig in Program NCAs from base applications and updates:
* Up until now, replacing both the public RSA key in the ACID section from the main.npdm file (ExeFS) and the NPDM header signature (NCA header) has been the default, non-configurable behaviour whenever Program NCA modifications were needed.
* This option is enabled by default - if Program NCA modifications are needed, disabling this option will make the output NSP require ACID patches to function properly under any CFW (but at the same time, it will make the Program NCA verifiable by PC tools).
* The rest of the possible Program NCA modifications (content distribution change and/or Rights ID removal + key area replacement) will be applied when needed, even if this option is disabled.
* Changes related to the orphan content menu (Y button):
* Parent base application name is now retrieved for orphan updates and DLCs whenever possible, and used in menus and output NSP dumps.
* Moved the orphan content hint from the orphan content menu to the SD/eMMC menu.
* Changed application behaviour regarding the Lockpick_RCM keys file existence:
* SD/eMMC menu and NSP/ExeFS/RomFS related operations are now disabled if the keys file at "sdmc:/switch/prod.keys" is not available.
* An error message telling the user to run Lockpick_RCM will be displayed in the main menu if the keys file is not available.
* Additionally, error messages related to data decryption will now also suggest the user to run Lockpick_RCM.
* Changes to the generated update NSPs (thanks to [The-4n](https://github.com/The-4n) and [suchmememanyskill](https://github.com/suchmememanyskill)):
* Delta Fragments are, again, always excluded from output NSP dumps, regardless of their source storage and the selected dump settings.
* Patch Extended Data is no longer wiped from the CNMT NCA in update NSPs - only the content records are replaced accordingly.
* Furthermore, content records from Delta Fragments are preserved as well.
* Fixed CNMT PFS0 block hash calculation when the total PFS0 size exceeds the hash block size from the PFS0 superblock in the NCA header. Removes the `0x236E02` / `2002-4535` error in Goldleaf about an invalid PFS0, triggered by update NSPs with long a CNMT PFS0 section.
* Changes to the generated NSP XMLs:
* `RequiredDownloadSystemVersion` and `IdOffset` elements from the CNMT XML are now properly retrieved from their true locations in the CNMT NCA.
* Added support for the `RuntimeParameterDelivery` NACP field (introduced in HOS 9.X).
* Added support for the `IARCGeneric` value in the `RatingAge` NACP field (introduced in HOS 9.X).
* Fixed handling of `PlayLogQueryableApplicationId` values.
* Big thanks to [0Liam](https://github.com/0Liam) for documenting these changes!
* Changes related to the application update feature:
* Added a forced update prompt if the application is already on the latest version.
* The application update option will now be disabled after a successful update.
* Removed the FS service reinitialize step after closing the application's RomFS at startup. This was done because `romfsExit()` didn't close all open file handles to the NRO when I tested it with libnx v2.2.0 some time ago, thus making the application update fail. Nonetheless, the problem has been fixed.
* Fixed UI flickering when HFS0 partition data can't be retrieved from the gamecard.
* Furthermore, a warning about `nogc` spoofing is now displayed under this particular case.
* Added an extra NSP offset validation step for sequential NSP dumps.
* Minor codestyle fixes.
Big thanks to [FennecTECH](https://github.com/fennectech) and `Hannah (Luna)#8459` for providing with **lots** of testing for this release!
PSA: if you downloaded any new games from the eShop after updating to 9.0.0+ and used a previous release of nxdumptool to dump NSPs **with console specific data**, please redump them - their RSA certificate chain isn't the proper one. Dumps without console specific data (or without a ticket) are not affected by this.
**v1.1.6:** **v1.1.6:**
* Added sequential dump support: it is now possible to start a XCI/NSP dump procedure even if there's not enough space available in the SD card! * Added sequential dump support: it is now possible to start a XCI/NSP dump procedure even if there's not enough space available in the SD card!
* No setting has to be modified in order to enable this feature - the application will automatically ask the user if they want to use this mode if there's not enough space for the full dump. * No setting has to be modified in order to enable this feature - the application will automatically ask the user if they want to use this mode if there's not enough space for the full dump.

Binary file not shown.

Binary file not shown.

Binary file not shown.

File diff suppressed because it is too large Load diff

View file

@ -39,6 +39,8 @@ typedef struct {
FsStorageId storageId; // Source storage from which the data is dumped FsStorageId storageId; // Source storage from which the data is dumped
bool removeConsoleData; // Original value for the "Remove console specific data" option. Overrides the selected setting in the current session bool removeConsoleData; // Original value for the "Remove console specific data" option. Overrides the selected setting in the current session
bool tiklessDump; // Original value for the "Generate ticket-less dump" option. Overrides the selected setting in the current session. Ignored if removeConsoleData == false bool tiklessDump; // Original value for the "Generate ticket-less dump" option. Overrides the selected setting in the current session. Ignored if removeConsoleData == false
bool npdmAcidRsaPatch; // Original value for the "Change NPDM RSA key/sig in Program NCA" option. Overrides the selected setting in the current session
bool preInstall; // Indicates if we're dealing with a preinstalled title - e.g. if the user already accepted the missing ticket prompt
u8 partNumber; // Next part number u8 partNumber; // Next part number
u32 nspFileCount; // PFS0 file count u32 nspFileCount; // PFS0 file count
u32 ncaCount; // NCA count u32 ncaCount; // NCA count
@ -69,5 +71,6 @@ bool dumpRomFsSectionData(u32 titleIndex, selectedRomFsType curRomFsType, bool d
bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType curRomFsType, bool doSplitting); bool dumpFileFromRomFsSection(u32 titleIndex, u32 file_offset, selectedRomFsType curRomFsType, bool doSplitting);
bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, bool doSplitting); bool dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, bool doSplitting);
bool dumpGameCardCertificate(); bool dumpGameCardCertificate();
bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOptions *tikDumpCfg);
#endif #endif

View file

@ -33,7 +33,7 @@
/ 2: Enable with LF-CRLF conversion. */ / 2: Enable with LF-CRLF conversion. */
#define FF_USE_FIND 2 #define FF_USE_FIND 0
/* This option switches filtered directory read functions, f_findfirst() and /* This option switches filtered directory read functions, f_findfirst() and
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */ / f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
@ -68,7 +68,7 @@
/ Locale and Namespace Configurations / Locale and Namespace Configurations
/---------------------------------------------------------------------------*/ /---------------------------------------------------------------------------*/
#define FF_CODE_PAGE 932 #define FF_CODE_PAGE 850
/* This option specifies the OEM code page to be used on the target system. /* This option specifies the OEM code page to be used on the target system.
/ Incorrect code page setting can cause a file open failure. / Incorrect code page setting can cause a file open failure.
/ /
@ -97,7 +97,7 @@
*/ */
#define FF_USE_LFN 1 #define FF_USE_LFN 3
#define FF_MAX_LFN 255 #define FF_MAX_LFN 255
/* The FF_USE_LFN switches the support for LFN (long file name). /* The FF_USE_LFN switches the support for LFN (long file name).
/ /
@ -137,7 +137,7 @@
/ on character encoding. When LFN is not enabled, these options have no effect. */ / on character encoding. When LFN is not enabled, these options have no effect. */
#define FF_STRF_ENCODE 3 #define FF_STRF_ENCODE 0
/* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(), /* When FF_LFN_UNICODE >= 1 with LFN enabled, string I/O functions, f_gets(),
/ f_putc(), f_puts and f_printf() convert the character encoding in it. / f_putc(), f_puts and f_printf() convert the character encoding in it.
/ This option selects assumption of character encoding ON THE FILE to be / This option selects assumption of character encoding ON THE FILE to be
@ -150,7 +150,7 @@
*/ */
#define FF_FS_RPATH 1 #define FF_FS_RPATH 0
/* This option configures support for relative path. /* This option configures support for relative path.
/ /
/ 0: Disable relative path and remove related functions. / 0: Disable relative path and remove related functions.
@ -167,8 +167,8 @@
/* Number of volumes (logical drives) to be used. (1-10) */ /* Number of volumes (logical drives) to be used. (1-10) */
#define FF_STR_VOLUME_ID 0 #define FF_STR_VOLUME_ID 1
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3" #define FF_VOLUME_STRS "sys"
/* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings. /* FF_STR_VOLUME_ID switches support for volume ID in arbitrary strings.
/ When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive / When FF_STR_VOLUME_ID is set to 1 or 2, arbitrary strings can be used as drive
/ number in the path name. FF_VOLUME_STRS defines the volume ID strings for each / number in the path name. FF_VOLUME_STRS defines the volume ID strings for each
@ -239,7 +239,7 @@
#define FF_FS_NORTC 0 #define FF_FS_NORTC 0
#define FF_NORTC_MON 1 #define FF_NORTC_MON 1
#define FF_NORTC_MDAY 1 #define FF_NORTC_MDAY 1
#define FF_NORTC_YEAR 2018 #define FF_NORTC_YEAR 2019
/* The option FF_FS_NORTC switches timestamp functiton. If the system does not have /* The option FF_FS_NORTC switches timestamp functiton. If the system does not have
/ any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable / any RTC function or valid timestamp is not needed, set FF_FS_NORTC = 1 to disable
/ the timestamp function. Every object modified by FatFs will have a fixed timestamp / the timestamp function. Every object modified by FatFs will have a fixed timestamp

35
source/fatfs/ffsystem.c Normal file
View file

@ -0,0 +1,35 @@
/*------------------------------------------------------------------------*/
/* Sample Code of OS Dependent Functions for FatFs */
/* (C) ChaN, 2018 */
/* (C) CTCaer, 2018 */
/*------------------------------------------------------------------------*/
#include <stdlib.h>
#include "ff.h"
#if FF_USE_LFN == 3 /* Dynamic memory allocation */
/*------------------------------------------------------------------------*/
/* Allocate a memory block */
/*------------------------------------------------------------------------*/
void* ff_memalloc ( /* Returns pointer to the allocated memory block (null if not enough core) */
UINT msize /* Number of bytes to allocate */
)
{
return malloc(msize); /* Allocate a new memory block with POSIX API */
}
/*------------------------------------------------------------------------*/
/* Free a memory block */
/*------------------------------------------------------------------------*/
void ff_memfree (
void* mblock /* Pointer to the memory block to free (nothing to do if null) */
)
{
free(mblock); /* Free the memory block with POSIX API */
}
#endif

View file

@ -9,46 +9,24 @@
#include "ui.h" #include "ui.h"
#include "es.h" #include "es.h"
#include "set_ext.h" #include "set_ext.h"
#include "save.h"
/* Extern variables */ /* Extern variables */
extern int breaks; extern int breaks;
extern int font_height; extern int font_height;
extern u8 *dumpBuf;
extern char strbuf[NAME_BUF_LEN]; extern char strbuf[NAME_BUF_LEN];
/* Statically allocated variables */ /* Statically allocated variables */
nca_keyset_t nca_keyset; nca_keyset_t nca_keyset;
FsStorage fatFsStorage; static u8 eticket_data[ETICKET_DEVKEY_DATA_SIZE];
static bool setcal_eticket_retrieved = false;
static const char *cert_CA00000003_path = "romfs:/certificate/CA00000003";
static const char *cert_XS00000020_path = "romfs:/certificate/XS00000020";
static const char *cert_XS00000021_path = "romfs:/certificate/XS00000021";
static u8 cert_CA00000003_data[ETICKET_CA_CERT_SIZE];
static u8 cert_XS00000020_data[ETICKET_XS_CERT_SIZE];
static u8 cert_XS00000021_data[ETICKET_XS_CERT_SIZE];
static const u8 cert_CA00000003_hash[0x20] = {
0x62, 0x69, 0x0E, 0xC0, 0x4C, 0x62, 0x9D, 0x08, 0x38, 0xBB, 0xDF, 0x65, 0xC5, 0xA6, 0xB0, 0x9A,
0x54, 0x94, 0x2C, 0x87, 0x0E, 0x01, 0x55, 0x73, 0xCF, 0x7D, 0x58, 0xF2, 0x59, 0xFE, 0x36, 0xFA
};
static const u8 cert_XS00000020_hash[0x20] = {
0x55, 0x23, 0x17, 0xD4, 0x4B, 0xAF, 0x4C, 0xF5, 0x31, 0x8E, 0xF5, 0xC6, 0x4E, 0x0F, 0x75, 0xD9,
0x75, 0xD4, 0x03, 0xFD, 0x7B, 0x93, 0x7B, 0xAB, 0x46, 0x7D, 0x37, 0x94, 0x62, 0x39, 0x33, 0xE9
};
static const u8 cert_XS00000021_hash[0x20] = {
0xDE, 0xFF, 0x96, 0x01, 0x42, 0x1E, 0x00, 0xC1, 0x52, 0x60, 0x5C, 0x9F, 0x42, 0xCD, 0x91, 0xD7,
0x90, 0x01, 0xC5, 0x7F, 0xC3, 0x27, 0x58, 0x4B, 0xD9, 0x6F, 0x71, 0x78, 0xC9, 0x44, 0xD0, 0xAD
};
static const char *keysFilePath = "sdmc:/switch/prod.keys";
/* Variables related to the FS process */
static keyLocation FSRodata = { static keyLocation FSRodata = {
FS_TID, FS_TID,
SEG_RODATA, SEG_RODATA,
@ -132,19 +110,22 @@ bool retrieveProcessMemory(keyLocation *location)
// If not a kernel process, get PID from pm:dmnt // If not a kernel process, get PID from pm:dmnt
u64 pid; u64 pid;
if (R_FAILED(result = pmdmntGetTitlePid(&pid, location->titleID))) result = pmdmntGetTitlePid(&pid, location->titleID);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: pmdmntGetTitlePid failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: pmdmntGetTitlePid failed! (0x%08X)", result);
return false; return false;
} }
if (R_FAILED(result = svcDebugActiveProcess(&debug_handle, pid))) result = svcDebugActiveProcess(&debug_handle, pid);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcDebugActiveProcess failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcDebugActiveProcess failed! (0x%08X)", result);
return false; return false;
} }
if (R_FAILED(result = svcGetDebugEvent((u8*)&d, debug_handle))) result = svcGetDebugEvent((u8*)&d, debug_handle);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcGetDebugEvent failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcGetDebugEvent failed! (0x%08X)", result);
return false; return false;
@ -154,7 +135,8 @@ bool retrieveProcessMemory(keyLocation *location)
u64 pids[300]; u64 pids[300];
u32 num_processes; u32 num_processes;
if (R_FAILED(result = svcGetProcessList(&num_processes, pids, 300))) result = svcGetProcessList(&num_processes, pids, 300);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcGetProcessList failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcGetProcessList failed! (0x%08X)", result);
return false; return false;
@ -191,7 +173,8 @@ bool retrieveProcessMemory(keyLocation *location)
// Locate "real" .text segment as Atmosphere emuMMC has two // Locate "real" .text segment as Atmosphere emuMMC has two
for(;;) for(;;)
{ {
if (R_FAILED(result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr))) result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcQueryDebugProcessMemory failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcQueryDebugProcessMemory failed! (0x%08X)", result);
success = false; success = false;
@ -214,7 +197,8 @@ bool retrieveProcessMemory(keyLocation *location)
for(segment = 1; segment < BIT(3);) for(segment = 1; segment < BIT(3);)
{ {
if (R_FAILED(result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr))) result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcQueryDebugProcessMemory failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcQueryDebugProcessMemory failed! (0x%08X)", result);
success = false; success = false;
@ -238,7 +222,8 @@ bool retrieveProcessMemory(keyLocation *location)
memset(location->data + location->dataSize, 0, mem_info.size); memset(location->data + location->dataSize, 0, mem_info.size);
if (R_FAILED(result = svcReadDebugProcessMemory(location->data + location->dataSize, debug_handle, mem_info.addr, mem_info.size))) result = svcReadDebugProcessMemory(location->data + location->dataSize, debug_handle, mem_info.addr, mem_info.size);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcReadDebugProcessMemory failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: svcReadDebugProcessMemory failed! (0x%08X)", result);
success = false; success = false;
@ -337,13 +322,15 @@ bool loadMemoryKeys()
nca_keyset.memory_key_cnt++; nca_keyset.memory_key_cnt++;
// Derive NCA header key // Derive NCA header key
if (R_FAILED(result = splCryptoInitialize())) result = splCryptoInitialize();
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the spl:crypto service! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the spl:crypto service! (0x%08X)", result);
return false; return false;
} }
if (R_FAILED(result = splCryptoGenerateAesKek(nca_keyset.header_kek_source, 0, 0, nca_keyset.header_kek))) result = splCryptoGenerateAesKek(nca_keyset.header_kek_source, 0, 0, nca_keyset.header_kek);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKek(header_kek_source) failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKek(header_kek_source) failed! (0x%08X)", result);
splCryptoExit(); splCryptoExit();
@ -352,14 +339,16 @@ bool loadMemoryKeys()
nca_keyset.memory_key_cnt++; nca_keyset.memory_key_cnt++;
if (R_FAILED(result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x00, nca_keyset.header_key + 0x00))) result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x00, nca_keyset.header_key + 0x00);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKey(header_key_source + 0x00) failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKey(header_key_source + 0x00) failed! (0x%08X)", result);
splCryptoExit(); splCryptoExit();
return false; return false;
} }
if (R_FAILED(result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x10, nca_keyset.header_key + 0x10))) result = splCryptoGenerateAesKey(nca_keyset.header_kek, nca_keyset.header_key_source + 0x10, nca_keyset.header_key + 0x10);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKey(header_key_source + 0x10) failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKey(header_key_source + 0x10) failed! (0x%08X)", result);
splCryptoExit(); splCryptoExit();
@ -397,13 +386,15 @@ bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out)
u8 *kek_source = (dec_nca_header->kaek_ind == 0 ? nca_keyset.key_area_key_application_source : (dec_nca_header->kaek_ind == 1 ? nca_keyset.key_area_key_ocean_source : nca_keyset.key_area_key_system_source)); u8 *kek_source = (dec_nca_header->kaek_ind == 0 ? nca_keyset.key_area_key_application_source : (dec_nca_header->kaek_ind == 1 ? nca_keyset.key_area_key_ocean_source : nca_keyset.key_area_key_system_source));
if (R_FAILED(result = splCryptoInitialize())) result = splCryptoInitialize();
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the spl:crypto service! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the spl:crypto service! (0x%08X)", result);
return false; return false;
} }
if (R_FAILED(result = splCryptoGenerateAesKek(kek_source, crypto_type, 0, tmp_kek))) result = splCryptoGenerateAesKek(kek_source, crypto_type, 0, tmp_kek);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKek(kek_source) failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKek(kek_source) failed! (0x%08X)", result);
splCryptoExit(); splCryptoExit();
@ -415,7 +406,8 @@ bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out)
for(i = 0; i < NCA_KEY_AREA_KEY_CNT; i++) for(i = 0; i < NCA_KEY_AREA_KEY_CNT; i++)
{ {
if (R_FAILED(result = splCryptoGenerateAesKey(tmp_kek, dec_nca_header->nca_keys[i], decrypted_nca_keys[i]))) result = splCryptoGenerateAesKey(tmp_kek, dec_nca_header->nca_keys[i], decrypted_nca_keys[i]);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKey(nca_kaek_%02u) failed! (0x%08X)", i, result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splCryptoGenerateAesKey(nca_kaek_%02u) failed! (0x%08X)", i, result);
success = false; success = false;
@ -625,6 +617,11 @@ int readKeysFromFile(FILE *f)
{ {
if (!parse_hex_key(nca_keyset.eticket_rsa_kek, value, sizeof(nca_keyset.eticket_rsa_kek))) return 0; if (!parse_hex_key(nca_keyset.eticket_rsa_kek, value, sizeof(nca_keyset.eticket_rsa_kek))) return 0;
nca_keyset.ext_key_cnt++; nca_keyset.ext_key_cnt++;
} else
if (strcasecmp(key, "save_mac_key") == 0)
{
if (!parse_hex_key(nca_keyset.save_mac_key, value, sizeof(nca_keyset.save_mac_key))) return 0;
nca_keyset.ext_key_cnt++;
} else { } else {
memset(test_name, 0, sizeof(test_name)); memset(test_name, 0, sizeof(test_name));
@ -679,10 +676,10 @@ bool loadExternalKeys()
if (nca_keyset.ext_key_cnt > 0) return true; if (nca_keyset.ext_key_cnt > 0) return true;
// Open keys file // Open keys file
FILE *keysFile = fopen(keysFilePath, "rb"); FILE *keysFile = fopen(KEYS_FILE_PATH, "rb");
if (!keysFile) if (!keysFile)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to open \"%s\" to retrieve \"eticket_rsa_kek\", titlekeks and KAEKs!", keysFilePath); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to open \"%s\" to retrieve \"eticket_rsa_kek\", titlekeks and KAEKs!", KEYS_FILE_PATH);
return false; return false;
} }
@ -692,7 +689,7 @@ bool loadExternalKeys()
if (ret < 1) if (ret < 1)
{ {
if (ret == -1) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to parse necessary keys from \"%s\"! (keys file empty?)", keysFilePath); if (ret == -1) uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to parse necessary keys from \"%s\"! (keys file empty?)", KEYS_FILE_PATH);
return false; return false;
} }
@ -717,13 +714,15 @@ bool testKeyPair(const void *E, const void *D, const void *N)
X[0xFE] = 0xBA; X[0xFE] = 0xBA;
X[0xFF] = 0xBE; X[0xFF] = 0xBE;
if (R_FAILED(result = splUserExpMod(X, N, D, 0x100, Y))) result = splUserExpMod(X, N, D, 0x100, Y);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splUserExpMod failed! (testKeyPair #1) (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splUserExpMod failed! (testKeyPair #1) (0x%08X)", result);
return false; return false;
} }
if (R_FAILED(result = splUserExpMod(Y, N, E, 4, Z))) result = splUserExpMod(Y, N, E, 4, Z);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splUserExpMod failed! (testKeyPair #2) (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splUserExpMod failed! (testKeyPair #2) (0x%08X)", result);
return false; return false;
@ -770,15 +769,17 @@ void mgf1(const u8 *data, size_t data_length, u8 *mask, size_t mask_length)
free(data_counter); free(data_counter);
} }
bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key) int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key)
{ {
int ret = -1;
if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || (!out_tik && !out_dec_key && !out_enc_key)) if (!dec_nca_header || dec_nca_header->kaek_ind > 2 || (!out_tik && !out_dec_key && !out_enc_key))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve NCA ticket and/or titlekey."); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve NCA ticket and/or titlekey.");
return false; return ret;
} }
u32 i; u32 i, j;
bool has_rights_id = false; bool has_rights_id = false;
for(i = 0; i < 0x10; i++) for(i = 0; i < 0x10; i++)
@ -793,7 +794,7 @@ bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_e
if (!has_rights_id) if (!has_rights_id)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA doesn't use titlekey crypto."); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA doesn't use titlekey crypto.");
return false; return ret;
} }
u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type); u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type);
@ -802,7 +803,7 @@ bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_e
if (crypto_type >= 0x20) if (crypto_type >= 0x20)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA keyblob index."); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA keyblob index.");
return false; return ret;
} }
Result result; Result result;
@ -812,51 +813,58 @@ bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_e
bool foundRightsId = false; bool foundRightsId = false;
u8 rightsIdType = 0; // 1 = Common, 2 = Personalized u8 rightsIdType = 0; // 1 = Common, 2 = Personalized
u8 eticket_data[ETICKET_DEVKEY_DATA_SIZE];
memset(eticket_data, 0, ETICKET_DEVKEY_DATA_SIZE);
Aes128CtrContext eticket_aes_ctx; Aes128CtrContext eticket_aes_ctx;
unsigned char ctr[0x10]; unsigned char ctr[0x10];
u8 *D = NULL, *N = NULL, *E = NULL; u8 *D = NULL, *N = NULL, *E = NULL;
FATFS fs;
FRESULT fr; FRESULT fr;
FIL eTicketSave; FIL eTicketSave;
u64 offset = 0; save_ctx_t *save_ctx = NULL;
u8 eTicketEntry[ETICKET_ENTRY_SIZE], titleKeyBlock[0x100]; allocation_table_storage_ctx_t fat_storage;
u32 read_bytes = 0; save_fs_list_entry_t entry;
const char ticket_bin_path[SAVE_FS_LIST_MAX_NAME_LENGTH] = "/ticket.bin";
u32 buf_size = (ETICKET_ENTRY_SIZE * 0x10);
u32 br = buf_size;
u64 total_br = 0;
char tmp[NAME_BUF_LEN / 2] = {'\0'};
bool foundEticket = false, proceed = true; bool foundEticket = false, proceed = true;
u8 titlekey[0x10]; u8 titlekey[0x10];
Aes128Context titlekey_aes_ctx; Aes128Context titlekey_aes_ctx;
if (R_FAILED(result = esInitialize())) result = esInitialize();
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the ES service! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the ES service! (0x%08X)", result);
return false; return ret;
} }
if (R_FAILED(result = esCountCommonTicket(&common_count))) result = esCountCommonTicket(&common_count);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esCountCommonTicket failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esCountCommonTicket failed! (0x%08X)", result);
esExit(); esExit();
return false; return ret;
} }
if (R_FAILED(result = esCountPersonalizedTicket(&personalized_count))) result = esCountPersonalizedTicket(&personalized_count);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esCountPersonalizedTicket failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esCountPersonalizedTicket failed! (0x%08X)", result);
esExit(); esExit();
return false; return ret;
} }
if (!common_count && !personalized_count) if (!common_count && !personalized_count)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: no tickets available!"); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: no tickets available!");
esExit(); esExit();
return false; return ret;
} }
if (common_count) if (common_count)
@ -866,15 +874,16 @@ bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_e
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to allocate memory for common tickets' rights IDs!"); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to allocate memory for common tickets' rights IDs!");
esExit(); esExit();
return false; return ret;
} }
if (R_FAILED(result = esListCommonTicket(&ids_written, common_rights_ids, common_count * sizeof(FsRightsId)))) result = esListCommonTicket(&ids_written, common_rights_ids, common_count * sizeof(FsRightsId));
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esListCommonTicket failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esListCommonTicket failed! (0x%08X)", result);
free(common_rights_ids); free(common_rights_ids);
esExit(); esExit();
return false; return ret;
} }
for(i = 0; i < common_count; i++) for(i = 0; i < common_count; i++)
@ -897,15 +906,16 @@ bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_e
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to allocate memory for personalized tickets' rights IDs!"); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to allocate memory for personalized tickets' rights IDs!");
esExit(); esExit();
return false; return ret;
} }
if (R_FAILED(result = esListPersonalizedTicket(&ids_written, personalized_rights_ids, personalized_count * sizeof(FsRightsId)))) result = esListPersonalizedTicket(&ids_written, personalized_rights_ids, personalized_count * sizeof(FsRightsId));
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esListPersonalizedTicket failed! (0x%08X)", result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: esListPersonalizedTicket failed! (0x%08X)", result);
free(personalized_rights_ids); free(personalized_rights_ids);
esExit(); esExit();
return false; return ret;
} }
for(i = 0; i < personalized_count; i++) for(i = 0; i < personalized_count; i++)
@ -926,150 +936,195 @@ bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_e
if (!foundRightsId || (rightsIdType != 1 && rightsIdType != 2)) if (!foundRightsId || (rightsIdType != 1 && rightsIdType != 2))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA rights ID unavailable in this console!"); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA rights ID unavailable in this console!");
return false; ret = -2;
} return ret;
// Get extended eTicket RSA key from PRODINFO
if (R_FAILED(result = setcalInitialize()))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the set:cal service! (0x%08X)", result);
return false;
}
result = setcalGetEticketDeviceKey(eticket_data);
setcalExit();
if (R_FAILED(result))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: setcalGetEticketDeviceKey failed! (0x%08X)", result);
return false;
} }
// Load external keys // Load external keys
if (!loadExternalKeys()) return false; if (!loadExternalKeys()) return ret;
// Decrypt eTicket RSA key if (!setcal_eticket_retrieved)
memcpy(ctr, eticket_data + ETICKET_DEVKEY_CTR_OFFSET, 0x10);
aes128CtrContextCreate(&eticket_aes_ctx, nca_keyset.eticket_rsa_kek, ctr);
aes128CtrCrypt(&eticket_aes_ctx, eticket_data + ETICKET_DEVKEY_RSA_OFFSET, eticket_data + ETICKET_DEVKEY_RSA_OFFSET, ETICKET_DEVKEY_RSA_SIZE);
// Public exponent must use RSA-2048 SHA-1 signature method
// The value is stored use big endian byte order
if (bswap_32(*((u32*)(&(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200])))) != SIGTYPE_RSA2048_SHA1)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid public RSA exponent for eTicket data! Wrong keys?"); // Get extended eTicket RSA key from PRODINFO
return false; memset(eticket_data, 0, ETICKET_DEVKEY_DATA_SIZE);
}
result = setcalInitialize();
D = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET]); if (R_FAILED(result))
N = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x100]); {
E = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200]); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to initialize the set:cal service! (0x%08X)", result);
return ret;
if (!testKeyPair(E, D, N)) return false; }
if (R_FAILED(result = fsOpenBisStorage(&fatFsStorage, FsBisStorageId_System))) result = setcalGetEticketDeviceKey(eticket_data);
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to open BIS System partition! (0x%08X)", result); setcalExit();
return false;
if (R_FAILED(result))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: setcalGetEticketDeviceKey failed! (0x%08X)", result);
return ret;
}
// Decrypt eTicket RSA key
memcpy(ctr, eticket_data + ETICKET_DEVKEY_CTR_OFFSET, 0x10);
aes128CtrContextCreate(&eticket_aes_ctx, nca_keyset.eticket_rsa_kek, ctr);
aes128CtrCrypt(&eticket_aes_ctx, eticket_data + ETICKET_DEVKEY_RSA_OFFSET, eticket_data + ETICKET_DEVKEY_RSA_OFFSET, ETICKET_DEVKEY_RSA_SIZE);
// Public exponent must use RSA-2048 SHA-1 signature method
// The value is stored use big endian byte order
if (bswap_32(*((u32*)(&(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200])))) != SIGTYPE_RSA2048_SHA1)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid public RSA exponent for eTicket data! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.");
return ret;
}
D = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET]);
N = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x100]);
E = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200]);
if (!testKeyPair(E, D, N)) return ret;
setcal_eticket_retrieved = true;
} else {
D = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET]);
N = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x100]);
E = &(eticket_data[ETICKET_DEVKEY_RSA_OFFSET + 0x200]);
} }
// FatFs is used to mount the BIS System partition and read the ES savedata files to avoid 0xE02 (file already in use) errors // FatFs is used to mount the BIS System partition and read the ES savedata files to avoid 0xE02 (file already in use) errors
fr = f_mount(&fs, "sys", 1); fr = f_open(&eTicketSave, (rightsIdType == 1 ? BIS_COMMON_TIK_SAVE_NAME : BIS_PERSONALIZED_TIK_SAVE_NAME), FA_READ | FA_OPEN_EXISTING);
if (fr != FR_OK) if (fr)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to mount BIS System partition! (%u)", fr);
fsStorageClose(&fatFsStorage);
return false;
}
fr = f_chdir("/save");
if (fr != FR_OK)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to change BIS System partition directory! (%u)", fr);
f_unmount("sys");
fsStorageClose(&fatFsStorage);
return false;
}
fr = f_open(&eTicketSave, (rightsIdType == 1 ? COMMON_ETICKET_FILENAME : PERSONALIZED_ETICKET_FILENAME), FA_READ | FA_OPEN_EXISTING);
if (fr != FR_OK)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to open ES %s eTicket save! (%u)", (rightsIdType == 1 ? "common" : "personalized"), fr); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to open ES %s eTicket save! (%u)", (rightsIdType == 1 ? "common" : "personalized"), fr);
f_unmount("sys"); return ret;
fsStorageClose(&fatFsStorage);
return false;
} }
while(true) save_ctx = calloc(1, sizeof(save_ctx_t));
if (!save_ctx)
{ {
fr = f_read(&eTicketSave, eTicketEntry, ETICKET_ENTRY_SIZE, &read_bytes); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to allocate memory for ticket savefile context!");
if (fr || read_bytes != ETICKET_ENTRY_SIZE) break; f_close(&eTicketSave);
return ret;
// Only read eTicket entries with RSA-2048 SHA-256 signature method }
if (*((u32*)eTicketEntry) == SIGTYPE_RSA2048_SHA256)
save_ctx->file = &eTicketSave;
save_ctx->tool_ctx.action = 0;
memcpy(save_ctx->save_mac_key, nca_keyset.save_mac_key, 0x10);
if (!save_process(save_ctx))
{
strcat(strbuf, "\nError: failed to process ticket savefile!");
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf);
free(save_ctx);
f_close(&eTicketSave);
return ret;
}
if (!save_hierarchical_file_table_get_file_entry_by_path(&save_ctx->save_filesystem_core.file_table, ticket_bin_path, &entry))
{
snprintf(tmp, MAX_ELEMENTS(tmp), "\nError: failed to get file entry for \"%s\" in ticket savefile!", ticket_bin_path);
strcat(strbuf, tmp);
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf);
save_free_contexts(save_ctx);
free(save_ctx);
f_close(&eTicketSave);
return ret;
}
if (!save_open_fat_storage(&save_ctx->save_filesystem_core, &fat_storage, entry.value.save_file_info.start_block))
{
snprintf(tmp, MAX_ELEMENTS(tmp), "\nError: failed to open FAT storage at block 0x%X for \"%s\" in ticket savefile!", entry.value.save_file_info.start_block, ticket_bin_path);
strcat(strbuf, tmp);
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf);
save_free_contexts(save_ctx);
free(save_ctx);
f_close(&eTicketSave);
return ret;
}
while(br == buf_size && total_br < entry.value.save_file_info.length)
{
br = save_allocation_table_storage_read(&fat_storage, dumpBuf, total_br, buf_size);
if (br != buf_size)
{ {
// Check if our current eTicket entry matches our rights ID snprintf(tmp, MAX_ELEMENTS(tmp), "\nError: failed to read %u bytes chunk at offset 0x%lX from \"%s\" in ticket savefile!", buf_size, total_br, ticket_bin_path);
if (!memcmp(eTicketEntry + ETICKET_RIGHTSID_OFFSET, dec_nca_header->rights_id, 0x10)) strcat(strbuf, tmp);
{ uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf);
foundEticket = true; proceed = false;
break;
if (rightsIdType == 1)
{
// Common
memcpy(titlekey, eTicketEntry + ETICKET_TITLEKEY_OFFSET, 0x10);
} else {
// Personalized
u8 M[0x100], salt[0x20], db[0xDF];
memcpy(titleKeyBlock, eTicketEntry + ETICKET_TITLEKEY_OFFSET, 0x100);
if (R_FAILED(result = splUserExpMod(titleKeyBlock, N, D, 0x100, M)))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splUserExpMod failed! (titleKeyBlock) (0x%08X)", result);
proceed = false;
break;
}
// Decrypt the titlekey
mgf1(M + 0x21, 0xDF, salt, 0x20);
for(i = 0; i < 0x20; i++) salt[i] ^= M[i + 1];
mgf1(salt, 0x20, db, 0xDF);
for(i = 0; i < 0xDF; i++) db[i] ^= M[i + 0x21];
// Verify if it starts with a null string hash
if (memcmp(db, null_hash, 0x20) != 0)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: titlekey decryption failed! Wrong keys?");
proceed = false;
break;
}
memcpy(titlekey, db + 0xCF, 0x10);
}
break;
}
} }
offset += ETICKET_ENTRY_SIZE; if (dumpBuf[0] == 0) break;
total_br += br;
for(i = 0; i < buf_size; i += ETICKET_ENTRY_SIZE)
{
// Only read eTicket entries with RSA-2048 SHA-256 signature method
// Also check if our current eTicket entry matches our rights ID
if (*((u32*)(dumpBuf + i)) != SIGTYPE_RSA2048_SHA256 || memcmp(dumpBuf + i + ETICKET_RIGHTSID_OFFSET, dec_nca_header->rights_id, 0x10) != 0) continue;
foundEticket = true;
if (rightsIdType == 1)
{
// Common
memcpy(titlekey, dumpBuf + i + ETICKET_TITLEKEY_OFFSET, 0x10);
} else {
// Personalized
u8 M[0x100], salt[0x20], db[0xDF];
u8 *titleKeyBlock = (dumpBuf + i + ETICKET_TITLEKEY_OFFSET);
result = splUserExpMod(titleKeyBlock, N, D, 0x100, M);
if (R_FAILED(result))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: splUserExpMod failed! (titleKeyBlock) (0x%08X)", result);
proceed = false;
break;
}
// Decrypt the titlekey
mgf1(M + 0x21, 0xDF, salt, 0x20);
for(j = 0; j < 0x20; j++) salt[j] ^= M[j + 1];
mgf1(salt, 0x20, db, 0xDF);
for(j = 0; j < 0xDF; j++) db[j] ^= M[j + 0x21];
// Verify if it starts with a null string hash
if (memcmp(db, null_hash, 0x20) != 0)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: titlekey decryption failed! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.");
proceed = false;
break;
}
memcpy(titlekey, db + 0xCF, 0x10);
}
break;
}
if (foundEticket) break;
} }
save_free_contexts(save_ctx);
free(save_ctx);
f_close(&eTicketSave); f_close(&eTicketSave);
f_unmount("sys");
fsStorageClose(&fatFsStorage);
if (!proceed) return false; if (!proceed) return ret;
if (!foundEticket) if (!foundEticket)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find a matching eTicket entry for NCA rights ID!"); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to find a matching eTicket entry for NCA rights ID!");
return false; ret = -2;
return ret;
} }
ret = 0;
// Copy ticket data to output pointer // Copy ticket data to output pointer
if (out_tik != NULL) memcpy(out_tik, eTicketEntry, ETICKET_TIK_FILE_SIZE); if (out_tik != NULL) memcpy(out_tik, dumpBuf + i, ETICKET_TIK_FILE_SIZE);
// Copy encrypted titlekey to output pointer // Copy encrypted titlekey to output pointer
// It is used in personalized -> common ticket conversion // It is used in personalized -> common ticket conversion
@ -1083,7 +1138,7 @@ bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_e
aes128DecryptBlock(&titlekey_aes_ctx, out_dec_key, titlekey); aes128DecryptBlock(&titlekey_aes_ctx, out_dec_key, titlekey);
} }
return true; return ret;
} }
bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys) bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys)
@ -1112,75 +1167,3 @@ bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *d
return true; return true;
} }
bool readCertsFromApplicationRomFs()
{
FILE *certFile;
u64 certSize;
u8 i;
size_t read_bytes;
u8 tmp_hash[0x20];
for(i = 0; i < 3; i++)
{
const char *path = (i == 0 ? cert_CA00000003_path : (i == 1 ? cert_XS00000020_path : cert_XS00000021_path));
u8 *data_ptr = (i == 0 ? cert_CA00000003_data : (i == 1 ? cert_XS00000020_data : cert_XS00000021_data));
const u8 *hash_ptr = (i == 0 ? cert_CA00000003_hash : (i == 1 ? cert_XS00000020_hash : cert_XS00000021_hash));
u64 expected_size = (i == 0 ? ETICKET_CA_CERT_SIZE : ETICKET_XS_CERT_SIZE);
certFile = NULL;
certSize = 0;
certFile = fopen(path, "rb");
if (!certFile)
{
snprintf(strbuf, MAX_ELEMENTS(strbuf), "readCertsFromApplicationRomFs: failed to open file \"%s\".\n", path);
return false;
}
fseek(certFile, 0, SEEK_END);
certSize = ftell(certFile);
rewind(certFile);
if (certSize != expected_size)
{
snprintf(strbuf, MAX_ELEMENTS(strbuf), "readCertsFromApplicationRomFs: invalid file size for \"%s\".\n", path);
return false;
}
read_bytes = fread(data_ptr, 1, expected_size, certFile);
fclose(certFile);
if (read_bytes != expected_size)
{
snprintf(strbuf, MAX_ELEMENTS(strbuf), "readCertsFromApplicationRomFs: error reading file \"%s\".\n", path);
return false;
}
sha256CalculateHash(tmp_hash, data_ptr, expected_size);
if (memcmp(tmp_hash, hash_ptr, 0x20) != 0)
{
snprintf(strbuf, MAX_ELEMENTS(strbuf), "readCertsFromApplicationRomFs: invalid hash for file \"%s\".\n", path);
return false;
}
}
return true;
}
bool retrieveCertData(u8 *out_cert, bool personalized)
{
if (!out_cert)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid parameters to retrieve %s ticket certificate chain.", (!personalized ? "common" : "personalized"));
return false;
}
memcpy(out_cert, cert_CA00000003_data, ETICKET_CA_CERT_SIZE);
memcpy(out_cert + ETICKET_CA_CERT_SIZE, (!personalized ? cert_XS00000020_data : cert_XS00000021_data), ETICKET_XS_CERT_SIZE);
return true;
}

View file

@ -20,9 +20,6 @@
#define SIGTYPE_RSA2048_SHA1 (u32)0x10001 #define SIGTYPE_RSA2048_SHA1 (u32)0x10001
#define SIGTYPE_RSA2048_SHA256 (u32)0x10004 #define SIGTYPE_RSA2048_SHA256 (u32)0x10004
#define COMMON_ETICKET_FILENAME "80000000000000e1"
#define PERSONALIZED_ETICKET_FILENAME "80000000000000e2"
typedef struct { typedef struct {
u64 titleID; u64 titleID;
u8 mask; u8 mask;
@ -42,30 +39,31 @@ typedef struct {
u32 total_key_cnt; /* Total key counter. */ u32 total_key_cnt; /* Total key counter. */
// Needed to decrypt the NCA header using AES-128-XTS // Needed to decrypt the NCA header using AES-128-XTS
u8 header_kek_source[0x10]; /* Seed for header kek. */ u8 header_kek_source[0x10]; /* Seed for header kek. Retrieved from the .rodata section in the FS sysmodule. */
u8 header_key_source[0x20]; /* Seed for NCA header key. */ u8 header_key_source[0x20]; /* Seed for NCA header key. Retrieved from the .data section in the FS sysmodule. */
u8 header_kek[0x10]; /* NCA header kek. */ u8 header_kek[0x10]; /* NCA header kek. Generated from header_kek_source. */
u8 header_key[0x20]; /* NCA header key. */ u8 header_key[0x20]; /* NCA header key. Generated from header_kek and header_key_source. */
// Needed to derive the KAEK needed to decrypt the NCA key area // Needed to derive the KAEK used to decrypt the NCA key area
u8 key_area_key_application_source[0x10]; /* Seed for kaek 0. */ u8 key_area_key_application_source[0x10]; /* Seed for kaek 0. Retrieved from the .rodata section in the FS sysmodule. */
u8 key_area_key_ocean_source[0x10]; /* Seed for kaek 1. */ u8 key_area_key_ocean_source[0x10]; /* Seed for kaek 1. Retrieved from the .rodata section in the FS sysmodule. */
u8 key_area_key_system_source[0x10]; /* Seed for kaek 2. */ u8 key_area_key_system_source[0x10]; /* Seed for kaek 2. Retrieved from the .rodata section in the FS sysmodule. */
// Needed to decrypt the title key block from an eTicket // Needed to decrypt the title key block from an eTicket. Retrieved from the Lockpick_RCM keys file.
u8 eticket_rsa_kek[0x10]; /* eTicket RSA kek. */ u8 eticket_rsa_kek[0x10]; /* eTicket RSA kek. */
u8 titlekeks[0x20][0x10]; /* Title key encryption keys. */ u8 titlekeks[0x20][0x10]; /* Title key encryption keys. */
// Needed to reencrypt the NCA key area for tik-less NSP dumps // Needed to reencrypt the NCA key area for tik-less NSP dumps. Retrieved from the Lockpick_RCM keys file.
u8 key_area_keys[0x20][3][0x10]; /* Key area encryption keys. */ u8 key_area_keys[0x20][3][0x10]; /* Key area encryption keys. */
// Needed to decrypt saves from system and application titles
u8 save_mac_key[0x10];
} PACKED nca_keyset_t; } PACKED nca_keyset_t;
bool loadMemoryKeys(); bool loadMemoryKeys();
bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out); bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out);
bool loadExternalKeys(); bool loadExternalKeys();
bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key); int retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_enc_key, u8 *out_dec_key);
bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys); bool generateEncryptedNcaKeyAreaWithTitlekey(nca_header_t *dec_nca_header, u8 *decrypted_nca_keys);
bool readCertsFromApplicationRomFs();
bool retrieveCertData(u8 *out_cert, bool personalized);
#endif #endif

View file

@ -12,6 +12,8 @@
/* Extern variables */ /* Extern variables */
extern bool keysFileAvailable;
extern FsDeviceOperator fsOperatorInstance; extern FsDeviceOperator fsOperatorInstance;
extern FsEventNotifier fsGameCardEventNotifier; extern FsEventNotifier fsGameCardEventNotifier;
@ -44,319 +46,356 @@ int main(int argc, char *argv[])
/* Initialize UI */ /* Initialize UI */
if (!uiInit()) return -1; if (!uiInit()) return -1;
/* Zero out NCA keyset */
memset(&nca_keyset, 0, sizeof(nca_keyset_t));
/* Init ExeFS context */
initExeFsContext();
/* Init RomFS context */
initRomFsContext();
/* Init BKTR context */
initBktrContext();
/* Make sure output directories exist */
createOutputDirectories();
/* Load settings from configuration file */
loadConfig();
/* Check if the Lockpick_RCM keys file is available */
keysFileAvailable = checkIfFileExists(KEYS_FILE_PATH);
/* Enable CPU boost mode */ /* Enable CPU boost mode */
appletSetCpuBoostMode(ApmCpuBoostMode_Type1); appletSetCpuBoostMode(ApmCpuBoostMode_Type1);
int ret = 0;
Result result; Result result;
Thread thread;
int ret = 0;
bool exitMainLoop = false;
bool initNcm = false, initNs = false, initCsrng = false, initSpl = false, initPmdmnt = false;
bool openFsDevOp = false, openGcEvtNotifier = false, loadGcKernEvt = false, startGcThread = false;
/* Initialize the ncm service */ /* Initialize the ncm service */
result = ncmInitialize(); result = ncmInitialize();
if (R_SUCCEEDED(result)) if (R_FAILED(result))
{ {
/* Initialize the ns service */ uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the ncm service! (0x%08X)", result);
result = nsInitialize(); ret = -2;
if (R_SUCCEEDED(result)) goto out;
}
initNcm = true;
/* Initialize the ns service */
result = nsInitialize();
if (R_FAILED(result))
{
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the ns service! (0x%08X)", result);
ret = -3;
goto out;
}
initNs = true;
/* Initialize the csrng service */
result = csrngInitialize();
if (R_FAILED(result))
{
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the csrng service! (0x%08X)", result);
ret = -4;
goto out;
}
initCsrng = true;
/* Initialize the spl service */
result = splInitialize();
if (R_FAILED(result))
{
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the spl service! (0x%08X)", result);
ret = -5;
goto out;
}
initSpl = true;
/* Initialize the pm:dmnt service */
result = pmdmntInitialize();
if (R_FAILED(result))
{
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the pm:dmnt service! (0x%08X)", result);
ret = -6;
goto out;
}
initPmdmnt = true;
/* Open device operator */
result = fsOpenDeviceOperator(&fsOperatorInstance);
if (R_FAILED(result))
{
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to open device operator! (0x%08X)", result);
ret = -7;
goto out;
}
openFsDevOp = true;
/* Open gamecard detection event notifier */
result = fsOpenGameCardDetectionEventNotifier(&fsGameCardEventNotifier);
if (R_FAILED(result))
{
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to open gamecard detection event notifier! (0x%08X)", result);
ret = -8;
goto out;
}
openGcEvtNotifier = true;
/* Retrieve gamecard detection event handle */
result = fsEventNotifierGetEventHandle(&fsGameCardEventNotifier, &fsGameCardEventHandle);
if (R_FAILED(result))
{
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to retrieve gamecard detection event handle! (0x%08X)", result);
ret = -9;
goto out;
}
/* Retrieve initial gamecard status */
gameCardInserted = isGameCardInserted();
/* Load gamecard detection kernel event */
eventLoadRemote(&fsGameCardKernelEvent, fsGameCardEventHandle, false);
loadGcKernEvt = true;
/* Create usermode exit event */
ueventCreate(&exitEvent, false);
/* Create gamecard detection thread */
result = threadCreate(&thread, fsGameCardDetectionThreadFunc, NULL, 0x10000, 0x2C, -2);
if (R_FAILED(result))
{
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to create gamecard detection thread! (0x%08X)", result);
ret = -10;
goto out;
}
/* Start gamecard detection thread */
result = threadStart(&thread);
if (R_FAILED(result))
{
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to start gamecard detection thread! (0x%08X)", result);
ret = -11;
goto out;
}
startGcThread = true;
/* Mount BIS System partition from the eMMC */
if (!mountSysEmmcPartition())
{
ret = -12;
goto out;
}
/* Main application loop */
while(appletMainLoop())
{
UIResult result = uiProcess();
switch(result)
{ {
/* Initialize the csrng service */ case resultShowMainMenu:
result = csrngInitialize(); uiSetState(stateMainMenu);
if (R_SUCCEEDED(result)) break;
{ case resultShowGameCardMenu:
/* Initialize the spl service */ uiSetState(stateGameCardMenu);
result = splInitialize(); break;
if (R_SUCCEEDED(result)) case resultShowXciDumpMenu:
{ uiSetState(stateXciDumpMenu);
/* Initialize the pm:dmnt service */ break;
result = pmdmntInitialize(); case resultDumpXci:
if (R_SUCCEEDED(result)) uiSetState(stateDumpXci);
{ break;
/* Open device operator */ case resultShowNspDumpMenu:
result = fsOpenDeviceOperator(&fsOperatorInstance); uiSetState(stateNspDumpMenu);
if (R_SUCCEEDED(result)) break;
{ case resultShowNspAppDumpMenu:
/* Open gamecard detection event notifier */ uiSetState(stateNspAppDumpMenu);
result = fsOpenGameCardDetectionEventNotifier(&fsGameCardEventNotifier); break;
if (R_SUCCEEDED(result)) case resultShowNspPatchDumpMenu:
{ uiSetState(stateNspPatchDumpMenu);
/* Retrieve gamecard detection event handle */ break;
result = fsEventNotifierGetEventHandle(&fsGameCardEventNotifier, &fsGameCardEventHandle); case resultShowNspAddOnDumpMenu:
if (R_SUCCEEDED(result)) uiSetState(stateNspAddOnDumpMenu);
{ break;
/* Retrieve initial gamecard status */ case resultDumpNsp:
gameCardInserted = isGameCardInserted(); uiSetState(stateDumpNsp);
break;
/* Load gamecard detection kernel event */ case resultShowHfs0Menu:
eventLoadRemote(&fsGameCardKernelEvent, fsGameCardEventHandle, false); uiSetState(stateHfs0Menu);
break;
/* Create usermode exit event */ case resultShowRawHfs0PartitionDumpMenu:
ueventCreate(&exitEvent, false); uiSetState(stateRawHfs0PartitionDumpMenu);
break;
/* Create gamecard detection thread */ case resultDumpRawHfs0Partition:
Thread thread; uiSetState(stateDumpRawHfs0Partition);
result = threadCreate(&thread, fsGameCardDetectionThreadFunc, NULL, 0x10000, 0x2C, -2); break;
if (R_SUCCEEDED(result)) case resultShowHfs0PartitionDataDumpMenu:
{ uiSetState(stateHfs0PartitionDataDumpMenu);
/* Start gamecard detection thread */ break;
result = threadStart(&thread); case resultDumpHfs0PartitionData:
if (R_SUCCEEDED(result)) uiSetState(stateDumpHfs0PartitionData);
{ break;
/* Zero out NCA keyset */ case resultShowHfs0BrowserMenu:
memset(&nca_keyset, 0, sizeof(nca_keyset_t)); uiSetState(stateHfs0BrowserMenu);
break;
/* Init ExeFS context */ case resultHfs0BrowserGetList:
initExeFsContext(); uiSetState(stateHfs0BrowserGetList);
break;
/* Init RomFS context */ case resultShowHfs0Browser:
initRomFsContext(); uiSetState(stateHfs0Browser);
break;
/* Init BKTR context */ case resultHfs0BrowserCopyFile:
initBktrContext(); uiSetState(stateHfs0BrowserCopyFile);
break;
/* Make sure output directories exist */ case resultShowExeFsMenu:
createOutputDirectories(); uiSetState(stateExeFsMenu);
break;
/* Load settings from configuration file */ case resultShowExeFsSectionDataDumpMenu:
loadConfig(); uiSetState(stateExeFsSectionDataDumpMenu);
break;
/* Main application loop */ case resultDumpExeFsSectionData:
bool exitLoop = false; uiSetState(stateDumpExeFsSectionData);
while(appletMainLoop()) break;
{ case resultShowExeFsSectionBrowserMenu:
UIResult result = uiProcess(); uiSetState(stateExeFsSectionBrowserMenu);
switch(result) break;
{ case resultExeFsSectionBrowserGetList:
case resultShowMainMenu: uiSetState(stateExeFsSectionBrowserGetList);
uiSetState(stateMainMenu); break;
break; case resultShowExeFsSectionBrowser:
case resultShowGameCardMenu: uiSetState(stateExeFsSectionBrowser);
uiSetState(stateGameCardMenu); break;
break; case resultExeFsSectionBrowserCopyFile:
case resultShowXciDumpMenu: uiSetState(stateExeFsSectionBrowserCopyFile);
uiSetState(stateXciDumpMenu); break;
break; case resultShowRomFsMenu:
case resultDumpXci: uiSetState(stateRomFsMenu);
uiSetState(stateDumpXci); break;
break; case resultShowRomFsSectionDataDumpMenu:
case resultShowNspDumpMenu: uiSetState(stateRomFsSectionDataDumpMenu);
uiSetState(stateNspDumpMenu); break;
break; case resultDumpRomFsSectionData:
case resultShowNspAppDumpMenu: uiSetState(stateDumpRomFsSectionData);
uiSetState(stateNspAppDumpMenu); break;
break; case resultShowRomFsSectionBrowserMenu:
case resultShowNspPatchDumpMenu: uiSetState(stateRomFsSectionBrowserMenu);
uiSetState(stateNspPatchDumpMenu); break;
break; case resultRomFsSectionBrowserGetEntries:
case resultShowNspAddOnDumpMenu: uiSetState(stateRomFsSectionBrowserGetEntries);
uiSetState(stateNspAddOnDumpMenu); break;
break; case resultShowRomFsSectionBrowser:
case resultDumpNsp: uiSetState(stateRomFsSectionBrowser);
uiSetState(stateDumpNsp); break;
break; case resultRomFsSectionBrowserChangeDir:
case resultShowHfs0Menu: uiSetState(stateRomFsSectionBrowserChangeDir);
uiSetState(stateHfs0Menu); break;
break; case resultRomFsSectionBrowserCopyFile:
case resultShowRawHfs0PartitionDumpMenu: uiSetState(stateRomFsSectionBrowserCopyFile);
uiSetState(stateRawHfs0PartitionDumpMenu); break;
break; case resultRomFsSectionBrowserCopyDir:
case resultDumpRawHfs0Partition: uiSetState(stateRomFsSectionBrowserCopyDir);
uiSetState(stateDumpRawHfs0Partition); break;
break; case resultDumpGameCardCertificate:
case resultShowHfs0PartitionDataDumpMenu: uiSetState(stateDumpGameCardCertificate);
uiSetState(stateHfs0PartitionDataDumpMenu); break;
break; case resultShowSdCardEmmcMenu:
case resultDumpHfs0PartitionData: uiSetState(stateSdCardEmmcMenu);
uiSetState(stateDumpHfs0PartitionData); break;
break; case resultShowSdCardEmmcTitleMenu:
case resultShowHfs0BrowserMenu: uiSetState(stateSdCardEmmcTitleMenu);
uiSetState(stateHfs0BrowserMenu); break;
break; case resultShowSdCardEmmcOrphanPatchAddOnMenu:
case resultHfs0BrowserGetList: uiSetState(stateSdCardEmmcOrphanPatchAddOnMenu);
uiSetState(stateHfs0BrowserGetList); break;
break; case resultShowSdCardEmmcBatchModeMenu:
case resultShowHfs0Browser: uiSetState(stateSdCardEmmcBatchModeMenu);
uiSetState(stateHfs0Browser); break;
break; case resultSdCardEmmcBatchDump:
case resultHfs0BrowserCopyFile: uiSetState(stateSdCardEmmcBatchDump);
uiSetState(stateHfs0BrowserCopyFile); break;
break; case resultShowTicketMenu:
case resultShowExeFsMenu: uiSetState(stateTicketMenu);
uiSetState(stateExeFsMenu); break;
break; case resultDumpTicket:
case resultShowExeFsSectionDataDumpMenu: uiSetState(stateDumpTicket);
uiSetState(stateExeFsSectionDataDumpMenu); break;
break; case resultShowUpdateMenu:
case resultDumpExeFsSectionData: uiSetState(stateUpdateMenu);
uiSetState(stateDumpExeFsSectionData); break;
break; case resultUpdateNSWDBXml:
case resultShowExeFsSectionBrowserMenu: uiSetState(stateUpdateNSWDBXml);
uiSetState(stateExeFsSectionBrowserMenu); break;
break; case resultUpdateApplication:
case resultExeFsSectionBrowserGetList: uiSetState(stateUpdateApplication);
uiSetState(stateExeFsSectionBrowserGetList); break;
break; case resultExit:
case resultShowExeFsSectionBrowser: exitMainLoop = true;
uiSetState(stateExeFsSectionBrowser); break;
break; default:
case resultExeFsSectionBrowserCopyFile: break;
uiSetState(stateExeFsSectionBrowserCopyFile);
break;
case resultShowRomFsMenu:
uiSetState(stateRomFsMenu);
break;
case resultShowRomFsSectionDataDumpMenu:
uiSetState(stateRomFsSectionDataDumpMenu);
break;
case resultDumpRomFsSectionData:
uiSetState(stateDumpRomFsSectionData);
break;
case resultShowRomFsSectionBrowserMenu:
uiSetState(stateRomFsSectionBrowserMenu);
break;
case resultRomFsSectionBrowserGetEntries:
uiSetState(stateRomFsSectionBrowserGetEntries);
break;
case resultShowRomFsSectionBrowser:
uiSetState(stateRomFsSectionBrowser);
break;
case resultRomFsSectionBrowserChangeDir:
uiSetState(stateRomFsSectionBrowserChangeDir);
break;
case resultRomFsSectionBrowserCopyFile:
uiSetState(stateRomFsSectionBrowserCopyFile);
break;
case resultRomFsSectionBrowserCopyDir:
uiSetState(stateRomFsSectionBrowserCopyDir);
break;
case resultDumpGameCardCertificate:
uiSetState(stateDumpGameCardCertificate);
break;
case resultShowSdCardEmmcMenu:
uiSetState(stateSdCardEmmcMenu);
break;
case resultShowSdCardEmmcTitleMenu:
uiSetState(stateSdCardEmmcTitleMenu);
break;
case resultShowSdCardEmmcOrphanPatchAddOnMenu:
uiSetState(stateSdCardEmmcOrphanPatchAddOnMenu);
break;
case resultShowSdCardEmmcBatchModeMenu:
uiSetState(stateSdCardEmmcBatchModeMenu);
break;
case resultSdCardEmmcBatchDump:
uiSetState(stateSdCardEmmcBatchDump);
break;
case resultShowUpdateMenu:
uiSetState(stateUpdateMenu);
break;
case resultUpdateNSWDBXml:
uiSetState(stateUpdateNSWDBXml);
break;
case resultUpdateApplication:
uiSetState(stateUpdateApplication);
break;
case resultExit:
exitLoop = true;
break;
default:
break;
}
if (exitLoop) break;
}
/* Signal the exit event to terminate the gamecard detection thread */
ueventSignal(&exitEvent);
/* Wait for the gamecard detection thread to exit */
threadWaitForExit(&thread);
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to start gamecard detection thread! (0x%08X)", result);
uiRefreshDisplay();
delay(5);
ret = -11;
}
/* Close gamecard detection thread */
threadClose(&thread);
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to create gamecard detection thread! (0x%08X)", result);
uiRefreshDisplay();
delay(5);
ret = -10;
}
/* Close gamecard detection kernel event */
eventClose(&fsGameCardKernelEvent);
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to retrieve gamecard detection event handle! (0x%08X)", result);
uiRefreshDisplay();
delay(5);
ret = -9;
}
/* Close gamecard detection event notifier */
fsEventNotifierClose(&fsGameCardEventNotifier);
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to open gamecard detection event notifier! (0x%08X)", result);
uiRefreshDisplay();
delay(5);
ret = -8;
}
/* Close device operator */
fsDeviceOperatorClose(&fsOperatorInstance);
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to open device operator! (0x%08X)", result);
uiRefreshDisplay();
delay(5);
ret = -7;
}
/* Denitialize the pm:dmnt service */
pmdmntExit();
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to initialize the pm:dmnt service! (0x%08X)", result);
uiRefreshDisplay();
delay(5);
ret = -6;
}
/* Denitialize the spl service */
splExit();
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to initialize the spl service! (0x%08X)", result);
uiRefreshDisplay();
delay(5);
ret = -5;
}
/* Denitialize the csrng service */
csrngExit();
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to initialize the csrng service! (0x%08X)", result);
uiRefreshDisplay();
delay(5);
ret = -4;
}
/* Denitialize the ns service */
nsExit();
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to initialize the ns service! (0x%08X)", result);
uiRefreshDisplay();
delay(5);
ret = -3;
} }
/* Denitialize the ncm service */ if (exitMainLoop) break;
ncmExit(); }
} else {
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to initialize the ncm service! (0x%08X)", result); /* Signal the exit event to terminate the gamecard detection thread */
ueventSignal(&exitEvent);
/* Wait for the gamecard detection thread to exit */
threadWaitForExit(&thread);
out:
if (ret < 0)
{
uiRefreshDisplay(); uiRefreshDisplay();
delay(5); delay(5);
ret = -2;
} }
/* Unmount BIS System partition from the eMMC */
unmountSysEmmcPartition();
/* Close gamecard detection thread */
if (startGcThread) threadClose(&thread);
/* Close gamecard detection kernel event */
if (loadGcKernEvt) eventClose(&fsGameCardKernelEvent);
/* Close gamecard detection event notifier */
if (openGcEvtNotifier) fsEventNotifierClose(&fsGameCardEventNotifier);
/* Close device operator */
if (openFsDevOp) fsDeviceOperatorClose(&fsOperatorInstance);
/* Denitialize the pm:dmnt service */
if (initPmdmnt) pmdmntExit();
/* Denitialize the spl service */
if (initSpl) splExit();
/* Denitialize the csrng service */
if (initCsrng) csrngExit();
/* Denitialize the ns service */
if (initNs) nsExit();
/* Denitialize the ncm service */
if (initNcm) ncmExit();
/* Disable CPU boost mode */ /* Disable CPU boost mode */
appletSetCpuBoostMode(ApmCpuBoostMode_Disabled); appletSetCpuBoostMode(ApmCpuBoostMode_Disabled);

View file

@ -150,13 +150,14 @@ void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_i
" <Size>%lu</Size>\n" \ " <Size>%lu</Size>\n" \
" <Hash>%s</Hash>\n" \ " <Hash>%s</Hash>\n" \
" <KeyGeneration>%u</KeyGeneration>\n" \ " <KeyGeneration>%u</KeyGeneration>\n" \
" <IdOffset>0</IdOffset>\n" \ " <IdOffset>%u</IdOffset>\n" \
" </Content>\n", \ " </Content>\n",
getContentType(xml_content_info[i].type), \ getContentType(xml_content_info[i].type), \
xml_content_info[i].nca_id_str, \ xml_content_info[i].nca_id_str, \
xml_content_info[i].size, \ xml_content_info[i].size, \
xml_content_info[i].hash_str, \ xml_content_info[i].hash_str, \
xml_content_info[i].keyblob); \ xml_content_info[i].keyblob, \
(xml_content_info[i].type != NcmContentType_CNMT ? xml_content_info[i].id_offset : 0));
strcat(out, tmp); strcat(out, tmp);
} }
@ -300,8 +301,8 @@ bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmNcaId *nc
Result result; Result result;
unsigned char ctr[0x10]; unsigned char ctr[0x10];
char nca_id[33] = {'\0'}; char nca_id[SHA256_HASH_SIZE + 1] = {'\0'};
convertDataToHexString(ncaId->c, 16, nca_id, 33); convertDataToHexString(ncaId->c, SHA256_HASH_SIZE / 2, nca_id, SHA256_HASH_SIZE + 1);
u64 block_start_offset = (offset - (offset % 0x10)); u64 block_start_offset = (offset - (offset % 0x10));
u64 block_end_offset = (u64)round_up(offset + bufSize, 0x10); u64 block_end_offset = (u64)round_up(offset + bufSize, 0x10);
@ -310,7 +311,8 @@ bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmNcaId *nc
u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size); u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size);
u64 output_block_size = (block_size > NCA_CTR_BUFFER_SIZE ? (NCA_CTR_BUFFER_SIZE - (offset - block_start_offset)) : bufSize); u64 output_block_size = (block_size > NCA_CTR_BUFFER_SIZE ? (NCA_CTR_BUFFER_SIZE - (offset - block_start_offset)) : bufSize);
if (R_FAILED(result = ncmContentStorageReadContentIdFile(ncmStorage, ncaId, block_start_offset, ncaCtrBuf, block_size_used))) result = ncmContentStorageReadContentIdFile(ncmStorage, ncaId, block_start_offset, ncaCtrBuf, block_size_used);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read encrypted %lu bytes block at offset 0x%016lX from NCA \"%s\"! (0x%08X)", block_size_used, block_start_offset, nca_id, result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Failed to read encrypted %lu bytes block at offset 0x%016lX from NCA \"%s\"! (0x%08X)", block_size_used, block_start_offset, nca_id, result);
return false; return false;
@ -509,7 +511,8 @@ bool bktrSectionPhysicalRead(void *outBuf, size_t bufSize)
{ {
u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size); u64 block_size_used = (block_size > NCA_CTR_BUFFER_SIZE ? NCA_CTR_BUFFER_SIZE : block_size);
if (R_FAILED(result = ncmContentStorageReadContentIdFile(&(bktrContext.ncmStorage), &(bktrContext.ncaId), block_start_offset, ncaCtrBuf, block_size_used))) result = ncmContentStorageReadContentIdFile(&(bktrContext.ncmStorage), &(bktrContext.ncaId), block_start_offset, ncaCtrBuf, block_size_used);
if (R_FAILED(result))
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "BKTR: failed to read encrypted %lu bytes block at offset 0x%016lX! (0x%08X)", block_size_used, block_start_offset, result); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "BKTR: failed to read encrypted %lu bytes block at offset 0x%016lX! (0x%08X)", block_size_used, block_start_offset, result);
return false; return false;
@ -655,6 +658,8 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title
if (!loadNcaKeyset()) return false; if (!loadNcaKeyset()) return false;
int ret;
u32 i; u32 i;
size_t crypt_res; size_t crypt_res;
Aes128XtsContext hdr_aes_ctx; Aes128XtsContext hdr_aes_ctx;
@ -702,7 +707,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title
} }
} }
} else { } else {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA magic word! Wrong header key? (0x%08X)", bswap_32(out->magic)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA magic word! Wrong header key? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", bswap_32(out->magic));
return false; return false;
} }
@ -732,16 +737,28 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title
if (retrieveTitleKeyData) if (retrieveTitleKeyData)
{ {
if (!retrieveNcaTikTitleKey(out, (u8*)(&(rights_info->tik_data)), rights_info->enc_titlekey, rights_info->dec_titlekey)) return false; ret = retrieveNcaTikTitleKey(out, (u8*)(&(rights_info->tik_data)), rights_info->enc_titlekey, rights_info->dec_titlekey);
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); if (ret >= 0)
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10); {
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
rights_info->retrieved_tik = true; memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
rights_info->retrieved_tik = true;
} else {
if (ret == -2)
{
// We are probably dealing with a pre-installed title
// Let's enable our missing ticket flag - we'll use it to display a prompt asking the user if they want to proceed anyway
rights_info->missing_tik = true;
} else {
return false;
}
}
} }
} else { } else {
// Copy what we already have // Copy what we already have
if (retrieveTitleKeyData) if (retrieveTitleKeyData && rights_info->retrieved_tik)
{ {
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10); memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
@ -753,7 +770,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title
{ {
u8 tmp_dec_titlekey[0x10]; u8 tmp_dec_titlekey[0x10];
if (!retrieveNcaTikTitleKey(out, NULL, NULL, tmp_dec_titlekey)) return false; if (retrieveNcaTikTitleKey(out, NULL, NULL, tmp_dec_titlekey) < 0) return false;
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE); memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), tmp_dec_titlekey, 0x10); memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), tmp_dec_titlekey, 0x10);
@ -854,13 +871,13 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca
if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)", bswap_32(nca_pfs0_header.magic)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", bswap_32(nca_pfs0_header.magic));
return false; return false;
} }
if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?"); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.");
return false; return false;
} }
@ -1085,7 +1102,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca
return true; return true;
} }
bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpType, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *output, title_rights_ctx *rights_info, bool replaceKeyArea) bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpType, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info, bool replaceKeyArea)
{ {
if (!ncaBuf || !xml_program_info || !xml_content_info || !output || !rights_info) if (!ncaBuf || !xml_program_info || !xml_content_info || !output || !rights_info)
{ {
@ -1095,7 +1112,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
nca_header_t dec_header; nca_header_t dec_header;
u32 i; u32 i, j, k = 0;
u64 section_offset; u64 section_offset;
u64 section_size; u64 section_size;
@ -1125,7 +1142,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
// Decrypt the NCA header // Decrypt the NCA header
// Don't retrieve the ticket and/or titlekey if we're dealing with a Patch with titlekey crypto bundled with the inserted gamecard // Don't retrieve the ticket and/or titlekey if we're dealing with a Patch with titlekey crypto bundled with the inserted gamecard
if (!decryptNcaHeader(ncaBuf, xml_content_info->size, &dec_header, rights_info, xml_content_info->decrypted_nca_keys, (curStorageId != FsStorageId_GameCard))) return false; if (!decryptNcaHeader(ncaBuf, xml_content_info[cnmtNcaIndex].size, &dec_header, rights_info, xml_content_info[cnmtNcaIndex].decrypted_nca_keys, (curStorageId != FsStorageId_GameCard))) return false;
if (dec_header.fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_header.fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) if (dec_header.fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_header.fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0)
{ {
@ -1172,7 +1189,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
if (has_rights_id && replaceKeyArea) if (has_rights_id && replaceKeyArea)
{ {
// Generate new encrypted NCA key area using titlekey // Generate new encrypted NCA key area using titlekey
if (!generateEncryptedNcaKeyAreaWithTitlekey(&dec_header, xml_content_info->decrypted_nca_keys)) return false; if (!generateEncryptedNcaKeyAreaWithTitlekey(&dec_header, xml_content_info[cnmtNcaIndex].decrypted_nca_keys)) return false;
// Remove rights ID from NCA // Remove rights ID from NCA
memset(dec_header.rights_id, 0, 0x10); memset(dec_header.rights_id, 0, 0x10);
@ -1200,7 +1217,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
} }
u8 ctr_key[NCA_KEY_AREA_KEY_SIZE]; u8 ctr_key[NCA_KEY_AREA_KEY_SIZE];
memcpy(ctr_key, xml_content_info->decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE); memcpy(ctr_key, xml_content_info[cnmtNcaIndex].decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), NCA_KEY_AREA_KEY_SIZE);
aes128CtrContextCreate(&aes_ctx, ctr_key, ctr); aes128CtrContextCreate(&aes_ctx, ctr_key, ctr);
section_data = malloc(section_size); section_data = malloc(section_size);
@ -1217,14 +1234,14 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid magic word for CNMT NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)", bswap_32(nca_pfs0_header.magic)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid magic word for CNMT NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", bswap_32(nca_pfs0_header.magic));
free(section_data); free(section_data);
return false; return false;
} }
if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: CNMT NCA section #0 PFS0 partition is empty! Wrong KAEK?"); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: CNMT NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.");
free(section_data); free(section_data);
return false; return false;
} }
@ -1270,14 +1287,48 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
// Fill information for our CNMT XML // Fill information for our CNMT XML
digest_offset = (title_cnmt_offset + title_cnmt_size - (u64)SHA256_HASH_SIZE); digest_offset = (title_cnmt_offset + title_cnmt_size - (u64)SHA256_HASH_SIZE);
memcpy(xml_program_info->digest, section_data + digest_offset, SHA256_HASH_SIZE); memcpy(xml_program_info->digest, section_data + digest_offset, SHA256_HASH_SIZE);
convertDataToHexString(xml_program_info->digest, 32, xml_program_info->digest_str, 65); convertDataToHexString(xml_program_info->digest, SHA256_HASH_SIZE, xml_program_info->digest_str, (SHA256_HASH_SIZE * 2) + 1);
xml_content_info->keyblob = (dec_header.crypto_type2 > dec_header.crypto_type ? dec_header.crypto_type2 : dec_header.crypto_type); xml_content_info[cnmtNcaIndex].keyblob = (dec_header.crypto_type2 > dec_header.crypto_type ? dec_header.crypto_type2 : dec_header.crypto_type);
xml_program_info->min_keyblob = (rights_info->has_rights_id ? rights_info->rights_id[15] : xml_content_info->keyblob); xml_program_info->required_dl_sysver = title_cnmt_header.required_dl_sysver;
xml_program_info->min_keyblob = (rights_info->has_rights_id ? rights_info->rights_id[15] : xml_content_info[cnmtNcaIndex].keyblob);
xml_program_info->min_sysver = title_cnmt_extended_header.min_sysver; xml_program_info->min_sysver = title_cnmt_extended_header.min_sysver;
xml_program_info->patch_tid = title_cnmt_extended_header.patch_tid; xml_program_info->patch_tid = title_cnmt_extended_header.patch_tid;
// Empty CNMT content records // Retrieve the ID offset and content record offset for each of our NCAs (except the CNMT NCA)
memset(section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.table_offset, 0, title_cnmt_size - sizeof(cnmt_header) - (u64)title_cnmt_header.table_offset - (u64)SHA256_HASH_SIZE); // Also wipe each of the content records we're gonna replace (excluding Delta Fragments)
for(i = 0; i < (xml_program_info->nca_cnt - 1); i++) // Discard CNMT NCA
{
for(j = 0; j < title_cnmt_header.content_cnt; j++)
{
cnmt_content_record cnt_record;
memcpy(&cnt_record, section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), sizeof(cnmt_content_record));
if (cnt_record.type == NCA_CONTENT_TYPE_DELTA) continue;
if (!memcmp(xml_content_info[i].nca_id, cnt_record.nca_id, SHA256_HASH_SIZE / 2) || xml_content_info[i].type == cnt_record.type)
{
// Save ID offset and content record offset
xml_content_info[i].id_offset = cnt_record.id_offset;
xml_content_info[i].cnt_record_offset = (j * sizeof(cnmt_content_record));
// Empty CNMT content record
memset(section_data + title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size + (j * sizeof(cnmt_content_record)), 0, sizeof(cnmt_content_record));
// Increase counter
k++;
break;
}
}
}
// Verify counter
if (k != (xml_program_info->nca_cnt - 1))
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid content record entries in the CNMT NCA!");
free(section_data);
return false;
}
// Replace input buffer data in-place // Replace input buffer data in-place
memcpy(ncaBuf, &dec_header, NCA_FULL_HEADER_LENGTH); memcpy(ncaBuf, &dec_header, NCA_FULL_HEADER_LENGTH);
@ -1293,6 +1344,8 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
output->section_offset = section_offset; output->section_offset = section_offset;
output->section_size = section_size; output->section_size = section_size;
output->hash_table_offset = (section_offset + dec_header.fs_headers[0].pfs0_superblock.hash_table_offset); output->hash_table_offset = (section_offset + dec_header.fs_headers[0].pfs0_superblock.hash_table_offset);
output->hash_block_size = dec_header.fs_headers[0].pfs0_superblock.block_size;
output->hash_block_cnt = (dec_header.fs_headers[0].pfs0_superblock.hash_table_size / SHA256_HASH_SIZE);
output->pfs0_offset = nca_pfs0_offset; output->pfs0_offset = nca_pfs0_offset;
output->pfs0_size = dec_header.fs_headers[0].pfs0_superblock.pfs0_size; output->pfs0_size = dec_header.fs_headers[0].pfs0_superblock.pfs0_size;
output->title_cnmt_offset = title_cnmt_offset; output->title_cnmt_offset = title_cnmt_offset;
@ -1310,52 +1363,47 @@ bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program
} }
u32 i; u32 i;
u32 nca_cnt;
u32 nca_cnt = (xml_program_info->nca_cnt - 1); // Discard CNMT NCA
cnmt_header title_cnmt_header; cnmt_header title_cnmt_header;
cnmt_content_record *title_cnmt_content_records = NULL; cnmt_content_record title_cnmt_content_record;
u64 title_cnmt_content_records_offset; u64 title_cnmt_content_records_offset;
u8 pfs0_block_hash[SHA256_HASH_SIZE];
nca_header_t dec_header; nca_header_t dec_header;
Aes128CtrContext aes_ctx; Aes128CtrContext aes_ctx;
// Update number of content records // Copy CNMT header
nca_cnt = (xml_program_info->nca_cnt - 1); // Discard CNMT NCA
memcpy(&title_cnmt_header, ncaBuf + cnmt_mod->title_cnmt_offset, sizeof(cnmt_header)); memcpy(&title_cnmt_header, ncaBuf + cnmt_mod->title_cnmt_offset, sizeof(cnmt_header));
title_cnmt_header.content_records_cnt = (u16)nca_cnt;
memcpy(ncaBuf + cnmt_mod->title_cnmt_offset, &title_cnmt_header, sizeof(cnmt_header));
// Allocate memory for our content records // Calculate the start offset for the content records
title_cnmt_content_records = calloc(nca_cnt, sizeof(cnmt_content_record)); title_cnmt_content_records_offset = (cnmt_mod->title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size);
if (!title_cnmt_content_records)
{
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: unable to allocate memory for CNMT NCA content records!");
return false;
}
title_cnmt_content_records_offset = (cnmt_mod->title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.table_offset);
memcpy(title_cnmt_content_records, ncaBuf + title_cnmt_content_records_offset, (u64)nca_cnt * sizeof(cnmt_content_record));
// Write content records // Write content records
for(i = 0; i < nca_cnt; i++) for(i = 0; i < nca_cnt; i++)
{ {
memcpy(title_cnmt_content_records[i].hash, xml_content_info[i].hash, 32); memset(&title_cnmt_content_record, 0, sizeof(cnmt_content_record));
memcpy(title_cnmt_content_records[i].nca_id, xml_content_info[i].nca_id, 16);
convertU64ToNcaSize(xml_content_info[i].size, title_cnmt_content_records[i].size);
title_cnmt_content_records[i].type = xml_content_info[i].type;
u64 cur_content_record = (title_cnmt_content_records_offset + ((u64)i * sizeof(cnmt_content_record))); memcpy(title_cnmt_content_record.hash, xml_content_info[i].hash, SHA256_HASH_SIZE);
memcpy(ncaBuf + cur_content_record, &(title_cnmt_content_records[i]), sizeof(cnmt_content_record)); memcpy(title_cnmt_content_record.nca_id, xml_content_info[i].nca_id, SHA256_HASH_SIZE / 2);
convertU64ToNcaSize(xml_content_info[i].size, title_cnmt_content_record.size);
title_cnmt_content_record.type = xml_content_info[i].type;
title_cnmt_content_record.id_offset = xml_content_info[i].id_offset;
memcpy(ncaBuf + title_cnmt_content_records_offset + xml_content_info[i].cnt_record_offset, &title_cnmt_content_record, sizeof(cnmt_content_record));
} }
free(title_cnmt_content_records); // Recalculate block hashes
for(i = 0; i < cnmt_mod->hash_block_cnt; i++)
// Calculate block hash {
sha256CalculateHash(pfs0_block_hash, ncaBuf + cnmt_mod->pfs0_offset, cnmt_mod->pfs0_size); u64 blk_offset = ((u64)i * cnmt_mod->hash_block_size);
memcpy(ncaBuf + cnmt_mod->hash_table_offset, pfs0_block_hash, SHA256_HASH_SIZE);
u64 rest_size = (cnmt_mod->pfs0_size - blk_offset);
u64 blk_size = (rest_size > cnmt_mod->hash_block_size ? cnmt_mod->hash_block_size : rest_size);
sha256CalculateHash(ncaBuf + cnmt_mod->hash_table_offset + (i * SHA256_HASH_SIZE), ncaBuf + cnmt_mod->pfs0_offset + blk_offset, blk_size);
}
// Copy header to struct // Copy header to struct
memcpy(&dec_header, ncaBuf, sizeof(nca_header_t)); memcpy(&dec_header, ncaBuf, sizeof(nca_header_t));
@ -1393,9 +1441,9 @@ bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program
// Calculate CNMT NCA SHA-256 checksum and fill information for our CNMT XML // Calculate CNMT NCA SHA-256 checksum and fill information for our CNMT XML
sha256CalculateHash(xml_content_info[xml_program_info->nca_cnt - 1].hash, ncaBuf, ncaBufSize); sha256CalculateHash(xml_content_info[xml_program_info->nca_cnt - 1].hash, ncaBuf, ncaBufSize);
convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].hash, 32, xml_content_info[xml_program_info->nca_cnt - 1].hash_str, 65); convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE, xml_content_info[xml_program_info->nca_cnt - 1].hash_str, (SHA256_HASH_SIZE * 2) + 1);
memcpy(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, xml_content_info[xml_program_info->nca_cnt - 1].hash, 16); memcpy(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, xml_content_info[xml_program_info->nca_cnt - 1].hash, SHA256_HASH_SIZE / 2);
convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[xml_program_info->nca_cnt - 1].nca_id_str, 33); convertDataToHexString(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, SHA256_HASH_SIZE / 2, xml_content_info[xml_program_info->nca_cnt - 1].nca_id_str, SHA256_HASH_SIZE + 1);
return true; return true;
} }
@ -1517,7 +1565,7 @@ bool readExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId,
if (!found_exefs) if (!found_exefs)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA doesn't hold an ExeFS section!"); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA doesn't hold an ExeFS section! Wrong keys?\nTry running Lockpick_RCM to generate the keys file from scratch.");
return false; return false;
} }
@ -1616,7 +1664,7 @@ bool readRomFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId,
if (bswap_32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic) != IVFC_MAGIC) if (bswap_32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic) != IVFC_MAGIC)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid IVFC magic word for NCA RomFS section! Wrong KAEK? (0x%08X)", bswap_32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid IVFC magic word for NCA RomFS section! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", bswap_32(dec_nca_header->fs_headers[romfs_index].romfs_superblock.ivfc_header.magic));
return false; return false;
} }
@ -1784,7 +1832,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId,
if (bswap_32(bktrContext.superblock.ivfc_header.magic) != IVFC_MAGIC) if (bswap_32(bktrContext.superblock.ivfc_header.magic) != IVFC_MAGIC)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid IVFC magic word for NCA BKTR section! Wrong KAEK? (0x%08X)", bswap_32(bktrContext.superblock.ivfc_header.magic)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid IVFC magic word for NCA BKTR section! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", bswap_32(bktrContext.superblock.ivfc_header.magic));
return false; return false;
} }
@ -1796,7 +1844,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId,
if (bswap_32(bktrContext.superblock.relocation_header.magic) != BKTR_MAGIC || bswap_32(bktrContext.superblock.subsection_header.magic) != BKTR_MAGIC) if (bswap_32(bktrContext.superblock.relocation_header.magic) != BKTR_MAGIC || bswap_32(bktrContext.superblock.subsection_header.magic) != BKTR_MAGIC)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid BKTR magic word for NCA BKTR relocation/subsection header! (0x%02X | 0x%02X)", bswap_32(bktrContext.superblock.relocation_header.magic), bswap_32(bktrContext.superblock.subsection_header.magic)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid BKTR magic word for NCA BKTR relocation/subsection header! Wrong KAEK? (0x%02X | 0x%02X)\nTry running Lockpick_RCM to generate the keys file from scratch.", bswap_32(bktrContext.superblock.relocation_header.magic), bswap_32(bktrContext.superblock.subsection_header.magic));
return false; return false;
} }
@ -2076,13 +2124,13 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId
if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)", bswap_32(nca_pfs0_header.magic)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid magic word for Program NCA section #0 PFS0 partition! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", bswap_32(nca_pfs0_header.magic));
return false; return false;
} }
if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?"); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: Program NCA section #0 PFS0 partition is empty! Wrong KAEK?\nTry running Lockpick_RCM to generate the keys file from scratch.");
return false; return false;
} }
@ -2160,7 +2208,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId
if (bswap_32(npdm_header.magic) != META_MAGIC) if (bswap_32(npdm_header.magic) != META_MAGIC)
{ {
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NPDM META magic word! (0x%08X)", bswap_32(npdm_header.magic)); uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NPDM META magic word! Wrong KAEK? (0x%08X)\nTry running Lockpick_RCM to generate the keys file from scratch.", bswap_32(npdm_header.magic));
goto out; goto out;
} }
@ -2526,6 +2574,9 @@ char *getNacpRatingAgeOrganizationName(u8 val)
case 11: case 11:
out = "OFLC"; out = "OFLC";
break; break;
case 12:
out = "IARCGeneric";
break;
default: default:
out = "Unknown"; out = "Unknown";
break; break;
@ -2717,6 +2768,29 @@ char *getNacpRuntimeAddOnContentInstall(u8 val)
return out; return out;
} }
char *getNacpRuntimeParameterDelivery(u8 val)
{
char *out = NULL;
switch(val)
{
case 0:
out = "Always";
break;
case 1:
out = "AlwaysIfUserStateMatched";
break;
case 2:
out = "OnRestart";
break;
default:
out = "Unknown";
break;
}
return out;
}
char *getNacpPlayLogQueryCapability(u8 val) char *getNacpPlayLogQueryCapability(u8 val)
{ {
char *out = NULL; char *out = NULL;
@ -3147,9 +3221,18 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaI
sprintf(tmp, " <RuntimeAddOnContentInstall>%s</RuntimeAddOnContentInstall>\n", getNacpRuntimeAddOnContentInstall(controlNacp.RuntimeAddOnContentInstall)); sprintf(tmp, " <RuntimeAddOnContentInstall>%s</RuntimeAddOnContentInstall>\n", getNacpRuntimeAddOnContentInstall(controlNacp.RuntimeAddOnContentInstall));
strcat(nacpXml, tmp); strcat(nacpXml, tmp);
sprintf(tmp, " <PlayLogQueryableApplicationId>0x%016lx</PlayLogQueryableApplicationId>\n", controlNacp.PlayLogQueryableApplicationId[0]); sprintf(tmp, " <RuntimeParameterDelivery>%s</RuntimeParameterDelivery>\n", getNacpRuntimeParameterDelivery(controlNacp.RuntimeParameterDelivery));
strcat(nacpXml, tmp); strcat(nacpXml, tmp);
for(i = 0; i < 16; i++)
{
if (controlNacp.PlayLogQueryableApplicationId[i] != 0)
{
sprintf(tmp, " <PlayLogQueryableApplicationId>0x%016lx</PlayLogQueryableApplicationId>\n", controlNacp.PlayLogQueryableApplicationId[i]);
strcat(nacpXml, tmp);
}
}
sprintf(tmp, " <PlayLogQueryCapability>%s</PlayLogQueryCapability>\n", getNacpPlayLogQueryCapability(controlNacp.PlayLogQueryCapability)); sprintf(tmp, " <PlayLogQueryCapability>%s</PlayLogQueryCapability>\n", getNacpPlayLogQueryCapability(controlNacp.PlayLogQueryCapability));
strcat(nacpXml, tmp); strcat(nacpXml, tmp);

View file

@ -228,10 +228,13 @@ typedef struct {
u32 version; u32 version;
u8 type; u8 type;
u8 unk1; u8 unk1;
u16 table_offset; u16 extended_header_size;
u16 content_records_cnt; u16 content_cnt;
u16 meta_records_cnt; u16 content_meta_cnt;
u8 unk2[12]; u8 attr;
u8 unk2[0x03];
u32 required_dl_sysver;
u8 unk3[0x04];
} PACKED cnmt_header; } PACKED cnmt_header;
typedef struct { typedef struct {
@ -244,7 +247,7 @@ typedef struct {
u8 nca_id[0x10]; u8 nca_id[0x10];
u8 size[6]; u8 size[6];
u8 type; u8 type;
u8 unk; u8 id_offset;
} PACKED cnmt_content_record; } PACKED cnmt_content_record;
typedef struct { typedef struct {
@ -253,8 +256,8 @@ typedef struct {
u32 version; u32 version;
u32 required_dl_sysver; u32 required_dl_sysver;
u32 nca_cnt; u32 nca_cnt;
u8 digest[32]; u8 digest[SHA256_HASH_SIZE];
char digest_str[65]; char digest_str[(SHA256_HASH_SIZE * 2) + 1];
u8 min_keyblob; u8 min_keyblob;
u32 min_sysver; u32 min_sysver;
u64 patch_tid; u64 patch_tid;
@ -262,12 +265,14 @@ typedef struct {
typedef struct { typedef struct {
u8 type; u8 type;
u8 nca_id[16]; u8 nca_id[SHA256_HASH_SIZE / 2];
char nca_id_str[33]; char nca_id_str[SHA256_HASH_SIZE + 1];
u64 size; u64 size;
u8 hash[32]; u8 hash[SHA256_HASH_SIZE];
char hash_str[65]; char hash_str[(SHA256_HASH_SIZE * 2) + 1];
u8 keyblob; u8 keyblob;
u8 id_offset;
u64 cnt_record_offset; // Relative to the start of the content record structs in the CNMT
u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE]; u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE];
u8 encrypted_header_mod[NCA_FULL_HEADER_LENGTH]; u8 encrypted_header_mod[NCA_FULL_HEADER_LENGTH];
} PACKED cnmt_xml_content_info; } PACKED cnmt_xml_content_info;
@ -286,6 +291,8 @@ typedef struct {
u64 section_offset; // Relative to NCA start u64 section_offset; // Relative to NCA start
u64 section_size; u64 section_size;
u64 hash_table_offset; // Relative to NCA start u64 hash_table_offset; // Relative to NCA start
u64 hash_block_size;
u32 hash_block_cnt;
u64 pfs0_offset; // Relative to NCA start u64 pfs0_offset; // Relative to NCA start
u64 pfs0_size; u64 pfs0_size;
u64 title_cnmt_offset; // Relative to NCA start u64 title_cnmt_offset; // Relative to NCA start
@ -321,6 +328,7 @@ typedef struct {
u8 cert_data[ETICKET_CERT_FILE_SIZE]; u8 cert_data[ETICKET_CERT_FILE_SIZE];
rsa2048_sha256_ticket tik_data; rsa2048_sha256_ticket tik_data;
bool retrieved_tik; bool retrieved_tik;
bool missing_tik;
} PACKED title_rights_ctx; } PACKED title_rights_ctx;
typedef struct { typedef struct {
@ -463,7 +471,8 @@ typedef struct {
u8 LogoType; u8 LogoType;
u8 LogoHandling; u8 LogoHandling;
u8 RuntimeAddOnContentInstall; u8 RuntimeAddOnContentInstall;
u8 Reserved_0x30F3[0x3]; u8 RuntimeParameterDelivery;
u8 Reserved_0x30F4[0x2];
u8 CrashReport; u8 CrashReport;
u8 Hdcp; u8 Hdcp;
u64 SeedForPseudoDeviceId; u64 SeedForPseudoDeviceId;
@ -515,7 +524,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title
bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data *output); bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, cnmt_xml_content_info *xml_content_info, nca_program_mod_data *output);
bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpType, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *output, title_rights_ctx *rights_info, bool replaceKeyArea); bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpType, u8 *ncaBuf, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, u32 cnmtNcaIndex, nca_cnmt_mod_data *output, title_rights_ctx *rights_info, bool replaceKeyArea);
bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod); bool patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod);

1931
source/save.c Normal file

File diff suppressed because it is too large Load diff

475
source/save.h Normal file
View file

@ -0,0 +1,475 @@
#pragma once
#ifndef _SAVE_H
#define _SAVE_H
#include <stddef.h>
#include <stdint.h>
#include <switch.h>
#include "fatfs/ff.h"
#include "nca.h"
#define SAVE_HEADER_SIZE 0x4000
#define SAVE_FAT_ENTRY_SIZE 8
#define SAVE_FS_LIST_MAX_NAME_LENGTH 0x40
#define SAVE_FS_LIST_ENTRY_SIZE 0x60
#define MAGIC_DISF 0x46534944
#define MAGIC_DPFS 0x53465044
#define MAGIC_JNGL 0x4C474E4A
#define MAGIC_SAVE 0x45564153
#define MAGIC_RMAP 0x50414D52
#define MAGIC_IVFC 0x43465649
#define ACTION_VERIFY (1<<2)
typedef enum {
VALIDITY_UNCHECKED = 0,
VALIDITY_INVALID,
VALIDITY_VALID
} validity_t;
typedef struct save_ctx_t save_ctx_t;
typedef struct {
u32 magic; /* DISF */
u32 version;
u8 hash[0x20];
u64 file_map_entry_offset;
u64 file_map_entry_size;
u64 meta_map_entry_offset;
u64 meta_map_entry_size;
u64 file_map_data_offset;
u64 file_map_data_size;
u64 duplex_l1_offset_a;
u64 duplex_l1_offset_b;
u64 duplex_l1_size;
u64 duplex_data_offset_a;
u64 duplex_data_offset_b;
u64 duplex_data_size;
u64 journal_data_offset;
u64 journal_data_size_a;
u64 journal_data_size_b;
u64 journal_size;
u64 duplex_master_offset_a;
u64 duplex_master_offset_b;
u64 duplex_master_size;
u64 ivfc_master_hash_offset_a;
u64 ivfc_master_hash_offset_b;
u64 ivfc_master_hash_size;
u64 journal_map_table_offset;
u64 journal_map_table_size;
u64 journal_physical_bitmap_offset;
u64 journal_physical_bitmap_size;
u64 journal_virtual_bitmap_offset;
u64 journal_virtual_bitmap_size;
u64 journal_free_bitmap_offset;
u64 journal_free_bitmap_size;
u64 ivfc_l1_offset;
u64 ivfc_l1_size;
u64 ivfc_l2_offset;
u64 ivfc_l2_size;
u64 ivfc_l3_offset;
u64 ivfc_l3_size;
u64 fat_offset;
u64 fat_size;
u64 duplex_index;
u64 fat_ivfc_master_hash_a;
u64 fat_ivfc_master_hash_b;
u64 fat_ivfc_l1_offset;
u64 fat_ivfc_l1_size;
u64 fat_ivfc_l2_offset;
u64 fat_ivfc_l2_size;
u8 _0x190[0x70];
} fs_layout_t;
typedef struct {
u64 offset;
u64 length;
u32 block_size_power;
} PACKED duplex_info_t;
typedef struct {
u32 magic; /* DPFS */
u32 version;
duplex_info_t layers[3];
} duplex_header_t;
typedef struct {
u32 version;
u32 main_data_block_count;
u32 journal_block_count;
u32 _0x0C;
} journal_map_header_t;
typedef struct {
u32 magic; /* JNGL */
u32 version;
u64 total_size;
u64 journal_size;
u64 block_size;
} journal_header_t;
typedef struct {
u32 magic; /* SAVE */
u32 version;
u64 block_count;
u64 block_size;
} save_fs_header_t;
typedef struct {
u64 block_size;
u64 allocation_table_offset;
u32 allocation_table_block_count;
u32 _0x14;
u64 data_offset;
u32 data_block_count;
u32 _0x24;
u32 directory_table_block;
u32 file_table_block;
} fat_header_t;
typedef struct {
u32 magic; /* RMAP */
u32 version;
u32 map_entry_count;
u32 map_segment_count;
u32 segment_bits;
u8 _0x14[0x2C];
} remap_header_t;
typedef struct remap_segment_ctx_t remap_segment_ctx_t;
typedef struct remap_entry_ctx_t remap_entry_ctx_t;
struct remap_entry_ctx_t {
u64 virtual_offset;
u64 physical_offset;
u64 size;
u32 alignment;
u32 _0x1C;
u64 virtual_offset_end;
u64 physical_offset_end;
remap_segment_ctx_t *segment;
remap_entry_ctx_t *next;
} PACKED;
struct remap_segment_ctx_t{
u64 offset;
u64 length;
remap_entry_ctx_t *entries;
u64 entry_count;
};
typedef struct {
u8 *data;
u8 *bitmap;
} duplex_bitmap_t;
typedef struct {
u32 block_size;
u8 *bitmap_storage;
u8 *data_a;
u8 *data_b;
duplex_bitmap_t bitmap;
u64 _length;
} duplex_storage_ctx_t;
enum base_storage_type {
STORAGE_BYTES = 0,
STORAGE_DUPLEX = 1,
STORAGE_REMAP = 2,
STORAGE_JOURNAL = 3
};
typedef struct {
remap_header_t *header;
remap_entry_ctx_t *map_entries;
remap_segment_ctx_t *segments;
enum base_storage_type type;
u64 base_storage_offset;
duplex_storage_ctx_t *duplex;
FIL *file;
} remap_storage_ctx_t;
typedef struct {
u64 title_id;
u8 user_id[0x10];
u64 save_id;
u8 save_data_type;
u8 _0x21[0x1F];
u64 save_owner_id;
u64 timestamp;
u64 _0x50;
u64 data_size;
u64 journal_size;
u64 commit_id;
} extra_data_t;
typedef struct {
u32 magic;
u32 id;
u32 master_hash_size;
u32 num_levels;
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
u8 salt_source[0x20];
} ivfc_save_hdr_t;
typedef struct {
u8 cmac[0x10];
u8 _0x10[0xF0];
fs_layout_t layout;
duplex_header_t duplex_header;
ivfc_save_hdr_t data_ivfc_header;
u32 _0x404;
journal_header_t journal_header;
journal_map_header_t map_header;
u8 _0x438[0x1D0];
save_fs_header_t save_header;
fat_header_t fat_header;
remap_header_t main_remap_header, meta_remap_header;
u64 _0x6D0;
extra_data_t extra_data;
u8 _0x748[0x390];
ivfc_save_hdr_t fat_ivfc_header;
u8 _0xB98[0x3468];
} PACKED save_header_t;
typedef struct {
duplex_storage_ctx_t layers[2];
duplex_storage_ctx_t data_layer;
u64 _length;
} hierarchical_duplex_storage_ctx_t;
typedef struct {
u8 *data_a;
u8 *data_b;
duplex_info_t info;
} duplex_fs_layer_info_t;
typedef struct {
u8 *map_storage;
u8 *physical_block_bitmap;
u8 *virtual_block_bitmap;
u8 *free_block_bitmap;
} journal_map_params_t;
typedef struct {
u32 physical_index;
u32 virtual_index;
} journal_map_entry_t;
typedef struct {
journal_map_header_t *header;
journal_map_entry_t *entries;
u8 *map_storage;
} journal_map_ctx_t;
typedef struct {
journal_map_ctx_t map;
journal_header_t *header;
u32 block_size;
u64 journal_data_offset;
u64 _length;
FIL *file;
} journal_storage_ctx_t;
typedef struct {
u64 data_offset;
u64 data_size;
u64 hash_offset;
u32 hash_block_size;
validity_t hash_validity;
enum base_storage_type type;
save_ctx_t *save_ctx;
} ivfc_level_save_ctx_t;
typedef struct {
ivfc_level_save_ctx_t *data;
u32 block_size;
u8 salt[0x20];
} integrity_verification_info_ctx_t;
typedef struct integrity_verification_storage_ctx_t integrity_verification_storage_ctx_t;
struct integrity_verification_storage_ctx_t {
ivfc_level_save_ctx_t *hash_storage;
ivfc_level_save_ctx_t *base_storage;
validity_t *block_validities;
u8 salt[0x20];
u32 sector_size;
u32 sector_count;
u64 _length;
integrity_verification_storage_ctx_t *next_level;
};
typedef struct {
ivfc_level_save_ctx_t levels[5];
ivfc_level_save_ctx_t *data_level;
validity_t **level_validities;
u64 _length;
integrity_verification_storage_ctx_t integrity_storages[4];
} hierarchical_integrity_verification_storage_ctx_t;
typedef struct {
u32 prev;
u32 next;
} allocation_table_entry_t;
typedef struct {
u32 free_list_entry_index;
void *base_storage;
fat_header_t *header;
} allocation_table_ctx_t;
typedef struct {
hierarchical_integrity_verification_storage_ctx_t *base_storage;
u32 block_size;
u32 initial_block;
allocation_table_ctx_t *fat;
u64 _length;
} allocation_table_storage_ctx_t;
typedef struct {
allocation_table_ctx_t *fat;
u32 virtual_block;
u32 physical_block;
u32 current_segment_size;
u32 next_block;
u32 prev_block;
} allocation_table_iterator_ctx_t;
typedef struct {
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
u32 parent;
} save_entry_key_t;
typedef struct {
u32 start_block;
u64 length;
u32 _0xC[2];
} PACKED save_file_info_t;
typedef struct {
u32 next_directory;
u32 next_file;
u32 _0x8[3];
} PACKED save_find_position_t;
typedef struct {
u32 next_sibling;
union { /* Save table entry type. Size = 0x14. */
save_file_info_t save_file_info;
save_find_position_t save_find_position;
};
} PACKED save_table_entry_t;
typedef struct {
u32 parent;
char name[SAVE_FS_LIST_MAX_NAME_LENGTH];
save_table_entry_t value;
u32 next;
} PACKED save_fs_list_entry_t;
typedef struct {
u32 free_list_head_index;
u32 used_list_head_index;
allocation_table_storage_ctx_t storage;
u32 capacity;
} save_filesystem_list_ctx_t;
typedef struct {
save_filesystem_list_ctx_t file_table;
save_filesystem_list_ctx_t directory_table;
} hierarchical_save_file_table_ctx_t;
typedef struct {
hierarchical_integrity_verification_storage_ctx_t *base_storage;
allocation_table_ctx_t allocation_table;
save_fs_header_t *header;
hierarchical_save_file_table_ctx_t file_table;
} save_filesystem_ctx_t;
struct save_ctx_t {
save_header_t header;
FIL *file;
struct {
FIL *file;
u32 action;
} tool_ctx;
validity_t header_cmac_validity;
validity_t header_hash_validity;
u8 *data_ivfc_master;
u8 *fat_ivfc_master;
remap_storage_ctx_t data_remap_storage;
remap_storage_ctx_t meta_remap_storage;
duplex_fs_layer_info_t duplex_layers[3];
hierarchical_duplex_storage_ctx_t duplex_storage;
journal_storage_ctx_t journal_storage;
journal_map_params_t journal_map_info;
hierarchical_integrity_verification_storage_ctx_t core_data_ivfc_storage;
hierarchical_integrity_verification_storage_ctx_t fat_ivfc_storage;
u8 *fat_storage;
save_filesystem_ctx_t save_filesystem_core;
u8 save_mac_key[0x10];
};
static inline u32 allocation_table_entry_index_to_block(u32 entry_index)
{
return (entry_index - 1);
}
static inline u32 allocation_table_block_to_entry_index(u32 block_index)
{
return (block_index + 1);
}
static inline int allocation_table_is_list_end(allocation_table_entry_t *entry)
{
return ((entry->next & 0x7FFFFFFF) == 0);
}
static inline int allocation_table_is_list_start(allocation_table_entry_t *entry)
{
return (entry->prev == 0x80000000);
}
static inline int allocation_table_get_next(allocation_table_entry_t *entry)
{
return (entry->next & 0x7FFFFFFF);
}
static inline int allocation_table_get_prev(allocation_table_entry_t *entry)
{
return (entry->prev & 0x7FFFFFFF);
}
static inline allocation_table_entry_t *save_allocation_table_read_entry(allocation_table_ctx_t *ctx, u32 entry_index)
{
return ((allocation_table_entry_t*)((u8*)ctx->base_storage + (entry_index * SAVE_FAT_ENTRY_SIZE)));
}
static inline u32 save_allocation_table_get_free_list_entry_index(allocation_table_ctx_t *ctx)
{
return allocation_table_get_next(save_allocation_table_read_entry(ctx, ctx->free_list_entry_index));
}
static inline u32 save_allocation_table_get_free_list_block_index(allocation_table_ctx_t *ctx)
{
return allocation_table_entry_index_to_block(save_allocation_table_get_free_list_entry_index(ctx));
}
bool save_process(save_ctx_t *ctx);
bool save_process_header(save_ctx_t *ctx);
void save_free_contexts(save_ctx_t *ctx);
bool save_open_fat_storage(save_filesystem_ctx_t *ctx, allocation_table_storage_ctx_t *storage_ctx, u32 block_index);
u32 save_allocation_table_storage_read(allocation_table_storage_ctx_t *ctx, void *buffer, u64 offset, size_t count);
bool save_fs_list_get_value(save_filesystem_list_ctx_t *ctx, u32 index, save_fs_list_entry_t *value);
u32 save_fs_get_index_from_key(save_filesystem_list_ctx_t *ctx, save_entry_key_t *key, u32 *prev_index);
bool save_hierarchical_file_table_find_path_recursive(hierarchical_save_file_table_ctx_t *ctx, save_entry_key_t *key, const char *path);
bool save_hierarchical_file_table_get_file_entry_by_path(hierarchical_save_file_table_ctx_t *ctx, const char *path, save_fs_list_entry_t *entry);
bool retrieveCertData(u8 *out_cert, bool personalized);
#endif

File diff suppressed because it is too large Load diff

View file

@ -105,6 +105,8 @@ typedef enum {
resultShowSdCardEmmcOrphanPatchAddOnMenu, resultShowSdCardEmmcOrphanPatchAddOnMenu,
resultShowSdCardEmmcBatchModeMenu, resultShowSdCardEmmcBatchModeMenu,
resultSdCardEmmcBatchDump, resultSdCardEmmcBatchDump,
resultShowTicketMenu,
resultDumpTicket,
resultShowUpdateMenu, resultShowUpdateMenu,
resultUpdateNSWDBXml, resultUpdateNSWDBXml,
resultUpdateApplication, resultUpdateApplication,
@ -152,6 +154,8 @@ typedef enum {
stateSdCardEmmcOrphanPatchAddOnMenu, stateSdCardEmmcOrphanPatchAddOnMenu,
stateSdCardEmmcBatchModeMenu, stateSdCardEmmcBatchModeMenu,
stateSdCardEmmcBatchDump, stateSdCardEmmcBatchDump,
stateTicketMenu,
stateDumpTicket,
stateUpdateMenu, stateUpdateMenu,
stateUpdateNSWDBXml, stateUpdateNSWDBXml,
stateUpdateApplication stateUpdateApplication

File diff suppressed because it is too large Load diff

View file

@ -15,6 +15,9 @@
#define ROMFS_DUMP_PATH NXDUMPTOOL_BASE_PATH "RomFS/" #define ROMFS_DUMP_PATH NXDUMPTOOL_BASE_PATH "RomFS/"
#define CERT_DUMP_PATH NXDUMPTOOL_BASE_PATH "Certificate/" #define CERT_DUMP_PATH NXDUMPTOOL_BASE_PATH "Certificate/"
#define BATCH_OVERRIDES_PATH NSP_DUMP_PATH "BatchOverrides/" #define BATCH_OVERRIDES_PATH NSP_DUMP_PATH "BatchOverrides/"
#define TICKET_PATH NXDUMPTOOL_BASE_PATH "Ticket/"
#define KEYS_FILE_PATH APP_BASE_PATH "prod.keys"
#define KiB (1024.0) #define KiB (1024.0)
#define MiB (1024.0 * KiB) #define MiB (1024.0 * KiB)
@ -93,6 +96,11 @@
#define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) // Returns the max number of elements that can be stored in an array #define MAX_ELEMENTS(x) ((sizeof((x))) / (sizeof((x)[0]))) // Returns the max number of elements that can be stored in an array
#define BIS_MOUNT_NAME "sys:"
#define BIS_CERT_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e0"
#define BIS_COMMON_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e1"
#define BIS_PERSONALIZED_TIK_SAVE_NAME BIS_MOUNT_NAME "/save/80000000000000e2"
typedef struct { typedef struct {
u64 file_offset; u64 file_offset;
u64 file_size; u64 file_size;
@ -128,11 +136,35 @@ typedef enum {
ROMFS_TYPE_ADDON ROMFS_TYPE_ADDON
} selectedRomFsType; } selectedRomFsType;
typedef enum {
TICKET_TYPE_APP = 0,
TICKET_TYPE_PATCH,
TICKET_TYPE_ADDON
} selectedTicketType;
typedef struct { typedef struct {
u32 index; u32 index;
u8 type; // 1 = Patch, 2 = AddOn u8 type; // 1 = Patch, 2 = AddOn
char name[NACP_APPNAME_LEN + 1];
char fixedName[NACP_APPNAME_LEN + 1];
} PACKED orphan_patch_addon_entry; } PACKED orphan_patch_addon_entry;
typedef struct {
bool isFat32;
bool setXciArchiveBit;
bool keepCert;
bool trimDump;
bool calcCrc;
} PACKED xciOptions;
typedef struct {
bool isFat32;
bool calcCrc;
bool removeConsoleData;
bool tiklessDump;
bool npdmAcidRsaPatch;
} PACKED nspOptions;
typedef enum { typedef enum {
BATCH_SOURCE_ALL = 0, BATCH_SOURCE_ALL = 0,
BATCH_SOURCE_SDCARD, BATCH_SOURCE_SDCARD,
@ -140,21 +172,6 @@ typedef enum {
BATCH_SOURCE_CNT BATCH_SOURCE_CNT
} batchModeSourceStorage; } batchModeSourceStorage;
typedef struct {
bool isFat32;
bool keepCert;
bool trimDump;
bool calcCrc;
bool setXciArchiveBit;
} PACKED xciOptions;
typedef struct {
bool isFat32;
bool calcCrc;
bool removeConsoleData;
bool tiklessDump;
} PACKED nspOptions;
typedef struct { typedef struct {
bool dumpAppTitles; bool dumpAppTitles;
bool dumpPatchTitles; bool dumpPatchTitles;
@ -162,15 +179,21 @@ typedef struct {
bool isFat32; bool isFat32;
bool removeConsoleData; bool removeConsoleData;
bool tiklessDump; bool tiklessDump;
bool npdmAcidRsaPatch;
bool skipDumpedTitles; bool skipDumpedTitles;
batchModeSourceStorage batchModeSrc;
bool rememberDumpedTitles; bool rememberDumpedTitles;
batchModeSourceStorage batchModeSrc;
} PACKED batchOptions; } PACKED batchOptions;
typedef struct {
bool removeConsoleData;
} PACKED ticketOptions;
typedef struct { typedef struct {
xciOptions xciDumpCfg; xciOptions xciDumpCfg;
nspOptions nspDumpCfg; nspOptions nspDumpCfg;
batchOptions batchDumpCfg; batchOptions batchDumpCfg;
ticketOptions tikDumpCfg;
} PACKED dumpOptions; } PACKED dumpOptions;
void loadConfig(); void loadConfig();
@ -181,6 +204,10 @@ bool isGameCardInserted();
void fsGameCardDetectionThreadFunc(void *arg); void fsGameCardDetectionThreadFunc(void *arg);
bool mountSysEmmcPartition();
void unmountSysEmmcPartition();
bool isServiceRunning(const char *serviceName); bool isServiceRunning(const char *serviceName);
bool checkSxOsServices(); bool checkSxOsServices();
@ -223,7 +250,7 @@ bool getPartitionHfs0Header(u32 partition);
bool getHfs0FileList(u32 partition); bool getHfs0FileList(u32 partition);
bool getPartitionHfs0FileByName(FsStorage *gameCardStorage, const char *filename, u8 *outBuf, u64 outBufSize); bool getFileFromHfs0PartitionByName(FsStorage *gameCardStorage, const char *filename, u8 *outBuf, u64 outBufSize);
bool calculateExeFsExtractedDataSize(u64 *out); bool calculateExeFsExtractedDataSize(u64 *out);
@ -231,6 +258,10 @@ bool calculateRomFsFullExtractedSize(bool usePatch, u64 *out);
bool calculateRomFsExtractedDirSize(u32 dir_offset, bool usePatch, u64 *out); bool calculateRomFsExtractedDirSize(u32 dir_offset, bool usePatch, u64 *out);
bool retrieveNcaContentRecords(FsStorageId curStorageId, u8 filter, u32 titleCount, u32 titleIndex, NcmContentRecord **outContentRecords, u32 *outContentRecordsCnt);
void removeConsoleDataFromTicket(title_rights_ctx *rights_info);
bool readNcaExeFsSection(u32 titleIndex, bool usePatch); bool readNcaExeFsSection(u32 titleIndex, bool usePatch);
bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType); bool readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType);
@ -253,6 +284,8 @@ void retrieveDescriptionForPatchOrAddOn(u64 titleID, u32 version, bool addOn, bo
bool checkOrphanPatchOrAddOn(bool addOn); bool checkOrphanPatchOrAddOn(bool addOn);
void freeOrphanPatchOrAddOnList();
void generateOrphanPatchOrAddOnList(); void generateOrphanPatchOrAddOnList();
bool checkIfBaseApplicationHasPatchOrAddOn(u32 appIndex, bool addOn); bool checkIfBaseApplicationHasPatchOrAddOn(u32 appIndex, bool addOn);
@ -291,6 +324,6 @@ void gameCardDumpNSWDBCheck(u32 crc);
void updateNSWDBXml(); void updateNSWDBXml();
void updateApplication(); bool updateApplication();
#endif #endif