mirror of
https://github.com/DarkMatterCore/nxdumptool.git
synced 2024-12-23 00:52:10 +00:00
Update to v1.1.7.
This commit is contained in:
parent
bb450df38d
commit
3a16147c59
20 changed files with 5907 additions and 2514 deletions
2
Makefile
2
Makefile
|
@ -33,7 +33,7 @@ include $(DEVKITPRO)/libnx/switch_rules
|
|||
|
||||
VERSION_MAJOR := 1
|
||||
VERSION_MINOR := 1
|
||||
VERSION_MICRO := 6
|
||||
VERSION_MICRO := 7
|
||||
|
||||
APP_TITLE := nxdumptool
|
||||
APP_AUTHOR := MCMrARM, DarkMatterCore
|
||||
|
|
62
README.md
62
README.md
|
@ -7,9 +7,13 @@ Main features
|
|||
* 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.
|
||||
* 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 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.
|
||||
* CRC32 checksum calculation for XCI/NSP dumps.
|
||||
* 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.
|
||||
* Compatible with base applications, updates and DLCs (if available).
|
||||
* Supports manual RomFS directory dumping.
|
||||
* Manual gamecard certificate dump.
|
||||
* Free SD card space checks in place.
|
||||
* File splitting support for all operations.
|
||||
* 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.
|
||||
* 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.
|
||||
|
||||
Please launch the application through title override (hold R while launching a game) whenever possible to avoid memory allocation problems.
|
||||
|
||||
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)).
|
||||
* [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.
|
||||
* 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 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
|
||||
--------------
|
||||
|
@ -60,6 +67,55 @@ If you like my work and you'd like to support me in any way, it's not necessary,
|
|||
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:**
|
||||
* 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.
|
||||
|
|
Binary file not shown.
Binary file not shown.
Binary file not shown.
1015
source/dumper.c
1015
source/dumper.c
File diff suppressed because it is too large
Load diff
|
@ -39,6 +39,8 @@ typedef struct {
|
|||
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 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
|
||||
u32 nspFileCount; // PFS0 file 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 dumpCurrentDirFromRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType, bool doSplitting);
|
||||
bool dumpGameCardCertificate();
|
||||
bool dumpTicketFromTitle(u32 titleIndex, selectedTicketType curTikType, ticketOptions *tikDumpCfg);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -33,7 +33,7 @@
|
|||
/ 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
|
||||
/ f_findnext(). (0:Disable, 1:Enable 2:Enable with matching altname[] too) */
|
||||
|
||||
|
@ -68,7 +68,7 @@
|
|||
/ 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.
|
||||
/ 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
|
||||
/* 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. */
|
||||
|
||||
|
||||
#define FF_STRF_ENCODE 3
|
||||
#define FF_STRF_ENCODE 0
|
||||
/* 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.
|
||||
/ 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.
|
||||
/
|
||||
/ 0: Disable relative path and remove related functions.
|
||||
|
@ -167,8 +167,8 @@
|
|||
/* Number of volumes (logical drives) to be used. (1-10) */
|
||||
|
||||
|
||||
#define FF_STR_VOLUME_ID 0
|
||||
#define FF_VOLUME_STRS "RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
|
||||
#define FF_STR_VOLUME_ID 1
|
||||
#define FF_VOLUME_STRS "sys"
|
||||
/* 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
|
||||
/ 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_NORTC_MON 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
|
||||
/ 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
|
||||
|
|
35
source/fatfs/ffsystem.c
Normal file
35
source/fatfs/ffsystem.c
Normal 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
|
505
source/keys.c
505
source/keys.c
|
@ -9,46 +9,24 @@
|
|||
#include "ui.h"
|
||||
#include "es.h"
|
||||
#include "set_ext.h"
|
||||
#include "save.h"
|
||||
|
||||
/* Extern variables */
|
||||
|
||||
extern int breaks;
|
||||
extern int font_height;
|
||||
|
||||
extern u8 *dumpBuf;
|
||||
|
||||
extern char strbuf[NAME_BUF_LEN];
|
||||
|
||||
/* Statically allocated variables */
|
||||
|
||||
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 = {
|
||||
FS_TID,
|
||||
SEG_RODATA,
|
||||
|
@ -132,19 +110,22 @@ bool retrieveProcessMemory(keyLocation *location)
|
|||
// If not a kernel process, get PID from pm:dmnt
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
return false;
|
||||
|
@ -154,7 +135,8 @@ bool retrieveProcessMemory(keyLocation *location)
|
|||
u64 pids[300];
|
||||
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);
|
||||
return false;
|
||||
|
@ -191,7 +173,8 @@ bool retrieveProcessMemory(keyLocation *location)
|
|||
// Locate "real" .text segment as Atmosphere emuMMC has two
|
||||
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);
|
||||
success = false;
|
||||
|
@ -214,7 +197,8 @@ bool retrieveProcessMemory(keyLocation *location)
|
|||
|
||||
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);
|
||||
success = false;
|
||||
|
@ -238,7 +222,8 @@ bool retrieveProcessMemory(keyLocation *location)
|
|||
|
||||
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);
|
||||
success = false;
|
||||
|
@ -337,13 +322,15 @@ bool loadMemoryKeys()
|
|||
nca_keyset.memory_key_cnt++;
|
||||
|
||||
// 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);
|
||||
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);
|
||||
splCryptoExit();
|
||||
|
@ -352,14 +339,16 @@ bool loadMemoryKeys()
|
|||
|
||||
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);
|
||||
splCryptoExit();
|
||||
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);
|
||||
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));
|
||||
|
||||
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);
|
||||
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);
|
||||
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++)
|
||||
{
|
||||
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);
|
||||
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;
|
||||
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 {
|
||||
memset(test_name, 0, sizeof(test_name));
|
||||
|
||||
|
@ -679,10 +676,10 @@ bool loadExternalKeys()
|
|||
if (nca_keyset.ext_key_cnt > 0) return true;
|
||||
|
||||
// Open keys file
|
||||
FILE *keysFile = fopen(keysFilePath, "rb");
|
||||
FILE *keysFile = fopen(KEYS_FILE_PATH, "rb");
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -692,7 +689,7 @@ bool loadExternalKeys()
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -717,13 +714,15 @@ bool testKeyPair(const void *E, const void *D, const void *N)
|
|||
X[0xFE] = 0xBA;
|
||||
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);
|
||||
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);
|
||||
return false;
|
||||
|
@ -770,15 +769,17 @@ void mgf1(const u8 *data, size_t data_length, u8 *mask, size_t mask_length)
|
|||
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))
|
||||
{
|
||||
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;
|
||||
|
||||
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)
|
||||
{
|
||||
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);
|
||||
|
@ -802,7 +803,7 @@ bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_e
|
|||
if (crypto_type >= 0x20)
|
||||
{
|
||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid NCA keyblob index.");
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
Result result;
|
||||
|
@ -812,51 +813,58 @@ bool retrieveNcaTikTitleKey(nca_header_t *dec_nca_header, u8 *out_tik, u8 *out_e
|
|||
bool foundRightsId = false;
|
||||
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;
|
||||
unsigned char ctr[0x10];
|
||||
|
||||
u8 *D = NULL, *N = NULL, *E = NULL;
|
||||
|
||||
FATFS fs;
|
||||
FRESULT fr;
|
||||
FIL eTicketSave;
|
||||
|
||||
u64 offset = 0;
|
||||
u8 eTicketEntry[ETICKET_ENTRY_SIZE], titleKeyBlock[0x100];
|
||||
u32 read_bytes = 0;
|
||||
save_ctx_t *save_ctx = NULL;
|
||||
allocation_table_storage_ctx_t fat_storage;
|
||||
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;
|
||||
|
||||
u8 titlekey[0x10];
|
||||
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);
|
||||
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);
|
||||
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);
|
||||
esExit();
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
if (!common_count && !personalized_count)
|
||||
{
|
||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: no tickets available!");
|
||||
esExit();
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
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!");
|
||||
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);
|
||||
free(common_rights_ids);
|
||||
esExit();
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
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!");
|
||||
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);
|
||||
free(personalized_rights_ids);
|
||||
esExit();
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
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))
|
||||
{
|
||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: NCA rights ID unavailable in this console!");
|
||||
return false;
|
||||
}
|
||||
|
||||
// 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;
|
||||
ret = -2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
// Load external keys
|
||||
if (!loadExternalKeys()) return false;
|
||||
if (!loadExternalKeys()) 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)
|
||||
if (!setcal_eticket_retrieved)
|
||||
{
|
||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: invalid public RSA exponent for eTicket data! Wrong keys?");
|
||||
return false;
|
||||
}
|
||||
|
||||
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 false;
|
||||
|
||||
if (R_FAILED(result = fsOpenBisStorage(&fatFsStorage, FsBisStorageId_System)))
|
||||
{
|
||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to open BIS System partition! (0x%08X)", result);
|
||||
return false;
|
||||
// Get extended eTicket RSA key from PRODINFO
|
||||
memset(eticket_data, 0, ETICKET_DEVKEY_DATA_SIZE);
|
||||
|
||||
result = setcalInitialize();
|
||||
if (R_FAILED(result))
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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 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
|
||||
fr = f_mount(&fs, "sys", 1);
|
||||
if (fr != FR_OK)
|
||||
{
|
||||
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)
|
||||
fr = f_open(&eTicketSave, (rightsIdType == 1 ? BIS_COMMON_TIK_SAVE_NAME : BIS_PERSONALIZED_TIK_SAVE_NAME), FA_READ | FA_OPEN_EXISTING);
|
||||
if (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");
|
||||
fsStorageClose(&fatFsStorage);
|
||||
return false;
|
||||
return ret;
|
||||
}
|
||||
|
||||
while(true)
|
||||
save_ctx = calloc(1, sizeof(save_ctx_t));
|
||||
if (!save_ctx)
|
||||
{
|
||||
fr = f_read(&eTicketSave, eTicketEntry, ETICKET_ENTRY_SIZE, &read_bytes);
|
||||
if (fr || read_bytes != ETICKET_ENTRY_SIZE) break;
|
||||
|
||||
// Only read eTicket entries with RSA-2048 SHA-256 signature method
|
||||
if (*((u32*)eTicketEntry) == SIGTYPE_RSA2048_SHA256)
|
||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, "Error: failed to allocate memory for ticket savefile context!");
|
||||
f_close(&eTicketSave);
|
||||
return ret;
|
||||
}
|
||||
|
||||
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
|
||||
if (!memcmp(eTicketEntry + ETICKET_RIGHTSID_OFFSET, dec_nca_header->rights_id, 0x10))
|
||||
{
|
||||
foundEticket = true;
|
||||
|
||||
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;
|
||||
}
|
||||
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);
|
||||
strcat(strbuf, tmp);
|
||||
uiDrawString(STRING_X_POS, STRING_Y_POS(breaks), FONT_COLOR_ERROR_RGB, strbuf);
|
||||
proceed = false;
|
||||
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_unmount("sys");
|
||||
fsStorageClose(&fatFsStorage);
|
||||
|
||||
if (!proceed) return false;
|
||||
if (!proceed) return ret;
|
||||
|
||||
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!");
|
||||
return false;
|
||||
ret = -2;
|
||||
return ret;
|
||||
}
|
||||
|
||||
ret = 0;
|
||||
|
||||
// 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
|
||||
// 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);
|
||||
}
|
||||
|
||||
return true;
|
||||
return ret;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
|
|
@ -20,9 +20,6 @@
|
|||
#define SIGTYPE_RSA2048_SHA1 (u32)0x10001
|
||||
#define SIGTYPE_RSA2048_SHA256 (u32)0x10004
|
||||
|
||||
#define COMMON_ETICKET_FILENAME "80000000000000e1"
|
||||
#define PERSONALIZED_ETICKET_FILENAME "80000000000000e2"
|
||||
|
||||
typedef struct {
|
||||
u64 titleID;
|
||||
u8 mask;
|
||||
|
@ -42,30 +39,31 @@ typedef struct {
|
|||
u32 total_key_cnt; /* Total key counter. */
|
||||
|
||||
// Needed to decrypt the NCA header using AES-128-XTS
|
||||
u8 header_kek_source[0x10]; /* Seed for header kek. */
|
||||
u8 header_key_source[0x20]; /* Seed for NCA header key. */
|
||||
u8 header_kek[0x10]; /* NCA header kek. */
|
||||
u8 header_key[0x20]; /* NCA header key. */
|
||||
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. Retrieved from the .data section in the FS sysmodule. */
|
||||
u8 header_kek[0x10]; /* NCA header kek. Generated from header_kek_source. */
|
||||
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
|
||||
u8 key_area_key_application_source[0x10]; /* Seed for kaek 0. */
|
||||
u8 key_area_key_ocean_source[0x10]; /* Seed for kaek 1. */
|
||||
u8 key_area_key_system_source[0x10]; /* Seed for kaek 2. */
|
||||
// Needed to derive the KAEK used to decrypt the NCA key area
|
||||
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. Retrieved from the .rodata section in the FS sysmodule. */
|
||||
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 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. */
|
||||
|
||||
// Needed to decrypt saves from system and application titles
|
||||
u8 save_mac_key[0x10];
|
||||
} PACKED nca_keyset_t;
|
||||
|
||||
bool loadMemoryKeys();
|
||||
bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out);
|
||||
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 readCertsFromApplicationRomFs();
|
||||
bool retrieveCertData(u8 *out_cert, bool personalized);
|
||||
|
||||
#endif
|
||||
|
|
635
source/main.c
635
source/main.c
|
@ -12,6 +12,8 @@
|
|||
|
||||
/* Extern variables */
|
||||
|
||||
extern bool keysFileAvailable;
|
||||
|
||||
extern FsDeviceOperator fsOperatorInstance;
|
||||
|
||||
extern FsEventNotifier fsGameCardEventNotifier;
|
||||
|
@ -44,319 +46,356 @@ int main(int argc, char *argv[])
|
|||
/* Initialize UI */
|
||||
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 */
|
||||
appletSetCpuBoostMode(ApmCpuBoostMode_Type1);
|
||||
|
||||
int ret = 0;
|
||||
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 */
|
||||
result = ncmInitialize();
|
||||
if (R_SUCCEEDED(result))
|
||||
if (R_FAILED(result))
|
||||
{
|
||||
/* Initialize the ns service */
|
||||
result = nsInitialize();
|
||||
if (R_SUCCEEDED(result))
|
||||
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_ERROR_RGB, "Failed to initialize the ncm service! (0x%08X)", result);
|
||||
ret = -2;
|
||||
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 */
|
||||
result = csrngInitialize();
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
/* Initialize the spl service */
|
||||
result = splInitialize();
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
/* Initialize the pm:dmnt service */
|
||||
result = pmdmntInitialize();
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
/* Open device operator */
|
||||
result = fsOpenDeviceOperator(&fsOperatorInstance);
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
/* Open gamecard detection event notifier */
|
||||
result = fsOpenGameCardDetectionEventNotifier(&fsGameCardEventNotifier);
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
/* Retrieve gamecard detection event handle */
|
||||
result = fsEventNotifierGetEventHandle(&fsGameCardEventNotifier, &fsGameCardEventHandle);
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
/* Retrieve initial gamecard status */
|
||||
gameCardInserted = isGameCardInserted();
|
||||
|
||||
/* Load gamecard detection kernel event */
|
||||
eventLoadRemote(&fsGameCardKernelEvent, fsGameCardEventHandle, false);
|
||||
|
||||
/* Create usermode exit event */
|
||||
ueventCreate(&exitEvent, false);
|
||||
|
||||
/* Create gamecard detection thread */
|
||||
Thread thread;
|
||||
result = threadCreate(&thread, fsGameCardDetectionThreadFunc, NULL, 0x10000, 0x2C, -2);
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
/* Start gamecard detection thread */
|
||||
result = threadStart(&thread);
|
||||
if (R_SUCCEEDED(result))
|
||||
{
|
||||
/* 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();
|
||||
|
||||
/* Main application loop */
|
||||
bool exitLoop = false;
|
||||
while(appletMainLoop())
|
||||
{
|
||||
UIResult result = uiProcess();
|
||||
switch(result)
|
||||
{
|
||||
case resultShowMainMenu:
|
||||
uiSetState(stateMainMenu);
|
||||
break;
|
||||
case resultShowGameCardMenu:
|
||||
uiSetState(stateGameCardMenu);
|
||||
break;
|
||||
case resultShowXciDumpMenu:
|
||||
uiSetState(stateXciDumpMenu);
|
||||
break;
|
||||
case resultDumpXci:
|
||||
uiSetState(stateDumpXci);
|
||||
break;
|
||||
case resultShowNspDumpMenu:
|
||||
uiSetState(stateNspDumpMenu);
|
||||
break;
|
||||
case resultShowNspAppDumpMenu:
|
||||
uiSetState(stateNspAppDumpMenu);
|
||||
break;
|
||||
case resultShowNspPatchDumpMenu:
|
||||
uiSetState(stateNspPatchDumpMenu);
|
||||
break;
|
||||
case resultShowNspAddOnDumpMenu:
|
||||
uiSetState(stateNspAddOnDumpMenu);
|
||||
break;
|
||||
case resultDumpNsp:
|
||||
uiSetState(stateDumpNsp);
|
||||
break;
|
||||
case resultShowHfs0Menu:
|
||||
uiSetState(stateHfs0Menu);
|
||||
break;
|
||||
case resultShowRawHfs0PartitionDumpMenu:
|
||||
uiSetState(stateRawHfs0PartitionDumpMenu);
|
||||
break;
|
||||
case resultDumpRawHfs0Partition:
|
||||
uiSetState(stateDumpRawHfs0Partition);
|
||||
break;
|
||||
case resultShowHfs0PartitionDataDumpMenu:
|
||||
uiSetState(stateHfs0PartitionDataDumpMenu);
|
||||
break;
|
||||
case resultDumpHfs0PartitionData:
|
||||
uiSetState(stateDumpHfs0PartitionData);
|
||||
break;
|
||||
case resultShowHfs0BrowserMenu:
|
||||
uiSetState(stateHfs0BrowserMenu);
|
||||
break;
|
||||
case resultHfs0BrowserGetList:
|
||||
uiSetState(stateHfs0BrowserGetList);
|
||||
break;
|
||||
case resultShowHfs0Browser:
|
||||
uiSetState(stateHfs0Browser);
|
||||
break;
|
||||
case resultHfs0BrowserCopyFile:
|
||||
uiSetState(stateHfs0BrowserCopyFile);
|
||||
break;
|
||||
case resultShowExeFsMenu:
|
||||
uiSetState(stateExeFsMenu);
|
||||
break;
|
||||
case resultShowExeFsSectionDataDumpMenu:
|
||||
uiSetState(stateExeFsSectionDataDumpMenu);
|
||||
break;
|
||||
case resultDumpExeFsSectionData:
|
||||
uiSetState(stateDumpExeFsSectionData);
|
||||
break;
|
||||
case resultShowExeFsSectionBrowserMenu:
|
||||
uiSetState(stateExeFsSectionBrowserMenu);
|
||||
break;
|
||||
case resultExeFsSectionBrowserGetList:
|
||||
uiSetState(stateExeFsSectionBrowserGetList);
|
||||
break;
|
||||
case resultShowExeFsSectionBrowser:
|
||||
uiSetState(stateExeFsSectionBrowser);
|
||||
break;
|
||||
case resultExeFsSectionBrowserCopyFile:
|
||||
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;
|
||||
case resultShowMainMenu:
|
||||
uiSetState(stateMainMenu);
|
||||
break;
|
||||
case resultShowGameCardMenu:
|
||||
uiSetState(stateGameCardMenu);
|
||||
break;
|
||||
case resultShowXciDumpMenu:
|
||||
uiSetState(stateXciDumpMenu);
|
||||
break;
|
||||
case resultDumpXci:
|
||||
uiSetState(stateDumpXci);
|
||||
break;
|
||||
case resultShowNspDumpMenu:
|
||||
uiSetState(stateNspDumpMenu);
|
||||
break;
|
||||
case resultShowNspAppDumpMenu:
|
||||
uiSetState(stateNspAppDumpMenu);
|
||||
break;
|
||||
case resultShowNspPatchDumpMenu:
|
||||
uiSetState(stateNspPatchDumpMenu);
|
||||
break;
|
||||
case resultShowNspAddOnDumpMenu:
|
||||
uiSetState(stateNspAddOnDumpMenu);
|
||||
break;
|
||||
case resultDumpNsp:
|
||||
uiSetState(stateDumpNsp);
|
||||
break;
|
||||
case resultShowHfs0Menu:
|
||||
uiSetState(stateHfs0Menu);
|
||||
break;
|
||||
case resultShowRawHfs0PartitionDumpMenu:
|
||||
uiSetState(stateRawHfs0PartitionDumpMenu);
|
||||
break;
|
||||
case resultDumpRawHfs0Partition:
|
||||
uiSetState(stateDumpRawHfs0Partition);
|
||||
break;
|
||||
case resultShowHfs0PartitionDataDumpMenu:
|
||||
uiSetState(stateHfs0PartitionDataDumpMenu);
|
||||
break;
|
||||
case resultDumpHfs0PartitionData:
|
||||
uiSetState(stateDumpHfs0PartitionData);
|
||||
break;
|
||||
case resultShowHfs0BrowserMenu:
|
||||
uiSetState(stateHfs0BrowserMenu);
|
||||
break;
|
||||
case resultHfs0BrowserGetList:
|
||||
uiSetState(stateHfs0BrowserGetList);
|
||||
break;
|
||||
case resultShowHfs0Browser:
|
||||
uiSetState(stateHfs0Browser);
|
||||
break;
|
||||
case resultHfs0BrowserCopyFile:
|
||||
uiSetState(stateHfs0BrowserCopyFile);
|
||||
break;
|
||||
case resultShowExeFsMenu:
|
||||
uiSetState(stateExeFsMenu);
|
||||
break;
|
||||
case resultShowExeFsSectionDataDumpMenu:
|
||||
uiSetState(stateExeFsSectionDataDumpMenu);
|
||||
break;
|
||||
case resultDumpExeFsSectionData:
|
||||
uiSetState(stateDumpExeFsSectionData);
|
||||
break;
|
||||
case resultShowExeFsSectionBrowserMenu:
|
||||
uiSetState(stateExeFsSectionBrowserMenu);
|
||||
break;
|
||||
case resultExeFsSectionBrowserGetList:
|
||||
uiSetState(stateExeFsSectionBrowserGetList);
|
||||
break;
|
||||
case resultShowExeFsSectionBrowser:
|
||||
uiSetState(stateExeFsSectionBrowser);
|
||||
break;
|
||||
case resultExeFsSectionBrowserCopyFile:
|
||||
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 resultShowTicketMenu:
|
||||
uiSetState(stateTicketMenu);
|
||||
break;
|
||||
case resultDumpTicket:
|
||||
uiSetState(stateDumpTicket);
|
||||
break;
|
||||
case resultShowUpdateMenu:
|
||||
uiSetState(stateUpdateMenu);
|
||||
break;
|
||||
case resultUpdateNSWDBXml:
|
||||
uiSetState(stateUpdateNSWDBXml);
|
||||
break;
|
||||
case resultUpdateApplication:
|
||||
uiSetState(stateUpdateApplication);
|
||||
break;
|
||||
case resultExit:
|
||||
exitMainLoop = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
/* Denitialize the ncm service */
|
||||
ncmExit();
|
||||
} else {
|
||||
uiDrawString(STRING_DEFAULT_POS, FONT_COLOR_RGB, "Failed to initialize the ncm service! (0x%08X)", result);
|
||||
if (exitMainLoop) break;
|
||||
}
|
||||
|
||||
/* 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();
|
||||
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 */
|
||||
appletSetCpuBoostMode(ApmCpuBoostMode_Disabled);
|
||||
|
||||
|
|
221
source/nca.c
221
source/nca.c
|
@ -150,13 +150,14 @@ void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_i
|
|||
" <Size>%lu</Size>\n" \
|
||||
" <Hash>%s</Hash>\n" \
|
||||
" <KeyGeneration>%u</KeyGeneration>\n" \
|
||||
" <IdOffset>0</IdOffset>\n" \
|
||||
" </Content>\n", \
|
||||
" <IdOffset>%u</IdOffset>\n" \
|
||||
" </Content>\n",
|
||||
getContentType(xml_content_info[i].type), \
|
||||
xml_content_info[i].nca_id_str, \
|
||||
xml_content_info[i].size, \
|
||||
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);
|
||||
}
|
||||
|
@ -300,8 +301,8 @@ bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmNcaId *nc
|
|||
Result result;
|
||||
unsigned char ctr[0x10];
|
||||
|
||||
char nca_id[33] = {'\0'};
|
||||
convertDataToHexString(ncaId->c, 16, nca_id, 33);
|
||||
char nca_id[SHA256_HASH_SIZE + 1] = {'\0'};
|
||||
convertDataToHexString(ncaId->c, SHA256_HASH_SIZE / 2, nca_id, SHA256_HASH_SIZE + 1);
|
||||
|
||||
u64 block_start_offset = (offset - (offset % 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 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);
|
||||
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);
|
||||
|
||||
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);
|
||||
return false;
|
||||
|
@ -655,6 +658,8 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title
|
|||
|
||||
if (!loadNcaKeyset()) return false;
|
||||
|
||||
int ret;
|
||||
|
||||
u32 i;
|
||||
size_t crypt_res;
|
||||
Aes128XtsContext hdr_aes_ctx;
|
||||
|
@ -702,7 +707,7 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title
|
|||
}
|
||||
}
|
||||
} 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;
|
||||
}
|
||||
|
||||
|
@ -732,16 +737,28 @@ bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title
|
|||
|
||||
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);
|
||||
memcpy(decrypted_nca_keys + (NCA_KEY_AREA_KEY_SIZE * 2), rights_info->dec_titlekey, 0x10);
|
||||
|
||||
rights_info->retrieved_tik = true;
|
||||
if (ret >= 0)
|
||||
{
|
||||
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
|
||||
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 {
|
||||
// Copy what we already have
|
||||
if (retrieveTitleKeyData)
|
||||
if (retrieveTitleKeyData && rights_info->retrieved_tik)
|
||||
{
|
||||
memset(decrypted_nca_keys, 0, NCA_KEY_AREA_SIZE);
|
||||
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];
|
||||
|
||||
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);
|
||||
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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1085,7 +1102,7 @@ bool processProgramNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca
|
|||
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)
|
||||
{
|
||||
|
@ -1095,7 +1112,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
|
|||
|
||||
nca_header_t dec_header;
|
||||
|
||||
u32 i;
|
||||
u32 i, j, k = 0;
|
||||
|
||||
u64 section_offset;
|
||||
u64 section_size;
|
||||
|
@ -1125,7 +1142,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
|
|||
|
||||
// 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
|
||||
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)
|
||||
{
|
||||
|
@ -1172,7 +1189,7 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
|
|||
if (has_rights_id && replaceKeyArea)
|
||||
{
|
||||
// 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
|
||||
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];
|
||||
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);
|
||||
|
||||
section_data = malloc(section_size);
|
||||
|
@ -1217,14 +1234,14 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
|
|||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
||||
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);
|
||||
return false;
|
||||
}
|
||||
|
@ -1270,14 +1287,48 @@ bool retrieveCnmtNcaData(FsStorageId curStorageId, nspDumpType selectedNspDumpTy
|
|||
// Fill information for our CNMT XML
|
||||
digest_offset = (title_cnmt_offset + title_cnmt_size - (u64)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);
|
||||
xml_content_info->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);
|
||||
convertDataToHexString(xml_program_info->digest, SHA256_HASH_SIZE, xml_program_info->digest_str, (SHA256_HASH_SIZE * 2) + 1);
|
||||
xml_content_info[cnmtNcaIndex].keyblob = (dec_header.crypto_type2 > dec_header.crypto_type ? dec_header.crypto_type2 : dec_header.crypto_type);
|
||||
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->patch_tid = title_cnmt_extended_header.patch_tid;
|
||||
|
||||
// Empty CNMT content records
|
||||
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);
|
||||
// Retrieve the ID offset and content record offset for each of our NCAs (except the CNMT NCA)
|
||||
// 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
|
||||
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_size = section_size;
|
||||
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_size = dec_header.fs_headers[0].pfs0_superblock.pfs0_size;
|
||||
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 nca_cnt;
|
||||
|
||||
u32 nca_cnt = (xml_program_info->nca_cnt - 1); // Discard CNMT NCA
|
||||
|
||||
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;
|
||||
|
||||
u8 pfs0_block_hash[SHA256_HASH_SIZE];
|
||||
|
||||
nca_header_t dec_header;
|
||||
|
||||
Aes128CtrContext aes_ctx;
|
||||
|
||||
// Update number of content records
|
||||
nca_cnt = (xml_program_info->nca_cnt - 1); // Discard CNMT NCA
|
||||
// Copy 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
|
||||
title_cnmt_content_records = calloc(nca_cnt, sizeof(cnmt_content_record));
|
||||
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));
|
||||
// Calculate the start offset for the content records
|
||||
title_cnmt_content_records_offset = (cnmt_mod->title_cnmt_offset + sizeof(cnmt_header) + (u64)title_cnmt_header.extended_header_size);
|
||||
|
||||
// Write content records
|
||||
for(i = 0; i < nca_cnt; i++)
|
||||
{
|
||||
memcpy(title_cnmt_content_records[i].hash, xml_content_info[i].hash, 32);
|
||||
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;
|
||||
memset(&title_cnmt_content_record, 0, sizeof(cnmt_content_record));
|
||||
|
||||
u64 cur_content_record = (title_cnmt_content_records_offset + ((u64)i * sizeof(cnmt_content_record)));
|
||||
memcpy(ncaBuf + cur_content_record, &(title_cnmt_content_records[i]), sizeof(cnmt_content_record));
|
||||
memcpy(title_cnmt_content_record.hash, xml_content_info[i].hash, SHA256_HASH_SIZE);
|
||||
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);
|
||||
|
||||
// Calculate block hash
|
||||
sha256CalculateHash(pfs0_block_hash, ncaBuf + cnmt_mod->pfs0_offset, cnmt_mod->pfs0_size);
|
||||
memcpy(ncaBuf + cnmt_mod->hash_table_offset, pfs0_block_hash, SHA256_HASH_SIZE);
|
||||
// Recalculate block hashes
|
||||
for(i = 0; i < cnmt_mod->hash_block_cnt; i++)
|
||||
{
|
||||
u64 blk_offset = ((u64)i * cnmt_mod->hash_block_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
|
||||
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
|
||||
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);
|
||||
memcpy(xml_content_info[xml_program_info->nca_cnt - 1].nca_id, xml_content_info[xml_program_info->nca_cnt - 1].hash, 16);
|
||||
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].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, 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, SHA256_HASH_SIZE + 1);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -1517,7 +1565,7 @@ bool readExeFsEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId,
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -1784,7 +1832,7 @@ bool readBktrEntryFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId,
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -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)
|
||||
{
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -2076,13 +2124,13 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -2160,7 +2208,7 @@ bool generateProgramInfoXml(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId
|
|||
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -2526,6 +2574,9 @@ char *getNacpRatingAgeOrganizationName(u8 val)
|
|||
case 11:
|
||||
out = "OFLC";
|
||||
break;
|
||||
case 12:
|
||||
out = "IARCGeneric";
|
||||
break;
|
||||
default:
|
||||
out = "Unknown";
|
||||
break;
|
||||
|
@ -2717,6 +2768,29 @@ char *getNacpRuntimeAddOnContentInstall(u8 val)
|
|||
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 *out = NULL;
|
||||
|
@ -3147,9 +3221,18 @@ bool retrieveNacpDataFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaI
|
|||
sprintf(tmp, " <RuntimeAddOnContentInstall>%s</RuntimeAddOnContentInstall>\n", getNacpRuntimeAddOnContentInstall(controlNacp.RuntimeAddOnContentInstall));
|
||||
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);
|
||||
|
||||
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));
|
||||
strcat(nacpXml, tmp);
|
||||
|
||||
|
|
35
source/nca.h
35
source/nca.h
|
@ -228,10 +228,13 @@ typedef struct {
|
|||
u32 version;
|
||||
u8 type;
|
||||
u8 unk1;
|
||||
u16 table_offset;
|
||||
u16 content_records_cnt;
|
||||
u16 meta_records_cnt;
|
||||
u8 unk2[12];
|
||||
u16 extended_header_size;
|
||||
u16 content_cnt;
|
||||
u16 content_meta_cnt;
|
||||
u8 attr;
|
||||
u8 unk2[0x03];
|
||||
u32 required_dl_sysver;
|
||||
u8 unk3[0x04];
|
||||
} PACKED cnmt_header;
|
||||
|
||||
typedef struct {
|
||||
|
@ -244,7 +247,7 @@ typedef struct {
|
|||
u8 nca_id[0x10];
|
||||
u8 size[6];
|
||||
u8 type;
|
||||
u8 unk;
|
||||
u8 id_offset;
|
||||
} PACKED cnmt_content_record;
|
||||
|
||||
typedef struct {
|
||||
|
@ -253,8 +256,8 @@ typedef struct {
|
|||
u32 version;
|
||||
u32 required_dl_sysver;
|
||||
u32 nca_cnt;
|
||||
u8 digest[32];
|
||||
char digest_str[65];
|
||||
u8 digest[SHA256_HASH_SIZE];
|
||||
char digest_str[(SHA256_HASH_SIZE * 2) + 1];
|
||||
u8 min_keyblob;
|
||||
u32 min_sysver;
|
||||
u64 patch_tid;
|
||||
|
@ -262,12 +265,14 @@ typedef struct {
|
|||
|
||||
typedef struct {
|
||||
u8 type;
|
||||
u8 nca_id[16];
|
||||
char nca_id_str[33];
|
||||
u8 nca_id[SHA256_HASH_SIZE / 2];
|
||||
char nca_id_str[SHA256_HASH_SIZE + 1];
|
||||
u64 size;
|
||||
u8 hash[32];
|
||||
char hash_str[65];
|
||||
u8 hash[SHA256_HASH_SIZE];
|
||||
char hash_str[(SHA256_HASH_SIZE * 2) + 1];
|
||||
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 encrypted_header_mod[NCA_FULL_HEADER_LENGTH];
|
||||
} PACKED cnmt_xml_content_info;
|
||||
|
@ -286,6 +291,8 @@ typedef struct {
|
|||
u64 section_offset; // Relative to NCA start
|
||||
u64 section_size;
|
||||
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_size;
|
||||
u64 title_cnmt_offset; // Relative to NCA start
|
||||
|
@ -321,6 +328,7 @@ typedef struct {
|
|||
u8 cert_data[ETICKET_CERT_FILE_SIZE];
|
||||
rsa2048_sha256_ticket tik_data;
|
||||
bool retrieved_tik;
|
||||
bool missing_tik;
|
||||
} PACKED title_rights_ctx;
|
||||
|
||||
typedef struct {
|
||||
|
@ -463,7 +471,8 @@ typedef struct {
|
|||
u8 LogoType;
|
||||
u8 LogoHandling;
|
||||
u8 RuntimeAddOnContentInstall;
|
||||
u8 Reserved_0x30F3[0x3];
|
||||
u8 RuntimeParameterDelivery;
|
||||
u8 Reserved_0x30F4[0x2];
|
||||
u8 CrashReport;
|
||||
u8 Hdcp;
|
||||
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 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);
|
||||
|
||||
|
|
1931
source/save.c
Normal file
1931
source/save.c
Normal file
File diff suppressed because it is too large
Load diff
475
source/save.h
Normal file
475
source/save.h
Normal 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
|
1056
source/ui.c
1056
source/ui.c
File diff suppressed because it is too large
Load diff
|
@ -105,6 +105,8 @@ typedef enum {
|
|||
resultShowSdCardEmmcOrphanPatchAddOnMenu,
|
||||
resultShowSdCardEmmcBatchModeMenu,
|
||||
resultSdCardEmmcBatchDump,
|
||||
resultShowTicketMenu,
|
||||
resultDumpTicket,
|
||||
resultShowUpdateMenu,
|
||||
resultUpdateNSWDBXml,
|
||||
resultUpdateApplication,
|
||||
|
@ -152,6 +154,8 @@ typedef enum {
|
|||
stateSdCardEmmcOrphanPatchAddOnMenu,
|
||||
stateSdCardEmmcBatchModeMenu,
|
||||
stateSdCardEmmcBatchDump,
|
||||
stateTicketMenu,
|
||||
stateDumpTicket,
|
||||
stateUpdateMenu,
|
||||
stateUpdateNSWDBXml,
|
||||
stateUpdateApplication
|
||||
|
|
2327
source/util.c
2327
source/util.c
File diff suppressed because it is too large
Load diff
|
@ -15,6 +15,9 @@
|
|||
#define ROMFS_DUMP_PATH NXDUMPTOOL_BASE_PATH "RomFS/"
|
||||
#define CERT_DUMP_PATH NXDUMPTOOL_BASE_PATH "Certificate/"
|
||||
#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 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 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 {
|
||||
u64 file_offset;
|
||||
u64 file_size;
|
||||
|
@ -128,11 +136,35 @@ typedef enum {
|
|||
ROMFS_TYPE_ADDON
|
||||
} selectedRomFsType;
|
||||
|
||||
typedef enum {
|
||||
TICKET_TYPE_APP = 0,
|
||||
TICKET_TYPE_PATCH,
|
||||
TICKET_TYPE_ADDON
|
||||
} selectedTicketType;
|
||||
|
||||
typedef struct {
|
||||
u32 index;
|
||||
u8 type; // 1 = Patch, 2 = AddOn
|
||||
char name[NACP_APPNAME_LEN + 1];
|
||||
char fixedName[NACP_APPNAME_LEN + 1];
|
||||
} 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 {
|
||||
BATCH_SOURCE_ALL = 0,
|
||||
BATCH_SOURCE_SDCARD,
|
||||
|
@ -140,21 +172,6 @@ typedef enum {
|
|||
BATCH_SOURCE_CNT
|
||||
} 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 {
|
||||
bool dumpAppTitles;
|
||||
bool dumpPatchTitles;
|
||||
|
@ -162,15 +179,21 @@ typedef struct {
|
|||
bool isFat32;
|
||||
bool removeConsoleData;
|
||||
bool tiklessDump;
|
||||
bool npdmAcidRsaPatch;
|
||||
bool skipDumpedTitles;
|
||||
batchModeSourceStorage batchModeSrc;
|
||||
bool rememberDumpedTitles;
|
||||
batchModeSourceStorage batchModeSrc;
|
||||
} PACKED batchOptions;
|
||||
|
||||
typedef struct {
|
||||
bool removeConsoleData;
|
||||
} PACKED ticketOptions;
|
||||
|
||||
typedef struct {
|
||||
xciOptions xciDumpCfg;
|
||||
nspOptions nspDumpCfg;
|
||||
batchOptions batchDumpCfg;
|
||||
ticketOptions tikDumpCfg;
|
||||
} PACKED dumpOptions;
|
||||
|
||||
void loadConfig();
|
||||
|
@ -181,6 +204,10 @@ bool isGameCardInserted();
|
|||
|
||||
void fsGameCardDetectionThreadFunc(void *arg);
|
||||
|
||||
bool mountSysEmmcPartition();
|
||||
|
||||
void unmountSysEmmcPartition();
|
||||
|
||||
bool isServiceRunning(const char *serviceName);
|
||||
|
||||
bool checkSxOsServices();
|
||||
|
@ -223,7 +250,7 @@ bool getPartitionHfs0Header(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);
|
||||
|
||||
|
@ -231,6 +258,10 @@ bool calculateRomFsFullExtractedSize(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 readNcaRomFsSection(u32 titleIndex, selectedRomFsType curRomFsType);
|
||||
|
@ -253,6 +284,8 @@ void retrieveDescriptionForPatchOrAddOn(u64 titleID, u32 version, bool addOn, bo
|
|||
|
||||
bool checkOrphanPatchOrAddOn(bool addOn);
|
||||
|
||||
void freeOrphanPatchOrAddOnList();
|
||||
|
||||
void generateOrphanPatchOrAddOnList();
|
||||
|
||||
bool checkIfBaseApplicationHasPatchOrAddOn(u32 appIndex, bool addOn);
|
||||
|
@ -291,6 +324,6 @@ void gameCardDumpNSWDBCheck(u32 crc);
|
|||
|
||||
void updateNSWDBXml();
|
||||
|
||||
void updateApplication();
|
||||
bool updateApplication();
|
||||
|
||||
#endif
|
||||
|
|
Loading…
Reference in a new issue