diff --git a/Makefile b/Makefile index 5d3e05bc..e0ebc008 100644 --- a/Makefile +++ b/Makefile @@ -33,7 +33,7 @@ include $(DEVKITPRO)/libnx/switch_rules VERSION_MAJOR := 1 VERSION_MINOR := 0 -VERSION_MICRO := 7 +VERSION_MICRO := 8 APP_TITLE := gcdumptool APP_AUTHOR := MCMrARM, DarkMatterCore diff --git a/README.md b/README.md index 97c84513..e55ed5fb 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,11 @@ Nintendo Switch Game Card Dump Tool Main features -------------- -* Generates XCI cartridge dumps with optional certificate removal and optional trimming. -* CRC32 checksum calculation for XCI dumps. +* 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. +* 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. @@ -14,7 +17,7 @@ Main features * Manual game card certificate dump. * Free SD card space checks in place. * File splitting support for all operations, using 2 GiB parts. -* Game card Title ID and Control.nacp retrieval support using NCM and NS services. +* Game card metadata retrieval using NCM and NS services. * Dump speed, ETA calculation and progress bar. Thanks to @@ -24,6 +27,7 @@ Thanks to * RSDuck, for their 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. * Björn Samuelsson, for his public domain CRC32 checksum calculation code for C (crc32_fast.c). * AnalogMan, for his constant support and ideas. * The folks from ReSwitched, for working towards the creation of a good homebrew ecosystem. @@ -31,6 +35,19 @@ Thanks to Changelog -------------- +**v1.0.8:** + +* Added proper metadata reading from multigame carts. +* Added gamecard -> NSP dump option: + - Compatible with file splitting (for FAT32 support). The same layout from splitNSP.py is used: a directory with numbered part files (00, 01, etc.). The archive bit is enabled right away in this directory to allow HOS to treat it as if it were a whole file. This way, it can be used with any application with NSP-handling capabilities. + - Compatible with CRC32 checksum calculation. Disclaimer: NSP dumps can't be verified against the XML database. + - Output NSPs contain a metadata XML file based on the information from the CNMT NCA for the application, which is decrypted using code from hactool. The necessary keyset is loaded from "sdmc:/switch/prod.keys", which can be generated using Lockpick. + - If a multigame cart is used, you'll be able to choose which application to dump from the menu. +* Dump verification process tweaked for multigame carts: it'll now look for a possible checksum match using the Title IDs from all bundled applications. +* Improved error reporting in dumper.c when a write operation fails. Furthermore, if a write error is produced when trying to write data to an offset past the FAT32 file size limit (0xFFFFFFFF bytes), the application will suggest the user to enable the file splitting option. +* Tweaked part sizes for splitted dumps: XCI/raw partition/manual file dump part size now matches the one used by XCI-Cutter, while the NSP part size matches the one used by splitNSP.py. +* Minor fixes to the UI code. + **v1.0.7:** * Fixed a segmentation fault when trying to free an invalid XML node data pointer when a Scene release from NSWReleases.xml with a matching Title ID misses data related to that node. diff --git a/source/aes.c b/source/aes.c new file mode 100644 index 00000000..42edecf5 --- /dev/null +++ b/source/aes.c @@ -0,0 +1,325 @@ +#include +#include +#include + +#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; +} diff --git a/source/aes.h b/source/aes.h new file mode 100644 index 00000000..ee6e0d1a --- /dev/null +++ b/source/aes.h @@ -0,0 +1,42 @@ +#pragma once + +#ifndef __AES_H__ +#define __AES_H__ + +#include +#include +#include + +/* 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 diff --git a/source/dumper.c b/source/dumper.c index 80f7f1c3..6dbc4ace 100644 --- a/source/dumper.c +++ b/source/dumper.c @@ -6,6 +6,7 @@ #include #include #include +#include #include "crc32_fast.h" #include "dumper.h" @@ -20,8 +21,8 @@ extern u64 freeSpace; extern int breaks; extern int font_height; -extern u64 gameCardSize, trimmedCardSize; -extern char gameCardSizeStr[32], trimmedCardSizeStr[32]; +extern u64 trimmedCardSize; +extern char trimmedCardSizeStr[32]; extern char *hfs0_header; extern u64 hfs0_offset, hfs0_size; @@ -31,22 +32,16 @@ extern char *partitionHfs0Header; extern u64 partitionHfs0HeaderOffset, partitionHfs0HeaderSize; extern u32 partitionHfs0FileCount, partitionHfs0StrTableSize; -extern u64 gameCardTitleID; -extern u32 gameCardVersion; -extern char fixedGameCardName[0x201]; - -extern u64 gameCardUpdateTitleID; -extern u32 gameCardUpdateVersion; -extern char gameCardUpdateVersionStr[128]; - -extern char *filenameBuffer; -extern int filenamesCount; +extern u32 gameCardAppCount; +extern u64 *gameCardTitleID; +extern u32 *gameCardVersion; +extern char **fixedGameCardName; extern AppletType programAppletType; -/* Statically allocated variables */ +extern char strbuf[NAME_BUF_LEN * 4]; -static char strbuf[NAME_BUF_LEN * 2] = {'\0'}; +/* Statically allocated variables */ void workaroundPartitionZeroAccess(FsDeviceOperator* fsOperator) { @@ -59,302 +54,16 @@ void workaroundPartitionZeroAccess(FsDeviceOperator* fsOperator) fsStorageClose(&gameCardStorage); } -bool getRootHfs0Header(FsDeviceOperator* fsOperator) -{ - u32 magic; - Result result; - FsGameCardHandle handle; - FsStorage gameCardStorage; - - hfs0_partition_cnt = 0; - - workaroundPartitionZeroAccess(fsOperator); - - if (R_FAILED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) - { - uiStatusMsg("getRootHfs0Header: GetGameCardHandle failed! (0x%08X)", result); - return false; - } - - // Get bundled FW version update - if (R_SUCCEEDED(fsDeviceOperatorUpdatePartitionInfo(fsOperator, &handle, &gameCardUpdateVersion, &gameCardUpdateTitleID))) - { - if (gameCardUpdateTitleID == GAMECARD_UPDATE_TITLEID) - { - char decimalVersion[64] = {'\0'}; - convertTitleVersionToDecimal(gameCardUpdateVersion, decimalVersion, sizeof(decimalVersion)); - - switch(gameCardUpdateVersion) - { - case SYSUPDATE_100: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "1.0.0 - v%s", decimalVersion); - break; - case SYSUPDATE_200: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.0.0 - v%s", decimalVersion); - break; - case SYSUPDATE_210: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.1.0 - v%s", decimalVersion); - break; - case SYSUPDATE_220: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.2.0 - v%s", decimalVersion); - break; - case SYSUPDATE_230: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.3.0 - v%s", decimalVersion); - break; - case SYSUPDATE_300: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.0 - v%s", decimalVersion); - break; - case SYSUPDATE_301: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.1 - v%s", decimalVersion); - break; - case SYSUPDATE_302: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.2 - v%s", decimalVersion); - break; - case SYSUPDATE_400: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.0.0 - v%s", decimalVersion); - break; - case SYSUPDATE_401: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.0.1 - v%s", decimalVersion); - break; - case SYSUPDATE_410: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.1.0 - v%s", decimalVersion); - break; - case SYSUPDATE_500: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.0 - v%s", decimalVersion); - break; - case SYSUPDATE_501: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.1 - v%s", decimalVersion); - break; - case SYSUPDATE_502: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.2 - v%s", decimalVersion); - break; - case SYSUPDATE_510: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.1.0 - v%s", decimalVersion); - break; - case SYSUPDATE_600: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.0.0 - v%s", decimalVersion); - break; - case SYSUPDATE_601: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.0.1 - v%s", decimalVersion); - break; - case SYSUPDATE_610: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.1.0 - v%s", decimalVersion); - break; - case SYSUPDATE_620: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.2.0 - v%s", decimalVersion); - break; - case SYSUPDATE_700: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "7.0.0 - v%s", decimalVersion); - break; - case SYSUPDATE_701: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "7.0.1 - v%s", decimalVersion); - break; - case SYSUPDATE_800: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "8.0.0 - v%s", decimalVersion); - break; - default: - snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "UNKNOWN - v%s", decimalVersion); - break; - } - } else { - gameCardUpdateTitleID = 0; - gameCardUpdateVersion = 0; - } - } - - if (R_FAILED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, 0))) - { - uiStatusMsg("getRootHfs0Header: OpenGameCardStorage failed! (0x%08X)", result); - return false; - } - - char *gamecard_header = (char*)malloc(GAMECARD_HEADER_SIZE); - if (!gamecard_header) - { - uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the gamecard header!"); - fsStorageClose(&gameCardStorage); - return false; - } - - if (R_FAILED(result = fsStorageRead(&gameCardStorage, 0, gamecard_header, GAMECARD_HEADER_SIZE))) - { - uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", GAMECARD_HEADER_SIZE, 0, result); - free(gamecard_header); - fsStorageClose(&gameCardStorage); - return false; - } - - u8 cardSize = (u8)gamecard_header[GAMECARD_SIZE_ADDR]; - - switch(cardSize) - { - case 0xFA: // 1 GiB - gameCardSize = GAMECARD_SIZE_1GiB; - break; - case 0xF8: // 2 GiB - gameCardSize = GAMECARD_SIZE_2GiB; - break; - case 0xF0: // 4 GiB - gameCardSize = GAMECARD_SIZE_4GiB; - break; - case 0xE0: // 8 GiB - gameCardSize = GAMECARD_SIZE_8GiB; - break; - case 0xE1: // 16 GiB - gameCardSize = GAMECARD_SIZE_16GiB; - break; - case 0xE2: // 32 GiB - gameCardSize = GAMECARD_SIZE_32GiB; - break; - default: - uiStatusMsg("getRootHfs0Header: Invalid game card size value: 0x%02X", cardSize); - free(gamecard_header); - fsStorageClose(&gameCardStorage); - return false; - } - - convertSize(gameCardSize, gameCardSizeStr, sizeof(gameCardSizeStr) / sizeof(gameCardSizeStr[0])); - - memcpy(&trimmedCardSize, gamecard_header + GAMECARD_DATAEND_ADDR, sizeof(u64)); - trimmedCardSize = (GAMECARD_HEADER_SIZE + (trimmedCardSize * MEDIA_UNIT_SIZE)); - convertSize(trimmedCardSize, trimmedCardSizeStr, sizeof(trimmedCardSizeStr) / sizeof(trimmedCardSizeStr[0])); - - memcpy(&hfs0_offset, gamecard_header + HFS0_OFFSET_ADDR, sizeof(u64)); - memcpy(&hfs0_size, gamecard_header + HFS0_SIZE_ADDR, sizeof(u64)); - - free(gamecard_header); - - hfs0_header = (char*)malloc(hfs0_size); - if (!hfs0_header) - { - uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the root HFS0 header!"); - - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - hfs0_offset = hfs0_size = 0; - - fsStorageClose(&gameCardStorage); - - return false; - } - - if (R_FAILED(result = fsStorageRead(&gameCardStorage, hfs0_offset, hfs0_header, hfs0_size))) - { - uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", hfs0_size, hfs0_offset, result); - - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - free(hfs0_header); - hfs0_header = NULL; - hfs0_offset = hfs0_size = 0; - - fsStorageClose(&gameCardStorage); - - return false; - } - - memcpy(&magic, hfs0_header, sizeof(u32)); - magic = bswap_32(magic); - if (magic != HFS0_MAGIC) - { - uiStatusMsg("getRootHfs0Header: Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC); - - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); - - free(hfs0_header); - hfs0_header = NULL; - hfs0_offset = hfs0_size = 0; - - fsStorageClose(&gameCardStorage); - - return false; - } - - memcpy(&hfs0_partition_cnt, hfs0_header + HFS0_FILE_COUNT_ADDR, sizeof(u32)); - - fsStorageClose(&gameCardStorage); - - return true; -} - -bool getHfs0EntryDetails(char *hfs0Header, u64 hfs0HeaderOffset, u64 hfs0HeaderSize, u32 num_entries, u32 entry_idx, bool isRoot, u32 partitionIndex, u64 *out_offset, u64 *out_size) -{ - if (hfs0Header == NULL) return false; - - if (entry_idx > (num_entries - 1)) return false; - - hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * num_entries); - if (!entryTable) return false; - - memcpy(entryTable, hfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * num_entries); - - // Determine the partition index that's going to be used for offset calculation - // If we're dealing with a root HFS0 header, just use entry_idx - // Otherwise, partitionIndex must be used, because entry_idx represents the file entry we must look for in the provided HFS0 partition header - u32 part_idx = (isRoot ? entry_idx : partitionIndex); - - switch(part_idx) - { - case 0: // Update (contained within IStorage instance with partition ID 0) - case 1: // Normal or Logo (depending on the gamecard type) (contained within IStorage instance with partition ID 0) - // Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start - // Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function) - *out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset); - break; - case 2: - // Check if we're dealing with a type 0x01 gamecard - if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT) - { - // Secure (contained within IStorage instance with partition ID 1) - // Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance - // Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation - *out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset)); - } else { - // Normal (contained within IStorage instance with partition ID 0) - // Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start - // Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function) - *out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset); - } - break; - case 3: // Secure (gamecard type 0x02) (contained within IStorage instance with partition ID 1) - // Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance - // Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation - *out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset)); - break; - default: - break; - } - - // Store the file size for the desired HFS0 entry - *out_size = entryTable[entry_idx].file_size; - - free(entryTable); - - return true; -} - -bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool trimDump, bool calcCrc) +bool dumpCartridgeImage(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool trimDump, bool calcCrc) { u64 partitionOffset = 0, fileOffset = 0, xciDataSize = 0, totalSize = 0, n; u64 partitionSizes[ISTORAGE_PARTITION_CNT]; - char partitionSizesStr[ISTORAGE_PARTITION_CNT][32] = {'\0'}, xciDataSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, totalSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN] = {'\0'}; + char partitionSizesStr[ISTORAGE_PARTITION_CNT][32] = {'\0'}, xciDataSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, totalSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN * 2] = {'\0'}; u32 partition; Result result; FsGameCardHandle handle; FsStorage gameCardStorage; - bool proceed = true, success = false; + bool proceed = true, success = false, fat32_error = false; FILE *outFile = NULL; char *buf = NULL; u8 splitIndex = 0; @@ -366,6 +75,16 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert char etaInfo[32] = {'\0'}; double lastSpeed = 0.0, averageSpeed = 0.0; + size_t write_res; + + char *dumpName = generateDumpName(); + if (!dumpName) + { + uiDrawString("Error: unable to generate output dump name!", 0, breaks * font_height, 255, 0, 0); + breaks += 2; + return false; + } + for(partition = 0; partition < ISTORAGE_PARTITION_CNT; partition++) { snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Getting partition #%u size...", partition); @@ -444,11 +163,11 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert { breaks++; - if (totalSize > SPLIT_FILE_MIN && isFat32) + if (totalSize > FAT32_FILESIZE_LIMIT && isFat32) { - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xc%u", fixedGameCardName, gameCardVersion, gameCardTitleID, splitIndex); + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s.xc%u", dumpName, splitIndex); } else { - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xci", fixedGameCardName, gameCardVersion, gameCardTitleID); + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s.xci", dumpName); } outFile = fopen(filename, "wb"); @@ -539,16 +258,17 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert } } - if (totalSize > SPLIT_FILE_MIN && isFat32 && (fileOffset + n) < totalSize && (fileOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) + if (totalSize > FAT32_FILESIZE_LIMIT && isFat32 && (fileOffset + n) < totalSize && (fileOffset + n) >= ((splitIndex + 1) * SPLIT_FILE_XCI_PART_SIZE)) { - u64 new_file_chunk_size = ((fileOffset + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); + u64 new_file_chunk_size = ((fileOffset + n) - ((splitIndex + 1) * SPLIT_FILE_XCI_PART_SIZE)); u64 old_file_chunk_size = (n - new_file_chunk_size); if (old_file_chunk_size > 0) { - if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) + write_res = fwrite(buf, 1, old_file_chunk_size, outFile); + if (write_res != old_file_chunk_size) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, fileOffset, splitIndex, write_res); uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); proceed = false; break; @@ -558,7 +278,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert fclose(outFile); splitIndex++; - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xc%u", fixedGameCardName, gameCardVersion, gameCardTitleID, splitIndex); + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s.xc%u", dumpName, splitIndex); outFile = fopen(filename, "wb"); if (!outFile) @@ -571,19 +291,28 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert if (new_file_chunk_size > 0) { - if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size) + write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile); + if (write_res != new_file_chunk_size) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset + old_file_chunk_size); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, fileOffset + old_file_chunk_size, splitIndex, write_res); uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); proceed = false; break; } } } else { - if (fwrite(buf, 1, n, outFile) != n) + write_res = fwrite(buf, 1, n, outFile); + if (write_res != n) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", fileOffset); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, fileOffset, write_res); uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + + if ((fileOffset + n) > FAT32_FILESIZE_LIMIT) + { + uiDrawString("You're probably using a FAT32 partition. Make sure to enable the \"Split output dump\" option.", 0, (breaks + 7) * font_height, 255, 255, 255); + fat32_error = true; + } + proceed = false; break; } @@ -648,6 +377,7 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert { uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, (((fileOffset + n) * (FB_WIDTH / 2)) / totalSize), font_height, 255, 0, 0); breaks += 5; + if (fat32_error) breaks += 2; } fsStorageClose(&gameCardStorage); @@ -721,26 +451,819 @@ bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert } else { if (outFile) fclose(outFile); - if (totalSize > SPLIT_FILE_MIN && isFat32) + if (totalSize > FAT32_FILESIZE_LIMIT && isFat32) + { + for(u8 i = 0; i <= splitIndex; i++) + { + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s.xc%u", dumpName, i); + remove(filename); + } + } else { + remove(filename); + } + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } else { + uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0); + } + } + + breaks += 2; + + free(dumpName); + + return success; +} + +bool dumpApplicationNSP(FsDeviceOperator* fsOperator, bool isFat32, bool calcCrc, u32 appIndex) +{ + Result result; + u32 i = 0, j = 0; + u32 written = 0; + u32 total = 0; + u32 appNcaCount = 0; + u32 partition = (hfs0_partition_cnt - 1); // Select the secure partition + + NcmContentMetaDatabase ncmDb; + NcmContentStorage ncmStorage; + NcmApplicationContentMetaKey *appList = NULL; + NcmContentRecord *appContentRecords = NULL; + size_t appListSize = (gameCardAppCount * sizeof(NcmApplicationContentMetaKey)); + + cnmt_xml_program_info xml_program_info; + cnmt_xml_content_info *xml_content_info = NULL; + + NcmNcaId ncaId; + char ncaHeader[NCA_FULL_HEADER_LENGTH] = {'\0'}; + nca_header_t dec_nca_header; + + u32 cnmtNcaIndex = 0; + char *cnmtNcaBuf = NULL; + bool cnmtFound = false; + + u64 cnmt_pfs0_offset; + u64 cnmt_pfs0_size; + pfs0_header cnmt_pfs0_header; + pfs0_entry_table *cnmt_pfs0_entries = NULL; + + u64 appCnmtOffset; + cnmt_header appCnmtHeader; + cnmt_application_header appCnmtAppHeader; + cnmt_content_record *appCnmtContentRecords = NULL; + + char *metadataXml = NULL; + + pfs0_header nspPfs0Header; + pfs0_entry_table *nspPfs0EntryTable = NULL; + char *nspPfs0StrTable = NULL; + u32 full_nsp_header_size = 0; + + u64 total_size = 0; + char dumpPath[NAME_BUF_LEN * 2] = {'\0'}, curSizeStr[32] = {'\0'}, totalSizeStr[32] = {'\0'}; + + u64 n, nca_offset, nsp_file_offset = 0; + FILE *outFile = NULL; + char *buf = NULL; + u8 splitIndex = 0; + u8 progress = 0; + u32 crc = 0; + bool proceed = true, success = false, fat32_error = false; + + u64 start, now, remainingTime; + struct tm *timeinfo; + char etaInfo[32] = {'\0'}; + double lastSpeed = 0.0, averageSpeed = 0.0; + + size_t write_res; + + // Generate filename for our required CNMT file + char cnmtFileName[40] = {'\0'}; + snprintf(cnmtFileName, sizeof(cnmtFileName) / sizeof(cnmtFileName[0]), "Application_%016lx.cnmt", gameCardTitleID[appIndex]); + + if (appIndex > (gameCardAppCount - 1)) + { + uiDrawString("Error: invalid application index!", 0, breaks * font_height, 255, 0, 0); + breaks += 2; + return false; + } + + workaroundPartitionZeroAccess(fsOperator); + + if (!getPartitionHfs0Header(partition)) return false; + + if (!partitionHfs0FileCount) + { + uiDrawString("The Secure HFS0 partition is empty!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + uiDrawString("Retrieving information from encrypted NCA content files...", 0, breaks * font_height, 255, 255, 255); + uiRefreshDisplay(); + breaks++; + + appList = (NcmApplicationContentMetaKey*)calloc(1, appListSize); + if (!appList) + { + uiDrawString("Error: unable to allocate memory for the ApplicationContentMetaKey struct!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + if (R_FAILED(result = ncmOpenContentMetaDatabase(FsStorageId_GameCard, &ncmDb))) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmOpenContentMetaDatabase failed! (0x%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + goto out; + } + + if (R_FAILED(result = ncmContentMetaDatabaseListApplication(&ncmDb, META_DB_REGULAR_APPLICATION, appList, appListSize, &written, &total))) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmContentMetaDatabaseListApplication failed! (0x%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + goto out; + } + + if (!written || !total) + { + uiDrawString("Error: ncmContentMetaDatabaseListApplication wrote no entries to output buffer!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + if (written != total || written != gameCardAppCount) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: application count mismatch in ncmContentMetaDatabaseListApplication (%u != %u)", written, gameCardAppCount); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + goto out; + } + + appContentRecords = (NcmContentRecord*)calloc(partitionHfs0FileCount, sizeof(NcmContentRecord)); + if (!appContentRecords) + { + uiDrawString("Error: unable to allocate memory for the ContentRecord struct!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + if (R_FAILED(result = ncmContentMetaDatabaseListContentInfo(&ncmDb, &(appList[appIndex].metaRecord), 0, appContentRecords, partitionHfs0FileCount * sizeof(NcmContentRecord), &written))) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmContentMetaDatabaseListContentInfo failed! (0x%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + goto out; + } + + appNcaCount = written; + + // Fill information for our XML + memset(&xml_program_info, 0, sizeof(cnmt_xml_program_info)); + xml_program_info.type = appList[appIndex].metaRecord.type; + xml_program_info.title_id = appList[appIndex].metaRecord.titleId; + xml_program_info.version = appList[appIndex].metaRecord.version; + xml_program_info.nca_cnt = appNcaCount; + + xml_content_info = (cnmt_xml_content_info*)calloc(appNcaCount, sizeof(cnmt_xml_content_info)); + if (!xml_content_info) + { + uiDrawString("Error: unable to allocate memory for the CNMT XML content info struct!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + if (R_FAILED(result = ncmOpenContentStorage(FsStorageId_GameCard, &ncmStorage))) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmOpenContentStorage failed! (0x%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + goto out; + } + + for(i = 0; i < appNcaCount; i++) + { + // Fill information for our XML + xml_content_info[i].type = appContentRecords[i].type; + memcpy(xml_content_info[i].nca_id, appContentRecords[i].ncaId.c, 16); + convertDataToHexString(appContentRecords[i].ncaId.c, 16, xml_content_info[i].nca_id_str, 33); + convertNcaSizeToU64(appContentRecords[i].size, &(xml_content_info[i].size)); + + memcpy(&ncaId, &(appContentRecords[i].ncaId), sizeof(NcmNcaId)); + + if (!cnmtFound && appContentRecords[i].type == NcmContentType_CNMT) + { + cnmtFound = true; + cnmtNcaIndex = i; + + cnmtNcaBuf = (char*)calloc(xml_content_info[i].size, sizeof(char)); + if (!cnmtNcaBuf) + { + uiDrawString("Error: unable to allocate memory for CNMT NCA data!", 0, breaks * font_height, 255, 0, 0); + proceed = false; + break; + } + + if (R_FAILED(result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, 0, cnmtNcaBuf, xml_content_info[i].size))) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmContentStorageReadContentIdFile failed for NCA \"%s\"! (0x%08X)", xml_content_info[i].nca_id_str, result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + proceed = false; + break; + } + + // Calculate SHA-256 checksum for the CNMT NCA + if (!calculateSHA256((u8*)cnmtNcaBuf, (u32)xml_content_info[i].size, xml_content_info[i].hash)) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "SHA-256 checksum calculation for CNMT NCA \"%s\" failed!", xml_content_info[i].nca_id_str); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + proceed = false; + break; + } + + // Fill information for our XML + convertDataToHexString(xml_content_info[i].hash, NCA_CNMT_DIGEST_SIZE, xml_content_info[i].hash_str, 65); + + // Decrypt the CNMT NCA buffer in-place + if (!decryptCnmtNca(cnmtNcaBuf, xml_content_info[i].size)) + { + proceed = false; + break; + } + + memcpy(&dec_nca_header, cnmtNcaBuf, sizeof(nca_header_t)); + } else { + if (R_FAILED(result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, 0, ncaHeader, NCA_FULL_HEADER_LENGTH))) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmContentStorageReadContentIdFile failed for NCA \"%s\"! (0x%08X)", xml_content_info[i].nca_id_str, result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + proceed = false; + break; + } + + // Decrypt the NCA header in-place + if (!decryptNcaHeader(ncaHeader, NCA_FULL_HEADER_LENGTH, &dec_nca_header)) + { + proceed = false; + break; + } + } + + // Fill information for our XML + xml_content_info[i].keyblob = (dec_nca_header.crypto_type2 > dec_nca_header.crypto_type ? dec_nca_header.crypto_type2 : dec_nca_header.crypto_type); + + // Modify distribution type + dec_nca_header.distribution = 0; + + // Reencrypt header + if (!encryptNcaHeader(&dec_nca_header, xml_content_info[i].encrypted_header_mod, NCA_FULL_HEADER_LENGTH)) + { + proceed = false; + break; + } + + /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "sdmc:/%s_header.nca", xml_content_info[i].nca_id_str); + FILE *mod_header = fopen(strbuf, "wb"); + if (mod_header) + { + fwrite(xml_content_info[i].encrypted_header_mod, 1, NCA_FULL_HEADER_LENGTH, mod_header); + fclose(mod_header); + }*/ + } + + if (proceed && !cnmtFound) + { + uiDrawString("Error: unable to find the NCA ID for the application's CNMT!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + if (!proceed) goto out; + + // Fill information for our XML + xml_program_info.min_keyblob = xml_content_info[cnmtNcaIndex].keyblob; + + memcpy(&dec_nca_header, cnmtNcaBuf, sizeof(nca_header_t)); + + cnmt_pfs0_offset = ((dec_nca_header.section_entries[0].media_start_offset * MEDIA_UNIT_SIZE) + dec_nca_header.fs_headers[0].pfs0_superblock.hash_table_offset + dec_nca_header.fs_headers[0].pfs0_superblock.pfs0_offset); + cnmt_pfs0_size = dec_nca_header.fs_headers[0].pfs0_superblock.pfs0_size; + + // Fill information for our XML + memcpy(xml_program_info.digest, cnmtNcaBuf + cnmt_pfs0_offset + cnmt_pfs0_size - NCA_CNMT_DIGEST_SIZE, NCA_CNMT_DIGEST_SIZE); + convertDataToHexString(xml_program_info.digest, NCA_CNMT_DIGEST_SIZE, xml_program_info.digest_str, 65); + + memcpy(&cnmt_pfs0_header, cnmtNcaBuf + cnmt_pfs0_offset, sizeof(pfs0_header)); + + cnmt_pfs0_entries = (pfs0_entry_table*)calloc(cnmt_pfs0_header.file_cnt, sizeof(pfs0_entry_table)); + if (!cnmt_pfs0_entries) + { + uiDrawString("Error: unable to allocate memory for the PFS0 File Entry Table from CNMT NCA section #0!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + cnmtFound = false; + + // Extract the decrypted CNMT in order to retrieve the remaining information for our XML + // It's filename in the PFS0 partition must match the "Application_{lower-case hex titleID}.cnmt" format + for(i = 0; i < cnmt_pfs0_header.file_cnt; i++) + { + u32 filename_offset = (cnmt_pfs0_offset + sizeof(pfs0_header) + (cnmt_pfs0_header.file_cnt * sizeof(pfs0_entry_table)) + cnmt_pfs0_entries[i].filename_offset); + if (!strncasecmp(cnmtNcaBuf + filename_offset, cnmtFileName, strlen(cnmtFileName))) + { + cnmtFound = true; + appCnmtOffset = (cnmt_pfs0_offset + sizeof(pfs0_header) + (cnmt_pfs0_header.file_cnt * sizeof(pfs0_entry_table)) + cnmt_pfs0_header.str_table_size + cnmt_pfs0_entries[i].file_offset); + break; + } + } + + if (!cnmtFound) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to find file \"%s\" in PFS0 partition from CNMT NCA section #0!", cnmtFileName); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + goto out; + } + + memcpy(&appCnmtHeader, cnmtNcaBuf + appCnmtOffset, sizeof(cnmt_header)); + memcpy(&appCnmtAppHeader, cnmtNcaBuf + appCnmtOffset + sizeof(cnmt_header), sizeof(cnmt_application_header)); + + // Fill information for our XML + xml_program_info.patch_tid = appCnmtAppHeader.patch_tid; + xml_program_info.min_sysver = (u32)appCnmtAppHeader.min_sysver; + + appCnmtContentRecords = (cnmt_content_record*)calloc(appCnmtHeader.content_records_cnt, sizeof(cnmt_content_record)); + if (!appCnmtContentRecords) + { + uiDrawString("Error: unable to allocate memory for the PFS0 File Entry Table from CNMT NCA section #0!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + memcpy(appCnmtContentRecords, cnmtNcaBuf + appCnmtOffset + sizeof(cnmt_header) + appCnmtHeader.table_offset, appCnmtHeader.content_records_cnt * sizeof(cnmt_content_record)); + + for(i = 0; i < appCnmtHeader.content_records_cnt; i++) + { + for(j = 0; j < appNcaCount; j++) + { + if (!memcmp(appCnmtContentRecords[i].nca_id, xml_content_info[j].nca_id, 16)) + { + // Fill information for our XML + memcpy(xml_content_info[j].hash, appCnmtContentRecords[i].hash, NCA_CNMT_DIGEST_SIZE); + convertDataToHexString(xml_content_info[j].hash, NCA_CNMT_DIGEST_SIZE, xml_content_info[j].hash_str, 65); + break; + } + } + } + + /*for(i = 0; i < appNcaCount; i++) + { + breaks++; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Content Record #%u:", i); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "NCA ID: %s", xml_content_info[i].nca_id_str); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Size: 0x%016lX", xml_content_info[i].size); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Type: 0x%02X", xml_content_info[i].type); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Hash: %s", xml_content_info[i].hash_str); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Keyblob: 0x%02X", xml_content_info[i].keyblob); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + uiRefreshDisplay(); + }*/ + + breaks++; + uiDrawString("Generating metadata XML...", 0, breaks * font_height, 255, 255, 255); + uiRefreshDisplay(); + breaks++; + + // Generate our metadata XML, making sure that the output buffer is big enough + metadataXml = (char*)calloc(NAME_BUF_LEN * 4, sizeof(char)); + if (!metadataXml) + { + uiDrawString("Error: unable to allocate memory for the metadata XML!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + generateCnmtMetadataXml(&xml_program_info, xml_content_info, metadataXml); + + /*char cnmtXmlFileName[50] = {'\0'}; + sprintf(cnmtXmlFileName, "%s.cnmt.xml", xml_content_info[cnmtNcaIndex].nca_id_str); + FILE *metaxml = fopen(cnmtXmlFileName, "wb"); + if (metaxml) + { + fwrite(metadataXml, 1, strlen(metadataXml), metaxml); + fclose(metaxml); + }*/ + + // Start NSP creation + breaks++; + uiDrawString("Generating PFS0 header...", 0, breaks * font_height, 255, 255, 255); + uiRefreshDisplay(); + breaks++; + + memset(&nspPfs0Header, 0, sizeof(pfs0_header)); + nspPfs0Header.magic = bswap_32((u32)PFS0_MAGIC); + nspPfs0Header.file_cnt = (appNcaCount + 1); // Make sure to consider the metadata XML + + nspPfs0EntryTable = (pfs0_entry_table*)calloc(appNcaCount + 1, sizeof(pfs0_entry_table)); + if (!nspPfs0EntryTable) + { + uiDrawString("Unable to allocate memory for the PFS0 file entries!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + // Make sure we have enough memory for the PFS0 String Table + nspPfs0StrTable = (char*)calloc(NAME_BUF_LEN * 4, sizeof(char)); + if (!nspPfs0StrTable) + { + uiDrawString("Unable to allocate memory for the PFS0 string table!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + // Fill our Entry and String Tables + u64 file_offset = 0; + u32 filename_offset = 0; + + for(i = 0; i < (appNcaCount + 1); i++) + { + char ncaFileName[50] = {'\0'}; + + if (i == appNcaCount) + { + sprintf(ncaFileName, "%s.cnmt.xml", xml_content_info[cnmtNcaIndex].nca_id_str); + nspPfs0EntryTable[i].file_size = strlen(metadataXml); + } else { + sprintf(ncaFileName, "%s.%s", xml_content_info[i].nca_id_str, (i == cnmtNcaIndex ? "cnmt.nca" : "nca")); + nspPfs0EntryTable[i].file_size = xml_content_info[i].size; + } + + nspPfs0EntryTable[i].file_offset = file_offset; + nspPfs0EntryTable[i].filename_offset = filename_offset; + + strcpy(nspPfs0StrTable + filename_offset, ncaFileName); + + file_offset += nspPfs0EntryTable[i].file_size; + filename_offset += (strlen(ncaFileName) + 1); + } + + filename_offset--; + + // Determine our full NSP header size + full_nsp_header_size = (sizeof(pfs0_header) + ((appNcaCount + 1) * sizeof(pfs0_entry_table)) + filename_offset); + full_nsp_header_size = round_up(full_nsp_header_size, 16); + + // Determine our String Table size + nspPfs0Header.str_table_size = (full_nsp_header_size - (sizeof(pfs0_header) + ((appNcaCount + 1) * sizeof(pfs0_entry_table)))); + + // Calculate total dump size + total_size = full_nsp_header_size; + for(i = 0; i < (appNcaCount + 1); i++) total_size += nspPfs0EntryTable[i].file_size; + + breaks++; + convertSize(total_size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Total NSP dump size: %s (%lu bytes).", totalSizeStr, total_size); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + uiRefreshDisplay(); + breaks++; + + if (total_size > freeSpace) + { + uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + if (total_size > FAT32_FILESIZE_LIMIT && isFat32) + { + snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s v%u (%016lX).nsp", fixedGameCardName[appIndex], gameCardVersion[appIndex], gameCardTitleID[appIndex]); + + mkdir(dumpPath, 0744); + + snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s v%u (%016lX).nsp/%02u", fixedGameCardName[appIndex], gameCardVersion[appIndex], gameCardTitleID[appIndex], splitIndex); + } else { + snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s v%u (%016lX).nsp", fixedGameCardName[appIndex], gameCardVersion[appIndex], gameCardTitleID[appIndex]); + } + + outFile = fopen(dumpPath, "wb"); + if (!outFile) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", dumpPath); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + goto out; + } + + buf = (char*)malloc(DUMP_BUFFER_SIZE); + if (!buf) + { + uiDrawString("Failed to allocate memory for the dump process!", 0, breaks * font_height, 255, 0, 0); + goto out; + } + + breaks++; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dump procedure started. Writing output to \"%.*s\". Hold B to cancel.", (int)((total_size > FAT32_FILESIZE_LIMIT && isFat32) ? (strlen(dumpPath) - 3) : strlen(dumpPath)), dumpPath); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + uiRefreshDisplay(); + breaks += 2; + + if (programAppletType != AppletType_Application && programAppletType != AppletType_SystemApplication) + { + uiDrawString("Do not press the HOME button. Doing so could corrupt the SD card filesystem.", 0, breaks * font_height, 255, 0, 0); + breaks += 2; + } + + // Write our full PFS0 header + memcpy(buf, &nspPfs0Header, sizeof(pfs0_header)); + memcpy(buf + sizeof(pfs0_header), nspPfs0EntryTable, sizeof(pfs0_entry_table) * (appNcaCount + 1)); + memcpy(buf + sizeof(pfs0_header) + (sizeof(pfs0_entry_table) * (appNcaCount + 1)), nspPfs0StrTable, nspPfs0Header.str_table_size); + + write_res = fwrite(buf, 1, full_nsp_header_size, outFile); + if (write_res != full_nsp_header_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %u bytes full PFS0 header to file offset 0x%016lX! (wrote %lu bytes)", full_nsp_header_size, (u64)0, write_res); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + goto out; + } + + nsp_file_offset = full_nsp_header_size; + + // Update CRC32 + if (calcCrc) crc32(buf, full_nsp_header_size, &crc); + + timeGetCurrentTime(TimeType_LocalSystemClock, &start); + + for(i = 0; i < appNcaCount; i++) + { + uiFill(0, breaks * font_height, FB_WIDTH, font_height * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping NCA content #%u...", i); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + + n = DUMP_BUFFER_SIZE; + memcpy(ncaId.c, xml_content_info[i].nca_id, 16); + + for(nca_offset = 0; nca_offset < xml_content_info[i].size; nca_offset += n, nsp_file_offset += n) + { + if (DUMP_BUFFER_SIZE > (xml_content_info[i].size - nca_offset)) n = (xml_content_info[i].size - nca_offset); + + if (R_FAILED(result = ncmContentStorageReadContentIdFile(&ncmStorage, &ncaId, nca_offset, buf, n))) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: ncmContentStorageReadContentIdFile failed (0x%08X) at offset 0x%016lX for NCA \"%s\".", result, nca_offset, xml_content_info[i].nca_id_str); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + + // Replace NCA header with our modified one + if (nca_offset == 0) memcpy(buf, xml_content_info[i].encrypted_header_mod, NCA_FULL_HEADER_LENGTH); + + // Update CRC32 + if (calcCrc) crc32(buf, n, &crc); + + if (total_size > FAT32_FILESIZE_LIMIT && isFat32 && (nsp_file_offset + n) < total_size && (nsp_file_offset + n) >= ((splitIndex + 1) * SPLIT_FILE_NSP_PART_SIZE)) + { + u64 new_file_chunk_size = ((nsp_file_offset + n) - ((splitIndex + 1) * SPLIT_FILE_NSP_PART_SIZE)); + u64 old_file_chunk_size = (n - new_file_chunk_size); + + if (old_file_chunk_size > 0) + { + write_res = fwrite(buf, 1, old_file_chunk_size, outFile); + if (write_res != old_file_chunk_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, nsp_file_offset, splitIndex, write_res); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + } + + fclose(outFile); + + splitIndex++; + snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s v%u (%016lX).nsp/%02u", fixedGameCardName[appIndex], gameCardVersion[appIndex], gameCardTitleID[appIndex], splitIndex); + + outFile = fopen(dumpPath, "wb"); + if (!outFile) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file for part #%u!", splitIndex); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + + if (new_file_chunk_size > 0) + { + write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile); + if (write_res != new_file_chunk_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, nsp_file_offset + old_file_chunk_size, splitIndex, write_res); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; + } + } + } else { + write_res = fwrite(buf, 1, n, outFile); + if (write_res != n) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, nsp_file_offset, write_res); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + + if ((nsp_file_offset + n) > FAT32_FILESIZE_LIMIT) { - for(u8 i = 0; i <= splitIndex; i++) - { - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX).xc%u", fixedGameCardName, gameCardVersion, gameCardTitleID, i); - remove(filename); - } - } else { - remove(filename); + uiDrawString("You're probably using a FAT32 partition. Make sure to enable the \"Split output dump\" option.", 0, (breaks + 7) * font_height, 255, 255, 255); + fat32_error = true; } + + proceed = false; + break; + } + } + + timeGetCurrentTime(TimeType_LocalSystemClock, &now); + + lastSpeed = (((double)(nsp_file_offset + n) / (double)DUMP_BUFFER_SIZE) / difftime((time_t)now, (time_t)start)); + averageSpeed = ((SMOOTHING_FACTOR * lastSpeed) + ((1 - SMOOTHING_FACTOR) * averageSpeed)); + if (!isnormal(averageSpeed)) averageSpeed = 0.00; // Very low values + + remainingTime = (u64)(((double)(total_size - (nsp_file_offset + n)) / (double)DUMP_BUFFER_SIZE) / averageSpeed); + timeinfo = localtime((time_t*)&remainingTime); + strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); + + progress = (u8)(((nsp_file_offset + n) * 100) / total_size); + + uiFill(0, ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%.2lf MiB/s [ETA: %s]", averageSpeed, etaInfo); + uiDrawString(strbuf, font_height * 2, (breaks + 2) * font_height, 255, 255, 255); + + uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, FB_WIDTH / 2, font_height, 0, 0, 0); + uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, (((nsp_file_offset + n) * (FB_WIDTH / 2)) / total_size), font_height, 0, 255, 0); + + uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + convertSize(nsp_file_offset + n, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); + uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 2) * font_height, 255, 255, 255); + + uiRefreshDisplay(); + + if ((nsp_file_offset + n) < total_size && ((nsp_file_offset / DUMP_BUFFER_SIZE) % 10) == 0) + { + hidScanInput(); + + u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (keysDown & KEY_B) + { + uiDrawString("Process canceled", 0, (breaks + 5) * font_height, 255, 0, 0); + proceed = false; + break; } - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to open output file \"%s\"!", filename); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); } + } + + if (!proceed) + { + uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, (((nsp_file_offset + n) * (FB_WIDTH / 2)) / total_size), font_height, 255, 0, 0); + break; + } + + // Support empty files + if (!xml_content_info[i].size) + { + uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, ((nsp_file_offset * (FB_WIDTH / 2)) / total_size), font_height, 0, 255, 0); + + uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + convertSize(nsp_file_offset, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); + uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 2) * font_height, 255, 255, 255); + + uiRefreshDisplay(); + } + } + + if (!proceed) goto out; + + // Write our metadata XML + uiFill(0, breaks * font_height, FB_WIDTH, font_height * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Writing metadata XML \"%s.cnmt.xml\"...", xml_content_info[cnmtNcaIndex].nca_id_str); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + + write_res = fwrite(metadataXml, 1, strlen(metadataXml), outFile); + if (write_res != strlen(metadataXml)) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes metadata XML to file offset 0x%016lX! (wrote %lu bytes)", strlen(metadataXml), nsp_file_offset, write_res); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + goto out; + } + + nsp_file_offset += strlen(metadataXml); + + if (nsp_file_offset < total_size) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Unexpected underdump error! Wrote %lu bytes, expected %lu bytes.", nsp_file_offset, total_size); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + goto out; + } + + success = true; + + // Update CRC32 + if (calcCrc) crc32(metadataXml, strlen(metadataXml), &crc); + + // Update progress + remainingTime = 0; + timeinfo = localtime((time_t*)&remainingTime); + strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); + + progress = 100; + + uiFill(FB_WIDTH / 4, ((breaks + 2) * font_height) + 2, FB_WIDTH / 2, font_height, 0, 255, 0); + + uiFill(FB_WIDTH - (FB_WIDTH / 4), ((breaks + 2) * font_height) - 4, FB_WIDTH / 4, font_height + 8, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); + convertSize(nsp_file_offset, curSizeStr, sizeof(curSizeStr) / sizeof(curSizeStr[0])); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%u%% [%s / %s]", progress, curSizeStr, totalSizeStr); + uiDrawString(strbuf, FB_WIDTH - (FB_WIDTH / 4) + (font_height * 2), (breaks + 2) * font_height, 255, 255, 255); + uiRefreshDisplay(); + + breaks += 5; + + // Finalize dump + now -= start; + timeinfo = localtime((time_t*)&now); + strftime(etaInfo, sizeof(etaInfo) / sizeof(etaInfo[0]), "%HH%MM%SS", timeinfo); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Process successfully completed after %s!", etaInfo); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + + if (calcCrc) + { + breaks++; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "NSP dump CRC32 checksum: %08X", crc); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + } + + // Set archive bit (only for FAT32) + if (total_size > FAT32_FILESIZE_LIMIT && isFat32) + { + snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s v%u (%016lX).nsp", fixedGameCardName[appIndex], gameCardVersion[appIndex], gameCardTitleID[appIndex]); + if (R_FAILED(result = fsdevSetArchiveBit(dumpPath))) + { + breaks++; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Warning: failed to set archive bit on output directory! (0x%08X)", result); + uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + breaks++; + } + } + +out: + if (buf) free(buf); + + if (outFile) fclose(outFile); + + if (!success) + { + breaks += 5; + if (fat32_error) breaks += 2; + + snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s v%u (%016lX).nsp", fixedGameCardName[appIndex], gameCardVersion[appIndex], gameCardTitleID[appIndex]); + + if (total_size > FAT32_FILESIZE_LIMIT && isFat32) + { + removeDirectory(dumpPath); } else { - uiDrawString("Error: not enough free space available in the SD card.", 0, breaks * font_height, 255, 0, 0); + remove(dumpPath); } } + if (nspPfs0StrTable) free(nspPfs0StrTable); + + if (nspPfs0EntryTable) free(nspPfs0EntryTable); + + if (metadataXml) free(metadataXml); + + if (appCnmtContentRecords) free(appCnmtContentRecords); + + if (cnmt_pfs0_entries) free(cnmt_pfs0_entries); + + if (cnmtNcaBuf) free(cnmtNcaBuf); + + if (xml_content_info) free(xml_content_info); + + if (appContentRecords) free(appContentRecords); + + if (appList) free(appList); + + if (partitionHfs0Header) + { + free(partitionHfs0Header); + partitionHfs0Header = NULL; + partitionHfs0HeaderSize = 0; + partitionHfs0FileCount = 0; + partitionHfs0StrTableSize = 0; + } + breaks += 2; return success; @@ -750,12 +1273,12 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt { Result result; u64 size, partitionOffset; - bool success = false; + bool success = false, fat32_error = false; char *buf; u64 off, n = DUMP_BUFFER_SIZE; FsGameCardHandle handle; FsStorage gameCardStorage; - char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN] = {'\0'}; + char totalSizeStr[32] = {'\0'}, curSizeStr[32] = {'\0'}, filename[NAME_BUF_LEN * 2] = {'\0'}; u8 progress = 0; FILE *outFile = NULL; u8 splitIndex = 0; @@ -765,6 +1288,16 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt char etaInfo[32] = {'\0'}; double lastSpeed = 0.0, averageSpeed = 0.0; + size_t write_res; + + char *dumpName = generateDumpName(); + if (!dumpName) + { + uiDrawString("Error: unable to generate output dump name!", 0, breaks * font_height, 255, 0, 0); + breaks += 2; + return false; + } + workaroundPartitionZeroAccess(fsOperator); if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) @@ -791,7 +1324,7 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt if (getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionOffset, &size)) { convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition size: %s (%lu bytes)", totalSizeStr, size); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition size: %s (%lu bytes).", totalSizeStr, size); uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); breaks++; @@ -801,11 +1334,11 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt if (size <= freeSpace) { - if (size > SPLIT_FILE_MIN && doSplitting) + if (size > FAT32_FILESIZE_LIMIT && doSplitting) { - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex); + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s - Partition %u (%s).hfs0.%02u", dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex); } else { - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s - Partition %u (%s).hfs0", dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); } outFile = fopen(filename, "wb"); @@ -814,7 +1347,7 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt buf = (char*)malloc(DUMP_BUFFER_SIZE); if (buf) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping raw HFS0 partition #%u to \"%.*s\". Hold B to cancel.", partition, (int)((size > SPLIT_FILE_MIN && doSplitting) ? (strlen(filename) - 3) : strlen(filename)), filename); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping raw HFS0 partition #%u to \"%.*s\". Hold B to cancel.", partition, (int)((size > FAT32_FILESIZE_LIMIT && doSplitting) ? (strlen(filename) - 3) : strlen(filename)), filename); uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); breaks += 2; @@ -839,16 +1372,17 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt break; } - if (size > SPLIT_FILE_MIN && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) + if (size > FAT32_FILESIZE_LIMIT && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) { - u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); + u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)); u64 old_file_chunk_size = (n - new_file_chunk_size); if (old_file_chunk_size > 0) { - if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) + write_res = fwrite(buf, 1, old_file_chunk_size, outFile); + if (write_res != old_file_chunk_size) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, off, splitIndex, write_res); uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0); break; } @@ -857,7 +1391,7 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt fclose(outFile); splitIndex++; - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex); + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s - Partition %u (%s).hfs0.%02u", dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), splitIndex); outFile = fopen(filename, "wb"); if (!outFile) @@ -869,18 +1403,27 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt if (new_file_chunk_size > 0) { - if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size) + write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile); + if (write_res != new_file_chunk_size) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off + old_file_chunk_size); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, off + old_file_chunk_size, splitIndex, write_res); uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0); break; } } } else { - if (fwrite(buf, 1, n, outFile) != n) + write_res = fwrite(buf, 1, n, outFile); + if (write_res != n) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, off, write_res); uiDrawString(strbuf, 0, (breaks + 3) * font_height, 255, 0, 0); + + if ((off + n) > FAT32_FILESIZE_LIMIT) + { + uiDrawString("You're probably using a FAT32 partition. Make sure to enable file splitting.", 0, (breaks + 5) * font_height, 255, 255, 255); + fat32_error = true; + } + break; } } @@ -949,6 +1492,7 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt } breaks += 3; + if (fat32_error) breaks += 2; free(buf); } else { @@ -959,11 +1503,11 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt if (!success) { - if (size > SPLIT_FILE_MIN && doSplitting) + if (size > FAT32_FILESIZE_LIMIT && doSplitting) { for(u8 i = 0; i <= splitIndex; i++) { - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s).hfs0.%02u", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), i); + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s - Partition %u (%s).hfs0.%02u", dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), i); remove(filename); } } else { @@ -993,155 +1537,7 @@ bool dumpRawPartition(FsDeviceOperator* fsOperator, u32 partition, bool doSplitt breaks += 2; - return success; -} - -bool getPartitionHfs0Header(FsDeviceOperator* fsOperator, u32 partition) -{ - if (hfs0_header == NULL) return false; - - if (partitionHfs0Header != NULL) - { - free(partitionHfs0Header); - partitionHfs0Header = NULL; - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; - } - - char *buf = NULL; - Result result; - FsGameCardHandle handle; - FsStorage gameCardStorage; - u64 partitionSize = 0; - u32 magic = 0; - bool success = false; - - if (getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionHfs0HeaderOffset, &partitionSize)) - { - workaroundPartitionZeroAccess(fsOperator); - - if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(fsOperator, &handle))) - { - /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks++;*/ - - // Same ugly hack from dumpRawPartition() - if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition)))) - { - /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks++;*/ - - buf = (char*)malloc(MEDIA_UNIT_SIZE); - if (buf) - { - // First read MEDIA_UNIT_SIZE bytes - if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, buf, MEDIA_UNIT_SIZE))) - { - // Check the HFS0 magic word - memcpy(&magic, buf, sizeof(u32)); - magic = bswap_32(magic); - if (magic == HFS0_MAGIC) - { - // Calculate the size for the partition HFS0 header - memcpy(&partitionHfs0FileCount, buf + HFS0_FILE_COUNT_ADDR, sizeof(u32)); - memcpy(&partitionHfs0StrTableSize, buf + HFS0_STR_TABLE_SIZE_ADDR, sizeof(u32)); - partitionHfs0HeaderSize = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + partitionHfs0StrTableSize); - - /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header offset (relative to IStorage instance): 0x%016lX", partition, partitionHfs0HeaderOffset); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks++; - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header size: %lu bytes", partition, partitionHfs0HeaderSize); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks++; - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u file count: %u", partition, partitionHfs0FileCount); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks++; - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u string table size: %u bytes", partition, partitionHfs0StrTableSize); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks++; - - uiRefreshDisplay();*/ - - // Round up the partition HFS0 header size to a MEDIA_UNIT_SIZE bytes boundary - partitionHfs0HeaderSize = round_up(partitionHfs0HeaderSize, MEDIA_UNIT_SIZE); - - partitionHfs0Header = (char*)malloc(partitionHfs0HeaderSize); - if (partitionHfs0Header) - { - // Check if we were dealing with the correct header size all along - if (partitionHfs0HeaderSize == MEDIA_UNIT_SIZE) - { - // Just copy what we already have - memcpy(partitionHfs0Header, buf, MEDIA_UNIT_SIZE); - success = true; - } else { - // Read the whole HFS0 header - if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, partitionHfs0Header, partitionHfs0HeaderSize))) - { - success = true; - } else { - free(partitionHfs0Header); - partitionHfs0Header = NULL; - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); - } - } - - if (success) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header successfully retrieved!", partition); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - } - } else { - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to allocate memory for the HFS0 header from partition #%u!", partition); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); - } - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); - } - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); - } - - free(buf); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to allocate memory for the HFS0 header from partition #%u!", partition); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); - } - - fsStorageClose(&gameCardStorage); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); - } - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); - } - } else { - uiDrawString("Error: unable to get partition details from the root HFS0 header!", 0, breaks * font_height, 255, 0, 0); - } - - breaks += 2; + free(dumpName); return success; } @@ -1149,7 +1545,7 @@ bool getPartitionHfs0Header(FsDeviceOperator* fsOperator, u32 partition) bool copyFileFromHfs0(FsDeviceOperator* fsOperator, u32 partition, const char* source, const char* dest, const u64 file_offset, const u64 size, bool doSplitting, bool calcEta) { Result result; - bool success = false; + bool success = false, fat32_error = false; char splitFilename[NAME_BUF_LEN] = {'\0'}; size_t destLen = strlen(dest); FILE *outFile = NULL; @@ -1167,6 +1563,8 @@ bool copyFileFromHfs0(FsDeviceOperator* fsOperator, u32 partition, const char* s char etaInfo[32] = {'\0'}; double lastSpeed = 0.0, averageSpeed = 0.0; + size_t write_res; + uiFill(0, breaks * font_height, FB_WIDTH, font_height * 4, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying \"%s\"...", source); @@ -1190,9 +1588,9 @@ bool copyFileFromHfs0(FsDeviceOperator* fsOperator, u32 partition, const char* s convertSize(size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); - if (size > SPLIT_FILE_MIN && doSplitting) snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, splitIndex); + if (size > FAT32_FILESIZE_LIMIT && doSplitting) snprintf(splitFilename, sizeof(splitFilename) / sizeof(splitFilename[0]), "%s.%02u", dest, splitIndex); - outFile = fopen(((size > SPLIT_FILE_MIN && doSplitting) ? splitFilename : dest), "wb"); + outFile = fopen(((size > FAT32_FILESIZE_LIMIT && doSplitting) ? splitFilename : dest), "wb"); if (outFile) { buf = (char*)malloc(DUMP_BUFFER_SIZE); @@ -1211,16 +1609,17 @@ bool copyFileFromHfs0(FsDeviceOperator* fsOperator, u32 partition, const char* s break; } - if (size > SPLIT_FILE_MIN && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_2GiB)) + if (size > FAT32_FILESIZE_LIMIT && doSplitting && (off + n) < size && (off + n) >= ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)) { - u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_2GiB)); + u64 new_file_chunk_size = ((off + n) - ((splitIndex + 1) * SPLIT_FILE_GENERIC_PART_SIZE)); u64 old_file_chunk_size = (n - new_file_chunk_size); if (old_file_chunk_size > 0) { - if (fwrite(buf, 1, old_file_chunk_size, outFile) != old_file_chunk_size) + write_res = fwrite(buf, 1, old_file_chunk_size, outFile); + if (write_res != old_file_chunk_size) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", old_file_chunk_size, off, splitIndex, write_res); uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); break; } @@ -1241,18 +1640,27 @@ bool copyFileFromHfs0(FsDeviceOperator* fsOperator, u32 partition, const char* s if (new_file_chunk_size > 0) { - if (fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile) != new_file_chunk_size) + write_res = fwrite(buf + old_file_chunk_size, 1, new_file_chunk_size, outFile); + if (write_res != new_file_chunk_size) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off + old_file_chunk_size); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX to part #%02u! (wrote %lu bytes)", new_file_chunk_size, off + old_file_chunk_size, splitIndex, write_res); uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); break; } } } else { - if (fwrite(buf, 1, n, outFile) != n) + write_res = fwrite(buf, 1, n, outFile); + if (write_res != n) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write chunk to offset 0x%016lX", off); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %lu bytes chunk from offset 0x%016lX! (wrote %lu bytes)", n, off, write_res); uiDrawString(strbuf, 0, (breaks + 5) * font_height, 255, 0, 0); + + if ((off + n) > FAT32_FILESIZE_LIMIT) + { + uiDrawString("You're probably using a FAT32 partition. Make sure to enable file splitting.", 0, (breaks + 7) * font_height, 255, 255, 255); + fat32_error = true; + } + break; } } @@ -1316,6 +1724,7 @@ bool copyFileFromHfs0(FsDeviceOperator* fsOperator, u32 partition, const char* s { uiFill(FB_WIDTH / 4, ((breaks + 3) * font_height) + 2, (((off + n) * (FB_WIDTH / 2)) / size), font_height, 255, 0, 0); breaks += 5; + if (fat32_error) breaks += 2; } free(buf); @@ -1339,7 +1748,7 @@ bool copyFileFromHfs0(FsDeviceOperator* fsOperator, u32 partition, const char* s uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); } } else { - if (size > SPLIT_FILE_MIN && doSplitting) + if (size > FAT32_FILESIZE_LIMIT && doSplitting) { for(u8 i = 0; i <= splitIndex; i++) { @@ -1430,11 +1839,19 @@ bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition) u64 total_size = 0; u32 i; hfs0_entry_table *entryTable = NULL; - char dumpPath[NAME_BUF_LEN] = {'\0'}, totalSizeStr[32] = {'\0'}; + char dumpPath[NAME_BUF_LEN * 2] = {'\0'}, totalSizeStr[32] = {'\0'}; + + char *dumpName = generateDumpName(); + if (!dumpName) + { + uiDrawString("Error: unable to generate output dump name!", 0, breaks * font_height, 255, 0, 0); + breaks += 2; + return false; + } workaroundPartitionZeroAccess(fsOperator); - if (getPartitionHfs0Header(fsOperator, partition)) + if (getPartitionHfs0Header(partition)) { if (partitionHfs0FileCount) { @@ -1447,13 +1864,13 @@ bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition) for(i = 0; i < partitionHfs0FileCount; i++) total_size += entryTable[i].file_size; convertSize(total_size, totalSizeStr, sizeof(totalSizeStr) / sizeof(totalSizeStr[0])); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Total partition data size: %s (%lu bytes)", totalSizeStr, total_size); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Total partition data size: %s (%lu bytes).", totalSizeStr, total_size); uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); breaks++; if (total_size <= freeSpace) { - snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); + snprintf(dumpPath, sizeof(dumpPath) / sizeof(dumpPath[0]), "sdmc:/%s - Partition %u (%s)", dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Copying partition #%u data to \"%s/\". Hold B to cancel.", partition, dumpPath); uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); @@ -1496,71 +1913,29 @@ bool dumpPartitionData(FsDeviceOperator* fsOperator, u32 partition) breaks += 2; + free(dumpName); + return success; } -bool getHfs0FileList(FsDeviceOperator* fsOperator, u32 partition) +bool dumpFileFromPartition(FsDeviceOperator* fsOperator, u32 partition, u32 file, char *filename) { - if (!getPartitionHfs0Header(fsOperator, partition)) return false; - if (!partitionHfs0Header) { uiDrawString("HFS0 partition header information unavailable!", 0, breaks * font_height, 255, 0, 0); return false; } - if (!partitionHfs0FileCount) - { - uiDrawString("The selected partition is empty!", 0, breaks * font_height, 255, 0, 0); - return false; - } - - if (partitionHfs0FileCount > FILENAME_MAX_CNT) - { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "HFS0 partition contains more than %u files! (%u entries)", FILENAME_MAX_CNT, partitionHfs0FileCount); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); - return false; - } - - hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * partitionHfs0FileCount); - if (!entryTable) - { - uiDrawString("Unable to allocate memory for the HFS0 file entries!", 0, breaks * font_height, 255, 0, 0); - return false; - } - - memcpy(entryTable, partitionHfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * partitionHfs0FileCount); - - memset(filenameBuffer, 0, FILENAME_BUFFER_SIZE); - - int i; - int max_elements = (int)partitionHfs0FileCount; - char *nextFilename = filenameBuffer; - - filenamesCount = 0; - - for(i = 0; i < max_elements; i++) - { - u32 filename_offset = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + entryTable[i].filename_offset); - addStringToFilenameBuffer(partitionHfs0Header + filename_offset, &nextFilename); - } - - free(entryTable); - - return true; -} - -bool dumpFileFromPartition(FsDeviceOperator* fsOperator, u32 partition, u32 file, char *filename) -{ - if (!partitionHfs0Header) + if (!filename || !*filename) { - uiDrawString("HFS0 partition header information unavailable!", 0, breaks * font_height, 255, 0, 0); + uiDrawString("Filename unavailable!", 0, breaks * font_height, 255, 0, 0); return false; } - if (!filename || !*filename) + char *dumpName = generateDumpName(); + if (!dumpName) { - uiDrawString("Filename unavailable!", 0, breaks * font_height, 255, 0, 0); + uiDrawString("Error: unable to generate output dump name!", 0, breaks * font_height, 255, 0, 0); return false; } @@ -1572,13 +1947,13 @@ bool dumpFileFromPartition(FsDeviceOperator* fsOperator, u32 partition, u32 file { if (file_size <= freeSpace) { - char destCopyPath[NAME_BUF_LEN] = {'\0'}; - snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); + char destCopyPath[NAME_BUF_LEN * 2] = {'\0'}; + snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s - Partition %u (%s)", dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition)); if ((strlen(destCopyPath) + 1 + strlen(filename)) < NAME_BUF_LEN) { mkdir(destCopyPath, 0744); - snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s v%u (%016lX) - Partition %u (%s)/%s", fixedGameCardName, gameCardVersion, gameCardTitleID, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), filename); + snprintf(destCopyPath, sizeof(destCopyPath) / sizeof(destCopyPath[0]), "sdmc:/%s - Partition %u (%s)/%s", dumpName, partition, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, partition), filename); uiDrawString("Hold B to cancel.", 0, breaks * font_height, 255, 255, 255); breaks += 2; @@ -1604,6 +1979,8 @@ bool dumpFileFromPartition(FsDeviceOperator* fsOperator, u32 partition, u32 file uiDrawString("Error: unable to get file details from the partition HFS0 header!", 0, breaks * font_height, 255, 0, 0); } + free(dumpName); + return success; } @@ -1615,8 +1992,17 @@ bool dumpGameCertificate(FsDeviceOperator* fsOperator) FsStorage gameCardStorage; bool success = false; FILE *outFile = NULL; - char filename[NAME_BUF_LEN] = {'\0'}; + char filename[NAME_BUF_LEN * 2] = {'\0'}; char *buf = NULL; + size_t write_res; + + char *dumpName = generateDumpName(); + if (!dumpName) + { + uiDrawString("Error: unable to generate output dump name!", 0, breaks * font_height, 255, 0, 0); + breaks += 2; + return false; + } workaroundPartitionZeroAccess(fsOperator); @@ -1642,9 +2028,9 @@ bool dumpGameCertificate(FsDeviceOperator* fsOperator) // Calculate CRC32 crc32(buf, CERT_SIZE, &crc); - snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s v%u (%016lX) - Certificate (%08X).bin", fixedGameCardName, gameCardVersion, gameCardTitleID, crc); + snprintf(filename, sizeof(filename) / sizeof(filename[0]), "sdmc:/%s - Certificate (%08X).bin", dumpName, crc); - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping game card certificate to file \"%s\"...", filename); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Dumping game card certificate to \"%s\"...", filename); uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); breaks += 2; @@ -1659,12 +2045,14 @@ bool dumpGameCertificate(FsDeviceOperator* fsOperator) outFile = fopen(filename, "wb"); if (outFile) { - if (fwrite(buf, 1, CERT_SIZE, outFile) == CERT_SIZE) + write_res = fwrite(buf, 1, CERT_SIZE, outFile); + if (write_res == CERT_SIZE) { success = true; uiDrawString("Process successfully completed!", 0, breaks * font_height, 0, 255, 0); } else { - uiDrawString("Failed to write certificate data!", 0, breaks * font_height, 255, 0, 0); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to write %u bytes certificate data! (wrote %lu bytes)", CERT_SIZE, write_res); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); } fclose(outFile); @@ -1698,5 +2086,7 @@ bool dumpGameCertificate(FsDeviceOperator* fsOperator) breaks += 2; + free(dumpName); + return success; } diff --git a/source/dumper.h b/source/dumper.h index 25d11154..8bf4767f 100644 --- a/source/dumper.h +++ b/source/dumper.h @@ -5,89 +5,25 @@ #include -#define DUMP_BUFFER_SIZE (u64)0x100000 // 1 MiB +#define DUMP_BUFFER_SIZE (u64)0x100000 // 1 MiB (1048576 bytes) #define ISTORAGE_PARTITION_CNT 2 -#define SPLIT_FILE_MIN (u64)0xEE6B2800 // 4 GB (4000000000 bytes) -#define SPLIT_FILE_2GiB (u64)0x80000000 -#define MEDIA_UNIT_SIZE 0x200 +#define FAT32_FILESIZE_LIMIT (u64)0xFFFFFFFF // 4 GiB - 1 (4294967295 bytes) + +#define SPLIT_FILE_XCI_PART_SIZE (u64)0xFFFF8000 // 4 GiB - 0x8000 (4294934528 bytes) (based on XCI-Cutter) +#define SPLIT_FILE_NSP_PART_SIZE (u64)0xFFFF0000 // 4 GiB - 0x10000 (4294901760 bytes) (based on splitNSP.py) +#define SPLIT_FILE_GENERIC_PART_SIZE SPLIT_FILE_XCI_PART_SIZE #define CERT_OFFSET 0x7000 #define CERT_SIZE 0x200 -#define GAMECARD_HEADER_SIZE 0x200 -#define GAMECARD_SIZE_ADDR 0x10D -#define GAMECARD_DATAEND_ADDR 0x118 - -#define HFS0_OFFSET_ADDR 0x130 -#define HFS0_SIZE_ADDR 0x138 -#define HFS0_MAGIC 0x48465330 // "HFS0" -#define HFS0_FILE_COUNT_ADDR 0x04 -#define HFS0_STR_TABLE_SIZE_ADDR 0x08 -#define HFS0_ENTRY_TABLE_ADDR 0x10 - -#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")))) -#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown")) - -#define HFS0_TO_ISTORAGE_IDX(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? ((y) < 2 ? 0 : 1) : ((y) < 3 ? 0 : 1)) - -#define GAMECARD_SIZE_1GiB (u64)0x40000000 -#define GAMECARD_SIZE_2GiB (u64)0x80000000 -#define GAMECARD_SIZE_4GiB (u64)0x100000000 -#define GAMECARD_SIZE_8GiB (u64)0x200000000 -#define GAMECARD_SIZE_16GiB (u64)0x400000000 -#define GAMECARD_SIZE_32GiB (u64)0x800000000 - -/* Reference: https://switchbrew.org/wiki/Title_list */ -#define GAMECARD_UPDATE_TITLEID (u64)0x0100000000000816 - -#define SYSUPDATE_100 (u32)450 -#define SYSUPDATE_200 (u32)65796 -#define SYSUPDATE_210 (u32)131162 -#define SYSUPDATE_220 (u32)196628 -#define SYSUPDATE_230 (u32)262164 -#define SYSUPDATE_300 (u32)201327002 -#define SYSUPDATE_301 (u32)201392178 -#define SYSUPDATE_302 (u32)201457684 -#define SYSUPDATE_400 (u32)268435656 -#define SYSUPDATE_401 (u32)268501002 -#define SYSUPDATE_410 (u32)269484082 -#define SYSUPDATE_500 (u32)335544750 -#define SYSUPDATE_501 (u32)335609886 -#define SYSUPDATE_502 (u32)335675432 -#define SYSUPDATE_510 (u32)336592976 -#define SYSUPDATE_600 (u32)402653544 -#define SYSUPDATE_601 (u32)402718730 -#define SYSUPDATE_610 (u32)403701850 -#define SYSUPDATE_620 (u32)404750376 -#define SYSUPDATE_700 (u32)469762248 -#define SYSUPDATE_701 (u32)469827614 -#define SYSUPDATE_800 (u32)536871442 - -#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 - #define SMOOTHING_FACTOR (double)0.01 -typedef struct -{ - u64 file_offset; - u64 file_size; - u32 filename_offset; - u32 hashed_region_size; - u64 reserved; - u8 hashed_region_sha256[0x20]; -} PACKED hfs0_entry_table; - -bool getRootHfs0Header(FsDeviceOperator* fsOperator); -bool dumpGameCartridge(FsDeviceOperator* fsOperator, bool isFat32, bool dumpCert, bool trimDump, bool calcCrc); +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 getHfs0FileList(FsDeviceOperator* fsOperator, u32 partition); bool dumpFileFromPartition(FsDeviceOperator* fsOperator, u32 partition, u32 file, char *filename); bool dumpGameCertificate(FsDeviceOperator *fsOperator); diff --git a/source/extkeys.c b/source/extkeys.c new file mode 100644 index 00000000..1cb1ff8f --- /dev/null +++ b/source/extkeys.c @@ -0,0 +1,250 @@ +#include +#include +#include + +#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; +} diff --git a/source/extkeys.h b/source/extkeys.h new file mode 100644 index 00000000..116739f4 --- /dev/null +++ b/source/extkeys.h @@ -0,0 +1,18 @@ +#pragma once + +#ifndef __EXTKEYS_H__ +#define __EXTKEYS_H__ + +#include +#include + +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 \ No newline at end of file diff --git a/source/main.c b/source/main.c index 9638fa93..534aafab 100644 --- a/source/main.c +++ b/source/main.c @@ -1,7 +1,6 @@ #include #include #include -#include #include #include @@ -9,6 +8,7 @@ #include "ui.h" #include "util.h" #include "fsext.h" +#include "extkeys.h" /* Extern variables */ @@ -19,11 +19,12 @@ extern Handle fsGameCardEventHandle; extern Event fsGameCardKernelEvent; extern UEvent exitEvent; -extern char *hfs0_header; -extern char *partitionHfs0Header; - extern bool gameCardInserted; +extern char strbuf[NAME_BUF_LEN * 4]; + +extern nca_keyset_t nca_keyset; + int main(int argc, char *argv[]) { /* Initialize UI */ @@ -31,7 +32,6 @@ int main(int argc, char *argv[]) int ret = 0; Result result; - char strbuf[512] = {'\0'}; /* Initialize the fsp-srv service */ result = fsInitialize(); @@ -79,6 +79,9 @@ int main(int argc, char *argv[]) result = threadStart(&thread); if (R_SUCCEEDED(result)) { + /* Zero out NCA keyset */ + memset(&nca_keyset, 0, sizeof(nca_keyset_t)); + /* Main application loop */ bool exitLoop = false; while(appletMainLoop()) @@ -95,6 +98,12 @@ int main(int argc, char *argv[]) case resultDumpXci: uiSetState(stateDumpXci); break; + case resultShowNspDumpMenu: + uiSetState(stateNspDumpMenu); + break; + case resultDumpNsp: + uiSetState(stateDumpNsp); + break; case resultShowRawPartitionDumpMenu: uiSetState(stateRawPartitionDumpMenu); break; @@ -231,9 +240,8 @@ int main(int argc, char *argv[]) ret = -2; } - /* Free resources */ - if (hfs0_header != NULL) free(hfs0_header); - if (partitionHfs0Header != NULL) free(partitionHfs0Header); + /* Free gamecard resources */ + freeGameCardInfo(); /* Deinitialize UI */ uiDeinit(); diff --git a/source/ui.c b/source/ui.c index edc0e6c8..d9ba198f 100644 --- a/source/ui.c +++ b/source/ui.c @@ -33,9 +33,12 @@ extern char *partitionHfs0Header; extern u64 partitionHfs0HeaderOffset, partitionHfs0HeaderSize; extern u32 partitionHfs0FileCount, partitionHfs0StrTableSize; -extern u64 gameCardTitleID; -extern u32 gameCardVersion; -extern char gameCardName[0x201], fixedGameCardName[0x201], gameCardAuthor[0x101], gameCardVersionStr[64]; +extern u32 gameCardAppCount; +extern u64 *gameCardTitleID; + +extern char **gameCardName; +extern char **gameCardAuthor; +extern char **gameCardVersionStr; extern char gameCardUpdateVersionStr[128]; @@ -43,6 +46,8 @@ extern char *filenameBuffer; extern char *filenames[FILENAME_MAX_CNT]; extern int filenamesCount; +extern char strbuf[NAME_BUF_LEN * 4]; + /* Statically allocated variables */ static PlFontData font; @@ -60,6 +65,7 @@ int font_height = 0; static u32 selectedPartitionIndex; static u32 selectedFileIndex; +static u32 selectedAppIndex; static bool highlight = false; @@ -68,13 +74,9 @@ static bool isFat32 = false, dumpCert = false, trimDump = false, calcCrc = true; static char statusMessage[2048] = {'\0'}; static int statusMessageFadeout = 0; -static int headlineCnt = 0; - u64 freeSpace = 0; static char freeSpaceStr[64] = {'\0'}; -static char titlebuf[NAME_BUF_LEN * 2] = {'\0'}; - static const int maxListElements = 15; static UIState uiState; @@ -82,8 +84,9 @@ static UIState uiState; static const char *appHeadline = "Nintendo Switch Game Card Dump Tool v" APP_VERSION ".\nOriginal codebase by MCMrARM.\nUpdated and maintained by DarkMatterCore.\n\n"; static const char *appControls = "[D-Pad / Analog Sticks] Move | [A] Select | [B] Back | [+] Exit"; -static const char *mainMenuItems[] = { "Full XCI dump", "Raw partition dump", "Partition data dump", "View game card files", "Dump game card certificate", "Update NSWDB.COM XML database", "Update application" }; +static const char *mainMenuItems[] = { "Cartridge Image (XCI) dump", "Nintendo Submission Package (NSP) dump", "Raw partition dump", "Partition data dump", "View game card files", "Dump game card certificate", "Update NSWDB.COM XML database", "Update application" }; static const char *xciDumpMenuItems[] = { "Start XCI dump process", "Split output dump (FAT32 support): ", "Dump certificate: ", "Trim output dump: ", "CRC32 checksum calculation + dump verification: " }; +static const char *nspDumpMenuItems[] = { "Start NSP dump process", "Split output dump (FAT32 support): ", "CRC32 checksum calculation: ", "Bundled application to dump: " }; static const char *partitionDumpType1MenuItems[] = { "Dump partition 0 (Update)", "Dump partition 1 (Normal)", "Dump partition 2 (Secure)" }; static const char *partitionDumpType2MenuItems[] = { "Dump partition 0 (Update)", "Dump partition 1 (Logo)", "Dump partition 2 (Normal)", "Dump partition 3 (Secure)" }; static const char *viewGameCardFsType1MenuItems[] = { "View files from partition 0 (Update)", "View files from partition 1 (Normal)", "View files from partition 2 (Secure)" }; @@ -192,6 +195,29 @@ void uiDrawChar(FT_Bitmap *bitmap, int x, int y, u8 r, u8 g, u8 b) } } +void uiScroll() +{ + if (framebuf == NULL) + { + /* Begin new frame */ + u32 stride; + framebuf = (u32*)framebufferBegin(&fb, &stride); + framebuf_width = (stride / sizeof(u32)); + } + + u32 lx, ly; + + for (ly = 0; ly < (FB_HEIGHT - font_height); ly++) + { + for (lx = 0; lx < FB_WIDTH; lx++) + { + framebuf[(ly * framebuf_width) + lx] = framebuf[((ly + font_height) * framebuf_width) + lx]; + } + } + + uiFill(0, FB_HEIGHT - font_height, FB_WIDTH, font_height, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); +} + void uiDrawString(const char *string, int x, int y, u8 r, u8 g, u8 b) { u32 tmpx = x; @@ -213,6 +239,12 @@ void uiDrawString(const char *string, int x, int y, u8 r, u8 g, u8 b) framebuf_width = (stride / sizeof(u32)); } + if (tmpy >= FB_HEIGHT) + { + tmpy = (FB_HEIGHT - font_height); + uiScroll(); + } + for(i = 0; i < str_size;) { unitcount = decode_utf8(&tmpchar, (const uint8_t*)&string[i]); @@ -221,8 +253,9 @@ void uiDrawString(const char *string, int x, int y, u8 r, u8 g, u8 b) if (tmpchar == '\n') { - tmpx = x; + tmpx = 0; tmpy += font_height; + breaks++; continue; } else if (tmpchar == '\t') @@ -242,6 +275,13 @@ void uiDrawString(const char *string, int x, int y, u8 r, u8 g, u8 b) if (ret) break; + if ((tmpx + (slot->advance.x >> 6)) >= FB_WIDTH) + { + tmpx = 0; + tmpy += font_height; + breaks++; + } + uiDrawChar(&slot->bitmap, tmpx + slot->bitmap_left, tmpy - slot->bitmap_top, r, g, b); tmpx += (slot->advance.x >> 6); @@ -273,26 +313,22 @@ void uiUpdateStatusMsg() { if (!strlen(statusMessage) || !statusMessageFadeout) return; - uiFill(0, FB_HEIGHT - (font_height * 2), FB_WIDTH, font_height * 2, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); - if ((statusMessageFadeout - 4) > BG_COLOR_RGB) { int fadeout = (statusMessageFadeout > 255 ? 255 : statusMessageFadeout); + uiFill(0, FB_HEIGHT - (font_height * 2), FB_WIDTH, font_height, BG_COLOR_RGB, BG_COLOR_RGB, BG_COLOR_RGB); uiDrawString(statusMessage, 0, FB_HEIGHT - (font_height * 2), fadeout, fadeout, fadeout); statusMessageFadeout -= 4; } else { statusMessageFadeout = 0; } - - uiRefreshDisplay(); } -void uiPleaseWait() +void uiPleaseWait(u8 wait) { - breaks = headlineCnt; uiDrawString("Please wait...", 0, breaks * font_height, 115, 115, 255); uiRefreshDisplay(); - delay(3); + if (wait) delay(wait); } void uiUpdateFreeSpace() @@ -312,8 +348,9 @@ void uiClearScreen() void uiPrintHeadline() { + breaks = 0; uiClearScreen(); - uiDrawString(appHeadline, 0, font_height, 255, 255, 255); + uiDrawString(appHeadline, 0, 0, 255, 255, 255); } int error_screen(const char *fmt, ...) @@ -394,18 +431,17 @@ int uiInit() uiState = stateMainMenu; cursor = 0; scroll = 0; - headlineCnt = 1; - - filenameBuffer = (char*)malloc(FILENAME_BUFFER_SIZE); - int i, headlineLen = strlen(appHeadline); - for(i = 0; i < headlineLen; i++) + filenameBuffer = (char*)calloc(1, FILENAME_BUFFER_SIZE); + if (!filenameBuffer) { - if (appHeadline[i] == '\n') headlineCnt++; + framebufferClose(&fb); + FT_Done_Face(face); + FT_Done_FreeType(library); + plExit(); + return error_screen("Failed to allocate memory for the filename buffer.\n"); } - if (headlineCnt == 1) headlineCnt += 2; - uiUpdateFreeSpace(); /* Disable screen dimming and auto sleep */ @@ -462,7 +498,6 @@ UIResult uiProcess() UIResult res = resultNone; int i, j; - breaks = headlineCnt; const char **menu = NULL; int menuItemsCount = 0; @@ -473,7 +508,7 @@ UIResult uiProcess() uiPrintHeadline(); loadGameCardInfo(); - if (uiState == stateMainMenu || uiState == stateXciDumpMenu || uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu || uiState == stateViewGameCardFsBrowser) + if (uiState == stateMainMenu || uiState == stateXciDumpMenu || uiState == stateNspDumpMenu || uiState == stateRawPartitionDumpMenu || uiState == statePartitionDataDumpMenu || uiState == stateViewGameCardFsMenu || uiState == stateViewGameCardFsBrowser) { uiDrawString(appControls, 0, breaks * font_height, 255, 255, 255); breaks += 2; @@ -483,51 +518,62 @@ UIResult uiProcess() if (uiState != stateViewGameCardFsBrowser) { - if (gameCardInserted && hfs0_header != NULL && (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) && gameCardTitleID != 0) + if (gameCardInserted && hfs0_header != NULL && (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) && gameCardAppCount > 0 && gameCardTitleID != NULL) { - uiDrawString("Game Card is inserted!", 0, breaks * font_height, 0, 255, 0); + uiDrawString("Game card is inserted!", 0, breaks * font_height, 0, 255, 0); breaks += 2; - /*snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Root HFS0 header offset: 0x%016lX", hfs0_offset); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Root HFS0 header offset: 0x%016lX", hfs0_offset); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); breaks++; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Root HFS0 header size: 0x%016lX", hfs0_size); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Root HFS0 header size: 0x%016lX", hfs0_size); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); breaks++;*/ - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Name: %s", gameCardName); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); - breaks++; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Developer: %s", gameCardAuthor); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); - breaks++; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Title ID: %016lX", gameCardTitleID); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); - breaks++; - - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Version: %s", gameCardVersionStr); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); - breaks++; + u32 app; + for(app = 0; app < gameCardAppCount; app++) + { + if (gameCardAppCount > 1) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Bundled application #%u:", app + 1); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + } + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Name: %s", gameCardName[app]); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Developer: %s", gameCardAuthor[app]); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Title ID: %016lX", gameCardTitleID[app]); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Version: %s", gameCardVersionStr[app]); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + breaks += (gameCardAppCount > 1 ? 2 : 1); + } - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Size: %s", gameCardSizeStr); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Size: %s", gameCardSizeStr); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); breaks++; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Used space: %s", trimmedCardSizeStr); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Used space: %s", trimmedCardSizeStr); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); breaks++; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Partition count: %u (%s)", hfs0_partition_cnt, GAMECARD_TYPE(hfs0_partition_cnt)); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition count: %u (%s)", hfs0_partition_cnt, GAMECARD_TYPE(hfs0_partition_cnt)); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); if (strlen(gameCardUpdateVersionStr)) { breaks++; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Bundled FW update: %s", gameCardUpdateVersionStr); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Bundled FW update: %s", gameCardUpdateVersionStr); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); } } else { if (gameCardInserted) @@ -536,26 +582,31 @@ UIResult uiProcess() { if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) { - uiDrawString("Error: unable to retrieve the game card Title ID!", 0, breaks * font_height, 255, 0, 0); - - if (strlen(gameCardUpdateVersionStr)) + if (gameCardAppCount > 0) { - breaks++; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Bundled FW Update: %s", gameCardUpdateVersionStr); - uiDrawString(titlebuf, 0, breaks * font_height, 0, 255, 0); - breaks++; + uiDrawString("Error: unable to retrieve the game card Title ID!", 0, breaks * font_height, 255, 0, 0); - uiDrawString("In order to be able to dump data from this cartridge, make sure your console is at least on this FW version.", 0, breaks * font_height, 255, 255, 255); + if (strlen(gameCardUpdateVersionStr)) + { + breaks++; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Bundled FW Update: %s", gameCardUpdateVersionStr); + uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + breaks++; + + uiDrawString("In order to be able to dump data from this cartridge, make sure your console is at least on this FW version.", 0, breaks * font_height, 255, 255, 255); + } + } else { + uiDrawString("Error: gamecard application count is zero!", 0, breaks * font_height, 255, 0, 0); } } else { - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Error: unknown root HFS0 header partition count! (%u)", hfs0_partition_cnt); - uiDrawString(titlebuf, 0, breaks * font_height, 255, 0, 0); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unknown root HFS0 header partition count! (%u)", hfs0_partition_cnt); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); } } else { uiDrawString("Error: unable to get root HFS0 header data!", 0, breaks * font_height, 255, 0, 0); } } else { - uiDrawString("Game Card is not inserted!", 0, breaks * font_height, 255, 0, 0); + uiDrawString("Game card is not inserted!", 0, breaks * font_height, 255, 0, 0); } res = resultShowMainMenu; @@ -564,7 +615,7 @@ UIResult uiProcess() breaks += 2; } - if (gameCardInserted && hfs0_header != NULL && (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) && gameCardTitleID != 0) + if (gameCardInserted && hfs0_header != NULL && (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT || hfs0_partition_cnt == GAMECARD_TYPE2_PARTITION_CNT) && gameCardAppCount > 0 && gameCardTitleID != NULL) { switch(uiState) { @@ -578,20 +629,27 @@ UIResult uiProcess() uiDrawString(mainMenuItems[0], 0, breaks * font_height, 115, 115, 255); + break; + case stateNspDumpMenu: + menu = nspDumpMenuItems; + menuItemsCount = sizeof(nspDumpMenuItems) / sizeof(nspDumpMenuItems[0]); + + uiDrawString(mainMenuItems[1], 0, breaks * font_height, 115, 115, 255); + break; case stateRawPartitionDumpMenu: case statePartitionDataDumpMenu: menu = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems : partitionDumpType2MenuItems); menuItemsCount = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? (sizeof(partitionDumpType1MenuItems) / sizeof(partitionDumpType1MenuItems[0])) : (sizeof(partitionDumpType2MenuItems) / sizeof(partitionDumpType2MenuItems[0]))); - uiDrawString((uiState == stateRawPartitionDumpMenu ? mainMenuItems[1] : mainMenuItems[2]), 0, breaks * font_height, 115, 115, 255); + uiDrawString((uiState == stateRawPartitionDumpMenu ? mainMenuItems[2] : mainMenuItems[3]), 0, breaks * font_height, 115, 115, 255); break; case stateViewGameCardFsMenu: menu = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems : viewGameCardFsType2MenuItems); menuItemsCount = (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? (sizeof(viewGameCardFsType1MenuItems) / sizeof(viewGameCardFsType1MenuItems[0])) : (sizeof(viewGameCardFsType2MenuItems) / sizeof(viewGameCardFsType2MenuItems[0]))); - uiDrawString(mainMenuItems[3], 0, breaks * font_height, 115, 115, 255); + uiDrawString(mainMenuItems[4], 0, breaks * font_height, 115, 115, 255); break; case stateViewGameCardFsBrowser: @@ -601,8 +659,8 @@ UIResult uiProcess() uiDrawString((hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems[selectedPartitionIndex] : viewGameCardFsType2MenuItems[selectedPartitionIndex]), 0, breaks * font_height, 115, 115, 255); breaks += 2; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "File count: %d | Current file: %d", menuItemsCount, cursor + 1); - uiDrawString(titlebuf, 0, breaks * font_height, 255, 255, 255); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "File count: %d | Current file: %d", menuItemsCount, cursor + 1); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); breaks++; break; @@ -620,65 +678,103 @@ UIResult uiProcess() { if (j >= maxListElements) break; - if ((j + scroll) == cursor) + // Avoid printing the "Bundled application to dump" option in the NSP dump menu if we're not dealing with a multigame cart + if (uiState != stateNspDumpMenu || (uiState == stateNspDumpMenu && (j < 3 || (j == 3 && gameCardAppCount > 1)))) { - highlight = true; - uiFill(0, (breaks * font_height) + (j * (font_height + 12)), FB_WIDTH / 2, font_height + 12, HIGHLIGHT_BG_COLOR_R, HIGHLIGHT_BG_COLOR_G, HIGHLIGHT_BG_COLOR_B); - uiDrawString(menu[i], 0, (breaks * font_height) + (j * (font_height + 12)) + 6, HIGHLIGHT_FONT_COLOR_R, HIGHLIGHT_FONT_COLOR_G, HIGHLIGHT_FONT_COLOR_B); - highlight = false; - } else { - uiDrawString(menu[i], 0, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 255, 255); - } - - // Print XCI dump menu settings values - if (uiState == stateXciDumpMenu && j > 0) - { - if ((j + scroll) == cursor) highlight = true; + if ((j + scroll) == cursor) + { + highlight = true; + uiFill(0, (breaks * font_height) + (j * (font_height + 12)), FB_WIDTH / 2, font_height + 12, HIGHLIGHT_BG_COLOR_R, HIGHLIGHT_BG_COLOR_G, HIGHLIGHT_BG_COLOR_B); + uiDrawString(menu[i], 0, (breaks * font_height) + (j * (font_height + 12)) + 6, HIGHLIGHT_FONT_COLOR_R, HIGHLIGHT_FONT_COLOR_G, HIGHLIGHT_FONT_COLOR_B); + highlight = false; + } else { + uiDrawString(menu[i], 0, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 255, 255); + } - switch(j) + // Print XCI dump menu settings values + if (uiState == stateXciDumpMenu && j > 0) { - case 1: // Split output dump (FAT32 support) - if (isFat32) - { - uiDrawString("Yes", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); - } else { - uiDrawString("No", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); - } - break; - case 2: // Dump certificate - if (dumpCert) - { - uiDrawString("Yes", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); - } else { - uiDrawString("No", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); - } - break; - case 3: // Trim output dump - if (trimDump) - { - uiDrawString("Yes", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); - } else { - uiDrawString("No", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); - } - break; - case 4: // CRC32 checksum calculation + dump verification - if (calcCrc) - { - uiDrawString("Yes", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); - } else { - uiDrawString("No", XCIDUMP_OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); - } - break; - default: - break; + if ((j + scroll) == cursor) highlight = true; + + switch(j) + { + case 1: // Split output dump (FAT32 support) + if (isFat32) + { + uiDrawString("Yes", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + case 2: // Dump certificate + if (dumpCert) + { + uiDrawString("Yes", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + case 3: // Trim output dump + if (trimDump) + { + uiDrawString("Yes", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + case 4: // CRC32 checksum calculation + dump verification + if (calcCrc) + { + uiDrawString("Yes", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + default: + break; + } + + if ((j + scroll) == cursor) highlight = false; } - if ((j + scroll) == cursor) highlight = false; + // Print NSP dump menu settings values + if (uiState == stateNspDumpMenu && j > 0) + { + if ((j + scroll) == cursor) highlight = true; + + switch(j) + { + case 1: // Split output dump (FAT32 support) + if (isFat32) + { + uiDrawString("Yes", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + case 2: // CRC32 checksum calculation + if (calcCrc) + { + uiDrawString("Yes", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 0, 255, 0); + } else { + uiDrawString("No", OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 0, 0); + } + break; + case 3: // Bundled application to dump + uiDrawString(gameCardName[selectedAppIndex], OPTIONS_X_POS, (breaks * font_height) + (j * (font_height + 12)) + 6, 255, 255, 255); + break; + default: + break; + } + + if ((j + scroll) == cursor) highlight = false; + } } } } } + uiUpdateStatusMsg(); uiRefreshDisplay(); hidScanInput(); @@ -748,6 +844,67 @@ UIResult uiProcess() // Go up if ((keysDown & KEY_DUP) || (keysHeld & KEY_LSTICK_UP) || (keysHeld & KEY_RSTICK_UP)) scrollAmount = -1; + // Go down + if ((keysDown & KEY_DDOWN) || (keysHeld & KEY_LSTICK_DOWN) || (keysHeld & KEY_RSTICK_DOWN)) scrollAmount = 1; + } else + if (uiState == stateNspDumpMenu) + { + // Select + if ((keysDown & KEY_A) && cursor == 0) res = resultDumpNsp; + + // Back + if (keysDown & KEY_B) res = resultShowMainMenu; + + // Change option to false + if (keysDown & KEY_LEFT) + { + switch(cursor) + { + case 1: // Split output dump (FAT32 support) + isFat32 = false; + break; + case 2: // CRC32 checksum calculation + calcCrc = false; + break; + case 3: // Bundled application to dump + if (selectedAppIndex > 0) + { + selectedAppIndex--; + } else { + selectedAppIndex = 0; + } + break; + default: + break; + } + } + + // Change option to true + if (keysDown & KEY_RIGHT) + { + switch(cursor) + { + case 1: // Split output dump (FAT32 support) + isFat32 = true; + break; + case 2: // CRC32 checksum calculation + calcCrc = true; + break; + case 3: // Bundled application to dump + if (gameCardAppCount > 1) + { + selectedAppIndex++; + if (selectedAppIndex >= gameCardAppCount) selectedAppIndex = (gameCardAppCount - 1); + } + break; + default: + break; + } + } + + // Go up + if ((keysDown & KEY_DUP) || (keysHeld & KEY_LSTICK_UP) || (keysHeld & KEY_RSTICK_UP)) scrollAmount = -1; + // Go down if ((keysDown & KEY_DDOWN) || (keysHeld & KEY_LSTICK_DOWN) || (keysHeld & KEY_RSTICK_DOWN)) scrollAmount = 1; } else { @@ -762,21 +919,25 @@ UIResult uiProcess() res = resultShowXciDumpMenu; break; case 1: - res = resultShowRawPartitionDumpMenu; + selectedAppIndex = 0; + res = resultShowNspDumpMenu; break; case 2: - res = resultShowPartitionDataDumpMenu; + res = resultShowRawPartitionDumpMenu; break; case 3: - res = resultShowViewGameCardFsMenu; + res = resultShowPartitionDataDumpMenu; break; case 4: - res = resultDumpGameCardCertificate; + res = resultShowViewGameCardFsMenu; break; case 5: - res = resultUpdateNSWDBXml; + res = resultDumpGameCardCertificate; break; case 6: + res = resultUpdateNSWDBXml; + break; + case 7: res = resultUpdateApplication; break; default: @@ -863,6 +1024,9 @@ UIResult uiProcess() } } } + + // Avoid placing the cursor on the "Bundled application to dump" option in the NSP dump menu if we're not dealing with multigame carts + if (uiState == stateNspDumpMenu && cursor == 3 && gameCardAppCount == 1) cursor = 2; } } } else @@ -871,35 +1035,65 @@ UIResult uiProcess() uiDrawString(mainMenuItems[0], 0, breaks * font_height, 115, 115, 255); breaks++; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[1], (isFat32 ? "Yes" : "No")); - uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%s%s", xciDumpMenuItems[1], (isFat32 ? "Yes" : "No")); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); breaks++; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[2], (dumpCert ? "Yes" : "No")); - uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%s%s", xciDumpMenuItems[2], (dumpCert ? "Yes" : "No")); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); breaks++; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[3], (trimDump ? "Yes" : "No")); - uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%s%s", xciDumpMenuItems[3], (trimDump ? "Yes" : "No")); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); breaks++; - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "%s%s", xciDumpMenuItems[4], (calcCrc ? "Yes" : "No")); - uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%s%s", xciDumpMenuItems[4], (calcCrc ? "Yes" : "No")); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); breaks += 2; uiRefreshDisplay(); - dumpGameCartridge(&fsOperatorInstance, isFat32, dumpCert, trimDump, calcCrc); + dumpCartridgeImage(&fsOperatorInstance, isFat32, dumpCert, trimDump, calcCrc); waitForButtonPress(); uiUpdateFreeSpace(); res = resultShowXciDumpMenu; } else + if (uiState == stateDumpNsp) + { + uiDrawString(mainMenuItems[1], 0, breaks * font_height, 115, 115, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%s%s", nspDumpMenuItems[1], (isFat32 ? "Yes" : "No")); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%s%s", nspDumpMenuItems[2], (calcCrc ? "Yes" : "No")); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); + + if (gameCardAppCount > 1) + { + breaks++; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "%s%s", nspDumpMenuItems[3], gameCardName[selectedAppIndex]); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); + } + + breaks += 2; + + uiRefreshDisplay(); + + dumpApplicationNSP(&fsOperatorInstance, isFat32, calcCrc, selectedAppIndex); + + waitForButtonPress(); + + uiUpdateFreeSpace(); + res = resultShowNspDumpMenu; + } else if (uiState == stateDumpRawPartition) { - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Raw %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedPartitionIndex] : partitionDumpType2MenuItems[selectedPartitionIndex])); - uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Raw %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedPartitionIndex] : partitionDumpType2MenuItems[selectedPartitionIndex])); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); breaks += 2; uiRefreshDisplay(); @@ -913,8 +1107,8 @@ UIResult uiProcess() } else if (uiState == stateDumpPartitionData) { - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Data %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedPartitionIndex] : partitionDumpType2MenuItems[selectedPartitionIndex])); - uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Data %s", (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? partitionDumpType1MenuItems[selectedPartitionIndex] : partitionDumpType2MenuItems[selectedPartitionIndex])); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); breaks += 2; uiRefreshDisplay(); @@ -931,9 +1125,10 @@ UIResult uiProcess() uiDrawString((hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT ? viewGameCardFsType1MenuItems[selectedPartitionIndex] : viewGameCardFsType2MenuItems[selectedPartitionIndex]), 0, breaks * font_height, 115, 115, 255); breaks += 2; - uiRefreshDisplay(); + uiPleaseWait(0); + breaks += 2; - if (getHfs0FileList(&fsOperatorInstance, selectedPartitionIndex)) + if (getHfs0FileList(selectedPartitionIndex)) { cursor = 0; scroll = 0; @@ -946,8 +1141,8 @@ UIResult uiProcess() } else if (uiState == stateViewGameCardFsBrowserCopyFile) { - snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Manual File Dump: %s (Partition %u [%s])", filenames[selectedFileIndex], selectedPartitionIndex, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, selectedPartitionIndex)); - uiDrawString(titlebuf, 0, breaks * font_height, 115, 115, 255); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Manual File Dump: %s (Partition %u [%s])", filenames[selectedFileIndex], selectedPartitionIndex, GAMECARD_PARTITION_NAME(hfs0_partition_cnt, selectedPartitionIndex)); + uiDrawString(strbuf, 0, breaks * font_height, 115, 115, 255); breaks += 2; uiRefreshDisplay(); @@ -963,7 +1158,7 @@ UIResult uiProcess() } else if (uiState == stateDumpGameCardCertificate) { - uiDrawString(mainMenuItems[4], 0, breaks * font_height, 115, 115, 255); + uiDrawString(mainMenuItems[5], 0, breaks * font_height, 115, 115, 255); breaks += 2; dumpGameCertificate(&fsOperatorInstance); @@ -975,7 +1170,7 @@ UIResult uiProcess() } else if (uiState == stateUpdateNSWDBXml) { - uiDrawString(mainMenuItems[5], 0, breaks * font_height, 115, 115, 255); + uiDrawString(mainMenuItems[6], 0, breaks * font_height, 115, 115, 255); breaks += 2; updateNSWDBXml(); @@ -987,7 +1182,7 @@ UIResult uiProcess() } else if (uiState == stateUpdateApplication) { - uiDrawString(mainMenuItems[6], 0, breaks * font_height, 115, 115, 255); + uiDrawString(mainMenuItems[7], 0, breaks * font_height, 115, 115, 255); breaks += 2; updateApplication(); @@ -998,7 +1193,5 @@ UIResult uiProcess() res = resultShowMainMenu; } - uiUpdateStatusMsg(); - return res; } diff --git a/source/ui.h b/source/ui.h index a9ab3df0..b705ecaf 100644 --- a/source/ui.h +++ b/source/ui.h @@ -19,7 +19,7 @@ #define HIGHLIGHT_FONT_COLOR_G 255 #define HIGHLIGHT_FONT_COLOR_B 197 -#define XCIDUMP_OPTIONS_X_POS (35 * CHAR_PT_SIZE) +#define OPTIONS_X_POS (35 * CHAR_PT_SIZE) #define TAB_WIDTH 4 @@ -28,6 +28,8 @@ typedef enum { resultShowMainMenu, resultShowXciDumpMenu, resultDumpXci, + resultShowNspDumpMenu, + resultDumpNsp, resultShowRawPartitionDumpMenu, resultDumpRawPartition, resultShowPartitionDataDumpMenu, @@ -46,6 +48,8 @@ typedef enum { stateMainMenu, stateXciDumpMenu, stateDumpXci, + stateNspDumpMenu, + stateDumpNsp, stateRawPartitionDumpMenu, stateDumpRawPartition, statePartitionDataDumpMenu, @@ -69,7 +73,7 @@ void uiStatusMsg(const char *fmt, ...); void uiUpdateStatusMsg(); -void uiPleaseWait(); +void uiPleaseWait(u8 wait); void uiUpdateFreeSpace(); diff --git a/source/util.c b/source/util.c index 63d2bc7d..51e71736 100644 --- a/source/util.c +++ b/source/util.c @@ -15,11 +15,14 @@ #include #include #include +#include #include "dumper.h" #include "fsext.h" #include "ui.h" #include "util.h" +#include "aes.h" +#include "extkeys.h" /* Extern variables */ @@ -47,8 +50,12 @@ const char *gcDumpToolPath = "sdmc:/switch/gcdumptool.nro"; const char *userAgent = "gcdumptool/" APP_VERSION " (Nintendo Switch)"; +const char *keysFilePath = "sdmc:/switch/prod.keys"; + /* Statically allocated variables */ +nca_keyset_t nca_keyset; + static char *result_buf = NULL; static size_t result_sz = 0; static size_t result_written = 0; @@ -78,14 +85,21 @@ char *partitionHfs0Header = NULL; u64 partitionHfs0HeaderOffset = 0, partitionHfs0HeaderSize = 0; u32 partitionHfs0FileCount = 0, partitionHfs0StrTableSize = 0; -u64 gameCardTitleID = 0; -u32 gameCardVersion = 0; -char gameCardName[0x201] = {'\0'}, fixedGameCardName[0x201] = {'\0'}, gameCardAuthor[0x101] = {'\0'}, gameCardVersionStr[64] = {'\0'}; +u32 gameCardAppCount = 0; +u64 *gameCardTitleID = NULL; +u32 *gameCardVersion = NULL; + +char **gameCardName = NULL; +char **fixedGameCardName = NULL; +char **gameCardAuthor = NULL; +char **gameCardVersionStr = NULL; u64 gameCardUpdateTitleID = 0; u32 gameCardUpdateVersion = 0; char gameCardUpdateVersionStr[128] = {'\0'}; +char strbuf[NAME_BUF_LEN * 4] = {'\0'}; + bool isGameCardInserted() { bool inserted; @@ -127,265 +141,1444 @@ void delay(u8 seconds) uiRefreshDisplay(); } -bool getGameCardTitleIDAndVersion(u64 *titleID, u32 *version) +bool getGameCardTitleIDAndVersion() { + bool proceed = true; bool success = false; Result result; NcmContentMetaDatabase ncmDb; - NcmApplicationContentMetaKey *appList = (NcmApplicationContentMetaKey*)malloc(sizeof(NcmApplicationContentMetaKey)); - if (appList) + NcmApplicationContentMetaKey *appList = NULL; + NcmApplicationContentMetaKey *appListTmp = NULL; + size_t appListSize = sizeof(NcmApplicationContentMetaKey); + + u32 written = 0; + u32 total = 0; + + if (gameCardTitleID != NULL) + { + free(gameCardTitleID); + gameCardTitleID = NULL; + } + + if (gameCardVersion != NULL) + { + free(gameCardVersion); + gameCardVersion = NULL; + } + + appList = (NcmApplicationContentMetaKey*)calloc(1, appListSize); + if (appList) + { + if (R_SUCCEEDED(result = ncmOpenContentMetaDatabase(FsStorageId_GameCard, &ncmDb))) + { + if (R_SUCCEEDED(result = ncmContentMetaDatabaseListApplication(&ncmDb, META_DB_REGULAR_APPLICATION, appList, appListSize, &written, &total))) + { + if (written && total) + { + if (total > written) + { + appListSize *= total; + appListTmp = (NcmApplicationContentMetaKey*)realloc(appList, appListSize); + if (appListTmp) + { + appList = appListTmp; + memset(appList, 0, appListSize); + + if (R_SUCCEEDED(result = ncmContentMetaDatabaseListApplication(&ncmDb, META_DB_REGULAR_APPLICATION, appList, appListSize, &written, &total))) + { + if (written != total) + { + uiStatusMsg("getGameCardTitleIDAndVersion: application count mismatch in ncmContentMetaDatabaseListApplication (%u != %u)", written, total); + proceed = false; + } + } else { + uiStatusMsg("getGameCardTitleIDAndVersion: ncmContentMetaDatabaseListApplication failed! (0x%08X)", result); + proceed = false; + } + } else { + uiStatusMsg("getGameCardTitleIDAndVersion: error reallocating output buffer for ncmContentMetaDatabaseListApplication (%u %s).", total, (total == 1 ? "entry" : "entries")); + proceed = false; + } + } + + if (proceed) + { + gameCardTitleID = (u64*)calloc(total, sizeof(u64)); + gameCardVersion = (u32*)calloc(total, sizeof(u32)); + + if (gameCardTitleID != NULL && gameCardVersion != NULL) + { + u32 i; + for(i = 0; i < total; i++) + { + gameCardTitleID[i] = appList[i].metaRecord.titleId; + gameCardVersion[i] = appList[i].metaRecord.version; + } + + gameCardAppCount = total; + + success = true; + } else { + if (gameCardTitleID != NULL) + { + free(gameCardTitleID); + gameCardTitleID = NULL; + } + + if (gameCardVersion != NULL) + { + free(gameCardVersion); + gameCardVersion = NULL; + } + + uiStatusMsg("getGameCardTitleIDAndVersion: failed to allocate memory for TID/version buffer!"); + } + } + } else { + uiStatusMsg("getGameCardTitleIDAndVersion: ncmContentMetaDatabaseListApplication wrote no entries to output buffer!"); + } + } else { + uiStatusMsg("getGameCardTitleIDAndVersion: ncmContentMetaDatabaseListApplication failed! (0x%08X)", result); + } + } else { + uiStatusMsg("getGameCardTitleIDAndVersion: ncmOpenContentMetaDatabase failed! (0x%08X)", result); + } + + free(appList); + } else { + uiStatusMsg("getGameCardTitleIDAndVersion: unable to allocate memory for the ApplicationContentMetaKey struct."); + } + + return success; +} + +void convertTitleVersionToDecimal(u32 version, char *versionBuf, int versionBufSize) +{ + u8 major = (u8)((version >> 26) & 0x3F); + u8 middle = (u8)((version >> 20) & 0x3F); + u8 minor = (u8)((version >> 16) & 0xF); + u16 build = (u16)version; + + snprintf(versionBuf, versionBufSize, "%u (%u.%u.%u.%u)", version, major, middle, minor, build); +} + +bool getGameCardControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *authorBuf, int authorBufSize) +{ + if (titleID == 0) return false; + + bool success = false; + Result result; + size_t outsize = 0; + NsApplicationControlData *buf = NULL; + NacpLanguageEntry *langentry = NULL; + + buf = (NsApplicationControlData*)calloc(1, sizeof(NsApplicationControlData)); + if (buf) + { + if (R_SUCCEEDED(result = nsGetApplicationControlData(1, titleID, buf, sizeof(NsApplicationControlData), &outsize))) + { + if (outsize >= sizeof(buf->nacp)) + { + if (R_SUCCEEDED(result = nacpGetLanguageEntry(&buf->nacp, &langentry))) + { + strncpy(nameBuf, langentry->name, nameBufSize); + strncpy(authorBuf, langentry->author, authorBufSize); + success = true; + } else { + uiStatusMsg("getGameCardControlNacp: GetLanguageEntry failed! (0x%08X)", result); + } + } else { + uiStatusMsg("getGameCardControlNacp: Control.nacp buffer size (%u bytes) is too small! Expected: %u bytes", outsize, sizeof(buf->nacp)); + } + } else { + uiStatusMsg("getGameCardControlNacp: GetApplicationControlData failed! (0x%08X)", result); + } + + free(buf); + } else { + uiStatusMsg("getGameCardControlNacp: Unable to allocate memory for the ns service operations."); + } + + return success; +} + +void removeIllegalCharacters(char *name) +{ + u32 i, len = strlen(name); + for (i = 0; i < len; i++) + { + if (memchr("?[]/\\=+<>:;\",*|^", name[i], sizeof("?[]/\\=+<>:;\",*|^") - 1) || name[i] < 0x20 || name[i] > 0x7E) name[i] = '_'; + } +} + +void strtrim(char *str) +{ + if (!str || !*str) return; + + char *start = str; + char *end = start + strlen(str); + + while(--end >= start) + { + if (!isspace(*end)) break; + } + + *(++end) = '\0'; + + while(isspace(*start)) start++; + + if (start != str) memmove(str, start, end - start + 1); +} + +void freeStringsPtr(char **var) +{ + if (var) + { + u64 i; + for(i = 0; var[i]; i++) free(var[i]); + free(var); + } +} + +void freeGameCardInfo() +{ + if (hfs0_header != NULL) + { + free(hfs0_header); + hfs0_header = NULL; + } + + hfs0_offset = 0; + hfs0_size = 0; + hfs0_partition_cnt = 0; + + if (partitionHfs0Header != NULL) + { + free(partitionHfs0Header); + partitionHfs0Header = NULL; + } + + partitionHfs0HeaderOffset = 0; + partitionHfs0HeaderSize = 0; + partitionHfs0FileCount = 0; + partitionHfs0StrTableSize = 0; + + gameCardSize = 0; + memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); + + trimmedCardSize = 0; + memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + + if (gameCardTitleID != NULL) + { + free(gameCardTitleID); + gameCardTitleID = NULL; + } + + if (gameCardVersion != NULL) + { + free(gameCardVersion); + gameCardVersion = NULL; + } + + if (gameCardName != NULL) + { + freeStringsPtr(gameCardName); + gameCardName = NULL; + } + + if (fixedGameCardName != NULL) + { + freeStringsPtr(fixedGameCardName); + fixedGameCardName = NULL; + } + + if (gameCardAuthor != NULL) + { + freeStringsPtr(gameCardAuthor); + gameCardAuthor = NULL; + } + + if (gameCardVersionStr != NULL) + { + freeStringsPtr(gameCardVersionStr); + gameCardVersionStr = NULL; + } + + gameCardUpdateTitleID = 0; + gameCardUpdateVersion = 0; + memset(gameCardUpdateVersionStr, 0, sizeof(gameCardUpdateVersionStr)); +} + +bool getRootHfs0Header() +{ + u32 magic; + Result result; + FsGameCardHandle handle; + FsStorage gameCardStorage; + char gamecard_header[GAMECARD_HEADER_SIZE] = {'\0'}; + + hfs0_partition_cnt = 0; + + workaroundPartitionZeroAccess(&fsOperatorInstance); + + if (R_FAILED(result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle))) + { + uiStatusMsg("getRootHfs0Header: GetGameCardHandle failed! (0x%08X)", result); + return false; + } + + if (R_FAILED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, 0))) + { + uiStatusMsg("getRootHfs0Header: OpenGameCardStorage failed! (0x%08X)", result); + return false; + } + + if (R_FAILED(result = fsStorageRead(&gameCardStorage, 0, gamecard_header, GAMECARD_HEADER_SIZE))) + { + uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", GAMECARD_HEADER_SIZE, 0, result); + fsStorageClose(&gameCardStorage); + return false; + } + + u8 cardSize = (u8)gamecard_header[GAMECARD_SIZE_ADDR]; + + switch(cardSize) + { + case 0xFA: // 1 GiB + gameCardSize = GAMECARD_SIZE_1GiB; + break; + case 0xF8: // 2 GiB + gameCardSize = GAMECARD_SIZE_2GiB; + break; + case 0xF0: // 4 GiB + gameCardSize = GAMECARD_SIZE_4GiB; + break; + case 0xE0: // 8 GiB + gameCardSize = GAMECARD_SIZE_8GiB; + break; + case 0xE1: // 16 GiB + gameCardSize = GAMECARD_SIZE_16GiB; + break; + case 0xE2: // 32 GiB + gameCardSize = GAMECARD_SIZE_32GiB; + break; + default: + uiStatusMsg("getRootHfs0Header: Invalid game card size value: 0x%02X", cardSize); + fsStorageClose(&gameCardStorage); + return false; + } + + convertSize(gameCardSize, gameCardSizeStr, sizeof(gameCardSizeStr) / sizeof(gameCardSizeStr[0])); + + memcpy(&trimmedCardSize, gamecard_header + GAMECARD_DATAEND_ADDR, sizeof(u64)); + trimmedCardSize = (GAMECARD_HEADER_SIZE + (trimmedCardSize * MEDIA_UNIT_SIZE)); + convertSize(trimmedCardSize, trimmedCardSizeStr, sizeof(trimmedCardSizeStr) / sizeof(trimmedCardSizeStr[0])); + + memcpy(&hfs0_offset, gamecard_header + HFS0_OFFSET_ADDR, sizeof(u64)); + memcpy(&hfs0_size, gamecard_header + HFS0_SIZE_ADDR, sizeof(u64)); + + hfs0_header = (char*)malloc(hfs0_size); + if (!hfs0_header) + { + uiStatusMsg("getRootHfs0Header: Unable to allocate memory for the root HFS0 header!"); + + gameCardSize = 0; + memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); + + trimmedCardSize = 0; + memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + + hfs0_offset = 0; + hfs0_size = 0; + + fsStorageClose(&gameCardStorage); + + return false; + } + + if (R_FAILED(result = fsStorageRead(&gameCardStorage, hfs0_offset, hfs0_header, hfs0_size))) + { + uiStatusMsg("getRootHfs0Header: StorageRead failed to read %u-byte chunk from offset 0x%016lX! (0x%08X)", hfs0_size, hfs0_offset, result); + + gameCardSize = 0; + memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); + + trimmedCardSize = 0; + memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + + free(hfs0_header); + hfs0_header = NULL; + hfs0_offset = 0; + hfs0_size = 0; + + fsStorageClose(&gameCardStorage); + + return false; + } + + memcpy(&magic, hfs0_header, sizeof(u32)); + magic = bswap_32(magic); + if (magic != HFS0_MAGIC) + { + uiStatusMsg("getRootHfs0Header: Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC); + + gameCardSize = 0; + memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); + + trimmedCardSize = 0; + memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + + free(hfs0_header); + hfs0_header = NULL; + hfs0_offset = 0; + hfs0_size = 0; + + fsStorageClose(&gameCardStorage); + + return false; + } + + memcpy(&hfs0_partition_cnt, hfs0_header + HFS0_FILE_COUNT_ADDR, sizeof(u32)); + + fsStorageClose(&gameCardStorage); + + return true; +} + +void getGameCardUpdateInfo() +{ + Result result; + FsGameCardHandle handle; + + gameCardUpdateTitleID = 0; + gameCardUpdateVersion = 0; + + if (R_FAILED(result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle))) + { + uiStatusMsg("getGameCardUpdateInfo: GetGameCardHandle failed! (0x%08X)", result); + return; + } + + // Get bundled FW version update + if (R_SUCCEEDED(result = fsDeviceOperatorUpdatePartitionInfo(&fsOperatorInstance, &handle, &gameCardUpdateVersion, &gameCardUpdateTitleID))) + { + if (gameCardUpdateTitleID == GAMECARD_UPDATE_TITLEID) + { + char decimalVersion[64] = {'\0'}; + convertTitleVersionToDecimal(gameCardUpdateVersion, decimalVersion, sizeof(decimalVersion)); + + switch(gameCardUpdateVersion) + { + case SYSUPDATE_100: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "1.0.0 - v%s", decimalVersion); + break; + case SYSUPDATE_200: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.0.0 - v%s", decimalVersion); + break; + case SYSUPDATE_210: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.1.0 - v%s", decimalVersion); + break; + case SYSUPDATE_220: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.2.0 - v%s", decimalVersion); + break; + case SYSUPDATE_230: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "2.3.0 - v%s", decimalVersion); + break; + case SYSUPDATE_300: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.0 - v%s", decimalVersion); + break; + case SYSUPDATE_301: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.1 - v%s", decimalVersion); + break; + case SYSUPDATE_302: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "3.0.2 - v%s", decimalVersion); + break; + case SYSUPDATE_400: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.0.0 - v%s", decimalVersion); + break; + case SYSUPDATE_401: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.0.1 - v%s", decimalVersion); + break; + case SYSUPDATE_410: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "4.1.0 - v%s", decimalVersion); + break; + case SYSUPDATE_500: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.0 - v%s", decimalVersion); + break; + case SYSUPDATE_501: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.1 - v%s", decimalVersion); + break; + case SYSUPDATE_502: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.0.2 - v%s", decimalVersion); + break; + case SYSUPDATE_510: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "5.1.0 - v%s", decimalVersion); + break; + case SYSUPDATE_600: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.0.0 - v%s", decimalVersion); + break; + case SYSUPDATE_601: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.0.1 - v%s", decimalVersion); + break; + case SYSUPDATE_610: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.1.0 - v%s", decimalVersion); + break; + case SYSUPDATE_620: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "6.2.0 - v%s", decimalVersion); + break; + case SYSUPDATE_700: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "7.0.0 - v%s", decimalVersion); + break; + case SYSUPDATE_701: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "7.0.1 - v%s", decimalVersion); + break; + case SYSUPDATE_800: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "8.0.0 - v%s", decimalVersion); + break; + default: + snprintf(gameCardUpdateVersionStr, sizeof(gameCardUpdateVersionStr) / sizeof(gameCardUpdateVersionStr[0]), "UNKNOWN - v%s", decimalVersion); + break; + } + } else { + uiStatusMsg("getGameCardUpdateInfo: update Title ID mismatch! %016lX != %016lX", gameCardUpdateTitleID, GAMECARD_UPDATE_TITLEID); + } + } else { + uiStatusMsg("getGameCardUpdateInfo: UpdatePartitionInfo failed! (0x%08X)", result); + } +} + +void loadGameCardInfo() +{ + bool freeBuf = false; + + if (gameCardInserted) + { + if (hfs0_header == NULL) + { + /* Don't access the gamecard immediately to avoid conflicts with the fsp-srv, ncm and ns services */ + uiPleaseWait(GAMECARD_WAIT_TIME); + + if (getRootHfs0Header()) + { + getGameCardUpdateInfo(); + + if (getGameCardTitleIDAndVersion()) + { + gameCardName = calloc(gameCardAppCount + 1, sizeof(char*)); + fixedGameCardName = calloc(gameCardAppCount + 1, sizeof(char*)); + gameCardAuthor = calloc(gameCardAppCount + 1, sizeof(char*)); + gameCardVersionStr = calloc(gameCardAppCount + 1, sizeof(char*)); + + if (gameCardName != NULL && fixedGameCardName != NULL && gameCardAuthor != NULL && gameCardVersionStr != NULL) + { + u32 i; + for(i = 0; i < gameCardAppCount; i++) + { + gameCardName[i] = calloc(NACP_APPNAME_LEN + 1, sizeof(char)); + fixedGameCardName[i] = calloc(NACP_APPNAME_LEN + 1, sizeof(char)); + gameCardAuthor[i] = calloc(NACP_AUTHOR_LEN + 1, sizeof(char)); + gameCardVersionStr[i] = calloc(VERSION_STR_LEN + 1, sizeof(char)); + + if (gameCardName[i] != NULL && fixedGameCardName[i] != NULL && gameCardAuthor[i] != NULL && gameCardVersionStr[i] != NULL) + { + convertTitleVersionToDecimal(gameCardVersion[i], gameCardVersionStr[i], VERSION_STR_LEN); + + getGameCardControlNacp(gameCardTitleID[i], gameCardName[i], NACP_APPNAME_LEN, gameCardAuthor[i], NACP_AUTHOR_LEN); + strtrim(gameCardName[i]); + strtrim(gameCardAuthor[i]); + + if (strlen(gameCardName[i])) + { + snprintf(fixedGameCardName[i], NACP_APPNAME_LEN, gameCardName[i]); + removeIllegalCharacters(fixedGameCardName[i]); + } + } else { + freeBuf = true; + break; + } + } + } else { + freeBuf = true; + } + + if (freeBuf) + { + uiStatusMsg("loadGameCardInfo: error allocating memory for gamecard information."); + + if (gameCardName != NULL) + { + freeStringsPtr(gameCardName); + gameCardName = NULL; + } + + if (fixedGameCardName != NULL) + { + freeStringsPtr(fixedGameCardName); + fixedGameCardName = NULL; + } + + if (gameCardAuthor != NULL) + { + freeStringsPtr(gameCardAuthor); + gameCardAuthor = NULL; + } + + if (gameCardVersionStr != NULL) + { + freeStringsPtr(gameCardVersionStr); + gameCardVersionStr = NULL; + } + } + } + } + + uiPrintHeadline(); + } + } else { + freeGameCardInfo(); + } +} + +bool getHfs0EntryDetails(char *hfs0Header, u64 hfs0HeaderOffset, u64 hfs0HeaderSize, u32 num_entries, u32 entry_idx, bool isRoot, u32 partitionIndex, u64 *out_offset, u64 *out_size) +{ + if (hfs0Header == NULL) return false; + + if (entry_idx > (num_entries - 1)) return false; + + if ((HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * num_entries)) > hfs0HeaderSize) return false; + + hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * num_entries); + if (!entryTable) return false; + + memcpy(entryTable, hfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * num_entries); + + // Determine the partition index that's going to be used for offset calculation + // If we're dealing with a root HFS0 header, just use entry_idx + // Otherwise, partitionIndex must be used, because entry_idx represents the file entry we must look for in the provided HFS0 partition header + u32 part_idx = (isRoot ? entry_idx : partitionIndex); + + switch(part_idx) + { + case 0: // Update (contained within IStorage instance with partition ID 0) + case 1: // Normal or Logo (depending on the gamecard type) (contained within IStorage instance with partition ID 0) + // Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start + // Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function) + *out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset); + break; + case 2: + // Check if we're dealing with a type 0x01 gamecard + if (hfs0_partition_cnt == GAMECARD_TYPE1_PARTITION_CNT) + { + // Secure (contained within IStorage instance with partition ID 1) + // Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance + // Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation + *out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset)); + } else { + // Normal (contained within IStorage instance with partition ID 0) + // Root HFS0: the header offset used to calculate the partition offset is relative to the true gamecard image start + // Partition HFS0: the header offset used to calculate the file offset is also relative to the true gamecard image start (but it was calculated in a previous call to this function) + *out_offset = (hfs0HeaderOffset + hfs0HeaderSize + entryTable[entry_idx].file_offset); + } + break; + case 3: // Secure (gamecard type 0x02) (contained within IStorage instance with partition ID 1) + // Root HFS0: the resulting partition offset must be zero, because the secure partition is stored in a different IStorage instance + // Partition HFS0: the resulting file offset is relative to the start of the IStorage instance. Thus, it isn't necessary to use the header offset as part of the calculation + *out_offset = (isRoot ? 0 : (hfs0HeaderSize + entryTable[entry_idx].file_offset)); + break; + default: + break; + } + + // Store the file size for the desired HFS0 entry + *out_size = entryTable[entry_idx].file_size; + + free(entryTable); + + return true; +} + +bool getPartitionHfs0Header(u32 partition) +{ + if (hfs0_header == NULL) return false; + + if (partitionHfs0Header != NULL) + { + free(partitionHfs0Header); + partitionHfs0Header = NULL; + partitionHfs0HeaderOffset = 0; + partitionHfs0HeaderSize = 0; + partitionHfs0FileCount = 0; + partitionHfs0StrTableSize = 0; + } + + char *buf = NULL; + Result result; + FsGameCardHandle handle; + FsStorage gameCardStorage; + u64 partitionSize = 0; + u32 magic = 0; + bool success = false; + + if (getHfs0EntryDetails(hfs0_header, hfs0_offset, hfs0_size, hfs0_partition_cnt, partition, true, 0, &partitionHfs0HeaderOffset, &partitionSize)) + { + workaroundPartitionZeroAccess(&fsOperatorInstance); + + if (R_SUCCEEDED(result = fsDeviceOperatorGetGameCardHandle(&fsOperatorInstance, &handle))) + { + /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle succeeded: 0x%08X", handle.value); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++;*/ + + // Same ugly hack from dumpRawPartition() + if (R_SUCCEEDED(result = fsOpenGameCardStorage(&gameCardStorage, &handle, HFS0_TO_ISTORAGE_IDX(hfs0_partition_cnt, partition)))) + { + /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage succeeded: 0x%08X", handle); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++;*/ + + buf = (char*)malloc(MEDIA_UNIT_SIZE); + if (buf) + { + // First read MEDIA_UNIT_SIZE bytes + if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, buf, MEDIA_UNIT_SIZE))) + { + // Check the HFS0 magic word + memcpy(&magic, buf, sizeof(u32)); + magic = bswap_32(magic); + if (magic == HFS0_MAGIC) + { + // Calculate the size for the partition HFS0 header + memcpy(&partitionHfs0FileCount, buf + HFS0_FILE_COUNT_ADDR, sizeof(u32)); + memcpy(&partitionHfs0StrTableSize, buf + HFS0_STR_TABLE_SIZE_ADDR, sizeof(u32)); + partitionHfs0HeaderSize = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + partitionHfs0StrTableSize); + + /*snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header offset (relative to IStorage instance): 0x%016lX", partition, partitionHfs0HeaderOffset); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header size: %lu bytes", partition, partitionHfs0HeaderSize); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u file count: %u", partition, partitionHfs0FileCount); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u string table size: %u bytes", partition, partitionHfs0StrTableSize); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; + + uiRefreshDisplay();*/ + + // Round up the partition HFS0 header size to a MEDIA_UNIT_SIZE bytes boundary + partitionHfs0HeaderSize = round_up(partitionHfs0HeaderSize, MEDIA_UNIT_SIZE); + + partitionHfs0Header = (char*)malloc(partitionHfs0HeaderSize); + if (partitionHfs0Header) + { + // Check if we were dealing with the correct header size all along + if (partitionHfs0HeaderSize == MEDIA_UNIT_SIZE) + { + // Just copy what we already have + memcpy(partitionHfs0Header, buf, MEDIA_UNIT_SIZE); + success = true; + } else { + // Read the whole HFS0 header + if (R_SUCCEEDED(result = fsStorageRead(&gameCardStorage, partitionHfs0HeaderOffset, partitionHfs0Header, partitionHfs0HeaderSize))) + { + success = true; + } else { + free(partitionHfs0Header); + partitionHfs0Header = NULL; + partitionHfs0HeaderOffset = 0; + partitionHfs0HeaderSize = 0; + partitionHfs0FileCount = 0; + partitionHfs0StrTableSize = 0; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } + + /*if (success) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Partition #%u HFS0 header successfully retrieved!", partition); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + }*/ + } else { + partitionHfs0HeaderOffset = 0; + partitionHfs0HeaderSize = 0; + partitionHfs0FileCount = 0; + partitionHfs0StrTableSize = 0; + + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to allocate memory for the HFS0 header from partition #%u!", partition); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Magic word mismatch! 0x%08X != 0x%08X", magic, HFS0_MAGIC); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "StorageRead failed (0x%08X) at offset 0x%016lX", result, partitionHfs0HeaderOffset); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + + free(buf); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Failed to allocate memory for the HFS0 header from partition #%u!", partition); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + + fsStorageClose(&gameCardStorage); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "OpenGameCardStorage failed! (0x%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "GetGameCardHandle failed! (0x%08X)", result); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + } else { + uiDrawString("Error: unable to get partition details from the root HFS0 header!", 0, breaks * font_height, 255, 0, 0); + } + + if (!success) breaks += 2; + + return success; +} + +bool getHfs0FileList(u32 partition) +{ + if (!getPartitionHfs0Header(partition)) return false; + + if (!partitionHfs0Header) + { + uiDrawString("HFS0 partition header information unavailable!", 0, breaks * font_height, 255, 0, 0); + return false; + } + + if (!partitionHfs0FileCount) + { + uiDrawString("The selected partition is empty!", 0, breaks * font_height, 255, 0, 0); + return false; + } + + if (partitionHfs0FileCount > FILENAME_MAX_CNT) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "HFS0 partition contains more than %u files! (%u entries)", FILENAME_MAX_CNT, partitionHfs0FileCount); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + return false; + } + + hfs0_entry_table *entryTable = (hfs0_entry_table*)malloc(sizeof(hfs0_entry_table) * partitionHfs0FileCount); + if (!entryTable) + { + uiDrawString("Unable to allocate memory for the HFS0 file entries!", 0, breaks * font_height, 255, 0, 0); + return false; + } + + memcpy(entryTable, partitionHfs0Header + HFS0_ENTRY_TABLE_ADDR, sizeof(hfs0_entry_table) * partitionHfs0FileCount); + + memset(filenameBuffer, 0, FILENAME_BUFFER_SIZE); + + int i; + int max_elements = (int)partitionHfs0FileCount; + char *nextFilename = filenameBuffer; + + filenamesCount = 0; + + for(i = 0; i < max_elements; i++) + { + u32 filename_offset = (HFS0_ENTRY_TABLE_ADDR + (sizeof(hfs0_entry_table) * partitionHfs0FileCount) + entryTable[i].filename_offset); + addStringToFilenameBuffer(partitionHfs0Header + filename_offset, &nextFilename); + } + + free(entryTable); + + return true; +} + +int getSdCardFreeSpace(u64 *out) +{ + struct statvfs st; + int rc; + + rc = statvfs("sdmc:/", &st); + if (rc != 0) + { + uiStatusMsg("getSdCardFreeSpace: Unable to get SD card filesystem stats! statvfs: %d (%s).", errno, strerror(errno)); + } else { + *out = (u64)(st.f_bsize * st.f_bfree); + } + + return rc; +} + +void convertSize(u64 size, char *out, int bufsize) +{ + char buffer[16]; + double bytes = (double)size; + + if (bytes < 1000.0) + { + snprintf(buffer, sizeof(buffer), "%.0lf B", bytes); + } else + if (bytes < 10.0*KiB) + { + snprintf(buffer, sizeof(buffer), "%.2lf KiB", floor((bytes*100.0)/KiB)/100.0); + } else + if (bytes < 100.0*KiB) + { + snprintf(buffer, sizeof(buffer), "%.1lf KiB", floor((bytes*10.0)/KiB)/10.0); + } else + if (bytes < 1000.0*KiB) + { + snprintf(buffer, sizeof(buffer), "%.0lf KiB", floor(bytes/KiB)); + } else + if (bytes < 10.0*MiB) + { + snprintf(buffer, sizeof(buffer), "%.2lf MiB", floor((bytes*100.0)/MiB)/100.0); + } else + if (bytes < 100.0*MiB) + { + snprintf(buffer, sizeof(buffer), "%.1lf MiB", floor((bytes*10.0)/MiB)/10.0); + } else + if (bytes < 1000.0*MiB) + { + snprintf(buffer, sizeof(buffer), "%.0lf MiB", floor(bytes/MiB)); + } else + if (bytes < 10.0*GiB) + { + snprintf(buffer, sizeof(buffer), "%.2lf GiB", floor((bytes*100.0)/GiB)/100.0); + } else + if (bytes < 100.0*GiB) + { + snprintf(buffer, sizeof(buffer), "%.1lf GiB", floor((bytes*10.0)/GiB)/10.0); + } else { + snprintf(buffer, sizeof(buffer), "%.0lf GiB", floor(bytes/GiB)); + } + + snprintf(out, bufsize, "%s", buffer); +} + +char *generateDumpName() +{ + if (!gameCardAppCount || !fixedGameCardName || !gameCardVersion || !gameCardTitleID) return NULL; + + u32 i; + + char tmp[512] = {'\0'}; + char *fullname = NULL; + char *fullnameTmp = NULL; + + size_t strsize = NAME_BUF_LEN; + fullname = (char*)malloc(strsize); + if (!fullname) return NULL; + + memset(fullname, 0, strsize); + + for(i = 0; i < gameCardAppCount; i++) + { + snprintf(tmp, sizeof(tmp) / sizeof(tmp[0]), "%s v%u (%016lX)", fixedGameCardName[i], gameCardVersion[i], gameCardTitleID[i]); + + if ((strlen(fullname) + strlen(tmp) + 4) > strsize) + { + size_t fullname_len = strlen(fullname); + + strsize = (fullname_len + strlen(tmp) + 4); + fullnameTmp = (char*)realloc(fullname, strsize); + if (fullnameTmp) + { + fullname = fullnameTmp; + memset(fullname + fullname_len, 0, strlen(tmp) + 1); + } else { + free(fullname); + fullname = NULL; + break; + } + } + + if (i > 0) strcat(fullname, " + "); + + strcat(fullname, tmp); + } + + return fullname; +} + +void waitForButtonPress() +{ + uiDrawString("Press any button to continue", 0, breaks * font_height, 255, 255, 255); + + uiRefreshDisplay(); + + while(true) + { + hidScanInput(); + u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); + if (keysDown && !((keysDown & KEY_TOUCH) || (keysDown & KEY_LSTICK_LEFT) || (keysDown & KEY_LSTICK_RIGHT) || (keysDown & KEY_LSTICK_UP) || (keysDown & KEY_LSTICK_DOWN) || \ + (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_RSTICK_UP) || (keysDown & KEY_RSTICK_DOWN))) break; + } +} + +void convertDataToHexString(const u8 *data, const u32 dataSize, char *outBuf, const u32 outBufSize) +{ + if (!data || !dataSize || !outBuf || !outBufSize || outBufSize < ((dataSize * 2) + 1)) return; + + u32 i; + char tmp[3] = {'\0'}; + + memset(outBuf, 0, outBufSize); + + for(i = 0; i < dataSize; i++) + { + sprintf(tmp, "%02x", data[i]); + strcat(outBuf, tmp); + } +} + +void convertNcaSizeToU64(const u8 size[0x6], u64 *out) +{ + if (!size || !out) return; + + u64 tmp = 0; + + tmp |= (((u64)size[5] << 40) & (u64)0xFF0000000000); + tmp |= (((u64)size[4] << 32) & (u64)0x00FF00000000); + tmp |= (((u64)size[3] << 24) & (u64)0x0000FF000000); + tmp |= (((u64)size[2] << 16) & (u64)0x000000FF0000); + tmp |= (((u64)size[1] << 8) & (u64)0x00000000FF00); + tmp |= ((u64)size[0] & (u64)0x0000000000FF); + + *out = tmp; +} + +bool loadNcaKeyset() +{ + // Keyset already loaded + if (nca_keyset.key_cnt > 0) return true; + + // Open keys file + FILE *keysFile = fopen(keysFilePath, "rb"); + if (!keysFile) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to open \"%s\" to retrieve NCA keyset!", keysFilePath); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + return false; + } + + // Load keys + int ret = extkeys_initialize_keyset(&nca_keyset, keysFile); + fclose(keysFile); + + if (ret < 1) + { + if (ret == -1) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to parse necessary keys from \"%s\"! (keys file empty?)", keysFilePath); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + } + + return false; + } + + return true; +} + +bool encryptNcaHeader(nca_header_t *input, u8 *outBuf, u64 outBufSize) +{ + if (!input || !outBuf || !outBufSize || outBufSize < NCA_FULL_HEADER_LENGTH || (bswap_32(input->magic) != NCA3_MAGIC && bswap_32(input->magic) != NCA2_MAGIC)) + { + uiDrawString("Error: invalid NCA header encryption parameters.", 0, breaks * font_height, 255, 0, 0); + return false; + } + + if (!loadNcaKeyset()) return false; + + u32 i; + aes_ctx_t *hdr_aes_ctx = NULL; + aes_ctx_t *aes_ctx = NULL; + + u8 crypto_type = (input->crypto_type2 > input->crypto_type ? input->crypto_type2 : input->crypto_type); + if (crypto_type) crypto_type--; + + aes_ctx = new_aes_ctx(nca_keyset.key_area_keys[crypto_type][input->kaek_ind], 16, AES_MODE_ECB); + if (!aes_ctx) return false; + + if (!aes_encrypt(aes_ctx, input->nca_keys, input->nca_keys, NCA_KEY_AREA_SIZE)) + { + free_aes_ctx(aes_ctx); + return false; + } + + free_aes_ctx(aes_ctx); + + hdr_aes_ctx = new_aes_ctx(nca_keyset.header_key, 32, AES_MODE_XTS); + if (!hdr_aes_ctx) return false; + + if (bswap_32(input->magic) == NCA3_MAGIC) + { + if (!aes_xts_encrypt(hdr_aes_ctx, outBuf, input, NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE)) + { + free_aes_ctx(hdr_aes_ctx); + return false; + } + } else + if (bswap_32(input->magic) == NCA2_MAGIC) { - memset(appList, 0, sizeof(NcmApplicationContentMetaKey)); + if (!aes_xts_encrypt(hdr_aes_ctx, outBuf, input, NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE)) + { + free_aes_ctx(hdr_aes_ctx); + return false; + } - if (R_SUCCEEDED(result = ncmOpenContentMetaDatabase(FsStorageId_GameCard, &ncmDb))) + for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) { - if (R_SUCCEEDED(result = ncmContentMetaDatabaseListApplication(&ncmDb, META_DATABASE_FILTER, appList, sizeof(NcmApplicationContentMetaKey), NULL, NULL))) + if (!aes_xts_encrypt(hdr_aes_ctx, outBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), &(input->fs_headers[i]), NCA_SECTION_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE)) { - *titleID = appList->metaRecord.titleId; - *version = appList->metaRecord.version; - success = true; - } else { - uiStatusMsg("getGameCardTitleIDAndVersion: ncmContentMetaDatabaseListApplication failed! (0x%08X)", result); + free_aes_ctx(hdr_aes_ctx); + return false; } - } else { - uiStatusMsg("getGameCardTitleIDAndVersion: ncmOpenContentMetaDatabase failed! (0x%08X)", result); } - - free(appList); - } else { - uiStatusMsg("getGameCardTitleIDAndVersion: Unable to allocate memory for the ncm service operations."); } - return success; -} - -void convertTitleVersionToDecimal(u32 version, char *versionBuf, int versionBufSize) -{ - u8 major = (u8)((version >> 26) & 0x3F); - u8 middle = (u8)((version >> 20) & 0x3F); - u8 minor = (u8)((version >> 16) & 0xF); - u16 build = (u16)version; + free_aes_ctx(hdr_aes_ctx); - snprintf(versionBuf, versionBufSize, "%u (%u.%u.%u.%u)", version, major, middle, minor, build); + return true; } -bool getGameCardControlNacp(u64 titleID, char *nameBuf, int nameBufSize, char *authorBuf, int authorBufSize) +bool decryptNcaHeader(const char *ncaBuf, u64 ncaBufSize, nca_header_t *out) { - if (titleID == 0) return false; + if (!ncaBuf || !ncaBufSize || ncaBufSize < NCA_FULL_HEADER_LENGTH) + { + uiDrawString("Error: invalid NCA header decryption parameters.", 0, breaks * font_height, 255, 0, 0); + return false; + } - bool success = false; - Result result; - size_t outsize = 0; - NsApplicationControlData *buf = NULL; - NacpLanguageEntry *langentry = NULL; + if (!loadNcaKeyset()) return false; - buf = (NsApplicationControlData*)malloc(sizeof(NsApplicationControlData)); - if (buf) + u32 i; + aes_ctx_t *hdr_aes_ctx = NULL; + aes_ctx_t *aes_ctx = NULL; + + u8 crypto_type; + bool has_rights_id = false; + + u64 section_offset; + u64 section_size; + + hdr_aes_ctx = new_aes_ctx(nca_keyset.header_key, 32, AES_MODE_XTS); + if (!hdr_aes_ctx) return false; + + if (!aes_xts_decrypt(hdr_aes_ctx, out, ncaBuf, NCA_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE)) { - memset(buf, 0, sizeof(NsApplicationControlData)); - - if (R_SUCCEEDED(result = nsGetApplicationControlData(1, titleID, buf, sizeof(NsApplicationControlData), &outsize))) + free_aes_ctx(hdr_aes_ctx); + return false; + } + + if (bswap_32(out->magic) == NCA3_MAGIC) + { + if (!aes_xts_decrypt(hdr_aes_ctx, out, ncaBuf, NCA_FULL_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE)) { - if (outsize >= sizeof(buf->nacp)) + free_aes_ctx(hdr_aes_ctx); + return false; + } + } else + if (bswap_32(out->magic) == NCA2_MAGIC) + { + for(i = 0; i < NCA_SECTION_HEADER_CNT; i++) + { + if (out->fs_headers[i]._0x148[0] != 0 || memcmp(out->fs_headers[i]._0x148, out->fs_headers[i]._0x148 + 1, 0xB7)) { - if (R_SUCCEEDED(result = nacpGetLanguageEntry(&buf->nacp, &langentry))) + if (!aes_xts_decrypt(hdr_aes_ctx, &(out->fs_headers[i]), ncaBuf + NCA_HEADER_LENGTH + (i * NCA_SECTION_HEADER_LENGTH), NCA_SECTION_HEADER_LENGTH, 0, NCA_AES_XTS_SECTOR_SIZE)) { - strncpy(nameBuf, langentry->name, nameBufSize - 1); - strncpy(authorBuf, langentry->author, authorBufSize - 1); - success = true; - } else { - uiStatusMsg("getGameCardControlNacp: GetLanguageEntry failed! (0x%08X)", result); + free_aes_ctx(hdr_aes_ctx); + return false; } } else { - uiStatusMsg("getGameCardControlNacp: Control.nacp buffer size (%u bytes) is too small! Expected: %u bytes", outsize, sizeof(buf->nacp)); + memset(&(out->fs_headers[i]), 0, sizeof(nca_fs_header_t)); } - } else { - uiStatusMsg("getGameCardControlNacp: GetApplicationControlData failed! (0x%08X)", result); } - - free(buf); } else { - uiStatusMsg("getGameCardControlNacp: Unable to allocate memory for the ns service operations."); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: invalid NCA magic word! Wrong keys? (0x%08X)", out->magic); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + free_aes_ctx(hdr_aes_ctx); + return false; } - return success; -} - -void removeIllegalCharacters(char *name) -{ - u32 i, len = strlen(name); - for (i = 0; i < len; i++) + free_aes_ctx(hdr_aes_ctx); + + crypto_type = (out->crypto_type2 > out->crypto_type ? out->crypto_type2 : out->crypto_type); + if (crypto_type) crypto_type--; + + for(i = 0; i < 0x10; i++) { - if (memchr("?[]/\\=+<>:;\",*|^", name[i], sizeof("?[]/\\=+<>:;\",*|^") - 1) || name[i] < 0x20 || name[i] > 0x7E) name[i] = '_'; + if (out->rights_id[i] != 0) + { + has_rights_id = true; + break; + } } -} - -void strtrim(char *str) -{ - if (!str || !*str) return; - char *start = str; - char *end = start + strlen(str); + if (has_rights_id) + { + uiDrawString("Error: Rights ID field in NCA header not empty!", 0, breaks * font_height, 255, 0, 0); + return false; + } - while(--end >= start) + section_offset = (out->section_entries[0].media_start_offset * MEDIA_UNIT_SIZE); + section_size = ((out->section_entries[0].media_end_offset * MEDIA_UNIT_SIZE) - section_offset); + + if (!section_offset || !section_size || section_offset < NCA_FULL_HEADER_LENGTH) { - if (!isspace(*end)) break; + uiDrawString("Error: invalid size/offset for NCA section #0!", 0, breaks * font_height, 255, 0, 0); + return false; } - *(++end) = '\0'; + aes_ctx = new_aes_ctx(nca_keyset.key_area_keys[crypto_type][out->kaek_ind], 16, AES_MODE_ECB); + if (!aes_ctx) return false; - while(isspace(*start)) start++; + if (!aes_decrypt(aes_ctx, out->nca_keys, out->nca_keys, NCA_KEY_AREA_SIZE)) + { + free_aes_ctx(aes_ctx); + return false; + } - if (start != str) memmove(str, start, end - start + 1); + free_aes_ctx(aes_ctx); + + return true; } -void loadGameCardInfo() +bool decryptCnmtNca(char *ncaBuf, u64 ncaBufSize) { - if (gameCardInserted) + u32 i; + nca_header_t dec_header; + aes_ctx_t *aes_ctx = NULL; + + u8 crypto_type; + + u64 section_offset; + u64 section_size; + char *section_data = NULL; + + pfs0_header nca_pfs0_header; + + if (!decryptNcaHeader(ncaBuf, ncaBufSize, &dec_header)) return false; + + if (dec_header.fs_headers[0].partition_type != NCA_FS_HEADER_PARTITION_PFS0 || dec_header.fs_headers[0].fs_type != NCA_FS_HEADER_FSTYPE_PFS0) { - if (hfs0_header == NULL) + uiDrawString("Error: CNMT NCA section #0 doesn't hold a PFS0 partition.", 0, breaks * font_height, 255, 0, 0); + return false; + } + + if (!dec_header.fs_headers[0].pfs0_superblock.pfs0_size) + { + uiDrawString("Error: invalid size for PFS0 partition in CNMT NCA section #0.", 0, breaks * font_height, 255, 0, 0); + return false; + } + + crypto_type = (dec_header.crypto_type2 > dec_header.crypto_type ? dec_header.crypto_type2 : dec_header.crypto_type); + if (crypto_type) crypto_type--; + + section_offset = (dec_header.section_entries[0].media_start_offset * MEDIA_UNIT_SIZE); + section_size = ((dec_header.section_entries[0].media_end_offset * MEDIA_UNIT_SIZE) - section_offset); + + section_data = (char*)calloc(section_size, sizeof(char)); + if (!section_data) + { + uiDrawString("Error: unable to allocate memory for the decrypted CNMT NCA section #0.", 0, breaks * font_height, 255, 0, 0); + return false; + } + + if (dec_header.fs_headers[0].crypt_type != NCA_FS_HEADER_CRYPT_NONE) + { + if (dec_header.fs_headers[0].crypt_type == NCA_FS_HEADER_CRYPT_CTR || dec_header.fs_headers[0].crypt_type == NCA_FS_HEADER_CRYPT_BKTR) { - /* Don't access the gamecard immediately to avoid conflicts with the fsp-srv, ncm and ns services */ - uiPleaseWait(); - - if (getRootHfs0Header(&fsOperatorInstance)) + aes_ctx = new_aes_ctx(dec_header.nca_keys[2], 16, AES_MODE_CTR); + if (!aes_ctx) { - if (getGameCardTitleIDAndVersion(&gameCardTitleID, &gameCardVersion)) - { - convertTitleVersionToDecimal(gameCardVersion, gameCardVersionStr, sizeof(gameCardVersionStr)); - - getGameCardControlNacp(gameCardTitleID, gameCardName, sizeof(gameCardName), gameCardAuthor, sizeof(gameCardAuthor)); - - strtrim(gameCardName); - if (strlen(gameCardName)) - { - snprintf(fixedGameCardName, sizeof(fixedGameCardName) / sizeof(fixedGameCardName[0]), "%s", gameCardName); - removeIllegalCharacters(fixedGameCardName); - } - } + free(section_data); + return false; } - uiPrintHeadline(); - uiUpdateStatusMsg(); - } - } else { - if (hfs0_header != NULL) - { - gameCardSize = 0; - memset(gameCardSizeStr, 0, sizeof(gameCardSizeStr)); - - trimmedCardSize = 0; - memset(trimmedCardSizeStr, 0, sizeof(trimmedCardSizeStr)); + unsigned char ctr[0x10]; + u64 ofs = (section_offset >> 4); - free(hfs0_header); - hfs0_header = NULL; - hfs0_offset = hfs0_size = 0; - hfs0_partition_cnt = 0; + for(i = 0; i < 0x8; i++) + { + ctr[i] = dec_header.fs_headers[0].section_ctr[0x08 - i - 1]; + ctr[0x10 - i - 1] = (unsigned char)(ofs & 0xFF); + ofs >>= 8; + } - if (partitionHfs0Header != NULL) + if (!aes_setiv(aes_ctx, ctr, 0x10)) { - free(partitionHfs0Header); - partitionHfs0Header = NULL; - partitionHfs0HeaderOffset = 0; - partitionHfs0HeaderSize = 0; - partitionHfs0FileCount = 0; - partitionHfs0StrTableSize = 0; + free_aes_ctx(aes_ctx); + free(section_data); + return false; } - gameCardTitleID = 0; - gameCardVersion = 0; + if (!aes_decrypt(aes_ctx, section_data, ncaBuf + section_offset, section_size)) + { + free_aes_ctx(aes_ctx); + free(section_data); + return false; + } - memset(gameCardName, 0, sizeof(gameCardName)); - memset(fixedGameCardName, 0, sizeof(fixedGameCardName)); - memset(gameCardAuthor, 0, sizeof(gameCardAuthor)); - memset(gameCardVersionStr, 0, sizeof(gameCardVersionStr)); + free_aes_ctx(aes_ctx); + } else + if (dec_header.fs_headers[0].crypt_type == NCA_FS_HEADER_CRYPT_XTS) + { + aes_ctx = new_aes_ctx(dec_header.nca_keys, 32, AES_MODE_XTS); + if (!aes_ctx) + { + free(section_data); + return false; + } - gameCardUpdateTitleID = 0; - gameCardUpdateVersion = 0; + if (!aes_xts_decrypt(aes_ctx, section_data, ncaBuf + section_offset, section_size, 0, NCA_AES_XTS_SECTOR_SIZE)) + { + free_aes_ctx(aes_ctx); + free(section_data); + return false; + } - memset(gameCardUpdateVersionStr, 0, sizeof(gameCardUpdateVersionStr)); + free_aes_ctx(aes_ctx); + } else { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: invalid crypt type for CNMT NCA section #0! (0x%02X)", dec_header.fs_headers[0].crypt_type); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + free(section_data); + return false; } + } else { + memcpy(section_data, ncaBuf + section_offset, section_size); } -} - -int getSdCardFreeSpace(u64 *out) -{ - struct statvfs st; - int rc; - rc = statvfs("sdmc:/", &st); - if (rc != 0) + memcpy(&nca_pfs0_header, section_data + dec_header.fs_headers[0].pfs0_superblock.hash_table_offset + dec_header.fs_headers[0].pfs0_superblock.pfs0_offset, sizeof(pfs0_header)); + + if (bswap_32(nca_pfs0_header.magic) != PFS0_MAGIC) { - uiStatusMsg("getSdCardFreeSpace: Unable to get SD card filesystem stats! statvfs: %d (%s).", errno, strerror(errno)); - } else { - *out = (u64)(st.f_bsize * st.f_bfree); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: invalid magic word for CNMT NCA section #0 PFS0 partition! Wrong keys? (0x%08X)", nca_pfs0_header.magic); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + free(section_data); + return false; } - return rc; + if (!nca_pfs0_header.file_cnt || !nca_pfs0_header.str_table_size) + { + uiDrawString("Error: CNMT NCA section #0 PFS0 partition is empty! Wrong keys? (0x%08X)", 0, breaks * font_height, 255, 0, 0); + free(section_data); + return false; + } + + // Replace input buffer data in-place + memset(ncaBuf, 0, ncaBufSize); + memcpy(ncaBuf, &dec_header, sizeof(nca_header_t)); + memcpy(ncaBuf + section_offset, section_data, section_size); + + /*FILE *nca_file = fopen("sdmc:/decrypted_cnmt_nca.bin", "wb"); + if (nca_file) + { + fwrite(ncaBuf, 1, ncaBufSize, nca_file); + fclose(nca_file); + }*/ + + free(section_data); + + return true; } -void convertSize(u64 size, char *out, int bufsize) +bool calculateSHA256(const u8 *data, const u32 dataSize, u8 out[32]) { - char buffer[16]; - double bytes = (double)size; + int ret; + mbedtls_sha256_context sha256_ctx; - if (bytes < 1000.0) - { - snprintf(buffer, sizeof(buffer), "%.0lf B", bytes); - } else - if (bytes < 10.0*KiB) - { - snprintf(buffer, sizeof(buffer), "%.2lf KiB", floor((bytes*100.0)/KiB)/100.0); - } else - if (bytes < 100.0*KiB) - { - snprintf(buffer, sizeof(buffer), "%.1lf KiB", floor((bytes*10.0)/KiB)/10.0); - } else - if (bytes < 1000.0*KiB) - { - snprintf(buffer, sizeof(buffer), "%.0lf KiB", floor(bytes/KiB)); - } else - if (bytes < 10.0*MiB) - { - snprintf(buffer, sizeof(buffer), "%.2lf MiB", floor((bytes*100.0)/MiB)/100.0); - } else - if (bytes < 100.0*MiB) - { - snprintf(buffer, sizeof(buffer), "%.1lf MiB", floor((bytes*10.0)/MiB)/10.0); - } else - if (bytes < 1000.0*MiB) + mbedtls_sha256_init(&sha256_ctx); + + ret = mbedtls_sha256_starts_ret(&sha256_ctx, 0); + if (ret < 0) { - snprintf(buffer, sizeof(buffer), "%.0lf MiB", floor(bytes/MiB)); - } else - if (bytes < 10.0*GiB) + mbedtls_sha256_free(&sha256_ctx); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to start SHA-256 calculation! (%d)", ret); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + return false; + } + + ret = mbedtls_sha256_update_ret(&sha256_ctx, data, dataSize); + if (ret < 0) { - snprintf(buffer, sizeof(buffer), "%.2lf GiB", floor((bytes*100.0)/GiB)/100.0); - } else - if (bytes < 100.0*GiB) + mbedtls_sha256_free(&sha256_ctx); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to update SHA-256 calculation! (%d)", ret); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + return false; + } + + ret = mbedtls_sha256_finish_ret(&sha256_ctx, out); + if (ret < 0) { - snprintf(buffer, sizeof(buffer), "%.1lf GiB", floor((bytes*10.0)/GiB)/10.0); - } else { - snprintf(buffer, sizeof(buffer), "%.0lf GiB", floor(bytes/GiB)); + mbedtls_sha256_free(&sha256_ctx); + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to finish SHA-256 calculation! (%d)", ret); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + return false; } - snprintf(out, bufsize, "%s", buffer); + mbedtls_sha256_free(&sha256_ctx); + + return true; } -void waitForButtonPress() +void generateCnmtMetadataXml(cnmt_xml_program_info *xml_program_info, cnmt_xml_content_info *xml_content_info, char *out) { - uiDrawString("Press any button to continue", 0, breaks * font_height, 255, 255, 255); + if (!xml_program_info || !xml_content_info || !xml_program_info->nca_cnt || !out) return; - uiRefreshDisplay(); + u32 i; + char tmp[NAME_BUF_LEN] = {'\0'}; - while(true) + const char *contentTypes[] = { "Meta", "Program", "Data", "Control", "HtmlDocument", "LegalInformation", "DeltaFragment" }; + const u32 contentTypesCnt = (sizeof(contentTypes) / sizeof(contentTypes[0])); + + sprintf(out, "\r\n" \ + "\r\n" \ + " Application\r\n" \ + " 0x%016lx\r\n" \ + " %u\r\n" \ + " %u\r\n", \ + xml_program_info->title_id, \ + xml_program_info->version, \ + xml_program_info->required_dl_sysver); + + for(i = 0; i < xml_program_info->nca_cnt; i++) { - hidScanInput(); - u32 keysDown = hidKeysDown(CONTROLLER_P1_AUTO); - if (keysDown && !((keysDown & KEY_TOUCH) || (keysDown & KEY_LSTICK_LEFT) || (keysDown & KEY_LSTICK_RIGHT) || (keysDown & KEY_LSTICK_UP) || (keysDown & KEY_LSTICK_DOWN) || \ - (keysDown & KEY_RSTICK_LEFT) || (keysDown & KEY_RSTICK_RIGHT) || (keysDown & KEY_RSTICK_UP) || (keysDown & KEY_RSTICK_DOWN))) break; + sprintf(tmp, " \r\n" \ + " %s\r\n" \ + " %s\r\n" \ + " %lu\r\n" \ + " %s\r\n" \ + " %u\r\n" \ + " \r\n", \ + (xml_content_info[i].type < contentTypesCnt ? contentTypes[xml_content_info[i].type] : "Unknown"), \ + xml_content_info[i].nca_id_str, \ + xml_content_info[i].size, \ + xml_content_info[i].hash_str, \ + xml_content_info[i].keyblob); \ + + strcat(out, tmp); } + + sprintf(tmp, " %s\r\n" \ + " %u\r\n" \ + " %u\r\n" \ + " 0x%016lx\r\n" \ + "", \ + xml_program_info->digest_str, \ + xml_program_info->min_keyblob, \ + xml_program_info->min_sysver, \ + xml_program_info->patch_tid); + + strcat(out, tmp); } void addStringToFilenameBuffer(const char *string, char **nextFilename) @@ -423,7 +1616,7 @@ void removeDirectory(const char *path) } } -bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc) +bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u64 gc_tid, u32 crc) { if (!doc || !cur) return false; @@ -438,7 +1631,6 @@ bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc) char xmlReleaseName[256] = {'\0'}; bool found = false; - char strbuf[512] = {'\0'}; while(node != NULL) { @@ -484,18 +1676,6 @@ bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc) /*if (xmlImageSize && xmlTitleID && strlen(xmlReleaseName)) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Cartridge Image Size: %u.", imageSize); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks++; - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Cartridge Title ID: %016lX.", gameCardTitleID); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks++; - - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Cartridge Image CRC32: %08X.", crc); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks += 2; - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "XML Image Size: %u.", xmlImageSize); uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); breaks++; @@ -513,7 +1693,7 @@ bool parseNSWDBRelease(xmlDocPtr doc, xmlNodePtr cur, u32 crc) breaks += 2; }*/ - if (xmlImageSize == imageSize && xmlTitleID == gameCardTitleID && xmlCrc == crc) + if (xmlImageSize == imageSize && xmlTitleID == gc_tid && xmlCrc == crc) { snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Found matching Scene release: \"%s\" (CRC32: %08X). This is a good dump!", xmlReleaseName, xmlCrc); uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); @@ -547,48 +1727,61 @@ xmlXPathObjectPtr getNodeSet(xmlDocPtr doc, xmlChar *xpath) void gameCardDumpNSWDBCheck(u32 crc) { - if (!gameCardTitleID || !hfs0_partition_cnt || !crc) return; + if (!gameCardAppCount || !gameCardTitleID || !hfs0_partition_cnt || !crc) return; xmlDocPtr doc = NULL; bool found = false; - char strbuf[512] = {'\0'}; doc = xmlParseFile(nswReleasesXmlPath); if (doc) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "//%s/%s[.//%s='%016lX']", nswReleasesRootElement, nswReleasesChildren, nswReleasesChildrenTitleID, gameCardTitleID); - xmlXPathObjectPtr nodeSet = getNodeSet(doc, (xmlChar*)strbuf); - if (nodeSet) + u32 i; + for(i = 0; i < gameCardAppCount; i++) { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Found %d %s with Title ID \"%016lX\".", nodeSet->nodesetval->nodeNr, (nodeSet->nodesetval->nodeNr > 1 ? "releases" : "release"), gameCardTitleID); - uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); - breaks++; - - uiRefreshDisplay(); - - u32 i; - for(i = 0; i < nodeSet->nodesetval->nodeNr; i++) + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "//%s/%s[.//%s='%016lX']", nswReleasesRootElement, nswReleasesChildren, nswReleasesChildrenTitleID, gameCardTitleID[i]); + xmlXPathObjectPtr nodeSet = getNodeSet(doc, (xmlChar*)strbuf); + if (nodeSet) { - xmlNodePtr node = nodeSet->nodesetval->nodeTab[i]->xmlChildrenNode; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Found %d %s with Title ID \"%016lX\".", nodeSet->nodesetval->nodeNr, (nodeSet->nodesetval->nodeNr > 1 ? "releases" : "release"), gameCardTitleID[i]); + uiDrawString(strbuf, 0, breaks * font_height, 255, 255, 255); + breaks++; - found = parseNSWDBRelease(doc, node, crc); - if (found) break; - } - - if (!found) - { - uiDrawString("No matches found in XML document! This could either be a bad dump or an undumped cartridge.", 0, breaks * font_height, 255, 0, 0); + uiRefreshDisplay(); + + u32 i; + for(i = 0; i < nodeSet->nodesetval->nodeNr; i++) + { + xmlNodePtr node = nodeSet->nodesetval->nodeTab[i]->xmlChildrenNode; + + found = parseNSWDBRelease(doc, node, gameCardTitleID[i], crc); + if (found) break; + } + + xmlXPathFreeObject(nodeSet); + + if (!found) + { + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "No checksum matches found in XML document for Title ID \"%016lX\"!", gameCardTitleID[i]); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + if ((i + 1) < gameCardAppCount) breaks += 2; + } else { + breaks--; + break; + } } else { - breaks--; + snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "No records with Title ID \"%016lX\" found within the XML document!", gameCardTitleID[i]); + uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); + if ((i + 1) < gameCardAppCount) breaks += 2; } - - xmlXPathFreeObject(nodeSet); - } else { - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: unable to find records with Title ID \"%016lX\" within the XML document!", gameCardTitleID); - uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); } xmlFreeDoc(doc); + + if (!found) + { + breaks++; + uiDrawString("This could either be a bad dump or an undumped cartridge.", 0, breaks * font_height, 255, 0, 0); + } } else { snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to open and/or parse \"%s\"!", nswReleasesXmlPath); uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); @@ -654,7 +1847,6 @@ void updateNSWDBXml() CURLcode res; long http_code = 0; double size = 0.0; - char strbuf[512] = {'\0'}; bool success = false; if (R_SUCCEEDED(result = networkInit())) @@ -850,7 +2042,7 @@ void updateApplication() CURLcode res; long http_code = 0; double size = 0.0; - char strbuf[1024] = {'\0'}, downloadUrl[512] = {'\0'}, releaseTag[32] = {'\0'}; + char downloadUrl[512] = {'\0'}, releaseTag[32] = {'\0'}; bool success = false; struct json_object *jobj, *name, *assets; FILE *gcDumpToolNro = NULL; @@ -971,8 +2163,7 @@ void updateApplication() uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); breaks++; - snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Please restart the application to reflect the changes.", size); - uiDrawString(strbuf, 0, breaks * font_height, 0, 255, 0); + uiDrawString("Please restart the application to reflect the changes.", 0, breaks * font_height, 0, 255, 0); } else { snprintf(strbuf, sizeof(strbuf) / sizeof(strbuf[0]), "Error: failed to request latest update binary! HTTP status code: %ld", http_code); uiDrawString(strbuf, 0, breaks * font_height, 255, 0, 0); diff --git a/source/util.h b/source/util.h index 93ff705a..aa7ac354 100644 --- a/source/util.h +++ b/source/util.h @@ -5,20 +5,250 @@ #include -#define KiB (1024.0) -#define MiB (1024.0 * KiB) -#define GiB (1024.0 * MiB) +#define APP_VERSION "1.0.8" -#define APP_VERSION "1.0.7" +#define KiB (1024.0) +#define MiB (1024.0 * KiB) +#define GiB (1024.0 * MiB) -#define NAME_BUF_LEN 4096 +#define NAME_BUF_LEN 4096 -#define SOCK_BUFFERSIZE 65536 +#define SOCK_BUFFERSIZE 65536 -#define META_DATABASE_FILTER 0x80 // Regular Application +#define META_DB_REGULAR_APPLICATION 0x80 -#define FILENAME_BUFFER_SIZE (1024 * 512) // 512 KiB -#define FILENAME_MAX_CNT 2048 +#define FILENAME_BUFFER_SIZE (1024 * 512) // 512 KiB +#define FILENAME_MAX_CNT 2048 + +#define NACP_APPNAME_LEN 0x200 +#define NACP_AUTHOR_LEN 0x100 +#define VERSION_STR_LEN 0x40 + +#define GAMECARD_WAIT_TIME 3 // 3 seconds + +#define GAMECARD_HEADER_SIZE 0x200 +#define GAMECARD_SIZE_ADDR 0x10D +#define GAMECARD_DATAEND_ADDR 0x118 + +#define HFS0_OFFSET_ADDR 0x130 +#define HFS0_SIZE_ADDR 0x138 +#define HFS0_MAGIC 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_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")))) +#define GAMECARD_PARTITION_NAME(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? GAMECARD_TYPE1_PART_NAMES(y) : ((x) == GAMECARD_TYPE2_PARTITION_CNT ? GAMECARD_TYPE2_PART_NAMES(y) : "Unknown")) + +#define HFS0_TO_ISTORAGE_IDX(x, y) ((x) == GAMECARD_TYPE1_PARTITION_CNT ? ((y) < 2 ? 0 : 1) : ((y) < 3 ? 0 : 1)) + +#define GAMECARD_SIZE_1GiB (u64)0x40000000 +#define GAMECARD_SIZE_2GiB (u64)0x80000000 +#define GAMECARD_SIZE_4GiB (u64)0x100000000 +#define GAMECARD_SIZE_8GiB (u64)0x200000000 +#define GAMECARD_SIZE_16GiB (u64)0x400000000 +#define GAMECARD_SIZE_32GiB (u64)0x800000000 + +/* Reference: https://switchbrew.org/wiki/Title_list */ +#define GAMECARD_UPDATE_TITLEID (u64)0x0100000000000816 + +#define SYSUPDATE_100 (u32)450 +#define SYSUPDATE_200 (u32)65796 +#define SYSUPDATE_210 (u32)131162 +#define SYSUPDATE_220 (u32)196628 +#define SYSUPDATE_230 (u32)262164 +#define SYSUPDATE_300 (u32)201327002 +#define SYSUPDATE_301 (u32)201392178 +#define SYSUPDATE_302 (u32)201457684 +#define SYSUPDATE_400 (u32)268435656 +#define SYSUPDATE_401 (u32)268501002 +#define SYSUPDATE_410 (u32)269484082 +#define SYSUPDATE_500 (u32)335544750 +#define SYSUPDATE_501 (u32)335609886 +#define SYSUPDATE_502 (u32)335675432 +#define SYSUPDATE_510 (u32)336592976 +#define SYSUPDATE_600 (u32)402653544 +#define SYSUPDATE_601 (u32)402718730 +#define SYSUPDATE_610 (u32)403701850 +#define SYSUPDATE_620 (u32)404750376 +#define SYSUPDATE_700 (u32)469762248 +#define SYSUPDATE_701 (u32)469827614 +#define SYSUPDATE_800 (u32)536871442 + +#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 +{ + u64 file_offset; + u64 file_size; + u32 filename_offset; + u32 hashed_region_size; + u64 reserved; + 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; bool isGameCardInserted(); @@ -32,14 +262,40 @@ void removeIllegalCharacters(char *name); void strtrim(char *str); +void freeStringsPtr(char **var); + +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 getPartitionHfs0Header(u32 partition); + +bool getHfs0FileList(u32 partition); + int getSdCardFreeSpace(u64 *out); void convertSize(u64 size, char *out, int bufsize); +char *generateDumpName(); + void waitForButtonPress(); +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);