1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-12-23 00:52:10 +00:00

Update to v1.1.0.

This commit is contained in:
Pablo Curiel 2019-06-05 18:44:18 -04:00
parent 211cca9945
commit aab7e24778
27 changed files with 8091 additions and 3105 deletions

View file

@ -32,8 +32,8 @@ include $(DEVKITPRO)/libnx/switch_rules
#---------------------------------------------------------------------------------
VERSION_MAJOR := 1
VERSION_MINOR := 0
VERSION_MICRO := 8
VERSION_MINOR := 1
VERSION_MICRO := 0
APP_TITLE := gcdumptool
APP_AUTHOR := MCMrARM, DarkMatterCore
@ -45,28 +45,29 @@ SOURCES := source
DATA := data
INCLUDES := include
EXEFS_SRC := exefs_src
#ROMFS := romfs
ROMFS := romfs
#---------------------------------------------------------------------------------
# options for code generation
#---------------------------------------------------------------------------------
ARCH := -march=armv8-a -mtune=cortex-a57 -mtp=soft -fPIE
ARCH := -march=armv8-a+crc+crypto -mtune=cortex-a57 -mtp=soft -fPIE
CFLAGS := -g -Wall -O2 -ffunction-sections \
$(ARCH) $(DEFINES)
CFLAGS += $(INCLUDE) -D__SWITCH__ -D__LINUX_ERRNO_EXTENSIONS__
CFLAGS += $(INCLUDE) -D__SWITCH__ -D__LINUX_ERRNO_EXTENSIONS__ -DAPP_VERSION=\"${APP_VERSION}\"
CFLAGS += `freetype-config --cflags`
CFLAGS += `aarch64-none-elf-pkg-config zlib --cflags`
CFLAGS += `aarch64-none-elf-pkg-config libxml-2.0 --cflags`
CFLAGS += `aarch64-none-elf-pkg-config json-c --cflags`
CFLAGS += `aarch64-none-elf-pkg-config libturbojpeg --cflags`
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions -std=gnu++11
CXXFLAGS := $(CFLAGS) -fno-rtti -fno-exceptions
ASFLAGS := -g $(ARCH)
LDFLAGS = -specs=$(DEVKITPRO)/libnx/switch.specs -g $(ARCH) -Wl,-Map,$(notdir $*.map)
LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -lz -lnx -ljson-c -lm `freetype-config --libs`
LIBS := -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lxml2 -lz -lnx -ljson-c -lm `freetype-config --libs` -lturbojpeg
#---------------------------------------------------------------------------------
# list of directories containing libraries, this must be the top level containing

View file

@ -4,37 +4,95 @@ Nintendo Switch Game Card Dump Tool
Main features
--------------
* Generates full cartridge image dumps (XCI) with optional certificate removal and optional trimming.
* Generates installable packages (NSP) from cartridge applications.
- You'll need to retrieve the full NCA keyset beforehand, using Lockpick. It must be stored in "sdmc:/switch/prod.keys".
* Supports multigame carts.
* Generates full Cartridge Image dumps (XCI) with optional certificate removal and optional trimming.
* Generates installable Nintendo Submission Packages (NSP) from base applications, updates and DLCs stored in the inserted game card.
* Compatible with multigame carts.
* CRC32 checksum calculation for XCI/NSP dumps.
* Full XCI dump verification using XML database from NSWDB.COM (NSWreleases.xml).
* XML database and in-app update via libcurl.
* Precise HFS0 raw partition dumping, using the root HFS0 header from the game card.
* Partition filesystem data dumping.
* Partition filesystem browser with manual file dump support.
* HFS0 partition file data dumping.
* HFS0 partition file browser with manual file dump support.
* RomFS section file data dumping.
* RomFS section file browser with manual file dump support.
* Manual game card certificate dump.
* Free SD card space checks in place.
* File splitting support for all operations.
* Game card metadata retrieval using NCM and NS services.
* Dump speed, ETA calculation and progress bar.
* Dump speed calculation, ETA calculation and progress bar.
Thanks to
--------------
* MCMrARM, for creating the original application.
* RSDuck, for their vba-next-switch port. It's UI menu code was taken as a basis for this application.
* RSDuck, for vba-next-switch port. It's UI menu code was taken as a basis for this application.
* Foen, for giving me some pretty good hints about how to use the NCM service.
* Yellows8, for helping me fix a silly bug in my implementation of some NCM service IPC calls.
* SciresM, for hactool. It's AES cipher handling and external keys file parsing code is used during the NSP dump process.
* SciresM, for hactool. It's NCA content handling procedure is reproduced during the NSP dump process.
* The-4n, for 4NXCI and hacPack. The NCA content patching procedure used in 4NXCI is replicated in the application, as well as the NACP XML generation from hacPack.
* shchmue, for Lockpick. It was used as a reference for the key-collection algorithm needed for the NSP dump and RomFS dump/browse procedures.
* Björn Samuelsson, for his public domain CRC32 checksum calculation code for C (crc32_fast.c).
* AnalogMan, for his constant support and ideas.
* 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 folks from ReSwitched, for working towards the creation of a good homebrew ecosystem.
Changelog
--------------
**v1.1.0:**
* Replaced the application icon with a new, stylish one made by RattletraPM. Thanks a lot!
* Gamecard base application icons are now retrieved and displayed in the menu.
* L/ZL/R/ZR buttons can now be used to change the displayed base application info if a multigame cart is inserted, instead of displaying everything right away.
* The Nintendo Extension shared font is now used to display bitmaps representing controller buttons and sticks instead of just using text to reference them.
* Replaced the mbedtls-based AES and SHA-256 implementations with functions from the hardware accelerated cryptography API from libnx.
* Added an option to generate split XCI dumps using a directory with the archive bit set, just like split NSP dumps. It will only appear if "Split output dump" is enabled.
* Fixed ETA calculation.
* Enabled ETA calculation in full HFS0 partition data dumps.
* Fixed CRC32 checksum calculation for gamecard certificate dumps.
* Added Program NCA RomFS section parser:
- Supports filesystem dumping, filesystem browsing, manual file dumping and file splitting. Enjoy datamining your gamecards!
- Compatible with multigame carts. You'll be able to choose which base application RomFS will be dumped/browsed from a submenu.
- Output files will be saved to: "sdmc:/[GameName] v[GameVersion] ([TitleID]) (RomFS)/".
* Added high contrast directory/file icons from GNOME project to file browsing modes (HFS0 / RomFS).
* Fixed the NSP generation code (based on 4NXCI / hacPack):
- Delta Fragment NCAs are now discarded.
- The SHA-256 checksum is recalculated for every NCA content after being modified, resulting in new NCA IDs.
- The ACID public key is replaced in the NPDM section from the Program NCA. All the related NCA/PFS0 Superblock SHA-256 hashes are recalculated.
- The NPDM signature in the Program NCA header is now replaced as well.
- The content records from the Application CNMT are updated with proper SHA-256 hashes and new NCA IDs. All the related NCA/PFS0 Superblock hashes are recalculated.
- NACP XMLs are now generated as well.
- Because of all these changes, the CRC32 checksum can't be calculated until the dump procedure is complete.
- If this option is enabled, the application will take extra time after the NSP dump has been completed to calculate the CRC32 checksum. Nonetheless, you'll be able to cancel this procedure.
- A warning message will appear in the NSP dump menu if CRC32 checksum calculation is enabled to inform the user about this extra step.
- Furthermore, the output CRC32 checksum will be different on each new dump. This is because the NPDM signature in the Program NCA header uses a random seed.
- This effectively makes the generated NSPs only need ES patches to work. ACID patches shouldn't be needed anymore.
* Added NSP dumping support for Patch and AddOnContent title types with gamecards that include bundled Updates/DLCs:
- The information displayed in the main menu now shows how many Updates/DLCs are bundled in the inserted gamecard (per application and in total).
- If a bundled gamecard update features a populated Rights ID bitfield, both its Ticket and Certificate will get added to the output NSP.
- Additionally, the NSP dump menu has been divided in three subcategories: base application, update and DLC.
- Each submenu will only appear if the inserted gamecard holds at least one title belonging to the category it represents.
- If only the base application is included, like most gamecards, choosing the NSP dump option in the main menu will take you right to the base application dump menu.
- Once you enter a submenu, you'll be able to choose exactly which title to dump belonging to that category.
- Output update NSPs will not be modified in any way. Thus, unlike NSPs from base applications and DLCs, their CRC32 checksums will always be the same.
* Fixed the minimum system version field size in the extended CNMT header struct. Thanks to @0Liam !
* Changed the naming convention for output NSP dumps:
- Base application: "sdmc:/[GameName] v[GameVersion] ([TitleID]) (BASE).nsp".
- Update: "sdmc:/[GameName] v[UpdateVersion] ([UpdateTitleID]) (UPD).nsp".
- If a matching base application isn't found: "sdmc:/[UpdateTitleID] v[UpdateVersion] (UPD).nsp".
- DLC: "sdmc:/[GameName] v[DLCVersion] ([DLCTitleID]) (DLC).nsp".
- If a matching base application isn't found: "sdmc:/[DLCTitleID] v[DLCVersion] (DLC).nsp".
* The application is now able to retrieve the NCA header key and perform NCA key area decryption at runtime, using the SPL services. Thus, is isn't needed to run Lockpick beforehand anymore to dump NSPs (nor to dump/browse RomFS data).
* If the inserted gamecard includes a bundled update, its version number will now be used in the output filename for XCI, HFS0 and gamecard certificate dumps.
* Minor improvements to the file splitting code.
- Additionally, the filename for the current part will now be displayed and updated for all operations if file splitting is enabled.
* The application update feature will now use the launch path from argv if it's available. Otherwise, it defaults to "sdmc:/switch/gcdumptool.nro".
* Cosmetic fixes to the UI layout.
* NCM service resources are now properly closed.
* Removed unnecessary service (de)initializations.
Big thanks to PatrickD85, unvaluablespace, wartutor and Slim45 for testing these changes!
**v1.0.8:**
* Added proper metadata reading from multigame carts.

BIN
icon.jpg

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

After

Width:  |  Height:  |  Size: 32 KiB

BIN
romfs/dir_highlight.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 629 B

BIN
romfs/dir_normal.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 434 B

BIN
romfs/file_highlight.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 630 B

BIN
romfs/file_normal.jpg Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 435 B

View file

