1
0
Fork 0
mirror of https://github.com/DarkMatterCore/nxdumptool.git synced 2024-10-18 19:31:43 +01:00

Update to v1.0.8.

This commit is contained in:
Pablo Curiel 2019-05-01 16:24:13 -04:00
parent fd4b7e933b
commit 0713a13151
13 changed files with 3545 additions and 915 deletions

View file

@ -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

View file

@ -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.

325
source/aes.c Normal file
View file

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

42
source/aes.h Normal file
View file

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

File diff suppressed because it is too large Load diff

View file

@ -5,89 +5,25 @@
#include <switch.h>
#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);

250
source/extkeys.c Normal file
View file

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

18
source/extkeys.h Normal file
View file

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

View file

@ -1,7 +1,6 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <malloc.h>
#include <switch.h>
#include <memory.h>
@ -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();

View file

@ -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);
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(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]), "Developer: %s", gameCardAuthor);
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]), "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++;
snprintf(titlebuf, sizeof(titlebuf) / sizeof(titlebuf[0]), "Size: %s", gameCardSizeStr);
uiDrawString(titlebuf, 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);
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;
switch(j)
if ((j + scroll) == cursor)
{
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;
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);
}
if ((j + scroll) == cursor) highlight = false;
// Print XCI dump menu settings values
if (uiState == stateXciDumpMenu && 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: // 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;
}
// 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;
}

View file

@ -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();

File diff suppressed because it is too large Load diff

View file

@ -5,20 +5,250 @@
#include <switch.h>
#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);