Skip to content

Commit

Permalink
Update to v1.0.8.
Browse files Browse the repository at this point in the history
  • Loading branch information
DarkMatterCore committed May 1, 2019
1 parent fd4b7e9 commit 0713a13
Show file tree
Hide file tree
Showing 13 changed files with 3,672 additions and 1,042 deletions.
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
23 changes: 20 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand All @@ -24,13 +27,27 @@ 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.

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.
Expand Down
325 changes: 325 additions & 0 deletions source/aes.c
Original file line number Diff line number Diff line change
@@ -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;
}
Loading

0 comments on commit 0713a13

Please sign in to comment.