@ -1,325 +0,0 @@
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "aes.h"
#include "ui.h"
#include "util.h"
/* Extern variables */
extern int breaks;
extern int font_height;
extern char strbuf[NAME_BUF_LEN * 4];
/* Allocate a new context. */
aes_ctx_t *new_aes_ctx(const void *key, unsigned int key_size, aes_mode_t mode)
{
int ret;
aes_ctx_t *ctx;
if ((ctx = malloc(sizeof(*ctx))) == NULL)
{
uiDrawString("Error: failed to allocate aes_ctx_t!", 0, breaks * font_height, 255, 0, 0);
return NULL;
}
mbedtls_cipher_init(&ctx->cipher_dec);
mbedtls_cipher_init(&ctx->cipher_enc);
ret = mbedtls_cipher_setup(&ctx->cipher_dec, mbedtls_cipher_info_from_type(mode));
if (ret)
{
free_aes_ctx(ctx);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to set up AES decryption context! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return NULL;
}
ret = mbedtls_cipher_setup(&ctx->cipher_enc, mbedtls_cipher_info_from_type(mode));
if (ret)
{
free_aes_ctx(ctx);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to set up AES encryption context! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return NULL;
}
ret = mbedtls_cipher_setkey(&ctx->cipher_dec, key, key_size * 8, AES_DECRYPT);
if (ret)
{
free_aes_ctx(ctx);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to set key for AES decryption context! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return NULL;
}
ret = mbedtls_cipher_setkey(&ctx->cipher_enc, key, key_size * 8, AES_ENCRYPT);
if (ret)
{
free_aes_ctx(ctx);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to set key for AES encryption context! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return NULL;
}
return ctx;
}
/* Free an allocated context. */
void free_aes_ctx(aes_ctx_t *ctx)
{
/* Explicitly allow NULL. */
if (ctx == NULL) return;
mbedtls_cipher_free(&ctx->cipher_dec);
mbedtls_cipher_free(&ctx->cipher_enc);
free(ctx);
}
/* Set AES CTR or IV for a context. */
int aes_setiv(aes_ctx_t *ctx, const void *iv, size_t l)
{
int ret;
ret = mbedtls_cipher_set_iv(&ctx->cipher_dec, iv, l);
if (ret)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to set IV for AES decryption context! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
ret = mbedtls_cipher_set_iv(&ctx->cipher_enc, iv, l);
if (ret)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to set IV for AES encryption context! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
return 1;
}
/* Calculate CMAC. */
int aes_calculate_cmac(void *dst, void *src, size_t size, const void *key)
{
int ret;
mbedtls_cipher_context_t m_ctx;
mbedtls_cipher_init(&m_ctx);
ret = mbedtls_cipher_setup(&m_ctx, mbedtls_cipher_info_from_type(MBEDTLS_CIPHER_AES_128_ECB));
if (ret)
{
mbedtls_cipher_free(&m_ctx);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to set up AES context for CMAC calculation! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
ret = mbedtls_cipher_cmac_starts(&m_ctx, key, 0x80);
if (ret)
{
mbedtls_cipher_free(&m_ctx);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to start CMAC calculation! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
ret = mbedtls_cipher_cmac_update(&m_ctx, src, size);
if (ret)
{
mbedtls_cipher_free(&m_ctx);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to update CMAC calculation! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
ret = mbedtls_cipher_cmac_finish(&m_ctx, dst);
if (ret)
{
mbedtls_cipher_free(&m_ctx);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to finish CMAC calculation! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
mbedtls_cipher_free(&m_ctx);
return 1;
}
/* Encrypt with context. */
int aes_encrypt(aes_ctx_t *ctx, void *dst, const void *src, size_t l)
{
int ret;
size_t out_len = 0;
/* Prepare context */
mbedtls_cipher_reset(&ctx->cipher_enc);
/* XTS doesn't need per-block updating */
if (mbedtls_cipher_get_cipher_mode(&ctx->cipher_enc) == MBEDTLS_MODE_XTS)
{
ret = mbedtls_cipher_update(&ctx->cipher_enc, (const unsigned char *)src, l, (unsigned char *)dst, &out_len);
} else {
unsigned int blk_size = mbedtls_cipher_get_block_size(&ctx->cipher_enc);
/* Do per-block updating */
for (int offset = 0; (unsigned int)offset < l; offset += blk_size)
{
int len = (((unsigned int)(l - offset) > blk_size) ? blk_size : (unsigned int)(l - offset));
ret = mbedtls_cipher_update(&ctx->cipher_enc, (const unsigned char *)src + offset, len, (unsigned char *)dst + offset, &out_len);
if (ret) break;
}
}
if (ret)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: AES encryption failed! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
/* Flush all data */
size_t strbuf_size = sizeof(strbuf);
ret = mbedtls_cipher_finish(&ctx->cipher_enc, (unsigned char *)strbuf, &strbuf_size); // Looks ugly, but using NULL,NULL with mbedtls on Switch is a no-no
if (ret)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to finalize cipher for AES encryption! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
return 1;
}
/* Decrypt with context. */
int aes_decrypt(aes_ctx_t *ctx, void *dst, const void *src, size_t l)
{
int ret;
bool src_equals_dst = false;
if (src == dst)
{
src_equals_dst = true;
dst = malloc(l);
if (dst == NULL)
{
uiDrawString("Error: failed to allocate buffer for AES decryption!", 0, breaks * font_height, 255, 0, 0);
return 0;
}
}
size_t out_len = 0;
/* Prepare context */
mbedtls_cipher_reset(&ctx->cipher_dec);
/* XTS doesn't need per-block updating */
if (mbedtls_cipher_get_cipher_mode(&ctx->cipher_dec) == MBEDTLS_MODE_XTS)
{
ret = mbedtls_cipher_update(&ctx->cipher_dec, (const unsigned char *)src, l, (unsigned char *)dst, &out_len);
} else {
unsigned int blk_size = mbedtls_cipher_get_block_size(&ctx->cipher_dec);
/* Do per-block updating */
for (int offset = 0; (unsigned int)offset < l; offset += blk_size)
{
int len = (((unsigned int)(l - offset) > blk_size) ? blk_size : (unsigned int)(l - offset));
ret = mbedtls_cipher_update(&ctx->cipher_dec, (const unsigned char *)src + offset, len, (unsigned char *)dst + offset, &out_len);
if (ret) break;
}
}
if (ret)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: AES decryption failed! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
/* Flush all data */
size_t strbuf_size = sizeof(strbuf);
ret = mbedtls_cipher_finish(&ctx->cipher_dec, (unsigned char *)strbuf, &strbuf_size); // Looks ugly, but using NULL,NULL with mbedtls on Switch is a no-no
if (ret)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to finalize cipher for AES decryption! (%d)", ret);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
if (src_equals_dst)
{
memcpy((void*)src, dst, l);
free(dst);
}
return 1;
}
static void get_tweak(unsigned char *tweak, size_t sector)
{
/* Nintendo LE custom tweak... */
for (int i = 0xF; i >= 0; i--)
{
tweak[i] = (unsigned char)(sector & 0xFF);
sector >>= 8;
}
}
/* Encrypt with context for XTS. */
int aes_xts_encrypt(aes_ctx_t *ctx, void *dst, const void *src, size_t l, size_t sector, size_t sector_size)
{
int ret = 0;
unsigned char tweak[0x10];
if ((l % sector_size) != 0)
{
uiDrawString("Error: length must be a multiple of sector size in AES-XTS.", 0, breaks * font_height, 255, 0, 0);
return 0;
}
for (size_t i = 0; i < l; i += sector_size)
{
/* Workaround for Nintendo's custom sector...manually generate the tweak. */
get_tweak(tweak, sector++);
ret = aes_setiv(ctx, tweak, 16);
if (!ret) break;
ret = aes_encrypt(ctx, (char *)dst + i, (const char *)src + i, sector_size);
if (!ret) break;
}
return ret;
}
/* Decrypt with context for XTS. */
int aes_xts_decrypt(aes_ctx_t *ctx, void *dst, const void *src, size_t l, size_t sector, size_t sector_size)
{
int ret = 0;
unsigned char tweak[0x10];
if ((l % sector_size) != 0)
{
uiDrawString("Error: length must be a multiple of sector size in AES-XTS.", 0, breaks * font_height, 255, 0, 0);
return 0;
}
for (size_t i = 0; i < l; i += sector_size)
{
/* Workaround for Nintendo's custom sector...manually generate the tweak. */
get_tweak(tweak, sector++);
ret = aes_setiv(ctx, tweak, 16);
if (!ret) break;
ret = aes_decrypt(ctx, (char *)dst + i, (const char *)src + i, sector_size);
if (!ret) break;
}
return ret;
}

View file

@ -1,42 +0,0 @@
#pragma once
#ifndef __AES_H__
#define __AES_H__
#include <switch.h>
#include <mbedtls/cipher.h>
#include <mbedtls/cmac.h>
/* Enumerations. */
typedef enum {
AES_MODE_ECB = MBEDTLS_CIPHER_AES_128_ECB,
AES_MODE_CTR = MBEDTLS_CIPHER_AES_128_CTR,
AES_MODE_XTS = MBEDTLS_CIPHER_AES_128_XTS
} aes_mode_t;
typedef enum {
AES_DECRYPT = MBEDTLS_DECRYPT,
AES_ENCRYPT = MBEDTLS_ENCRYPT,
} aes_operation_t;
/* Define structs. */
typedef struct {
mbedtls_cipher_context_t cipher_enc;
mbedtls_cipher_context_t cipher_dec;
} aes_ctx_t;
/* Function prototypes. */
aes_ctx_t *new_aes_ctx(const void *key, unsigned int key_size, aes_mode_t mode);
void free_aes_ctx(aes_ctx_t *ctx);
int aes_setiv(aes_ctx_t *ctx, const void *iv, size_t l);
int aes_encrypt(aes_ctx_t *ctx, void *dst, const void *src, size_t l);
int aes_decrypt(aes_ctx_t *ctx, void *dst, const void *src, size_t l);
int aes_calculate_cmac(void *dst, void *src, size_t size, const void *key);
int aes_xts_encrypt(aes_ctx_t *ctx, void *dst, const void *src, size_t l, size_t sector, size_t sector_size);
int aes_xts_decrypt(aes_ctx_t *ctx, void *dst, const void *src, size_t l, size_t sector, size_t sector_size);
#endif

File diff suppressed because it is too large Load diff

View file

@ -4,6 +4,7 @@
#define __DUMPER_H__
#include <switch.h>
#include "util.h"
#define DUMP_BUFFER_SIZE (u64)0x100000 // 1 MiB (1048576 bytes)
#define ISTORAGE_PARTITION_CNT 2
@ -17,14 +18,16 @@
#define CERT_OFFSET 0x7000
#define CERT_SIZE 0x200
#define SMOOTHING_FACTOR (double)0.01
#define SMOOTHING_FACTOR (double)0.05
void workaroundPartitionZeroAccess(FsDeviceOperator* fsOperator);
bool dumpCartridgeImage(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool trimDump, bool calcCrc);
bool dumpApplicationNSP(FsDeviceOperator* fsOperator, bool isFat32, bool calcCrc, u32 appIndex);
bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitting);
bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition);
bool dumpFileFromPartition(FsDeviceOperator* fsOperator, u32 partition, u32 file, char *filename);
bool dumpCartridgeImage(FsDeviceOperator* fsOperator, bool isFat32, bool setXciArchiveBit, bool dumpCert, bool trimDump, bool calcCrc);
bool dumpNintendoSubmissionPackage(FsDeviceOperator* fsOperator, nspDumpType selectedNspDumpType, u32 titleIndex, bool isFat32, bool calcCrc);
bool dumpRawHfs0Partition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitting);
bool dumpHfs0PartitionData(FsDeviceOperator* fsOperator, u32 partition);
bool dumpFileFromHfs0Partition(FsDeviceOperator* fsOperator, u32 partition, u32 file, char *filename);
bool dumpRomFsSectionData(FsDeviceOperator* fsOperator, u32 appIndex);
bool dumpFileFromRomFsSection(u32 appIndex, u32 file_offset, bool doSplitting);
bool dumpGameCertificate(FsDeviceOperator *fsOperator);
#endif

View file

@ -1,250 +0,0 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "extkeys.h"
#include "ui.h"
#include "util.h"
/* Extern variables */
extern int breaks;
extern int font_height;
extern char strbuf[NAME_BUF_LEN * 4];
/**
* Reads a line from file f and parses out the key and value from it.
* The format of a line must match /^ *[A-Za-z0-9_] *[,=] *.+$/.
* If a line ends in \r, the final \r is stripped.
* The input file is assumed to have been opened with the 'b' flag.
* The input file is assumed to contain only ASCII.
*
* A line cannot exceed 512 bytes in length.
* Lines that are excessively long will be silently truncated.
*
* On success, *key and *value will be set to point to the key and value in
* the input line, respectively.
* *key and *value may also be NULL in case of empty lines.
* On failure, *key and *value will be set to NULL.
* End of file is considered failure.
*
* Because *key and *value will point to a static buffer, their contents must be
* copied before calling this function again.
* For the same reason, this function is not thread-safe.
*
* The key will be converted to lowercase.
* An empty key is considered a parse error, but an empty value is returned as
* success.
*
* This function assumes that the file can be trusted not to contain any NUL in
* the contents.
*
* Whitespace (' ', ASCII 0x20, as well as '\t', ASCII 0x09) at the beginning of
* the line, at the end of the line as well as around = (or ,) will be ignored.
*
* @param f the file to read
* @param key pointer to change to point to the key
* @param value pointer to change to point to the value
* @return 0 on success,
* 1 on end of file,
* -1 on parse error (line too long, line malformed)
* -2 on I/O error
*/
static int get_kv(FILE *f, char **key, char **value)
{
#define SKIP_SPACE(p) do {\
for (; (*p == ' ' || *p == '\t'); ++p);\
} while(0);
static char line[512];
char *k, *v, *p, *end;
*key = *value = NULL;
errno = 0;
if (fgets(line, (int)sizeof(line), f) == NULL)
{
if (feof(f))
{
return 1;
} else {
return -2;
}
}
if (errno != 0) return -2;
if (*line == '\n' || *line == '\r' || *line == '\0') return 0;
/* Not finding \r or \n is not a problem.
* The line might just be exactly 512 characters long, we have no way to
* tell.
* Additionally, it's possible that the last line of a file is not actually
* a line (i.e., does not end in '\n'); we do want to handle those.
*/
if ((p = strchr(line, '\r')) != NULL || (p = strchr(line, '\n')) != NULL)
{
end = p;
*p = '\0';
} else {
end = (line + strlen(line) + 1);
}
p = line;
SKIP_SPACE(p);
k = p;
/* Validate key and convert to lower case. */
for (; *p != ' ' && *p != ',' && *p != '\t' && *p != '='; ++p)
{
if (*p == '\0') return -1;
if (*p >= 'A' && *p <= 'Z')
{
*p = 'a' + (*p - 'A');
continue;
}
if (*p != '_' && (*p < '0' && *p > '9') && (*p < 'a' && *p > 'z')) return -1;
}
/* Bail if the final ++p put us at the end of string */
if (*p == '\0') return -1;
/* We should be at the end of key now and either whitespace or [,=]
* follows.
*/
if (*p == '=' || *p == ',')
{
*p++ = '\0';
} else {
*p++ = '\0';
SKIP_SPACE(p);
if (*p != '=' && *p != ',') return -1;
*p++ = '\0';
}
/* Empty key is an error. */
if (*k == '\0') return -1;
SKIP_SPACE(p);
v = p;
/* Skip trailing whitespace */
for (p = end - 1; *p == '\t' || *p == ' '; --p);
*(p + 1) = '\0';
*key = k;
*value = v;
return 0;
#undef SKIP_SPACE
}
static int ishex(char c)
{
if ('a' <= c && c <= 'f') return 1;
if ('A' <= c && c <= 'F') return 1;
if ('0' <= c && c <= '9') return 1;
return 0;
}
static char hextoi(char c)
{
if ('a' <= c && c <= 'f') return (c - 'a' + 0xA);
if ('A' <= c && c <= 'F') return (c - 'A' + 0xA);
if ('0' <= c && c <= '9') return (c - '0');
return 0;
}
int parse_hex_key(unsigned char *key, const char *hex, unsigned int len)
{
if (strlen(hex) != (2 * len))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: key (%s) must be %u hex digits!", hex, 2 * len);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
u32 i;
for(i = 0; i < (2 * len); i++)
{
if (!ishex(hex[i]))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: key (%s) must be %u hex digits!", hex, 2 * len);
uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0);
return 0;
}
}
memset(key, 0, len);
for(i = 0; i < (2 * len); i++)
{
char val = hextoi(hex[i]);
if ((i & 1) == 0) val <<= 4;
key[i >> 1] |= val;
}
return 1;
}
int extkeys_initialize_keyset(nca_keyset_t *keyset, FILE *f)
{
u32 i;
int ret;
char *key, *value;
char test_name[0x100];
memset(keyset, 0, sizeof(nca_keyset_t));
while((ret = get_kv(f, &key, &value)) != 1 && ret != -2)
{
if (ret == 0)
{
if (key == NULL || value == NULL) continue;
if (strcmp(key, "header_key") == 0)
{
if (!parse_hex_key(keyset->header_key, value, sizeof(keyset->header_key))) return 0;
keyset->key_cnt++;
} else {
memset(test_name, 0, sizeof(test_name));
for(i = 0; i < 0x20; i++)
{
snprintf(test_name, sizeof(test_name), "key_area_key_application_%02x", i);
if (strcmp(key, test_name) == 0)
{
if (!parse_hex_key(keyset->key_area_keys[i][0], value, sizeof(keyset->key_area_keys[i][0]))) return 0;
keyset->key_cnt++;
break;
}
snprintf(test_name, sizeof(test_name), "key_area_key_ocean_%02x", i);
if (strcmp(key, test_name) == 0)
{
if (!parse_hex_key(keyset->key_area_keys[i][1], value, sizeof(keyset->key_area_keys[i][1]))) return 0;
keyset->key_cnt++;
break;
}
snprintf(test_name, sizeof(test_name), "key_area_key_system_%02x", i);
if (strcmp(key, test_name) == 0)
{
if (!parse_hex_key(keyset->key_area_keys[i][2], value, sizeof(keyset->key_area_keys[i][2]))) return 0;
keyset->key_cnt++;
break;
}
}
}
}
}
if (!keyset->key_cnt) return -1;
return 1;
}

View file

@ -1,18 +0,0 @@
#pragma once
#ifndef __EXTKEYS_H__
#define __EXTKEYS_H__
#include <switch.h>
#include <string.h>
typedef struct {
u32 key_cnt;
unsigned char header_key[0x20]; /* NCA header key. */
unsigned char key_area_keys[0x20][3][0x10]; /* Key area encryption keys. */
} nca_keyset_t;
int parse_hex_key(unsigned char *key, const char *hex, unsigned int len);
int extkeys_initialize_keyset(nca_keyset_t *keyset, FILE *f);
#endif

View file

@ -2,7 +2,7 @@
#include <stdlib.h>
#include <string.h>
#include "fsext.h"
#include "fs_ext.h"
// IFileSystemProxy
Result fsOpenGameCardStorage(FsStorage* out, const FsGameCardHandle* handle, u32 partition)

View file

@ -1,7 +1,7 @@
#pragma once
#ifndef __FSEXT_H__
#define __FSEXT_H__
#ifndef __FS_EXT_H__
#define __FS_EXT_H__
#include <switch/types.h>
#include <switch/services/fs.h>

400
source/keys.c Normal file
View file

@ -0,0 +1,400 @@
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include "keys.h"
#include "util.h"
#include "ui.h"
/* Extern variables */
extern int breaks;
extern int font_height;
extern char strbuf[NAME_BUF_LEN * 4];
/* Statically allocated variables */
nca_keyset_t nca_keyset;
static keyLocation FSRodata = {
FS_TID,
SEG_RODATA,
NULL,
0
};
static keyLocation FSData = {
FS_TID,
SEG_DATA,
NULL,
0
};
static const keyInfo header_kek_source = {
"header_kek_source",
{ 0x18, 0x88, 0xCA, 0xED, 0x55, 0x51, 0xB3, 0xED, 0xE0, 0x14, 0x99, 0xE8, 0x7C, 0xE0, 0xD8, 0x68,
0x27, 0xF8, 0x08, 0x20, 0xEF, 0xB2, 0x75, 0x92, 0x10, 0x55, 0xAA, 0x4E, 0x2A, 0xBD, 0xFF, 0xC2 },
0x10
};
static const keyInfo header_key_source = {
"header_key_source",
{ 0x8F, 0x78, 0x3E, 0x46, 0x85, 0x2D, 0xF6, 0xBE, 0x0B, 0xA4, 0xE1, 0x92, 0x73, 0xC4, 0xAD, 0xBA,
0xEE, 0x16, 0x38, 0x00, 0x43, 0xE1, 0xB8, 0xC4, 0x18, 0xC4, 0x08, 0x9A, 0x8B, 0xD6, 0x4A, 0xA6 },
0x20
};
static const keyInfo key_area_key_application_source = {
"key_area_key_application_source",
{ 0x04, 0xAD, 0x66, 0x14, 0x3C, 0x72, 0x6B, 0x2A, 0x13, 0x9F, 0xB6, 0xB2, 0x11, 0x28, 0xB4, 0x6F,
0x56, 0xC5, 0x53, 0xB2, 0xB3, 0x88, 0x71, 0x10, 0x30, 0x42, 0x98, 0xD8, 0xD0, 0x09, 0x2D, 0x9E },
0x10
};
static const keyInfo key_area_key_ocean_source = {
"key_area_key_ocean_source",
{ 0xFD, 0x43, 0x40, 0x00, 0xC8, 0xFF, 0x2B, 0x26, 0xF8, 0xE9, 0xA9, 0xD2, 0xD2, 0xC1, 0x2F, 0x6B,
0xE5, 0x77, 0x3C, 0xBB, 0x9D, 0xC8, 0x63, 0x00, 0xE1, 0xBD, 0x99, 0xF8, 0xEA, 0x33, 0xA4, 0x17 },
0x10
};
static const keyInfo key_area_key_system_source = {
"key_area_key_system_source",
{ 0x1F, 0x17, 0xB1, 0xFD, 0x51, 0xAD, 0x1C, 0x23, 0x79, 0xB5, 0x8F, 0x15, 0x2C, 0xA4, 0x91, 0x2E,
0xC2, 0x10, 0x64, 0x41, 0xE5, 0x17, 0x22, 0xF3, 0x87, 0x00, 0xD5, 0x93, 0x7A, 0x11, 0x62, 0xF7 },
0x10
};
void freeProcessMemory(keyLocation *location)
{
if (location && location->data)
{
free(location->data);
location->data = NULL;
}
}
bool retrieveProcessMemory(keyLocation *location)
{
if (!location || !location->titleID || !location->mask)
{
uiDrawString("Error: invalid parameters to retrieve keys from process memory.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
return false;
}
Result result;
Handle debug_handle = INVALID_HANDLE;
u64 d[8];
memset(d, 0, 8 * sizeof(u64));
if ((location->titleID > 0x0100000000000005) && (location->titleID != 0x0100000000000028))
{
// If not a kernel process, get PID from pm:dmnt
u64 pid;
if (R_FAILED(result = pmdmntGetTitlePid(&pid, location->titleID)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: pmdmntGetTitlePid failed! (0x%08X)", result);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
return false;
}
if (R_FAILED(result = svcDebugActiveProcess(&debug_handle, pid)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: svcDebugActiveProcess failed! (0x%08X)", result);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
return false;
}
if (R_FAILED(result = svcGetDebugEvent((u8*)&d, debug_handle)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: svcGetDebugEvent failed! (0x%08X)", result);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
return false;
}
} else {
// Otherwise, query svc for the process list
u64 pids[300];
u32 num_processes;
if (R_FAILED(result = svcGetProcessList(&num_processes, pids, 300)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: svcGetProcessList failed! (0x%08X)", result);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
return false;
}
u32 i;
for(i = 0; i < (num_processes - 1); i++)
{
if (R_SUCCEEDED(svcDebugActiveProcess(&debug_handle, pids[i])) && R_SUCCEEDED(svcGetDebugEvent((u8*)&d, debug_handle)) && (d[2] == location->titleID)) break;
if (debug_handle) svcCloseHandle(debug_handle);
}
if (i == (num_processes - 1))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to retrieve debug handle for process with Title ID %016lX!", location->titleID);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
if (debug_handle) svcCloseHandle(debug_handle);
return false;
}
}
MemoryInfo mem_info;
memset(&mem_info, 0, sizeof(MemoryInfo));
u32 page_info;
u64 addr = 0;
u8 segment;
u8 *dataTmp = NULL;
bool success = true;
for(segment = 1; segment < BIT(3);)
{
if (R_FAILED(result = svcQueryDebugProcessMemory(&mem_info, &page_info, debug_handle, addr)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: svcQueryDebugProcessMemory failed! (0x%08X)", result);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
success = false;
break;
}
// Weird code to allow for bitmasking segments
if ((mem_info.perm & Perm_R) && ((mem_info.type & 0xff) >= MemType_CodeStatic) && ((mem_info.type & 0xff) < MemType_Heap) && ((segment <<= 1) >> 1 & location->mask) > 0)
{
// If location->data == NULL, realloc will essentially act as a malloc
dataTmp = realloc(location->data, location->dataSize + mem_info.size);
if (!dataTmp)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to resize key location data buffer to %lu bytes.", location->dataSize + mem_info.size);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
success = false;
break;
}
location->data = dataTmp;
dataTmp = NULL;
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)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: svcReadDebugProcessMemory failed! (0x%08X)", result);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
success = false;
break;
}
location->dataSize += mem_info.size;
}
addr = (mem_info.addr + mem_info.size);
if (addr == 0) break;
}
svcCloseHandle(debug_handle);
if (success)
{
if (!location->data || !location->dataSize) success = false;
}
return success;
}
bool findKeyInProcessMemory(const keyLocation *location, const keyInfo *findKey, u8 *out)
{
if (!location || !location->data || !location->dataSize || !findKey || !strlen(findKey->name) || !findKey->size)
{
uiDrawString("Error: invalid parameters to locate key in process memory.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
return false;
}
u64 i;
u8 temp_hash[SHA256_HASH_LENGTH];
bool found = false;
// Hash every key-length-sized byte chunk in data until it matches a key hash
for(i = 0; i < location->dataSize; i++)
{
if (!found && (location->dataSize - i) < findKey->size) break;
sha256CalculateHash(temp_hash, location->data + i, findKey->size);
if (!memcmp(temp_hash, findKey->hash, SHA256_HASH_LENGTH))
{
// Jackpot
memcpy(out, location->data + i, findKey->size);
found = true;
break;
}
}
if (!found)
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to locate key \"%s\" in process memory!", findKey->name);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
}
return found;
}
bool findFSRodataKeys(keyLocation *location)
{
if (!location || location->titleID != FS_TID || location->mask != SEG_RODATA || !location->data || !location->dataSize)
{
uiDrawString("Error: invalid parameters to locate keys in FS RODATA segment.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
return false;
}
if (!findKeyInProcessMemory(location, &header_kek_source, nca_keyset.header_kek_source)) return false;
nca_keyset.key_cnt++;
if (!findKeyInProcessMemory(location, &key_area_key_application_source, nca_keyset.key_area_key_application_source)) return false;
nca_keyset.key_cnt++;
if (!findKeyInProcessMemory(location, &key_area_key_ocean_source, nca_keyset.key_area_key_ocean_source)) return false;
nca_keyset.key_cnt++;
if (!findKeyInProcessMemory(location, &key_area_key_system_source, nca_keyset.key_area_key_system_source)) return false;
nca_keyset.key_cnt++;
return true;
}
bool getNcaKeys()
{
Result result;
u8 nca_header_kek[0x10];
if (!retrieveProcessMemory(&FSRodata)) return false;
if (!retrieveProcessMemory(&FSData))
{
freeProcessMemory(&FSRodata);
return false;
}
if (!findFSRodataKeys(&FSRodata))
{
freeProcessMemory(&FSData);
freeProcessMemory(&FSRodata);
return false;
}
if (!findKeyInProcessMemory(&FSData, &header_key_source, nca_keyset.header_key_source))
{
freeProcessMemory(&FSData);
freeProcessMemory(&FSRodata);
return false;
}
nca_keyset.key_cnt++;
// Derive NCA header key
if (R_FAILED(result = splCryptoInitialize()))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to initialize the spl:crypto service! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
freeProcessMemory(&FSData);
freeProcessMemory(&FSRodata);
return false;
}
if (R_FAILED(result = splCryptoGenerateAesKek(nca_keyset.header_kek_source, 0, 0, nca_header_kek)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: splCryptoGenerateAesKek(header_kek_source) failed! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
splCryptoExit();
freeProcessMemory(&FSData);
freeProcessMemory(&FSRodata);
return false;
}
if (R_FAILED(result = splCryptoGenerateAesKey(nca_header_kek, nca_keyset.header_key_source + 0x00, nca_keyset.header_key + 0x00)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: splCryptoGenerateAesKey(header_key_source + 0x00) failed! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
splCryptoExit();
freeProcessMemory(&FSData);
freeProcessMemory(&FSRodata);
return false;
}
if (R_FAILED(result = splCryptoGenerateAesKey(nca_header_kek, nca_keyset.header_key_source + 0x10, nca_keyset.header_key + 0x10)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: splCryptoGenerateAesKey(header_key_source + 0x10) failed! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
splCryptoExit();
freeProcessMemory(&FSData);
freeProcessMemory(&FSRodata);
return false;
}
nca_keyset.key_cnt++;
splCryptoExit();
freeProcessMemory(&FSData);
freeProcessMemory(&FSRodata);
return true;
}
bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out)
{
if (!dec_nca_header || dec_nca_header->kaek_ind > 2)
{
uiDrawString("Error: invalid parameters to decrypt NCA key area.", 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
return false;
}
Result result;
u8 i;
u8 tmp_kek[0x10];
u8 crypto_type = (dec_nca_header->crypto_type2 > dec_nca_header->crypto_type ? dec_nca_header->crypto_type2 : dec_nca_header->crypto_type);
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()))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to initialize the spl:crypto service! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
return false;
}
if (R_FAILED(result = splCryptoGenerateAesKek(kek_source, crypto_type, 0, tmp_kek)))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: splCryptoGenerateAesKek(kek_source) failed! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
splCryptoExit();
return false;
}
bool success = true;
u8 decrypted_nca_keys[NCA_KEY_AREA_KEY_CNT][NCA_KEY_AREA_KEY_SIZE];
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])))
{
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: splCryptoGenerateAesKey(nca_kaek_%02u) failed! (0x%08X)", i, result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
success = false;
break;
}
}
splCryptoExit();
memcpy(out, decrypted_nca_keys, NCA_KEY_AREA_SIZE);
return success;
}

44
source/keys.h Normal file
View file

@ -0,0 +1,44 @@
#pragma once
#ifndef __KEYS_H__
#define __KEYS_H__
#include <switch.h>
#include "nca.h"
#define FS_TID (u64)0x0100000000000000
#define SEG_TEXT BIT(0)
#define SEG_RODATA BIT(1)
#define SEG_DATA BIT(2)
#define SHA256_HASH_LENGTH 0x20
typedef struct {
u64 titleID;
u8 mask;
u8 *data;
u64 dataSize;
} PACKED keyLocation;
typedef struct {
char name[128];
u8 hash[SHA256_HASH_LENGTH];
u64 size;
} PACKED keyInfo;
typedef struct {
u32 key_cnt; /* Should be equal to 6. */
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. */
u8 header_kek_source[0x10]; /* Seed for header kek. */
u8 header_key_source[0x20]; /* Seed for NCA header key. */
u8 header_key[0x20]; /* NCA header key. */
} PACKED nca_keyset_t;
bool getNcaKeys();
bool decryptNcaKeyArea(nca_header_t *dec_nca_header, u8 *out);
#endif

View file

@ -7,8 +7,8 @@
#include "dumper.h"
#include "ui.h"
#include "util.h"
#include "fsext.h"
#include "extkeys.h"
#include "fs_ext.h"
#include "keys.h"
/* Extern variables */
@ -23,218 +23,290 @@ extern bool gameCardInserted;
extern char strbuf[NAME_BUF_LEN * 4];
extern char appLaunchPath[NAME_BUF_LEN];
extern nca_keyset_t nca_keyset;
int main(int argc, char *argv[])
{
/* Copy launch path */
if (!envIsNso() && argc > 0)
{
int i;
for(i = 0; i < argc; i++)
{
if (strlen(argv[i]) > 10 && !strncasecmp(argv[i], "sdmc:/", 6) && !strncasecmp(argv[i] + strlen(argv[i]) - 4, ".nro", 4))
{
snprintf(appLaunchPath, sizeof(appLaunchPath) / sizeof(appLaunchPath[0]), argv[i]);
break;
}
}
}
/* Initialize UI */
if (!uiInit()) return -1;
int ret = 0;
Result result;
/* Initialize the fsp-srv service */
result = fsInitialize();
/* Initialize the ncm service */
result = ncmInitialize();
if (R_SUCCEEDED(result))
{
/* Open device operator */
result = fsOpenDeviceOperator(&fsOperatorInstance);
/* Initialize the ns service */
result = nsInitialize();
if (R_SUCCEEDED(result))
{
/* Initialize the ncm service */
result = ncmInitialize();
/* Initialize the csrng service */
result = csrngInitialize();
if (R_SUCCEEDED(result))
{
/* Initialize the ns service */
result = nsInitialize();
/* Initialize the spl service */
result = splInitialize();
if (R_SUCCEEDED(result))
{
/* Initialize the time service */
result = timeInitialize();
/* Initialize the pm:dmnt service */
result = pmdmntInitialize();
if (R_SUCCEEDED(result))
{
/* Open gamecard detection event notifier */
result = fsOpenGameCardDetectionEventNotifier(&fsGameCardEventNotifier);
/* Open device operator */
result = fsOpenDeviceOperator(&fsOperatorInstance);
if (R_SUCCEEDED(result))
{
/* Retrieve gamecard detection event handle */
result = fsEventNotifierGetEventHandle(&fsGameCardEventNotifier, &fsGameCardEventHandle);
/* Open gamecard detection event notifier */
result = fsOpenGameCardDetectionEventNotifier(&fsGameCardEventNotifier);
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);
/* Retrieve gamecard detection event handle */
result = fsEventNotifierGetEventHandle(&fsGameCardEventNotifier, &fsGameCardEventHandle);
if (R_SUCCEEDED(result))
{
/* Start gamecard detection thread */
result = threadStart(&thread);
/* 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))
{
/* Zero out NCA keyset */
memset(&nca_keyset, 0, sizeof(nca_keyset_t));
/* Main application loop */
bool exitLoop = false;
while(appletMainLoop())
/* Start gamecard detection thread */
result = threadStart(&thread);
if (R_SUCCEEDED(result))
{
UIResult result = uiProcess();
switch(result)
/* Zero out NCA keyset */
memset(&nca_keyset, 0, sizeof(nca_keyset_t));
/* Init RomFS context */
initRomFsContext();
/* Main application loop */
bool exitLoop = false;
while(appletMainLoop())
{
case resultShowMainMenu:
uiSetState(stateMainMenu);
break;
case resultShowXciDumpMenu:
uiSetState(stateXciDumpMenu);
break;
case resultDumpXci:
uiSetState(stateDumpXci);
break;
case resultShowNspDumpMenu:
uiSetState(stateNspDumpMenu);
break;
case resultDumpNsp:
uiSetState(stateDumpNsp);
break;
case resultShowRawPartitionDumpMenu:
uiSetState(stateRawPartitionDumpMenu);
break;
case resultDumpRawPartition:
uiSetState(stateDumpRawPartition);
break;
case resultShowPartitionDataDumpMenu:
uiSetState(statePartitionDataDumpMenu);
break;
case resultDumpPartitionData:
uiSetState(stateDumpPartitionData);
break;
case resultShowViewGameCardFsMenu:
uiSetState(stateViewGameCardFsMenu);
break;
case resultShowViewGameCardFsGetList:
uiSetState(stateViewGameCardFsGetList);
break;
case resultShowViewGameCardFsBrowser:
uiSetState(stateViewGameCardFsBrowser);
break;
case resultViewGameCardFsBrowserCopyFile:
uiSetState(stateViewGameCardFsBrowserCopyFile);
break;
case resultDumpGameCardCertificate:
uiSetState(stateDumpGameCardCertificate);
break;
case resultUpdateNSWDBXml:
uiSetState(stateUpdateNSWDBXml);
break;
case resultUpdateApplication:
uiSetState(stateUpdateApplication);
break;
case resultExit:
exitLoop = true;
break;
default:
break;
UIResult result = uiProcess();
switch(result)
{
case resultShowMainMenu:
uiSetState(stateMainMenu);
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 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 resultDumpGameCardCertificate:
uiSetState(stateDumpGameCardCertificate);
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;
}
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 {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to start gamecard detection thread! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -11;
}
/* Signal the exit event to terminate the gamecard detection thread */
ueventSignal(&exitEvent);
/* Wait for the gamecard detection thread to exit */
threadWaitForExit(&thread);
/* Close gamecard detection thread */
threadClose(&thread);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to start gamecard detection thread! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to create gamecard detection thread! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -10;
}
/* Close gamecard detection thread */
threadClose(&thread);
/* Close gamecard detection kernel event */
eventClose(&fsGameCardKernelEvent);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to create gamecard detection thread! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to retrieve gamecard detection event handle! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -9;
}
/* Close gamecard detection kernel event */
eventClose(&fsGameCardKernelEvent);
/* Close gamecard detection event notifier */
fsEventNotifierClose(&fsGameCardEventNotifier);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to retrieve gamecard detection event handle! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open gamecard detection event notifier! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -8;
}
/* Close gamecard detection event notifier */
fsEventNotifierClose(&fsGameCardEventNotifier);
/* Close device operator */
fsDeviceOperatorClose(&fsOperatorInstance);
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open gamecard detection event notifier! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open device operator! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -7;
}
/* Denitialize the time service */
timeExit();
/* Denitialize the pm:dmnt service */
pmdmntExit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the time service! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the pm:dmnt service! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -6;
}
/* Denitialize the ns service */
nsExit();
/* Denitialize the spl service */
splExit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ns service! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the spl service! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -5;
}
/* Denitialize the ncm service */
ncmExit();
/* Denitialize the csrng service */
csrngExit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ncm service! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the csrng service! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -4;
}
/* Close device operator */
fsDeviceOperatorClose(&fsOperatorInstance);
/* Denitialize the ns service */
nsExit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open device operator! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ns service! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -3;
}
/* Denitialize the fs-srv service */
fsExit();
/* Denitialize the ncm service */
ncmExit();
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the fsp-srv service! (0x%08X)", result);
uiDrawString(strbuf, 0, 0, 255, 255, 255);
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to initialize the ncm service! (0x%08X)", result);
uiDrawString(strbuf, 8, 8, 255, 255, 255);
uiRefreshDisplay();
delay(5);
ret = -2;

2014
source/nca.c Normal file

File diff suppressed because it is too large Load diff

362
source/nca.h Normal file
View file

@ -0,0 +1,362 @@
#pragma once
#ifndef __NCA_H__
#define __NCA_H__
#include <switch.h>
#define NCA3_MAGIC (u32)0x4E434133 // "NCA3"
#define NCA2_MAGIC (u32)0x4E434132 // "NCA2"
#define NCA_HEADER_LENGTH 0x400
#define NCA_SECTION_HEADER_LENGTH 0x200
#define NCA_SECTION_HEADER_CNT 4
#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT))
#define NCA_CONTENT_TYPE_DELTA 0x06
#define NCA_AES_XTS_SECTOR_SIZE 0x200
#define NCA_KEY_AREA_KEY_CNT 4
#define NCA_KEY_AREA_KEY_SIZE 0x10
#define NCA_KEY_AREA_SIZE (NCA_KEY_AREA_KEY_CNT * NCA_KEY_AREA_KEY_SIZE)
#define NCA_FS_HEADER_PARTITION_PFS0 0x01
#define NCA_FS_HEADER_FSTYPE_PFS0 0x02
#define NCA_FS_HEADER_PARTITION_ROMFS 0x00
#define NCA_FS_HEADER_FSTYPE_ROMFS 0x03
#define NCA_FS_HEADER_CRYPT_NONE 0x01
#define NCA_FS_HEADER_CRYPT_XTS 0x02
#define NCA_FS_HEADER_CRYPT_CTR 0x03
#define NCA_FS_HEADER_CRYPT_BKTR 0x04
#define PFS0_MAGIC (u32)0x50465330 // "PFS0"
#define IVFC_MAGIC (u32)0x49564643 // "IVFC"
#define IVFC_MAX_LEVEL 6
#define ROMFS_HEADER_SIZE 0x50
#define ROMFS_ENTRY_EMPTY (u32)0xFFFFFFFF
#define ROMFS_NONAME_DIRENTRY_SIZE 0x18
#define ROMFS_NONAME_FILEENTRY_SIZE 0x20
#define ROMFS_ENTRY_DIR 1
#define ROMFS_ENTRY_FILE 2
#define META_MAGIC (u32)0x4D455441 // "META"
#define NPDM_SIGNATURE_SIZE 0x100
#define NPDM_SIGNATURE_AREA_SIZE 0x200
#define NSP_NCA_FILENAME_LENGTH 0x25 // NCA ID + ".nca" + NULL terminator
#define NSP_CNMT_FILENAME_LENGTH 0x2A // NCA ID + ".cnmt.nca" / ".cnmt.xml" + NULL terminator
#define NSP_NACP_FILENAME_LENGTH 0x2A // NCA ID + ".nacp.xml" + NULL terminator
#define NSP_TIK_FILENAME_LENGTH 0x25 // Rights ID + ".tik" + NULL terminator
#define NSP_CERT_FILENAME_LENGTH 0x26 // Rights ID + ".cert" + NULL terminator
typedef enum {
DUMP_APP_NSP = 0,
DUMP_PATCH_NSP,
DUMP_ADDON_NSP
} nspDumpType;
typedef struct {
u32 magic;
u32 file_cnt;
u32 str_table_size;
u32 reserved;
} PACKED pfs0_header;
typedef struct {
u64 file_offset;
u64 file_size;
u32 filename_offset;
u32 reserved;
} PACKED pfs0_entry_table;
typedef struct {
u32 media_start_offset;
u32 media_end_offset;
u8 _0x8[0x8]; /* Padding. */
} PACKED nca_section_entry_t;
typedef struct {
u8 master_hash[0x20]; /* SHA-256 hash of the hash table. */
u32 block_size; /* In bytes. */
u32 always_2;
u64 hash_table_offset; /* Normally zero. */
u64 hash_table_size;
u64 pfs0_offset;
u64 pfs0_size;
u8 _0x48[0xF0];
} PACKED pfs0_superblock_t;
typedef struct {
u64 logical_offset;
u64 hash_data_size;
u32 block_size;
u32 reserved;
} PACKED ivfc_level_hdr_t;
typedef struct {
u32 magic;
u32 id;
u32 master_hash_size;
u32 num_levels;
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
u8 _0xA0[0x20];
u8 master_hash[0x20];
} PACKED ivfc_hdr_t;
typedef struct {
ivfc_hdr_t ivfc_header;
u8 _0xE0[0x58];
} PACKED romfs_superblock_t;
/* NCA FS header. */
typedef struct {
u8 _0x0;
u8 _0x1;
u8 partition_type;
u8 fs_type;
u8 crypt_type;
u8 _0x5[0x3];
union { /* FS-specific superblock. Size = 0x138. */
pfs0_superblock_t pfs0_superblock;
romfs_superblock_t romfs_superblock;
};
union {
u8 section_ctr[0x8];
struct {
u32 section_ctr_low;
u32 section_ctr_high;
};
};
u8 _0x148[0xB8]; /* Padding. */
} PACKED nca_fs_header_t;
/* Nintendo content archive header. */
typedef struct {
u8 fixed_key_sig[0x100]; /* RSA-PSS signature over header with fixed key. */
u8 npdm_key_sig[0x100]; /* RSA-PSS signature over header with key in NPDM. */
u32 magic;
u8 distribution; /* System vs gamecard. */
u8 content_type;
u8 crypto_type; /* Which keyblob (field 1) */
u8 kaek_ind; /* Which kaek index? */
u64 nca_size; /* Entire archive size. */
u64 title_id;
u8 _0x218[0x4]; /* Padding. */
union {
u32 sdk_version; /* What SDK was this built with? */
struct {
u8 sdk_revision;
u8 sdk_micro;
u8 sdk_minor;
u8 sdk_major;
};
};
u8 crypto_type2; /* Which keyblob (field 2) */
u8 _0x221[0xF]; /* Padding. */
u8 rights_id[0x10]; /* Rights ID (for titlekey crypto). */
nca_section_entry_t section_entries[4]; /* Section entry metadata. */
u8 section_hashes[4][0x20]; /* SHA-256 hashes for each section header. */
u8 nca_keys[4][0x10]; /* Key area (encrypted) */
u8 _0x340[0xC0]; /* Padding. */
nca_fs_header_t fs_headers[4]; /* FS section headers. */
} PACKED nca_header_t;
typedef struct {
u32 magic;
u32 _0x4;
u32 _0x8;
u8 mmu_flags;
u8 _0xD;
u8 main_thread_prio;
u8 default_cpuid;
u64 _0x10;
u32 process_category;
u32 main_stack_size;
char title_name[0x50];
u32 aci0_offset;
u32 aci0_size;
u32 acid_offset;
u32 acid_size;
} PACKED npdm_t;
typedef struct {
u64 title_id;
u32 version;
u8 type;
u8 unk1;
u16 table_offset;
u16 content_records_cnt;
u16 meta_records_cnt;
u8 unk2[12];
} PACKED cnmt_header;
typedef struct {
u64 patch_tid; /* Patch TID / Original TID / Application TID */
u32 min_sysver; /* Minimum system/application version */
} PACKED cnmt_extended_header;
typedef struct {
u8 hash[0x20];
u8 nca_id[0x10];
u8 size[6];
u8 type;
u8 unk;
} PACKED cnmt_content_record;
typedef struct {
u8 type;
u64 title_id;
u32 version;
u32 required_dl_sysver;
u32 nca_cnt;
u8 digest[32];
char digest_str[65];
u8 min_keyblob;
u32 min_sysver;
u64 patch_tid;
} PACKED cnmt_xml_program_info;
typedef struct {
u8 type;
u8 nca_id[16];
char nca_id_str[33];
u64 size;
u8 hash[32];
char hash_str[65];
u8 keyblob;
u8 decrypted_nca_keys[NCA_KEY_AREA_SIZE];
u8 encrypted_header_mod[NCA_FULL_HEADER_LENGTH];
} PACKED cnmt_xml_content_info;
typedef struct {
u8 *hash_table;
u64 hash_table_offset; // Relative to NCA start
u64 hash_table_size;
u8 block_mod_cnt;
u8 *block_data[2];
u64 block_offset[2]; // Relative to NCA start
u64 block_size[2];
} PACKED nca_program_mod_data;
typedef struct {
u64 section_offset; // Relative to NCA start
u64 section_size;
u64 hash_table_offset; // Relative to NCA start
u64 pfs0_offset; // Relative to NCA start
u64 pfs0_size;
u64 title_cnmt_offset; // Relative to NCA start
u64 title_cnmt_size;
} PACKED nca_cnmt_mod_data;
typedef struct {
NacpLanguageEntry lang[16];
char Isbn[0x25];
u8 StartupUserAccount;
u8 UserAccountSwitchLock;
u8 AddOnContentRegistrationType;
u32 AttributeFlag;
u32 SupportedLanguageFlag;
u32 ParentalControlFlag;
u8 Screenshot;
u8 VideoCapture;
u8 DataLossConfirmation;
u8 PlayLogPolicy;
u64 PresenceGroupId;
u8 RatingAge[0x20];
char DisplayVersion[0x10];
u64 AddOnContentBaseId;
u64 SaveDataOwnerId;
u64 UserAccountSaveDataSize;
u64 UserAccountSaveDataJournalSize;
u64 DeviceSaveDataSize;
u64 DeviceSaveDataJournalSize;
u64 BcatDeliveryCacheStorageSize;
char ApplicationErrorCodeCategory[0x8];
u64 LocalCommunicationId[0x8];
u8 LogoType;
u8 LogoHandling;
u8 RuntimeAddOnContentInstall;
u8 _0x30F3[0x3];
u8 CrashReport;
u8 Hdcp;
u64 SeedForPseudoDeviceId;
char BcatPassphrase[0x41];
u8 StartupUserAccountOptionFlag;
u8 ReservedForUserAccountSaveDataOperation[0x6];
u64 UserAccountSaveDataSizeMax;
u64 UserAccountSaveDataJournalSizeMax;
u64 DeviceSaveDataSizeMax;
u64 DeviceSaveDataJournalSizeMax;
u64 TemporaryStorageSize;
u64 CacheStorageSize;
u64 CacheStorageJournalSize;
u64 CacheStorageDataAndJournalSizeMax;
u16 CacheStorageIndexMax;
u8 reserved_0x318a[0x6];
u64 PlayLogQueryableApplicationId[0x10];
u8 PlayLogQueryCapability;
u8 RepairFlag;
u8 ProgramIndex;
u8 RequiredNetworkServiceLicenseOnLaunchFlag;
u8 Reserved[0xDEC];
} PACKED nacp_t;
typedef struct {
bool has_rights_id;
u8 rights_id[0x10];
char rights_id_str[33];
char tik_filename[37];
char cert_filename[38];
} PACKED title_rights_ctx;
typedef struct {
NcmContentStorage ncmStorage;
NcmNcaId ncaId;
Aes128CtrContext aes_ctx;
u64 romfs_offset; // Relative to NCA start
u64 romfs_size;
u64 romfs_dirtable_offset; // Relative to NCA start
u64 romfs_dirtable_size;
romfs_dir *romfs_dir_entries;
u64 romfs_filetable_offset; // Relative to NCA start
u64 romfs_filetable_size;
romfs_file *romfs_file_entries;
u64 romfs_filedata_offset; // Relative to NCA start
} PACKED romfs_ctx_t;
typedef struct {
u8 type; // 1 = Dir, 2 = File
u64 offset; // Relative to directory/file table, depending on type
} PACKED romfs_browser_entry;
void generateCnmtXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, char *out);
void convertNcaSizeToU64(const u8 size[0x6], u64 *out);
void convertU64ToNcaSize(const u64 size, u8 out[0x6]);
bool processNcaCtrSectionBlock(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, u64 offset, void *outBuf, size_t bufSize, Aes128CtrContext *ctx, bool encrypt);
bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize);
bool decryptNcaHeader(const u8 *ncaBuf, u64 ncaBufSize, nca_header_t *out, title_rights_ctx *rights_info, u8 *decrypted_nca_keys);
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(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 patchCnmtNca(u8 *ncaBuf, u64 ncaBufSize, cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, nca_cnmt_mod_data *cnmt_mod);
bool readRomFsEntriesFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys);
bool generateNacpXmlFromNca(NcmContentStorage *ncmStorage, const NcmNcaId *ncaId, nca_header_t *dec_nca_header, u8 *decrypted_nca_keys, char **outBuf);
#endif

83
source/rsa.c Normal file
View file

@ -0,0 +1,83 @@
#include <stdio.h>
#include <string.h>
#include <mbedtls/entropy.h>
#include <mbedtls/ctr_drbg.h>
#include <mbedtls/md.h>
#include <mbedtls/rsa.h>
#include <mbedtls/x509.h>
#include "rsa.h"
#include "rsa_keys.h"
#include "ui.h"
#include "util.h"
/* Extern variables */
extern int breaks;
extern int font_height;
extern char strbuf[NAME_BUF_LEN * 4];
bool rsa_sign(void* input, size_t input_size, unsigned char* output, size_t output_size)
{
unsigned char hash[32];
unsigned char buf[MBEDTLS_MPI_MAX_SIZE];
const char *pers = "rsa_sign_pss";
size_t olen = 0;
int ret;
bool success = false;
mbedtls_pk_context pk;
mbedtls_entropy_context entropy;
mbedtls_ctr_drbg_context ctr_drbg;
mbedtls_entropy_init(&entropy);
mbedtls_pk_init(&pk);
mbedtls_ctr_drbg_init(&ctr_drbg);
// Calculate SHA-256 checksum for the input data
sha256CalculateHash(hash, input, input_size);
// Seed the random number generator
ret = mbedtls_ctr_drbg_seed(&ctr_drbg, mbedtls_entropy_func, &entropy, (const unsigned char *)pers, strlen(pers));
if (ret == 0)
{
// Parse private key
ret = mbedtls_pk_parse_key(&pk, (unsigned char*)rsa_private_key, strlen(rsa_private_key) + 1, NULL, 0);
if (ret == 0)
{
// Set RSA padding
mbedtls_rsa_set_padding(mbedtls_pk_rsa(pk), MBEDTLS_RSA_PKCS_V21, MBEDTLS_MD_SHA256);
// Calculate hash signature
ret = mbedtls_pk_sign(&pk, MBEDTLS_MD_SHA256, hash, 0, buf, &olen, mbedtls_ctr_drbg_random, &ctr_drbg);
if (ret == 0)
{
// Copy signature to output
memcpy(output, buf, output_size);
success = true;
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "rsa_sign: mbedtls_pk_sign failed! (%d)", ret);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "rsa_sign: mbedtls_pk_parse_key failed! (%d)", ret);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
}
} else {
snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "rsa_sign: mbedtls_ctr_drbg_seed failed! (%d)", ret);
uiDrawString(strbuf, 8, (breaks * (font_height + (font_height / 4))) + (font_height / 8), 255, 0, 0);
}
mbedtls_ctr_drbg_free(&ctr_drbg);
mbedtls_pk_free(&pk);
mbedtls_entropy_free(&entropy);
return success;
}
const unsigned char *rsa_get_public_key()
{
return rsa_public_key;
}

11
source/rsa.h Normal file
View file

@ -0,0 +1,11 @@
#pragma once
#ifndef __RSA_H__
#define __RSA_H__
#include <switch.h>
bool rsa_sign(void* input, size_t input_size, unsigned char* output, size_t output_size);
const unsigned char *rsa_get_public_key();
#endif

55
source/rsa_keys.h Normal file
View file

@ -0,0 +1,55 @@
#pragma once
#ifndef __RSA_KEYS_H__
#define __RSA_KEYS_H__
// Self-generated private key
const char rsa_private_key[] = "-----BEGIN RSA PRIVATE KEY-----\r\n"
"MIIEowIBAAKCAQEAvVRzt+8mE7oE4RkmSh3ws4CGlBj7uhHkfwCpPFsn4TNVdLRo\r\n"
"YYY17jQYWTtcOYPMcHxwUpgJyspGN8QGXEkJqY8jILv2eO0jBGtg7Br2afUBp6/x\r\n"
"BOMT2RlYVX6H4a1UA19Hzmcn+T1hdDwS6oBYpi8rJSm0+q+yB34dueNkVsk4eKbj\r\n"
"CNNKFi+XgyNBi41d57SPCrkcm/9tkagRorE8vLcFPcXcYOjdXH3L4XTXq7sxxytA\r\n"
"I66erfSc4XunkoLifcbfMOB3gjGCoQs6GfaiAU3TwxewQ7hdoqvj5Gm9VyHqzeDF\r\n"
"5mUTlmed2I6m4ELxbV1b0lUguR5ZEzwXwiVWxwIDAQABAoIBADvLYkijFOmCBGx7\r\n"
"HualkhF+9AHt6gKYCAw8Tzaqq2uqZMDZAWZblsjGVzJHVxcrEvQruOW88srDG24d\r\n"
"UMzwnEaa2ENMWclTS43nw9KNqWlJYd5t6LbcaLZWFNnbflq9/RybiPgdCDjlM9Qb\r\n"
"7PV214iUuRGhnHDX8GgBYq4ErPnjQ7+Gv1ducpMYjZencLWCl4fFX86U0/MU0+Qf\r\n"
"jKGegQTnk52aaeScbDOjjx5h+m0hkDNSfsmXTlvJt2c8wy/Yx+leVgCPjMC1nbft\r\n"
"Ob1TlpjuEAKBOGt4+DkWwVmIlxilmx9wCTZnwvPKd7A0e0FGsdHnQienPrMqlgbl\r\n"
"JPYwJuECgYEA6yLZHTfX3ebpzcdQQqmuHZtbOcs+EGRy24gAzd+9vCGKf0VtKSl9\r\n"
"3oA3XBOe2C2TgSgbWFZ7v/2efWRjgwJta0BQlpkzkh6NUQa2LI2M3zgZwHCZ7Ihr\r\n"
"skG73qZsMHOOv7VQz/wDp6AZNasfz21Mcyh4uFzpkb3NKLXqsJ9LeG8CgYEAziEb\r\n"
"yBCuhCKq7YZt/cHlbCbi7HbCYbub0isOCUtV0qPsX+kVZdPS+oGLPq1905JKdAe9\r\n"
"O+4SltCw6qn9RgYnCCVQ47SGHg7KO8Z5vdcNUiDvsQ+jNFlmM5QBuf1UV/Y+DV/Q\r\n"
"fZdA06OeYxkfPuBMtjdS9qMKwm3OsCkiQasWQykCgYAqALieAoq6JfSgALmyntLu\r\n"
"kQDzyv2UOg1Wb+4M2KnxAGDYKVO9pZ7Jb0f0V8DpRwLxcHOqDRDgE/MK3TL1hSp8\r\n"
"nSmILWfL8081KSjDvqlqeoAHI1YrrZbnadyggkQTR6E5V69O5+rTN8MpFh+Bkzmz\r\n"
"3IfsDxTeJvSOECkTUfFOWwKBgQDG/id3yMLxRRaGH5TnuNvmwNOpPC0DdL5E8tOm\r\n"
"HVhI9X8oSDgkCY5Pz+fBJnOmYEAIK8B/rqG7ftSMdnbPtvjPYFbqvEgNlHGfq0e0\r\n"
"AXwWoT1ETbhcvUFw4Z2ZE/rswAe/mZQI6o/mwLoTKRmE9byY3Gf3OgcVFDTI060C\r\n"
"gEwJoQKBgHpOmtGum3JuLpPc+PTXZOe29tdWndkFWktjPoow60d+NO2jpTFuEpmW\r\n"
"XRW35vXI8PqMCmHOQ8YU59aMN9juAnsJmPUxbAW5fZfvVwWUo0cTOenfT6syrEYO\r\n"
"n5NEG+mY4WZaOFRNiZu8+4aJI1yycXMyA22iKcU8+nN/sMAJs3Nx\r\n"
"-----END RSA PRIVATE KEY-----\r\n";
// Self-generated public key
const unsigned char rsa_public_key[] = {
0xbd, 0x54, 0x73, 0xb7, 0xef, 0x26, 0x13, 0xba, 0x04, 0xe1, 0x19, 0x26, 0x4a, 0x1d, 0xf0, 0xb3,
0x80, 0x86, 0x94, 0x18, 0xfb, 0xba, 0x11, 0xe4, 0x7f, 0x00, 0xa9, 0x3c, 0x5b, 0x27, 0xe1, 0x33,
0x55, 0x74, 0xb4, 0x68, 0x61, 0x86, 0x35, 0xee, 0x34, 0x18, 0x59, 0x3b, 0x5c, 0x39, 0x83, 0xcc,
0x70, 0x7c, 0x70, 0x52, 0x98, 0x09, 0xca, 0xca, 0x46, 0x37, 0xc4, 0x06, 0x5c, 0x49, 0x09, 0xa9,
0x8f, 0x23, 0x20, 0xbb, 0xf6, 0x78, 0xed, 0x23, 0x04, 0x6b, 0x60, 0xec, 0x1a, 0xf6, 0x69, 0xf5,
0x01, 0xa7, 0xaf, 0xf1, 0x04, 0xe3, 0x13, 0xd9, 0x19, 0x58, 0x55, 0x7e, 0x87, 0xe1, 0xad, 0x54,
0x03, 0x5f, 0x47, 0xce, 0x67, 0x27, 0xf9, 0x3d, 0x61, 0x74, 0x3c, 0x12, 0xea, 0x80, 0x58, 0xa6,
0x2f, 0x2b, 0x25, 0x29, 0xb4, 0xfa, 0xaf, 0xb2, 0x07, 0x7e, 0x1d, 0xb9, 0xe3, 0x64, 0x56, 0xc9,
0x38, 0x78, 0xa6, 0xe3, 0x08, 0xd3, 0x4a, 0x16, 0x2f, 0x97, 0x83, 0x23, 0x41, 0x8b, 0x8d, 0x5d,
0xe7, 0xb4, 0x8f, 0x0a, 0xb9, 0x1c, 0x9b, 0xff, 0x6d, 0x91, 0xa8, 0x11, 0xa2, 0xb1, 0x3c, 0xbc,
0xb7, 0x05, 0x3d, 0xc5, 0xdc, 0x60, 0xe8, 0xdd, 0x5c, 0x7d, 0xcb, 0xe1, 0x74, 0xd7, 0xab, 0xbb,
0x31, 0xc7, 0x2b, 0x40, 0x23, 0xae, 0x9e, 0xad, 0xf4, 0x9c, 0xe1, 0x7b, 0xa7, 0x92, 0x82, 0xe2,
0x7d, 0xc6, 0xdf, 0x30, 0xe0, 0x77, 0x82, 0x31, 0x82, 0xa1, 0x0b, 0x3a, 0x19, 0xf6, 0xa2, 0x01,
0x4d, 0xd3, 0xc3, 0x17, 0xb0, 0x43, 0xb8, 0x5d, 0xa2, 0xab, 0xe3, 0xe4, 0x69, 0xbd, 0x57, 0x21,
0xea, 0xcd, 0xe0, 0xc5, 0xe6, 0x65, 0x13, 0x96, 0x67, 0x9d, 0xd8, 0x8e, 0xa6, 0xe0, 0x42, 0xf1,
0x6d, 0x5d, 0x5b, 0xd2, 0x55, 0x20, 0xb9, 0x1e, 0x59, 0x13, 0x3c, 0x17, 0xc2, 0x25, 0x56, 0xc7
};
#endif

File diff suppressed because it is too large Load diff

View file

@ -19,26 +19,57 @@
#define HIGHLIGHT_FONT_COLOR_G 255
#define HIGHLIGHT_FONT_COLOR_B 197
#define OPTIONS_X_POS (35 * CHAR_PT_SIZE)
#define COMMON_MAX_ELEMENTS 8
#define HFS0_MAX_ELEMENTS 14
#define ROMFS_MAX_ELEMENTS 12
#define OPTIONS_X_POS (35 * CHAR_PT_SIZE)
#define TAB_WIDTH 4
#define BROWSER_ICON_DIMENSION 16
#define NINTENDO_FONT_A "\xE0\xA0"
#define NINTENDO_FONT_B "\xE0\xA1"
#define NINTENDO_FONT_L "\xE0\xA4"
#define NINTENDO_FONT_R "\xE0\xA5"
#define NINTENDO_FONT_ZL "\xE0\xA6"
#define NINTENDO_FONT_ZR "\xE0\xA7"
#define NINTENDO_FONT_DPAD "\xE0\xAA"
#define NINTENDO_FONT_PLUS "\xE0\xB5"
#define NINTENDO_FONT_HOME "\xE0\xB9"
#define NINTENDO_FONT_LSTICK "\xE0\xC1"
#define NINTENDO_FONT_RSTICK "\xE0\xC2"
typedef enum {
resultNone,
resultShowMainMenu,
resultShowXciDumpMenu,
resultDumpXci,
resultShowNspDumpMenu,
resultShowNspAppDumpMenu,
resultShowNspPatchDumpMenu,
resultShowNspAddOnDumpMenu,
resultDumpNsp,
resultShowRawPartitionDumpMenu,
resultDumpRawPartition,
resultShowPartitionDataDumpMenu,
resultDumpPartitionData,
resultShowViewGameCardFsMenu,
resultShowViewGameCardFsGetList,
resultShowViewGameCardFsBrowser,
resultViewGameCardFsBrowserCopyFile,
resultShowHfs0Menu,
resultShowRawHfs0PartitionDumpMenu,
resultDumpRawHfs0Partition,
resultShowHfs0PartitionDataDumpMenu,
resultDumpHfs0PartitionData,
resultShowHfs0BrowserMenu,
resultHfs0BrowserGetList,
resultShowHfs0Browser,
resultHfs0BrowserCopyFile,
resultShowRomFsMenu,
resultShowRomFsSectionDataDumpMenu,
resultDumpRomFsSectionData,
resultShowRomFsSectionBrowserMenu,
resultRomFsSectionBrowserGetEntries,
resultShowRomFsSectionBrowser,
resultRomFsSectionBrowserChangeDir,
resultRomFsSectionBrowserCopyFile,
resultDumpGameCardCertificate,
resultShowUpdateMenu,
resultUpdateNSWDBXml,
resultUpdateApplication,
resultExit
@ -49,22 +80,41 @@ typedef enum {
stateXciDumpMenu,
stateDumpXci,
stateNspDumpMenu,
stateNspAppDumpMenu,
stateNspPatchDumpMenu,
stateNspAddOnDumpMenu,
stateDumpNsp,
stateRawPartitionDumpMenu,
stateDumpRawPartition,
statePartitionDataDumpMenu,
stateDumpPartitionData,
stateViewGameCardFsMenu,
stateViewGameCardFsGetList,
stateViewGameCardFsBrowser,
stateViewGameCardFsBrowserCopyFile,
stateHfs0Menu,
stateRawHfs0PartitionDumpMenu,
stateDumpRawHfs0Partition,
stateHfs0PartitionDataDumpMenu,
stateDumpHfs0PartitionData,
stateHfs0BrowserMenu,
stateHfs0BrowserGetList,
stateHfs0Browser,
stateHfs0BrowserCopyFile,
stateRomFsMenu,
stateRomFsSectionDataDumpMenu,
stateDumpRomFsSectionData,
stateRomFsSectionBrowserMenu,
stateRomFsSectionBrowserGetEntries,
stateRomFsSectionBrowser,
stateRomFsSectionBrowserChangeDir,
stateRomFsSectionBrowserCopyFile,
stateDumpGameCardCertificate,
stateUpdateMenu,
stateUpdateNSWDBXml,
stateUpdateApplication
} UIState;
void uiFill(int x, int y, int width, int height, u8 r, u8 g, u8 b);
void uiDrawIcon(const u8 *icon, int width, int height, int x, int y);
bool uiLoadJpgFromMem(u8 *rawJpg, size_t rawJpgSize, int expectedWidth, int expectedHeight, int desiredWidth, int desiredHeight, u8 **outBuf);
bool uiLoadJpgFromFile(const char *filename, int expectedWidth, int expectedHeight, int desiredWidth, int desiredHeight, u8 **outBuf);
void uiDrawString(const char *string, int x, int y, u8 r, u8 g, u8 b);
void uiRefreshDisplay();
@ -81,10 +131,10 @@ void uiClearScreen();
void uiPrintHeadline();
int uiInit();
void uiDeinit();
int uiInit();
void uiSetState(UIState state);
UIState uiGetState();

File diff suppressed because it is too large Load diff

View file

@ -4,8 +4,7 @@
#define __UTIL_H__
#include <switch.h>
#define APP_VERSION "1.0.8"
#include "nca.h"
#define KiB (1024.0)
#define MiB (1024.0 * KiB)
@ -16,15 +15,20 @@
#define SOCK_BUFFERSIZE 65536
#define META_DB_REGULAR_APPLICATION 0x80
#define META_DB_PATCH 0x81
#define META_DB_ADDON 0x82
#define FILENAME_BUFFER_SIZE (1024 * 512) // 512 KiB
#define FILENAME_MAX_CNT 2048
#define APPLICATION_PATCH_BITMASK (u64)0x800
#define APPLICATION_ADDON_BITMASK (u64)0xFFFFFFFFFFFF0000
#define FILENAME_MAX_CNT 20000
#define FILENAME_BUFFER_SIZE (512 * FILENAME_MAX_CNT) // 10000 KiB
#define NACP_APPNAME_LEN 0x200
#define NACP_AUTHOR_LEN 0x100
#define VERSION_STR_LEN 0x40
#define GAMECARD_WAIT_TIME 3 // 3 seconds
#define GAMECARD_WAIT_TIME 3 // 3 seconds
#define GAMECARD_HEADER_SIZE 0x200
#define GAMECARD_SIZE_ADDR 0x10D
@ -32,40 +36,15 @@
#define HFS0_OFFSET_ADDR 0x130
#define HFS0_SIZE_ADDR 0x138
#define HFS0_MAGIC 0x48465330 // "HFS0"
#define HFS0_MAGIC (u32)0x48465330 // "HFS0"
#define HFS0_FILE_COUNT_ADDR 0x04
#define HFS0_STR_TABLE_SIZE_ADDR 0x08
#define HFS0_ENTRY_TABLE_ADDR 0x10
#define PFS0_MAGIC 0x50465330 // "PFS0"
#define MEDIA_UNIT_SIZE 0x200
#define NCA3_MAGIC 0x4E434133 // "NCA3"
#define NCA2_MAGIC 0x4E434132 // "NCA2"
#define NCA_HEADER_LENGTH 0x400
#define NCA_SECTION_HEADER_LENGTH 0x200
#define NCA_SECTION_HEADER_CNT 4
#define NCA_FULL_HEADER_LENGTH (NCA_HEADER_LENGTH + (NCA_SECTION_HEADER_LENGTH * NCA_SECTION_HEADER_CNT))
#define NCA_AES_XTS_SECTOR_SIZE 0x200
#define NCA_KEY_AREA_KEY_CNT 4
#define NCA_KEA_AREA_KEY_SIZE 0x10
#define NCA_KEY_AREA_SIZE (NCA_KEY_AREA_KEY_CNT * NCA_KEA_AREA_KEY_SIZE)
#define NCA_FS_HEADER_PARTITION_PFS0 0x01
#define NCA_FS_HEADER_FSTYPE_PFS0 0x02
#define NCA_FS_HEADER_CRYPT_NONE 0x01
#define NCA_FS_HEADER_CRYPT_XTS 0x02
#define NCA_FS_HEADER_CRYPT_CTR 0x03
#define NCA_FS_HEADER_CRYPT_BKTR 0x04
#define NCA_CNMT_DIGEST_SIZE 0x20
#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "update" (2)
#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "update" (3)
#define GAMECARD_TYPE1_PARTITION_CNT 3 // "update" (0), "normal" (1), "update" (2)
#define GAMECARD_TYPE2_PARTITION_CNT 4 // "update" (0), "logo" (1), "normal" (2), "update" (3)
#define GAMECARD_TYPE(x) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? "Type 0x01" : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? "Type 0x02" : "Unknown"))
#define GAMECARD_TYPE1_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Normal" : ((x) == 2 ? "Secure" : "Unknown")))
#define GAMECARD_TYPE2_PART_NAMES(x) ((x) == 0 ? "Update" : ((x) == 1 ? "Logo" : ((x) == 2 ? "Normal" : ((x) == 3 ? "Secure" : "Unknown"))))
@ -106,11 +85,13 @@
#define SYSUPDATE_701 (u32)469827614
#define SYSUPDATE_800 (u32)536871442
#define NACP_ICON_SQUARE_DIMENSION 256
#define NACP_ICON_DOWNSCALED 96
#define bswap_32(a) ((((a) << 24) & 0xff000000) | (((a) << 8) & 0xff0000) | (((a) >> 8) & 0xff00) | (((a) >> 24) & 0xff))
#define round_up(x, y) ((x) + (((y) - ((x) % (y))) % (y))) // Aligns 'x' bytes to a 'y' bytes boundary
typedef struct
{
typedef struct {
u64 file_offset;
u64 file_size;
u32 filename_offset;
@ -119,136 +100,20 @@ typedef struct
u8 hashed_region_sha256[0x20];
} PACKED hfs0_entry_table;
typedef struct
{
u32 magic;
u32 file_cnt;
u32 str_table_size;
u32 reserved;
} PACKED pfs0_header;
typedef struct
{
u64 file_offset;
u64 file_size;
u32 filename_offset;
u32 reserved;
} PACKED pfs0_entry_table;
typedef struct {
u32 media_start_offset;
u32 media_end_offset;
u8 _0x8[0x8]; /* Padding. */
} PACKED nca_section_entry_t;
typedef struct {
u8 master_hash[0x20]; /* SHA-256 hash of the hash table. */
u32 block_size; /* In bytes. */
u32 always_2;
u64 hash_table_offset; /* Normally zero. */
u64 hash_table_size;
u64 pfs0_offset;
u64 pfs0_size;
u8 _0x48[0xF0];
} PACKED pfs0_superblock_t;
/* NCA FS header. */
typedef struct {
u8 _0x0;
u8 _0x1;
u8 partition_type;
u8 fs_type;
u8 crypt_type;
u8 _0x5[0x3];
pfs0_superblock_t pfs0_superblock; /* FS-specific superblock. Size = 0x138. */
union {
u8 section_ctr[0x8];
struct {
u32 section_ctr_low;
u32 section_ctr_high;
};
};
u8 _0x148[0xB8]; /* Padding. */
} PACKED nca_fs_header_t;
/* Nintendo content archive header. */
typedef struct {
u8 fixed_key_sig[0x100]; /* RSA-PSS signature over header with fixed key. */
u8 npdm_key_sig[0x100]; /* RSA-PSS signature over header with key in NPDM. */
u32 magic;
u8 distribution; /* System vs gamecard. */
u8 content_type;
u8 crypto_type; /* Which keyblob (field 1) */
u8 kaek_ind; /* Which kaek index? */
u64 nca_size; /* Entire archive size. */
u64 title_id;
u8 _0x218[0x4]; /* Padding. */
union {
u32 sdk_version; /* What SDK was this built with? */
struct {
u8 sdk_revision;
u8 sdk_micro;
u8 sdk_minor;
u8 sdk_major;
};
};
u8 crypto_type2; /* Which keyblob (field 2) */
u8 _0x221[0xF]; /* Padding. */
u8 rights_id[0x10]; /* Rights ID (for titlekey crypto). */
nca_section_entry_t section_entries[4]; /* Section entry metadata. */
u8 section_hashes[4][0x20]; /* SHA-256 hashes for each section header. */
u8 nca_keys[4][0x10]; /* Key area (encrypted, but later decrypted by decryptNcaHeader()) */
u8 _0x340[0xC0]; /* Padding. */
nca_fs_header_t fs_headers[4]; /* FS section headers. */
} PACKED nca_header_t;
typedef struct {
u64 title_id;
u32 version;
u8 type;
u8 unk1;
u16 table_offset;
u16 content_records_cnt;
u16 meta_records_cnt;
u8 unk2[12];
} PACKED cnmt_header;
typedef struct {
u64 patch_tid;
u64 min_sysver;
} PACKED cnmt_application_header;
typedef struct {
u8 hash[0x20];
u8 nca_id[0x10];
u8 size[6];
u8 type;
u8 unk;
} PACKED cnmt_content_record;
typedef struct {
u8 type;
u64 title_id;
u32 version;
u32 required_dl_sysver;
u32 nca_cnt;
u8 digest[32];
char digest_str[65];
u8 min_keyblob;
u32 min_sysver;
u64 patch_tid;
} PACKED cnmt_xml_program_info;
typedef struct {
u8 type;
u8 nca_id[16];
char nca_id_str[33];
u64 size;
u8 hash[32];
char hash_str[65];
u8 keyblob;
u8 encrypted_header_mod[NCA_FULL_HEADER_LENGTH];
} PACKED cnmt_xml_content_info;
int line_offset;
u64 totalSize;
char totalSizeStr[32];
u64 curOffset;
char curOffsetStr[32];
u8 progress;
u64 start;
u64 now;
u64 remainingTime;
char etaInfo[32];
double lastSpeed;
double averageSpeed;
} PACKED progress_ctx_t;
bool isGameCardInserted();
@ -256,7 +121,9 @@ void fsGameCardDetectionThreadFunc(void *arg);
void delay(u8 seconds);
void convertTitleVersionToDecimal(u32 version, char *versionBuf, int versionBufSize);
void formatETAString(u64 curTime, char *output, u32 outSize);
void convertTitleVersionToDecimal(u32 version, char *versionBuf, size_t versionBufSize);
void removeIllegalCharacters(char *name);
@ -264,38 +131,46 @@ void strtrim(char *str);
void freeStringsPtr(char **var);
void initRomFsContext();
void freeRomFsContext();
void freeGameCardInfo();
void loadGameCardInfo();
bool getHfs0EntryDetails(char *hfs0Header, u64 hfs0HeaderOffset, u64 hfs0HeaderSize, u32 num_entries, u32 entry_idx, bool isRoot, u32 partitionIndex, u64 *out_offset, u64 *out_size);
bool getHfs0EntryDetails(u8 *hfs0Header, u64 hfs0HeaderOffset, u64 hfs0HeaderSize, u32 num_entries, u32 entry_idx, bool isRoot, u32 partitionIndex, u64 *out_offset, u64 *out_size);
bool getPartitionHfs0Header(u32 partition);
bool getHfs0FileList(u32 partition);
u8 *getPartitionHfs0FileByName(FsStorage *gameCardStorage, const char *filename, u64 *outSize);
bool calculateRomFsExtractedDataSize(u64 *out);
bool readProgramNcaRomFs(u32 appIndex);
bool getRomFsFileList(u32 dir_offset);
int getSdCardFreeSpace(u64 *out);
void convertSize(u64 size, char *out, int bufsize);
char *generateDumpName();
char *generateDumpFullName();
char *generateNSPDumpName(nspDumpType selectedNspDumpType, u32 titleIndex);
void retrieveDescriptionForPatchOrAddOn(u64 titleID, u32 version, bool addOn, const char *prefix);
void waitForButtonPress();
void printProgressBar(progress_ctx_t *progressCtx, bool calcData, u64 chunkSize);
void setProgressBarError(progress_ctx_t *progressCtx);
void convertDataToHexString(const u8 *data, const u32 dataSize, char *outBuf, const u32 outBufSize);
void convertNcaSizeToU64(const u8 size[0x6], u64 *out);
bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize);
bool decryptNcaHeader(const char *ncaBuf, u64 ncaBufSize, nca_header_t *out);
bool decryptCnmtNca(char *ncaBuf, u64 ncaBufSize);
bool calculateSHA256(const u8 *data, const u32 dataSize, u8 out[32]);
void generateCnmtMetadataXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, char *out);
void addStringToFilenameBuffer(const char *string, char **nextFilename);
void removeDirectory(const char *path);