First Public commit.

This commit is contained in:
Michael Scire 2018-01-24 09:52:25 -08:00
commit e2c9c1116f
26 changed files with 3289 additions and 0 deletions

11
.gitignore vendored Normal file
View file

@ -0,0 +1,11 @@
config.mk
ncatool
ncatool.exe
pki_zero.h
pki_full.h
pki_staging.h
*.o
*.dll
bin
test
test/*

15
LICENSE Normal file
View file

@ -0,0 +1,15 @@
ISC License
Copyright (c) 2018, SciresM
Permission to use, copy, modify, and/or distribute this software for any
purpose with or without fee is hereby granted, provided that the above
copyright notice and this permission notice appear in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

45
Makefile Normal file
View file

@ -0,0 +1,45 @@
include config.mk
ifeq ($(OS),Windows_NT)
LDFLAGS += -liconv
endif
.PHONY: clean
CFLAGS += -D_BSD_SOURCE -D_POSIX_SOURCE -D_POSIX_C_SOURCE=2 -D_DEFAULT_SOURCE -D__USE_MINGW_ANSI_STDIO=1 -D_FILE_OFFSET_BITS=64
all: ncatool
.c.o:
$(CC) -c $(CFLAGS) -o $@ $<
ncatool: sha.o aes.o rsa.o npdm.o utils.o nca.o main.o filepath.o
$(CC) -o $@ $^ $(LDFLAGS)
aes.o: aes.h types.h
filepath.o: filepath.c types.h
main.o: main.c pki.h types.h
nca.o: nca.h aes.h sha.h rsa.h filepath.h types.h
npdm.o: npdm.c types.h
rsa.o: rsa.h sha.h types.h
sha.o: sha.h types.h
utils.o: utils.h types.h
clean:
rm -f *.o ncatool ncatool.exe
dist:
$(eval NCATOOLVER = $(shell grep '\bNCATOOL_VERSION\b' version.h \
| cut -d' ' -f2 \
| sed -e 's/"//g'))
mkdir ncatool-$(NCATOOLVER)
cp *.c *.h config.mk.template Makefile README.md LICENSE ncatool-$(NCATOOLVER)
tar czf ncatool-$(NCATOOLVER).tar.gz ncatool-$(NCATOOLVER)
rm -r ncatool-$(NCATOOLVER)

51
README.md Normal file
View file

@ -0,0 +1,51 @@
# ncatool
![License](https://img.shields.io/badge/license-ISC-blue.svg)
ncatool is a tool to view information about, decrypt, and extract Nintendo Content Archives.
It is heavily inspired by [ctrtool](https://github.com/profi200/Project_CTR/tree/master/ctrtool).
## Usage
```
Usage: ncatool [options...] <file>
Options:
-i, --info Show file info.
This is the default action.
-x, --extract Extract data from file.
This is also the default action.
-r, --raw Keep raw data, don't unpack.
-y, --verify Verify hashes and signatures.
-d, --dev Decrypt with development keys instead of retail.
--titlekey=key Set title key for Rights ID crypto titles.
--contentkey=key Set raw key for NCA body decryption.
NCA options:
--section0=file Specify Section 0 file path.
--section1=file Specify Section 1 file path.
--section2=file Specify Section 2 file path.
--section3=file Specify Section 3 file path.
--section0dir=dir Specify Section 0 directory path.
--section1dir=dir Specify Section 1 directory path.
--section2dir=dir Specify Section 2 directory path.
--section3dir=dir Specify Section 3 directory path.
--exefs=file Specify ExeFS file path. Overrides appropriate section file path.
--exefsdir=dir Specify ExeFS directory path. Overrides appropriate section directory path.
--romfs=file Specify RomFS file path. Overrides appropriate section file path.
--romfsdir=dir Specify RomFS directory path. Overrides appropriate section directory path.
--listromfs List files in RomFS.
```
## Building
Copy `config.mk.template` to `config.mk`, make changes as required, and then run `make`.
If your `make` is not GNU make (e.g. on BSD variants), you need to call `gmake` instead.
To build under windows, you will need to build [libgpgerror](https://www.gnupg.org/(fr)/related_software/libgpg-error/index.html), and [libgcrypt](https://www.gnu.org/software/libgcrypt/).
You may need [libiconv](https://www.gnu.org/software/libiconv/) when not building on Linux.
Fairly recent versions (~1.8.0) are required of the libraries in order to support AES-XTS operations. I recommend using MinGW.
## Licensing
This software is licensed under the terms of the ISC License.
You can find a copy of the license in the LICENSE file.

112
aes.c Normal file
View file

@ -0,0 +1,112 @@
#include <stdlib.h>
#include <stdio.h>
#include "aes.h"
#include "types.h"
#include "utils.h"
/* Initialize the wrapper library. */
void aes_init(void) {
if (!gcry_check_version("1.8.0")) {
FATAL_ERROR("Error: gcrypt version is less than 1.8.0");
}
gcry_control(GCRYCTL_DISABLE_SECMEM, 0);
gcry_control(GCRYCTL_INITIALIZATION_FINISHED, 0);
}
/* Allocate a new context. */
aes_ctx_t *new_aes_ctx(const void *key, unsigned int key_size, int mode) {
aes_ctx_t *ctx;
if ((ctx = malloc(sizeof(*ctx))) == NULL) {
FATAL_ERROR("Failed to allocate aes_ctx_t!");
}
ctx->mode = mode;
if (gcry_cipher_open(&ctx->cipher, GCRY_CIPHER_AES128, mode, 0) != 0) {
FATAL_ERROR("Failed to open aes_ctx_t!");
}
if (gcry_cipher_setkey(ctx->cipher, key, key_size) != 0) {
FATAL_ERROR("Failed to set key!");
}
return ctx;
}
/* Free an allocated context. */
void free_aes_ctx(aes_ctx_t *ctx) {
/* Explicitly allow NULL. */
if (ctx == NULL) {
return;
}
gcry_cipher_close(ctx->cipher);
free(ctx);
}
/* Set AES CTR or IV for a context. */
void aes_setiv(aes_ctx_t *ctx, const void *iv, size_t l) {
if (ctx->mode == GCRY_CIPHER_MODE_CTR) {
if (gcry_cipher_setctr(ctx->cipher, iv, l) != 0) {
FATAL_ERROR("Failed to set ctr!");
}
} else {
if (gcry_cipher_setiv(ctx->cipher, iv, l) != 0) {
FATAL_ERROR("Failed to set iv!");
}
}
}
/* Encrypt with context. */
void aes_encrypt(aes_ctx_t *ctx, void *dst, void *src, size_t l) {
if (gcry_cipher_encrypt(ctx->cipher, dst, l, src, (src == NULL) ? 0 : l) != 0) {
FATAL_ERROR("Failed to encrypt!");
}
}
/* Decrypt with context. */
void aes_decrypt(aes_ctx_t *ctx, void *dst, void *src, size_t l) {
if (gcry_cipher_decrypt(ctx->cipher, dst, l, src, (src == NULL) ? 0 : l) != 0) {
FATAL_ERROR("Failed to decrypt!");
}
}
void get_tweak(unsigned char *tweak, size_t sector) {
for (int i = 0xF; i >= 0; i--) { /* Nintendo LE custom tweak... */
tweak[i] = (unsigned char)(sector & 0xFF);
sector >>= 8;
}
}
/* Encrypt with context. */
void aes_xts_encrypt(aes_ctx_t *ctx, void *dst, void *src, size_t l, size_t sector, size_t sector_size) {
unsigned char tweak[0x10];
if (l % sector_size != 0) {
FATAL_ERROR("Length must be multiple of sectors!");
}
for (size_t i = 0; i < l; i += sector_size) {
/* Workaround for Nintendo's custom sector...manually generate the tweak. */
get_tweak(tweak, sector++);
aes_setiv(ctx, tweak, 16);
aes_encrypt(ctx, ((char *)dst) + i, (src == NULL) ? NULL : ((char *)src) + i, sector_size);
}
}
/* Decrypt with context. */
void aes_xts_decrypt(aes_ctx_t *ctx, void *dst, void *src, size_t l, size_t sector, size_t sector_size) {
unsigned char tweak[0x10];
if (l % sector_size != 0) {
FATAL_ERROR("Length must be multiple of sectors!");
}
for (size_t i = 0; i < l; i += sector_size) {
/* Workaround for Nintendo's custom sector...manually generate the tweak. */
get_tweak(tweak, sector++);
aes_setiv(ctx, tweak, 16);
aes_decrypt(ctx, ((char *)dst) + i, (src == NULL) ? NULL : ((char *)src) + i, sector_size);
}
}

24
aes.h Normal file
View file

@ -0,0 +1,24 @@
#ifndef NCATOOL_AES_H
#define NCATOOL_AES_H
#define GCRYPT_NO_DEPRECATED
#include <gcrypt.h>
typedef struct {
gcry_cipher_hd_t cipher; /* gcrypt context for this cryptor. */
int mode;
} aes_ctx_t;
void aes_init(void);
aes_ctx_t *new_aes_ctx(const void *key, unsigned int key_size, int mode);
void free_aes_ctx(aes_ctx_t *ctx);
void aes_setiv(aes_ctx_t *ctx, const void *iv, size_t l);
void aes_encrypt(aes_ctx_t *ctx, void *dst, void *src, size_t l);
void aes_decrypt(aes_ctx_t *ctx, void *dst, void *src, size_t l);
void aes_xts_encrypt(aes_ctx_t *ctx, void *dst, void *src, size_t l, size_t sector, size_t sector_size);
void aes_xts_decrypt(aes_ctx_t *ctx, void *dst, void *src, size_t l, size_t sector, size_t sector_size);
#endif

3
config.mk.template Normal file
View file

@ -0,0 +1,3 @@
CC=gcc
CFLAGS=-O2 -Wall -Wextra -pedantic -std=gnu11 -fPIC
LDFLAGS= -lgcrypt

110
filepath.c Normal file
View file

@ -0,0 +1,110 @@
#include <sys/stat.h>
#include <stdio.h>
#include <string.h>
#include <stdarg.h>
#include "types.h"
#include "filepath.h"
#include <iconv.h>
void os_strcpy(oschar_t *dst, const char *src) {
#ifdef _WIN32
if (src == NULL) return;
uint32_t src_len, dst_len;
size_t in_bytes, out_bytes;
char *in, *out;
src_len = strlen(src);
dst_len = src_len + 1;
in = (char *)src;
out = (char *)dst;
in_bytes = src_len;
out_bytes = dst_len;
iconv_t cd = iconv_open("UTF-16LE", "UTF-8");
iconv(cd, &in, &in_bytes, &out, &out_bytes);
iconv_close(cd);
#else
strcpy(dst, src);
#endif
}
int os_makedir(const oschar_t *dir) {
#ifdef _WIN32
return _wmkdir(dir);
#else
return mkdir(dir, 0777);
#endif
}
void filepath_update(filepath_t *fpath) {
memset(fpath->os_path, 0, MAX_PATH * sizeof(oschar_t));
os_strcpy(fpath->os_path, fpath->char_path);
}
void filepath_init(filepath_t *fpath) {
fpath->valid = VALIDITY_INVALID;
}
void filepath_copy(filepath_t *fpath, filepath_t *copy) {
if (copy != NULL && copy->valid == VALIDITY_VALID)
memcpy(fpath, copy, sizeof(filepath_t));
else
memset(fpath, 0, sizeof(filepath_t));
}
void filepath_append(filepath_t *fpath, const char *format, ...) {
char tmppath[MAX_PATH];
va_list args;
if (fpath->valid == VALIDITY_INVALID)
return;
memset(tmppath, 0, MAX_PATH);
va_start(args, format);
vsnprintf(tmppath, sizeof(tmppath), format, args);
va_end(args);
strcat(fpath->char_path, OS_PATH_SEPARATOR);
strcat(fpath->char_path, tmppath);
filepath_update(fpath);
}
void filepath_append_n(filepath_t *fpath, uint32_t n, const char *format, ...) {
char tmppath[MAX_PATH];
va_list args;
if (fpath->valid == VALIDITY_INVALID || n > MAX_PATH)
return;
memset(tmppath, 0, MAX_PATH);
va_start(args, format);
vsnprintf(tmppath, sizeof(tmppath), format, args);
va_end(args);
strcat(fpath->char_path, OS_PATH_SEPARATOR);
strncat(fpath->char_path, tmppath, n);
filepath_update(fpath);
}
void filepath_set(filepath_t *fpath, const char *path) {
if (strlen(path) < MAX_PATH) {
fpath->valid = VALIDITY_VALID;
memset(fpath->char_path, 0, MAX_PATH);
strncpy(fpath->char_path, path, MAX_PATH);
filepath_update(fpath);
} else {
fpath->valid = VALIDITY_INVALID;
}
}
oschar_t *filepath_get(filepath_t *fpath) {
if (fpath->valid == VALIDITY_INVALID)
return NULL;
else
return fpath->os_path;
}

45
filepath.h Normal file
View file

@ -0,0 +1,45 @@
#ifndef NCATOOL_FILEPATH_H
#define NCATOOL_FILEPATH_H
#include "types.h"
#include "utils.h"
typedef uint16_t utf16char_t;
#ifdef _WIN32
typedef wchar_t oschar_t; /* utf-16 */
#define os_fopen _wfopen
#define OS_MODE_READ L"rb"
#define OS_MODE_WRITE L"wb"
#define OS_MODE_EDIT L"rb+"
#define OS_PATH_SEPARATOR "\\"
#else
typedef char oschar_t; /* utf-8 */
#define os_fopen fopen
#define OS_MODE_READ "rb"
#define OS_MODE_WRITE "wb"
#define OS_MODE_EDIT "rb+"
#define OS_PATH_SEPARATOR "/"
#endif
typedef struct {
char char_path[MAX_PATH];
oschar_t os_path[MAX_PATH];
validity_t valid;
} filepath_t;
void os_strcpy(oschar_t *dst, const char *src);
int os_makedir(const oschar_t *dir);
void filepath_init(filepath_t *fpath);
void filepath_copy(filepath_t *fpath, filepath_t *copy);
void filepath_append(filepath_t *fpath, const char *format, ...);
void filepath_append_n(filepath_t *fpath, uint32_t n, const char *format, ...);
void filepath_set(filepath_t *fpath, const char *path);
oschar_t *filepath_get(filepath_t *fpath);
#endif

82
ivfc.h Normal file
View file

@ -0,0 +1,82 @@
#ifndef NCATOOL_IVFC_H
#define NCATOOL_IVFC_H
#include "types.h"
#define IVFC_HEADER_SIZE 0xE0
#define IVFC_MAX_LEVEL 6
#define IVFC_MAX_BUFFERSIZE 0x4000
#define MAGIC_IVFC 0x43465649
typedef struct {
uint64_t logical_offset;
uint64_t hash_data_size;
uint32_t block_size;
uint32_t reserved;
} ivfc_level_hdr_t;
typedef struct {
uint64_t data_offset;
uint64_t data_size;
uint64_t hash_offset;
uint32_t hash_block_size;
validity_t hash_validity;
} ivfc_level_ctx_t;
typedef struct {
uint32_t magic;
uint32_t id;
uint32_t master_hash_size;
uint32_t num_levels;
ivfc_level_hdr_t level_headers[IVFC_MAX_LEVEL];
uint8_t _0xA0[0x20];
uint8_t master_hash[0x20];
} ivfc_hdr_t;
/* RomFS structs. */
#define ROMFS_HEADER_SIZE 0x00000050
typedef struct {
uint64_t header_size;
uint64_t dir_hash_table_offset;
uint64_t dir_hash_table_size;
uint64_t dir_meta_table_offset;
uint64_t dir_meta_table_size;
uint64_t file_hash_table_offset;
uint64_t file_hash_table_size;
uint64_t file_meta_table_offset;
uint64_t file_meta_table_size;
uint64_t data_offset;
} romfs_hdr_t;
typedef struct {
uint32_t sibling;
uint32_t child;
uint32_t file;
uint32_t hash;
uint32_t name_size;
char name[];
} romfs_direntry_t;
typedef struct {
uint32_t parent;
uint32_t sibling;
uint64_t offset;
uint64_t size;
uint32_t hash;
uint32_t name_size;
char name[];
} romfs_fentry_t;
#define ROMFS_ENTRY_EMPTY 0xFFFFFFFF
static inline romfs_direntry_t *romfs_get_direntry(romfs_direntry_t *directories, uint32_t offset) {
return (romfs_direntry_t *)((char *)directories + offset);
}
static inline romfs_fentry_t *romfs_get_fentry(romfs_fentry_t *files, uint32_t offset) {
return (romfs_fentry_t *)((char *)files + offset);
}
#endif

212
main.c Normal file
View file

@ -0,0 +1,212 @@
#include <getopt.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "utils.h"
#include "settings.h"
#include "pki.h"
#include "nca.h"
static char *prog_name = "ncatool";
/* Print usage. Taken largely from ctrtool. */
static void usage(void) {
fprintf(stderr,
"ncatool (c) SciresM.\n"
"Built: %s %s\n"
"\n"
"Usage: %s [options...] <file>\n"
"Options:\n"
"-i, --info Show file info.\n"
" This is the default action.\n"
"-x, --extract Extract data from file.\n"
" This is also the default action.\n"
" -r, --raw Keep raw data, don't unpack.\n"
" -y, --verify Verify hashes and signatures.\n"
" -d, --dev Decrypt with development keys instead of retail.\n"
" --titlekey=key Set title key for Rights ID crypto titles.\n"
" --contentkey=key Set raw key for NCA body decryption.\n"
"NCA options:\n"
" --section0=file Specify Section 0 file path.\n"
" --section1=file Specify Section 1 file path.\n"
" --section2=file Specify Section 2 file path.\n"
" --section3=file Specify Section 3 file path.\n"
" --section0dir=dir Specify Section 0 directory path.\n"
" --section1dir=dir Specify Section 1 directory path.\n"
" --section2dir=dir Specify Section 2 directory path.\n"
" --section3dir=dir Specify Section 3 directory path.\n"
" --exefs=file Specify ExeFS file path. Overrides appropriate section file path.\n"
" --exefsdir=dir Specify ExeFS directory path. Overrides appropriate section directory path.\n"
" --romfs=file Specify RomFS file path. Overrides appropriate section file path.\n"
" --romfsdir=dir Specify RomFS directory path. Overrides appropriate section directory path.\n"
" --listromfs List files in RomFS.\n"
/* TODO: " --basenca Set Base NCA to use with update partitions.\n" */
"\n", __TIME__, __DATE__, prog_name);
exit(EXIT_FAILURE);
}
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;
}
void parse_hex_key(unsigned char *key, const char *hex) {
if (strlen(hex) != 32) {
fprintf(stderr, "Key must be 32 hex digits!\n");
usage();
}
for (unsigned int i = 0; i < 32; i++) {
if (!ishex(hex[i])) {
fprintf(stderr, "Key must be 32 hex digits!\n");
usage();
}
}
memset(key, 0, 16);
for (unsigned int i = 0; i < 32; i++) {
char val = hextoi(hex[i]);
if ((i & 1) == 0) {
val <<= 4;
}
key[i >> 1] |= val;
}
}
int main(int argc, char **argv) {
ncatool_ctx_t tool_ctx;
nca_ctx_t ctx;
char input_name[0x200];
prog_name = (argc < 1) ? "ncatool" : argv[0];
nca_init(&ctx);
memset(&tool_ctx, 0, sizeof(tool_ctx));
memset(input_name, 0, sizeof(input_name));
ctx.tool_ctx = &tool_ctx;
ctx.tool_ctx->action = ACTION_INFO | ACTION_EXTRACT;
pki_initialize_keyset(&tool_ctx.settings.keyset, KEYSET_RETAIL);
while (1) {
int option_index;
char c;
static struct option long_options[] =
{
{"extract", 0, NULL, 'x'},
{"info", 0, NULL, 'i'},
{"dev", 0, NULL, 'd'},
{"verify", 0, NULL, 'y'},
{"raw", 0, NULL, 'r'},
{"section0", 1, NULL, 0},
{"section1", 1, NULL, 1},
{"section2", 1, NULL, 2},
{"section3", 1, NULL, 3},
{"section0dir", 1, NULL, 4},
{"section1dir", 1, NULL, 5},
{"section2dir", 1, NULL, 6},
{"section3dir", 1, NULL, 7},
{"exefs", 1, NULL, 8},
{"romfs", 1, NULL, 9},
{"exefsdir", 1, NULL, 10},
{"romfsdir", 1, NULL, 11},
{"titlekey", 1, NULL, 12},
{"contentkey", 1, NULL, 13},
{"listromfs", 0, NULL, 14},
/* TODO: {"basenca", 1, NULL, 15}, */
{NULL, 0, NULL, 0},
};
c = getopt_long(argc, argv, "dryxi", long_options, &option_index);
if (c == -1)
break;
switch (c)
{
case 'i':
ctx.tool_ctx->action |= ACTION_INFO;
break;
case 'x':
ctx.tool_ctx->action |= ACTION_EXTRACT;
break;
case 'y':
ctx.tool_ctx->action |= ACTION_VERIFY;
break;
case 'r':
ctx.tool_ctx->action |= ACTION_RAW;
break;
case 'd':
pki_initialize_keyset(&tool_ctx.settings.keyset, KEYSET_DEV);
break;
case 0: filepath_set(&ctx.tool_ctx->settings.section_paths[0], optarg); break;
case 1: filepath_set(&ctx.tool_ctx->settings.section_paths[1], optarg); break;
case 2: filepath_set(&ctx.tool_ctx->settings.section_paths[2], optarg); break;
case 3: filepath_set(&ctx.tool_ctx->settings.section_paths[3], optarg); break;
case 4: filepath_set(&ctx.tool_ctx->settings.section_dir_paths[0], optarg); break;
case 5: filepath_set(&ctx.tool_ctx->settings.section_dir_paths[1], optarg); break;
case 6: filepath_set(&ctx.tool_ctx->settings.section_dir_paths[2], optarg); break;
case 7: filepath_set(&ctx.tool_ctx->settings.section_dir_paths[3], optarg); break;
case 8:
ctx.tool_ctx->settings.exefs_path.enabled = 1;
filepath_set(&ctx.tool_ctx->settings.exefs_path.path, optarg);
break;
case 9:
ctx.tool_ctx->settings.romfs_path.enabled = 1;
filepath_set(&ctx.tool_ctx->settings.romfs_path.path, optarg);
break;
case 10:
ctx.tool_ctx->settings.exefs_dir_path.enabled = 1;
filepath_set(&ctx.tool_ctx->settings.exefs_dir_path.path, optarg);
break;
case 11:
ctx.tool_ctx->settings.romfs_dir_path.enabled = 1;
filepath_set(&ctx.tool_ctx->settings.romfs_dir_path.path, optarg);
break;
case 12:
parse_hex_key(ctx.tool_ctx->settings.titlekey, optarg);
ctx.tool_ctx->settings.has_titlekey = 1;
break;
case 13:
parse_hex_key(ctx.tool_ctx->settings.contentkey, optarg);
ctx.tool_ctx->settings.has_contentkey = 1;
break;
case 14:
ctx.tool_ctx->action |= ACTION_LISTROMFS;
break;
default:
usage();
return EXIT_FAILURE;
}
}
if (optind == argc - 1) {
/* Copy input file. */
strncpy(input_name, argv[optind], sizeof(input_name));
} else if ((optind < argc) || (argc == 1)) {
usage();
}
if ((tool_ctx.file = fopen(input_name, "rb")) == NULL) {
fprintf(stderr, "unable to open %s: %s\n", input_name, strerror(errno));
}
ctx.file = tool_ctx.file;
nca_process(&ctx);
nca_free_section_contexts(&ctx);
printf("Done!\n");
return EXIT_SUCCESS;
}

937
nca.c Normal file
View file

@ -0,0 +1,937 @@
#include <stdlib.h>
#include "nca.h"
#include "aes.h"
#include "sha.h"
#include "rsa.h"
#include "utils.h"
#include "filepath.h"
/* Initialize the context. */
void nca_init(nca_ctx_t *ctx) {
memset(ctx, 0, sizeof(*ctx));
}
/* Updates the CTR for an offset. */
void nca_update_ctr(unsigned char *ctr, uint64_t ofs) {
ofs >>= 4;
for (unsigned int j = 0; j < 0x8; j++) {
ctr[0x10-j-1] = (unsigned char)(ofs & 0xFF);
ofs >>= 8;
}
}
/* Seek to an offset within a section. */
void nca_section_fseek(nca_section_ctx_t *ctx, uint64_t offset) {
if (ctx->is_decrypted) {
fseeko64(ctx->file, (ctx->offset + offset), SEEK_SET);
ctx->cur_seek = (ctx->offset + offset);
} else if (ctx->header->crypt_type == CRYPT_XTS) {
fseeko64(ctx->file, (ctx->offset + offset) & ~0x1FF, SEEK_SET);
ctx->cur_seek = (ctx->offset + offset) & ~0x1FF;
ctx->sector_num = offset / 0x200;
ctx->sector_ofs = offset & 0x1FF;
} else if (ctx->header->crypt_type != CRYPT_NONE) {
fseeko64(ctx->file, (ctx->offset + offset) & ~0xF, SEEK_SET);
ctx->cur_seek = (ctx->offset + offset) & ~0xF;
nca_update_ctr(ctx->ctr, ctx->offset + offset);
ctx->sector_ofs = offset & 0xF;
}
}
size_t nca_section_fread(nca_section_ctx_t *ctx, void *buffer, size_t count) {
size_t read = 0; /* XXX */
size_t size = 1;
char block_buf[0x10];
if (ctx->is_decrypted) {
read = fread(buffer, size, count, ctx->file);
return read;
}
if (ctx->header->crypt_type == CRYPT_XTS) { /* AES-XTS requires special handling... */
unsigned char sector_buf[0x200];
if ((read = fread(&sector_buf, size, 0x200, ctx->file)) != 0x200) {
return 0;
}
aes_xts_decrypt(ctx->aes, &sector_buf, NULL, 0x200, ctx->sector_num, 0x200);
if (count > 0x200 - ctx->sector_ofs) { /* We're leaving the sector... */
memcpy(buffer, &sector_buf + ctx->sector_ofs, 0x200 - ctx->sector_ofs);
ctx->sector_num++;
ctx->sector_ofs = 0;
size_t remaining = count - (0x200 - ctx->sector_ofs);
size_t ofs = (0x200 - ctx->sector_ofs);
if (remaining & ~0x1FF) { /* Read intermediate sectors. */
if ((read = fread((char *)buffer + ofs, size, (remaining & ~0x1FF), ctx->file)) != (remaining & ~0x1FF)) {
return ofs;
}
aes_xts_decrypt(ctx->aes, (char *)buffer + ofs, NULL, remaining & ~0x1FF, ctx->sector_num, 0x200);
ctx->sector_num += remaining / 0x200;
ofs += remaining & ~0x1FF;
remaining &= 0x1FF;
}
if (remaining) { /* Read last sector. */
if ((read = fread(&sector_buf, size, 0x200, ctx->file)) != 0x200) {
return ofs;
}
aes_xts_decrypt(ctx->aes, &sector_buf, NULL, 0x200, ctx->sector_num, 0x200);
memcpy((char *)buffer + ofs, &sector_buf, remaining);
ctx->sector_ofs = remaining;
read = count;
}
} else {
memcpy(buffer, &sector_buf + ctx->sector_ofs, count);
ctx->sector_num += (ctx->sector_ofs + count) / 0x200;
ctx->sector_ofs += count;
ctx->sector_ofs &= 0x1FF;
}
} else {
/* Perform decryption, if necessary. */
switch (ctx->header->crypt_type) {
case CRYPT_CTR: { /* AES-CTR. */
if (ctx->sector_ofs) {
if ((read = fread(block_buf, 1, 0x10, ctx->file)) != 0x10) {
return 0;
}
aes_setiv(ctx->aes, ctx->ctr, 0x10);
aes_decrypt(ctx->aes, block_buf, NULL, 0x10);
if (count + ctx->sector_ofs < 0x10) {
memcpy(buffer, block_buf + ctx->sector_ofs, count);
ctx->sector_ofs += count;
nca_section_fseek(ctx, ctx->cur_seek - ctx->offset);
return count;
}
memcpy(buffer, block_buf + ctx->sector_ofs, 0x10 - ctx->sector_ofs);
uint32_t read_in_block = 0x10 - ctx->sector_ofs;
nca_section_fseek(ctx, ctx->cur_seek - ctx->offset + 0x10);
return read_in_block + nca_section_fread(ctx, (char *)buffer + read_in_block, count - read_in_block);
}
if ((read = fread(buffer, 1, count, ctx->file)) != count) {
return 0;
}
aes_setiv(ctx->aes, ctx->ctr, 16);
aes_decrypt(ctx->aes, buffer, NULL, count);
nca_section_fseek(ctx, ctx->cur_seek - ctx->offset + count);
break;
}
case CRYPT_BKTR: { /* Spooky BKTR AES-CTR. */
// TODO: Implement
break;
}
default:
break;
}
}
return read;
}
void nca_free_section_contexts(nca_ctx_t *ctx) {
for (unsigned int i = 0; i < 4; i++) {
if (ctx->section_contexts[i].is_present) {
if (ctx->section_contexts[i].aes) {
free_aes_ctx(ctx->section_contexts[i].aes);
}
if (ctx->section_contexts[i].pfs0_ctx.is_exefs) {
free(ctx->section_contexts[i].pfs0_ctx.npdm);
} else if (ctx->section_contexts[i].type == ROMFS) {
if (ctx->section_contexts[i].romfs_ctx.directories) {
free(ctx->section_contexts[i].romfs_ctx.directories);
}
if (ctx->section_contexts[i].romfs_ctx.files) {
free(ctx->section_contexts[i].romfs_ctx.files);
}
}
}
}
}
void nca_process(nca_ctx_t *ctx) {
/* First things first, decrypt header. */
if (!nca_decrypt_header(ctx)) {
fprintf(stderr, "Invalid NCA header!\n");
return;
}
if (rsa2048_pss_verify(&ctx->header.magic, 0x200, ctx->header.fixed_key_sig, ctx->tool_ctx->settings.keyset.nca_hdr_fixed_key_modulus)) {
ctx->fixed_sig_validity = VALIDITY_VALID;
} else {
ctx->fixed_sig_validity = VALIDITY_INVALID;
}
/* Sort out crypto type. */
ctx->crypto_type = ctx->header.crypto_type;
if (ctx->header.crypto_type2 > ctx->header.crypto_type)
ctx->crypto_type = ctx->header.crypto_type2;
if (ctx->crypto_type)
ctx->crypto_type--; /* 0, 1 are both master key 0. */
/* Rights ID. */
for (unsigned int i = 0; i < 0x10; i++) {
if (ctx->header.rights_id[i] != 0) {
ctx->has_rights_id = 1;
break;
}
}
/* Decrypt key area if required. */
if (!ctx->has_rights_id) {
nca_decrypt_key_area(ctx);
} else {
/* Decrypt title key. */
if (ctx->tool_ctx->settings.has_titlekey) {
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.titlekeks[ctx->crypto_type], 16, GCRY_CIPHER_MODE_ECB);
aes_decrypt(aes_ctx, ctx->tool_ctx->settings.dec_titlekey, ctx->tool_ctx->settings.titlekey, 0x10);
free_aes_ctx(aes_ctx);
}
}
/* Parse sections. */
for (unsigned int i = 0; i < 4; i++) {
if (ctx->header.section_entries[i].media_start_offset) { /* Section exists. */
ctx->section_contexts[i].is_present = 1;
ctx->section_contexts[i].is_decrypted = ctx->is_decrypted;
ctx->section_contexts[i].tool_ctx = ctx->tool_ctx;
ctx->section_contexts[i].file = ctx->file;
ctx->section_contexts[i].section_num = i;
ctx->section_contexts[i].offset = media_to_real(ctx->header.section_entries[i].media_start_offset);
ctx->section_contexts[i].size = media_to_real(ctx->header.section_entries[i].media_end_offset) - ctx->section_contexts[i].offset;
ctx->section_contexts[i].header = &ctx->header.fs_headers[i];
if (ctx->section_contexts[i].header->partition_type == PARTITION_PFS0 && ctx->section_contexts[i].header->fs_type == FS_TYPE_PFS0) {
ctx->section_contexts[i].type = PFS0;
ctx->section_contexts[i].pfs0_ctx.superblock = &ctx->section_contexts[i].header->pfs0_superblock;
} else if (ctx->section_contexts[i].header->partition_type == PARTITION_ROMFS && ctx->section_contexts[i].header->fs_type == FS_TYPE_ROMFS) {
if (ctx->section_contexts[i].header->crypt_type == CRYPT_BKTR) {
ctx->section_contexts[i].type = BKTR;
ctx->section_contexts[i].bktr_ctx.superblock = &ctx->section_contexts[i].header->bktr_superblock;
} else {
ctx->section_contexts[i].type = ROMFS;
ctx->section_contexts[i].romfs_ctx.superblock = &ctx->section_contexts[i].header->romfs_superblock;
}
} else {
ctx->section_contexts[i].type = INVALID;
}
uint64_t ofs = ctx->section_contexts[i].offset >> 4;
for (unsigned int j = 0; j < 0x8; j++) {
ctx->section_contexts[i].ctr[j] = ctx->section_contexts[i].header->section_ctr[0x8-j-1];
ctx->section_contexts[i].ctr[0x10-j-1] = (unsigned char)(ofs & 0xFF);
ofs >>= 8;
}
ctx->section_contexts[i].sector_num = 0;
ctx->section_contexts[i].sector_ofs = 0;
if (ctx->section_contexts[i].header->crypt_type == CRYPT_NONE) {
ctx->section_contexts[i].is_decrypted = 1;
}
if (ctx->tool_ctx->settings.has_contentkey) {
ctx->section_contexts[i].aes = new_aes_ctx(ctx->tool_ctx->settings.contentkey, 16, GCRY_CIPHER_MODE_CTR);
} else {
if (ctx->has_rights_id) {
ctx->section_contexts[i].aes = new_aes_ctx(ctx->tool_ctx->settings.dec_titlekey, 16, GCRY_CIPHER_MODE_CTR);
} else {
if (ctx->section_contexts[i].header->crypt_type == CRYPT_CTR || ctx->section_contexts[i].header->crypt_type == CRYPT_BKTR) {
ctx->section_contexts[i].aes = new_aes_ctx(ctx->decrypted_keys[2], 16, GCRY_CIPHER_MODE_CTR);
} else if (ctx->section_contexts[i].header->crypt_type == CRYPT_XTS) {
ctx->section_contexts[i].aes = new_aes_ctx(ctx->decrypted_keys[0], 32, GCRY_CIPHER_MODE_XTS);
}
}
}
if (ctx->tool_ctx->action & ACTION_VERIFY) {
printf("Verifying section %"PRId32"...\n", i);
}
switch (ctx->section_contexts[i].type) {
case PFS0:
nca_process_pfs0_section(&ctx->section_contexts[i]);
/* Verify NPDM sig now, if we can... */
if (ctx->section_contexts[i].pfs0_ctx.is_exefs) {
ctx->npdm = ctx->section_contexts[i].pfs0_ctx.npdm;
if (rsa2048_pss_verify(&ctx->header.magic, 0x200, ctx->header.npdm_key_sig, npdm_get_acid(ctx->npdm)->modulus)) {
ctx->npdm_sig_validity = VALIDITY_VALID;
} else {
ctx->npdm_sig_validity = VALIDITY_INVALID;
}
}
break;
case ROMFS:
nca_process_ivfc_section(&ctx->section_contexts[i]);
break;
case BKTR:
nca_process_bktr_section(&ctx->section_contexts[i]);
break;
case INVALID:
default:
break;
}
}
}
nca_print(ctx);
for (unsigned int i = 0; i < 4; i++) {
if (ctx->section_contexts[i].is_present) {
/* printf("Saving section %"PRId32"...\n", i); */
nca_save_section(&ctx->section_contexts[i]);
printf("\n");
}
}
}
/* Decrypt NCA header. */
int nca_decrypt_header(nca_ctx_t *ctx) {
fseeko64(ctx->file, 0, SEEK_SET);
if (fread(&ctx->header, 1, 0xC00, ctx->file) != 0xC00) {
fprintf(stderr, "Failed to read NCA header!\n");
return 0;
}
/* Try to support decrypted NCA headers. */
if (ctx->header.magic == MAGIC_NCA3) {
if (ctx->header._0x340[0] == 0 && !memcmp(ctx->header._0x340, ctx->header._0x340 + 1, 0xBF)) {
ctx->is_decrypted = 1;
return 1;
}
}
ctx->is_decrypted = 0;
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.header_key, 32, GCRY_CIPHER_MODE_XTS);
aes_xts_decrypt(aes_ctx, &ctx->header, NULL, 0xC00, 0, 0x200);
free_aes_ctx(aes_ctx);
return ctx->header.magic == MAGIC_NCA3;
}
/* Decrypt key area. */
void nca_decrypt_key_area(nca_ctx_t *ctx) {
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.key_area_keys[ctx->crypto_type][ctx->header.kaek_ind], 16, GCRY_CIPHER_MODE_ECB);
aes_decrypt(aes_ctx, ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40);
free_aes_ctx(aes_ctx);
}
char *nca_get_distribution_type(nca_ctx_t *ctx) {
switch (ctx->header.distribution) {
case 0:
return "Download";
case 1:
return "Gamecard";
default:
return "Unknown";
}
}
char *nca_get_content_type(nca_ctx_t *ctx) {
switch (ctx->header.content_type) {
case 0:
return "Program";
case 1:
return "Meta";
case 2:
return "Control";
case 3:
return "Manual";
case 4:
return "Data";
default:
return "Unknown";
}
}
char *nca_get_master_key_summary(uint8_t master_key_rev) {
switch (master_key_rev) {
case 0:
return "1.0.0-2.3.0";
case 1:
return "3.0.0";
case 2:
return "3.0.1-3.0.2";
case 3:
return "4.0.0-4.1.0";
default:
return "Unknown";
}
}
char *nca_get_encryption_type(nca_ctx_t *ctx) {
if (ctx->has_rights_id) {
return "Titlekey crypto";
} else {
return "Standard crypto";
}
}
void nca_print_key_area(nca_ctx_t *ctx) {
printf("Key Area (Encrypted):\n");
for (unsigned int i = 0; i < 0x4; i++) {
printf(" Key %"PRId32" (Encrypted): ", i);
memdump(stdout, "", &ctx->header.encrypted_keys[i], 0x10);
}
printf("Key Area (Decrypted):\n");
for (unsigned int i = 0; i < 0x4; i++) {
printf(" Key %"PRId32" (Decrypted): ", i);
memdump(stdout, "", &ctx->decrypted_keys[i], 0x10);
}
}
char *nca_get_section_type(nca_section_ctx_t *meta) {
switch (meta->type) {
case PFS0: {
if (meta->pfs0_ctx.is_exefs) return "ExeFS";
return "PFS0";
}
case ROMFS: return "RomFS";
case BKTR: return "Patch RomFS";
case INVALID:
default:
return "Unknown/Invalid";
}
}
void nca_print_sections(nca_ctx_t *ctx) {
printf("Sections:\n");
for (unsigned int i = 0; i < 4; i++) {
if (ctx->section_contexts[i].is_present) { /* Section exists. */
printf(" Section %"PRId32":\n", i);
printf(" Offset: 0x%012"PRIx64"\n", ctx->section_contexts[i].offset);
printf(" Size: 0x%012"PRIx64"\n", ctx->section_contexts[i].size);
printf(" Partition Type: %s\n", nca_get_section_type(&ctx->section_contexts[i]));
memdump(stdout, " Section CTR: ", &ctx->section_contexts[i].ctr, 16);
switch (ctx->section_contexts[i].type) {
case PFS0: {
nca_print_pfs0_section(&ctx->section_contexts[i]);
break;
}
case ROMFS: {
nca_print_ivfc_section(&ctx->section_contexts[i]);
break;
}
case BKTR: {
nca_print_bktr_section(&ctx->section_contexts[i]);
break;
}
case INVALID:
default: {
printf(" Unknown/invalid superblock!");
}
}
}
}
}
/* Print out information about the NCA. */
void nca_print(nca_ctx_t *ctx) {
printf("\nNCA:\n");
print_magic("Magic: ", ctx->header.magic);
if (ctx->tool_ctx->action & ACTION_VERIFY && ctx->fixed_sig_validity != VALIDITY_UNCHECKED) {
if (ctx->fixed_sig_validity == VALIDITY_VALID) {
memdump(stdout, "Fixed-Key Signature (GOOD): ", &ctx->header.fixed_key_sig, 0x100);
} else {
memdump(stdout, "Fixed-Key Signature (FAIL): ", &ctx->header.fixed_key_sig, 0x100);
}
} else {
memdump(stdout, "Fixed-Key Signature: ", &ctx->header.fixed_key_sig, 0x100);
}
if (ctx->tool_ctx->action & ACTION_VERIFY && ctx->npdm_sig_validity != VALIDITY_UNCHECKED) {
if (ctx->npdm_sig_validity == VALIDITY_VALID) {
memdump(stdout, "NPDM Signature (GOOD): ", &ctx->header.npdm_key_sig, 0x100);
} else {
memdump(stdout, "NPDM Signature (FAIL): ", &ctx->header.npdm_key_sig, 0x100);
}
} else {
memdump(stdout, "NPDM Signature: ", &ctx->header.npdm_key_sig, 0x100);
}
printf("Content Size: 0x%012"PRIx64"\n", ctx->header.nca_size);
printf("Title ID: %016"PRIx64"\n", ctx->header.title_id);
printf("SDK Version: %"PRId8".%"PRId8".%"PRId8".%"PRId8"\n", ctx->header.sdk_major, ctx->header.sdk_minor, ctx->header.sdk_micro, ctx->header.sdk_revision);
printf("Distribution type: %s\n", nca_get_distribution_type(ctx));
printf("Content Type: %s\n", nca_get_content_type(ctx));
printf("Master Key Revision: %"PRIx8" (%s)\n", ctx->crypto_type, nca_get_master_key_summary(ctx->crypto_type));
printf("Encryption Type: %s\n", nca_get_encryption_type(ctx));
if (ctx->has_rights_id) {
memdump(stdout, "Rights ID: ", &ctx->header.rights_id, 0x10);
if (ctx->tool_ctx->settings.has_titlekey) {
memdump(stdout, "Titlekey (Encrypted) ", ctx->tool_ctx->settings.titlekey, 0x10);
memdump(stdout, "Titlekey (Decrypted) ", ctx->tool_ctx->settings.dec_titlekey, 0x10);
}
} else {
printf("Key Area Encryption Key: %"PRIx8"\n", ctx->header.kaek_ind);
nca_print_key_area(ctx);
}
if (ctx->npdm) {
npdm_print(ctx->npdm, ctx->tool_ctx);
}
nca_print_sections(ctx);
printf("\n");
}
validity_t nca_section_check_external_hash_table(nca_section_ctx_t *ctx, unsigned char *hash_table, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block) {
if (block_size == 0) {
/* Block size of 0 is always invalid. */
return VALIDITY_INVALID;
}
unsigned char cur_hash[0x20];
uint64_t read_size = block_size;
unsigned char *block = malloc(block_size);
if (block == NULL) {
fprintf(stderr, "Failed to allocate hash block!\n");
exit(EXIT_FAILURE);
}
validity_t result = VALIDITY_VALID;
unsigned char *cur_hash_table_entry = hash_table;
for (uint64_t ofs = 0; ofs < data_len; ofs += read_size) {
nca_section_fseek(ctx, ofs + data_ofs);
if (ofs + read_size > data_len) {
/* Last block... */
memset(block, 0, read_size);
read_size = data_len - ofs;
}
uint64_t r = nca_section_fread(ctx, block, read_size);
if (r != read_size) {
fprintf(stderr, "Failed to read section!\n");
exit(EXIT_FAILURE);
}
sha_hash_buffer(cur_hash, block, full_block ? block_size : read_size);
if (memcmp(cur_hash, cur_hash_table_entry, 0x20) != 0) {
result = VALIDITY_INVALID;
break;
}
cur_hash_table_entry += 0x20;
}
free(block);
return result;
}
validity_t nca_section_check_hash_table(nca_section_ctx_t *ctx, uint64_t hash_ofs, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block) {
if (block_size == 0) {
/* Block size of 0 is always invalid. */
return VALIDITY_INVALID;
}
uint64_t hash_table_size = data_len / block_size;
if (data_len % block_size) hash_table_size++;
hash_table_size *= 0x20;
unsigned char *hash_table = malloc(hash_table_size);
if (hash_table == NULL) {
fprintf(stderr, "Failed to allocate hash table!\n");
exit(EXIT_FAILURE);
}
nca_section_fseek(ctx, hash_ofs);
if (nca_section_fread(ctx, hash_table, hash_table_size) != hash_table_size) {
fprintf(stderr, "Failed to read section!\n");
exit(EXIT_FAILURE);
}
validity_t result = nca_section_check_external_hash_table(ctx, hash_table, data_ofs, data_len, block_size, full_block);
free(hash_table);
return result;
}
void nca_save_pfs0_file(nca_section_ctx_t *ctx, uint32_t i, filepath_t *dirpath) {
if (i >= ctx->pfs0_ctx.header->num_files) {
fprintf(stderr, "Could not save file %"PRId32"!\n", i);
exit(EXIT_FAILURE);
}
pfs0_file_entry_t *cur_file = pfs0_get_file_entry(ctx->pfs0_ctx.header, i);
if (cur_file->size >= ctx->size) {
fprintf(stderr, "File %"PRId32" too big in PFS0 (section %"PRId32")!\n", i, ctx->section_num);
exit(EXIT_FAILURE);
}
if (strlen(pfs0_get_file_name(ctx->pfs0_ctx.header, i)) >= MAX_PATH - strlen(dirpath->char_path) - 1) {
fprintf(stderr, "Filename too long in PFS0!\n");
exit(EXIT_FAILURE);
}
filepath_t filepath;
filepath_copy(&filepath, dirpath);
filepath_append(&filepath, "%s", pfs0_get_file_name(ctx->pfs0_ctx.header, i));
printf("Saving %s to %s...\n", pfs0_get_file_name(ctx->pfs0_ctx.header, i), filepath.char_path);
uint64_t ofs = ctx->pfs0_ctx.superblock->pfs0_offset + pfs0_get_header_size(ctx->pfs0_ctx.header) + cur_file->offset;
nca_save_section_file(ctx, ofs, cur_file->size, &filepath);
}
void nca_process_pfs0_section(nca_section_ctx_t *ctx) {
pfs0_superblock_t *sb = ctx->pfs0_ctx.superblock;
ctx->superblock_hash_validity = nca_section_check_external_hash_table(ctx, sb->master_hash, sb->hash_table_offset, sb->hash_table_size, sb->hash_table_size, 0);
if (ctx->tool_ctx->action & ACTION_VERIFY) {
/* Verify actual PFS0... */
ctx->pfs0_ctx.hash_table_validity = nca_section_check_hash_table(ctx, sb->hash_table_offset, sb->pfs0_offset, sb->pfs0_size, sb->block_size, 0);
}
if (ctx->superblock_hash_validity != VALIDITY_VALID) return;
/* Read *just* safe amount. */
pfs0_header_t raw_header;
nca_section_fseek(ctx, sb->pfs0_offset);
if (nca_section_fread(ctx, &raw_header, sizeof(raw_header)) != sizeof(raw_header)) {
fprintf(stderr, "Failed to read PFS0 header!\n");
exit(EXIT_FAILURE);
}
uint64_t header_size = pfs0_get_header_size(&raw_header);
ctx->pfs0_ctx.header = malloc(header_size);
if (ctx->pfs0_ctx.header == NULL) {
fprintf(stderr, "Failed to get PFS0 header size!\n");
exit(EXIT_FAILURE);
}
nca_section_fseek(ctx, sb->pfs0_offset);
if (nca_section_fread(ctx, ctx->pfs0_ctx.header, header_size) != header_size) {
fprintf(stderr, "Failed to read PFS0 header!\n");
exit(EXIT_FAILURE);
}
for (unsigned int i = 0; i < ctx->pfs0_ctx.header->num_files; i++) {
pfs0_file_entry_t *cur_file = pfs0_get_file_entry(ctx->pfs0_ctx.header, i);
if (strcmp(pfs0_get_file_name(ctx->pfs0_ctx.header, i), "main.npdm") == 0) {
/* We might have found the exefs... */
if (cur_file->size >= sb->pfs0_size) {
fprintf(stderr, "NPDM too big!\n");
exit(EXIT_FAILURE);
}
ctx->pfs0_ctx.npdm = malloc(cur_file->size);
if (ctx->pfs0_ctx.npdm == NULL) {
fprintf(stderr, "Failed to allocate NPDM!\n");
exit(EXIT_FAILURE);
}
nca_section_fseek(ctx, sb->pfs0_offset + pfs0_get_header_size(ctx->pfs0_ctx.header) + cur_file->offset);
if (nca_section_fread(ctx, ctx->pfs0_ctx.npdm, cur_file->size) != cur_file->size) {
fprintf(stderr, "Failed to read NPDM!\n");
exit(EXIT_FAILURE);
}
if (ctx->pfs0_ctx.npdm->magic == MAGIC_META) {
ctx->pfs0_ctx.is_exefs = 1;
}
}
}
}
void nca_process_ivfc_section(nca_section_ctx_t *ctx) {
romfs_superblock_t *sb = ctx->romfs_ctx.superblock;
for (unsigned int i = 0; i < IVFC_MAX_LEVEL; i++) {
/* Load in the current level's header data. */
ivfc_level_ctx_t *cur_level = &ctx->romfs_ctx.ivfc_levels[i];
cur_level->data_offset = sb->ivfc_header.level_headers[i].logical_offset;
cur_level->data_size = sb->ivfc_header.level_headers[i].hash_data_size;
cur_level->hash_block_size = 1 << sb->ivfc_header.level_headers[i].block_size;
if (i != 0) {
/* Hash table is previous level's data. */
cur_level->hash_offset = ctx->romfs_ctx.ivfc_levels[i-1].data_offset;
} else {
/* Hash table is the superblock hash. Always check the superblock hash. */
ctx->superblock_hash_validity = nca_section_check_external_hash_table(ctx, sb->ivfc_header.master_hash, cur_level->data_offset, cur_level->data_size, cur_level->hash_block_size, 1);
cur_level->hash_validity = ctx->superblock_hash_validity;
}
if (ctx->tool_ctx->action & ACTION_VERIFY && i != 0) {
/* Actually check the table. */
printf(" Verifying IVFC Level %"PRId32"...\n", i);
cur_level->hash_validity = nca_section_check_hash_table(ctx, cur_level->hash_offset, cur_level->data_offset, cur_level->data_size, cur_level->hash_block_size, 1);
}
}
ctx->romfs_ctx.romfs_offset = ctx->romfs_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_offset;
nca_section_fseek(ctx, ctx->romfs_ctx.romfs_offset);
if (nca_section_fread(ctx, &ctx->romfs_ctx.header, sizeof(romfs_hdr_t)) != sizeof(romfs_hdr_t)) {
fprintf(stderr, "Failed to read RomFS header!\n");
}
if ((ctx->tool_ctx->action & (ACTION_EXTRACT | ACTION_LISTROMFS)) && ctx->romfs_ctx.header.header_size == ROMFS_HEADER_SIZE) {
/* Pre-load the file/data entry caches. */
ctx->romfs_ctx.directories = calloc(1, ctx->romfs_ctx.header.dir_meta_table_size);
if (ctx->romfs_ctx.directories == NULL) {
fprintf(stderr, "Failed to allocate RomFS directory cache!\n");
exit(EXIT_FAILURE);
}
/* Switch RomFS has actual entries at table offset + 4 for no good reason. */
nca_section_fseek(ctx, ctx->romfs_ctx.romfs_offset + ctx->romfs_ctx.header.dir_meta_table_offset + 4);
if (nca_section_fread(ctx, ctx->romfs_ctx.directories, ctx->romfs_ctx.header.dir_meta_table_size) != ctx->romfs_ctx.header.dir_meta_table_size) {
fprintf(stderr, "Failed to read RomFS directory cache!\n");
}
ctx->romfs_ctx.files = calloc(1, ctx->romfs_ctx.header.file_meta_table_size);
if (ctx->romfs_ctx.files == NULL) {
fprintf(stderr, "Failed to allocate RomFS file cache!\n");
exit(EXIT_FAILURE);
}
nca_section_fseek(ctx, ctx->romfs_ctx.romfs_offset + ctx->romfs_ctx.header.file_meta_table_offset);
if (nca_section_fread(ctx, ctx->romfs_ctx.files, ctx->romfs_ctx.header.file_meta_table_size) != ctx->romfs_ctx.header.file_meta_table_size) {
fprintf(stderr, "Failed to read RomFS file cache!\n");
}
}
}
void nca_process_bktr_section(nca_section_ctx_t *ctx) {
}
void nca_print_pfs0_section(nca_section_ctx_t *ctx) {
if (ctx->tool_ctx->action & ACTION_VERIFY) {
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
memdump(stdout, " Superblock Hash (GOOD): ", &ctx->pfs0_ctx.superblock->master_hash, 0x20);
} else {
memdump(stdout, " Superblock Hash (FAIL): ", &ctx->pfs0_ctx.superblock->master_hash, 0x20);
}
printf(" Hash Table (%s):\n", GET_VALIDITY_STR(ctx->pfs0_ctx.hash_table_validity));
} else {
memdump(stdout, " Superblock Hash: ", &ctx->pfs0_ctx.superblock->master_hash, 0x20);
printf(" Hash Table:\n");
}
printf(" Offset: %012"PRIx64"\n", ctx->pfs0_ctx.superblock->hash_table_offset);
printf(" Size: %012"PRIx64"\n", ctx->pfs0_ctx.superblock->hash_table_size);
printf(" Block Size: 0x%"PRIx32"\n", ctx->pfs0_ctx.superblock->block_size);
printf(" PFS0 Offset: %012"PRIx64"\n", ctx->pfs0_ctx.superblock->pfs0_offset);
printf(" PFS0 Size: %012"PRIx64"\n", ctx->pfs0_ctx.superblock->pfs0_size);
}
void nca_print_ivfc_section(nca_section_ctx_t *ctx) {
if (ctx->tool_ctx->action & ACTION_VERIFY) {
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
memdump(stdout, " Superblock Hash (GOOD): ", &ctx->romfs_ctx.superblock->ivfc_header.master_hash, 0x20);
} else {
memdump(stdout, " Superblock Hash (FAIL): ", &ctx->romfs_ctx.superblock->ivfc_header.master_hash, 0x20);
}
} else {
memdump(stdout, " Superblock Hash: ", &ctx->romfs_ctx.superblock->ivfc_header.master_hash, 0x20);
}
print_magic(" Magic: ", ctx->romfs_ctx.superblock->ivfc_header.magic);
printf(" ID: %08"PRIx32"\n", ctx->romfs_ctx.superblock->ivfc_header.id);
for (unsigned int i = 0; i < IVFC_MAX_LEVEL; i++) {
if (ctx->tool_ctx->action & ACTION_VERIFY) {
printf(" Level %"PRId32" (%s):\n", i, GET_VALIDITY_STR(ctx->romfs_ctx.ivfc_levels[i].hash_validity));
} else {
printf(" Level %"PRId32":\n", i);
}
printf(" Data Offset: 0x%012"PRIx64"\n", ctx->romfs_ctx.ivfc_levels[i].data_offset);
printf(" Data Size: 0x%012"PRIx64"\n", ctx->romfs_ctx.ivfc_levels[i].data_size);
if (i != 0) printf(" Hash Offset: 0x%012"PRIx64"\n", ctx->romfs_ctx.ivfc_levels[i].hash_offset);
printf(" Hash Block Size: 0x%08"PRIx32"\n", ctx->romfs_ctx.ivfc_levels[i].hash_block_size);
}
}
void nca_print_bktr_section(nca_section_ctx_t *ctx) {
printf(" BKTR superblock support not yet added.\n");
}
void nca_save_section_file(nca_section_ctx_t *ctx, uint64_t ofs, uint64_t total_size, filepath_t *filepath) {
FILE *f_out = os_fopen(filepath->os_path, OS_MODE_WRITE);
if (f_out == NULL) {
fprintf(stderr, "Failed to open %s!\n", filepath->char_path);
return;
}
uint64_t read_size = 0x400000; /* 4 MB buffer. */
unsigned char *buf = malloc(read_size);
if (buf == NULL) {
fprintf(stderr, "Failed to allocate file-save buffer!\n");
exit(EXIT_FAILURE);
}
memset(buf, 0xCC, read_size); /* Debug in case I fuck this up somehow... */
uint64_t end_ofs = ofs + total_size;
nca_section_fseek(ctx, ofs);
while (ofs < end_ofs) {
if (ofs + read_size >= end_ofs) read_size = end_ofs - ofs;
if (nca_section_fread(ctx, buf, read_size) != read_size) {
fprintf(stderr, "Failed to read file!\n");
exit(EXIT_FAILURE);
}
fwrite(buf, 1, read_size, f_out);
ofs += read_size;
}
fclose(f_out);
free(buf);
}
void nca_save_section(nca_section_ctx_t *ctx) {
/* Save raw section file... */
uint64_t offset = 0;
uint64_t size = ctx->size;
if (!(ctx->tool_ctx->action & ACTION_RAW)) {
switch (ctx->type) {
case PFS0:
offset = ctx->pfs0_ctx.superblock->pfs0_offset;
size = ctx->pfs0_ctx.superblock->pfs0_size;
break;
case ROMFS:
offset = ctx->romfs_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_offset;
size = ctx->romfs_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_size;
break;
case BKTR:
break;
case INVALID:
break;
}
}
/* Extract to file. */
filepath_t *secpath = &ctx->tool_ctx->settings.section_paths[ctx->section_num];
/* Handle overrides. */
if (ctx->type == PFS0 && ctx->pfs0_ctx.is_exefs && ctx->tool_ctx->settings.exefs_path.enabled && ctx->tool_ctx->settings.exefs_path.path.valid == VALIDITY_VALID) {
secpath = &ctx->tool_ctx->settings.exefs_path.path;
} else if (ctx->type == ROMFS && ctx->tool_ctx->settings.romfs_path.enabled && ctx->tool_ctx->settings.romfs_path.path.valid == VALIDITY_VALID) {
secpath = &ctx->tool_ctx->settings.romfs_path.path;
}
if (secpath != NULL && secpath->valid == VALIDITY_VALID) {
printf("Saving Section %"PRId32" to %s...\n", ctx->section_num, secpath->char_path);
nca_save_section_file(ctx, offset, size, secpath);
}
switch (ctx->type) {
case PFS0:
nca_save_pfs0_section(ctx);
break;
case ROMFS:
nca_save_ivfc_section(ctx);
break;
case BKTR:
nca_save_bktr_section(ctx);
break;
case INVALID:
break;
}
}
void nca_save_pfs0_section(nca_section_ctx_t *ctx) {
if (ctx->superblock_hash_validity == VALIDITY_VALID && ctx->pfs0_ctx.header->magic == MAGIC_PFS0) {
/* Extract to directory. */
filepath_t *dirpath = NULL;
if (ctx->pfs0_ctx.is_exefs && ctx->tool_ctx->settings.exefs_dir_path.enabled) {
dirpath = &ctx->tool_ctx->settings.exefs_dir_path.path;
}
if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) {
dirpath = &ctx->tool_ctx->settings.section_dir_paths[ctx->section_num];
}
if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) {
os_makedir(dirpath->os_path);
for (uint32_t i = 0; i < ctx->pfs0_ctx.header->num_files; i++) {
nca_save_pfs0_file(ctx, i, dirpath);
}
}
} else {
fprintf(stderr, "Error: section %"PRId32" is corrupted!\n", ctx->section_num);
return;
}
}
/* RomFS functions... */
void nca_visit_romfs_file(nca_section_ctx_t *ctx, uint32_t file_offset, filepath_t *dir_path) {
romfs_fentry_t *entry = romfs_get_fentry(ctx->romfs_ctx.files, file_offset);
filepath_t *cur_path = calloc(1, sizeof(filepath_t));
if (cur_path == NULL) {
fprintf(stderr, "Failed to allocate filepath!\n");
exit(EXIT_FAILURE);
}
filepath_copy(cur_path, dir_path);
if (entry->name_size) {
filepath_append_n(cur_path, entry->name_size, "%s", entry->name);
}
/* If we're extracting... */
if ((ctx->tool_ctx->action & ACTION_LISTROMFS) == 0) {
printf("Saving %s...\n", cur_path->char_path);
nca_save_section_file(ctx, ctx->romfs_ctx.romfs_offset + ctx->romfs_ctx.header.data_offset + entry->offset, entry->size, cur_path);
} else {
printf("rom:%s\n", cur_path->char_path);
}
free(cur_path);
if (entry->sibling != ROMFS_ENTRY_EMPTY) {
nca_visit_romfs_file(ctx, entry->sibling, dir_path);
}
}
void nca_visit_romfs_dir(nca_section_ctx_t *ctx, uint32_t dir_offset, filepath_t *parent_path) {
romfs_direntry_t *entry = romfs_get_direntry(ctx->romfs_ctx.directories, dir_offset);
filepath_t *cur_path = calloc(1, sizeof(filepath_t));
if (cur_path == NULL) {
fprintf(stderr, "Failed to allocate filepath!\n");
exit(EXIT_FAILURE);
}
filepath_copy(cur_path, parent_path);
if (entry->name_size) {
filepath_append_n(cur_path, entry->name_size, "%s", entry->name);
}
/* If we're actually extracting the romfs, make directory. */
if ((ctx->tool_ctx->action & ACTION_LISTROMFS) == 0) {
os_makedir(cur_path->os_path);
}
if (entry->file != ROMFS_ENTRY_EMPTY) {
nca_visit_romfs_file(ctx, entry->file, cur_path);
}
if (entry->child != ROMFS_ENTRY_EMPTY) {
nca_visit_romfs_dir(ctx, entry->child, cur_path);
}
if (entry->sibling != ROMFS_ENTRY_EMPTY) {
nca_visit_romfs_dir(ctx, entry->sibling, parent_path);
}
free(cur_path);
}
void nca_save_ivfc_section(nca_section_ctx_t *ctx) {
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
if (ctx->romfs_ctx.header.header_size == ROMFS_HEADER_SIZE) {
if (ctx->tool_ctx->action & ACTION_LISTROMFS) {
filepath_t fakepath;
filepath_init(&fakepath);
filepath_set(&fakepath, "");
nca_visit_romfs_dir(ctx, 0, &fakepath);
} else {
filepath_t *dirpath = NULL;
if (ctx->tool_ctx->settings.romfs_dir_path.enabled) {
dirpath = &ctx->tool_ctx->settings.romfs_dir_path.path;
}
if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) {
dirpath = &ctx->tool_ctx->settings.section_dir_paths[ctx->section_num];
}
if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) {
os_makedir(dirpath->os_path);
nca_visit_romfs_dir(ctx, 0, dirpath);
}
}
return;
}
}
fprintf(stderr, "Error: section %"PRId32" is corrupted!\n", ctx->section_num);
}
void nca_save_bktr_section(nca_section_ctx_t *ctx) {
}

205
nca.h Normal file
View file

@ -0,0 +1,205 @@
#ifndef NCATOOL_NCA_H
#define NCATOOL_NCA_H
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "types.h"
#include "settings.h"
#include "aes.h"
#include "npdm.h"
#include "pfs0.h"
#include "ivfc.h"
#define MAGIC_NCA3 0x3341434E /* "NCA3" */
typedef struct {
uint32_t media_start_offset;
uint32_t media_end_offset;
uint8_t _0x8[0x8]; /* Padding. */
} nca_section_entry_t;
typedef struct {
uint8_t master_hash[0x20]; /* SHA-256 hash of the hash table. */
uint32_t block_size; /* In bytes. */
uint32_t always_2;
uint64_t hash_table_offset; /* Normally zero. */
uint64_t hash_table_size;
uint64_t pfs0_offset;
uint64_t pfs0_size;
uint8_t _0x48[0xF0];
} pfs0_superblock_t;
typedef struct {
ivfc_hdr_t ivfc_header;
uint8_t _0xE0[0x58];
} romfs_superblock_t;
typedef struct {
ivfc_hdr_t ivfc_header;
uint8_t _0xE0[0x58];
} bktr_superblock_t;
typedef struct {
pfs0_superblock_t *superblock;
validity_t superblock_hash_validity;
validity_t hash_table_validity;
int is_exefs;
npdm_t *npdm;
pfs0_header_t *header;
} pfs0_section_ctx_t;
typedef struct {
romfs_superblock_t *superblock;
validity_t superblock_hash_validity;
ivfc_level_ctx_t ivfc_levels[IVFC_MAX_LEVEL];
uint64_t romfs_offset;
romfs_hdr_t header;
romfs_direntry_t *directories;
romfs_fentry_t *files;
} romfs_section_ctx_t;
typedef struct {
bktr_superblock_t *superblock;
validity_t superblock_hash_validity;
ivfc_level_ctx_t ivfc_levels[IVFC_MAX_LEVEL];
} bktr_section_ctx_t;
typedef enum {
PARTITION_ROMFS = 0,
PARTITION_PFS0 = 1
} section_partition_type_t;
typedef enum {
FS_TYPE_PFS0 = 2,
FS_TYPE_ROMFS = 3
} section_fs_type_t;
typedef enum {
CRYPT_NONE = 1,
CRYPT_XTS = 2,
CRYPT_CTR = 3,
CRYPT_BKTR = 4
} section_crypt_type_t;
/* NCA FS header. */
typedef struct {
uint8_t _0x0;
uint8_t _0x1;
uint8_t partition_type;
uint8_t fs_type;
uint8_t crypt_type;
uint8_t _0x5[0x3];
union { /* FS-specific superblock. Size = 0x138. */
pfs0_superblock_t pfs0_superblock;
romfs_superblock_t romfs_superblock;
bktr_superblock_t bktr_superblock;
};
uint8_t section_ctr[0x8];
uint8_t _0x148[0xB8]; /* Padding. */
} nca_fs_header_t;
/* Nintendo content archive header. */
typedef struct {
uint8_t fixed_key_sig[0x100]; /* RSA-PSS signature over header with fixed key. */
uint8_t npdm_key_sig[0x100]; /* RSA-PSS signature over header with key in NPDM. */
uint32_t magic;
uint8_t distribution; /* System vs gamecard. */
uint8_t content_type;
uint8_t crypto_type; /* Which keyblob (field 1) */
uint8_t kaek_ind; /* Which kaek index? */
uint64_t nca_size; /* Entire archive size. */
uint64_t title_id;
uint8_t _0x218[0x4]; /* Padding. */
union {
uint32_t sdk_version; /* What SDK was this built with? */
struct {
uint8_t sdk_revision;
uint8_t sdk_micro;
uint8_t sdk_minor;
uint8_t sdk_major;
};
};
uint8_t crypto_type2; /* Which keyblob (field 2) */
uint8_t _0x221[0xF]; /* Padding. */
uint8_t rights_id[0x10]; /* Rights ID (for titlekey crypto). */
nca_section_entry_t section_entries[4]; /* Section entry metadata. */
uint8_t section_hashes[4][0x20]; /* SHA-256 hashes for each section header. */
uint8_t encrypted_keys[4][0x10]; /* Encrypted key area. */
uint8_t _0x340[0xC0]; /* Padding. */
nca_fs_header_t fs_headers[4]; /* FS section headers. */
} nca_header_t;
enum nca_section_type {
PFS0,
ROMFS,
BKTR,
INVALID
};
typedef struct {
int is_present;
enum nca_section_type type;
FILE *file; /* Pointer to file. */
uint64_t offset;
uint64_t size;
uint32_t section_num;
nca_fs_header_t *header;
int is_decrypted;
aes_ctx_t *aes; /* AES context for the section. */
ncatool_ctx_t *tool_ctx;
union {
pfs0_section_ctx_t pfs0_ctx;
romfs_section_ctx_t romfs_ctx;
bktr_section_ctx_t bktr_ctx;
};
validity_t superblock_hash_validity;
unsigned char ctr[0x10];
uint64_t cur_seek;
size_t sector_num;
uint32_t sector_ofs;
} nca_section_ctx_t;
typedef struct {
FILE *file; /* File for this NCA. */
size_t file_size;
unsigned char crypto_type;
int has_rights_id;
int is_decrypted;
validity_t fixed_sig_validity;
validity_t npdm_sig_validity;
ncatool_ctx_t *tool_ctx;
unsigned char decrypted_keys[4][0x10];
unsigned char title_key[0x10];
nca_section_ctx_t section_contexts[4];
npdm_t *npdm;
nca_header_t header;
} nca_ctx_t;
void nca_init(nca_ctx_t *ctx);
void nca_process(nca_ctx_t *ctx);
int nca_decrypt_header(nca_ctx_t *ctx);
void nca_decrypt_key_area(nca_ctx_t *ctx);
void nca_print(nca_ctx_t *ctx);
void nca_free_section_contexts(nca_ctx_t *ctx);
void nca_section_fseek(nca_section_ctx_t *ctx, uint64_t offset);
size_t nca_section_fread(nca_section_ctx_t *ctx, void *buffer, size_t count);
void nca_save_section_file(nca_section_ctx_t *ctx, uint64_t ofs, uint64_t total_size, filepath_t *filepath);
/* These have to be in nca.c, sadly... */
void nca_process_pfs0_section(nca_section_ctx_t *ctx);
void nca_process_ivfc_section(nca_section_ctx_t *ctx);
void nca_process_bktr_section(nca_section_ctx_t *ctx);
void nca_print_pfs0_section(nca_section_ctx_t *ctx);
void nca_print_ivfc_section(nca_section_ctx_t *ctx);
void nca_print_bktr_section(nca_section_ctx_t *ctx);
void nca_save_section(nca_section_ctx_t *ctx);
void nca_save_pfs0_section(nca_section_ctx_t *ctx);
void nca_save_ivfc_section(nca_section_ctx_t *ctx);
void nca_save_bktr_section(nca_section_ctx_t *ctx);
#endif

657
npdm.c Normal file
View file

@ -0,0 +1,657 @@
#include <stdlib.h>
#include "npdm.h"
#include "utils.h"
#include "settings.h"
#include "rsa.h"
const char *svc_names[0x80] = {
"svcUnknown",
"svcSetHeapSize",
"svcSetMemoryPermission",
"svcSetMemoryAttribute",
"svcMapMemory",
"svcUnmapMemory",
"svcQueryMemory",
"svcExitProcess",
"svcCreateThread",
"svcStartThread",
"svcExitThread",
"svcSleepThread",
"svcGetThreadPriority",
"svcSetThreadPriority",
"svcGetThreadCoreMask",
"svcSetThreadCoreMask",
"svcGetCurrentProcessorNumber",
"svcSignalEvent",
"svcClearEvent",
"svcMapSharedMemory",
"svcUnmapSharedMemory",
"svcCreateTransferMemory",
"svcCloseHandle",
"svcResetSignal",
"svcWaitSynchronization",
"svcCancelSynchronization",
"svcArbitrateLock",
"svcArbitrateUnlock",
"svcWaitProcessWideKeyAtomic",
"svcSignalProcessWideKey",
"svcGetSystemTick",
"svcConnectToNamedPort",
"svcSendSyncRequestLight",
"svcSendSyncRequest",
"svcSendSyncRequestWithUserBuffer",
"svcSendAsyncRequestWithUserBuffer",
"svcGetProcessId",
"svcGetThreadId",
"svcBreak",
"svcOutputDebugString",
"svcReturnFromException",
"svcGetInfo",
"svcFlushEntireDataCache",
"svcFlushDataCache",
"svcMapPhysicalMemory",
"svcUnmapPhysicalMemory",
"svcUnknown",
"svcGetLastThreadInfo",
"svcGetResourceLimitLimitValue",
"svcGetResourceLimitCurrentValue",
"svcSetThreadActivity",
"svcGetThreadContext3",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcDumpInfo",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcCreateSession",
"svcAcceptSession",
"svcReplyAndReceiveLight",
"svcReplyAndReceive",
"svcReplyAndReceiveWithUserBuffer",
"svcCreateEvent",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcUnknown",
"svcSleepSystem",
"svcReadWriteRegister",
"svcSetProcessActivity",
"svcCreateSharedMemory",
"svcMapTransferMemory",
"svcUnmapTransferMemory",
"svcCreateInterruptEvent",
"svcQueryPhysicalAddress",
"svcQueryIoMapping",
"svcCreateDeviceAddressSpace",
"svcAttachDeviceAddressSpace",
"svcDetachDeviceAddressSpace",
"svcMapDeviceAddressSpaceByForce",
"svcMapDeviceAddressSpaceAligned",
"svcMapDeviceAddressSpace",
"svcUnmapDeviceAddressSpace",
"svcInvalidateProcessDataCache",
"svcStoreProcessDataCache",
"svcFlushProcessDataCache",
"svcDebugActiveProcess",
"svcBreakDebugProcess",
"svcTerminateDebugProcess",
"svcGetDebugEvent",
"svcContinueDebugEvent",
"svcGetProcessList",
"svcGetThreadList",
"svcGetDebugThreadContext",
"svcSetDebugThreadContext",
"svcQueryDebugProcessMemory",
"svcReadDebugProcessMemory",
"svcWriteDebugProcessMemory",
"svcSetHardwareBreakPoint",
"svcGetDebugThreadParam",
"svcUnknown",
"svcUnknown",
"svcCreatePort",
"svcManageNamedPort",
"svcConnectToPort",
"svcSetProcessMemoryPermission",
"svcMapProcessMemory",
"svcUnmapProcessMemory",
"svcQueryProcessMemory",
"svcMapProcessCodeMemory",
"svcUnmapProcessCodeMemory",
"svcCreateProcess",
"svcStartProcess",
"svcTerminateProcess",
"svcGetProcessInfo",
"svcCreateResourceLimit",
"svcSetResourceLimitLimitValue",
"svcCallSecureMonitor"
};
#define MAX_FS_PERM_RW 0x27
#define MAX_FS_PERM_int 0x1B
#define FS_PERM_MASK_NODEBUG 0xBFFFFFFFFFFFFFFFULL
const fs_perm_t fs_permissions_rw[MAX_FS_PERM_RW] = {
{"MountContentType2", 0x8000000000000801},
{"MountContentType5", 0x8000000000000801},
{"MountContentType3", 0x8000000000000801},
{"MountContentType4", 0x8000000000000801},
{"MountContentType6", 0x8000000000000801},
{"MountContentType7", 0x8000000000000801},
{"Unknown (0x6)", 0x8000000000000000},
{"ContentStorageAccess", 0x8000000000000800},
{"ImageDirectoryAccess", 0x8000000000001000},
{"MountBisType28", 0x8000000000000084},
{"MountBisType29", 0x8000000000000080},
{"MountBisType30", 0x8000000000008080},
{"MountBisType31", 0x8000000000008080},
{"Unknown (0xD)", 0x8000000000000080},
{"SdCardAccess", 0xC000000000200000},
{"GameCardUser", 0x8000000000000010},
{"SaveDataAccess0", 0x8000000000040020},
{"SystemSaveDataAccess0", 0x8000000000000028},
{"SaveDataAccess1", 0x8000000000000020},
{"SystemSaveDataAccess1", 0x8000000000000020},
{"BisPartition0", 0x8000000000010082},
{"BisPartition10", 0x8000000000010080},
{"BisPartition20", 0x8000000000010080},
{"BisPartition21", 0x8000000000010080},
{"BisPartition22", 0x8000000000010080},
{"BisPartition23", 0x8000000000010080},
{"BisPartition24", 0x8000000000010080},
{"BisPartition25", 0x8000000000010080},
{"BisPartition26", 0x8000000000000080},
{"BisPartition27", 0x8000000000000084},
{"BisPartition28", 0x8000000000000084},
{"BisPartition29", 0x8000000000000080},
{"BisPartition30", 0x8000000000000080},
{"BisPartition31", 0x8000000000000080},
{"BisPartition32", 0x8000000000000080},
{"Unknown (0x23)", 0xC000000000200000},
{"GameCard_System", 0x8000000000000100},
{"MountContent_System", 0x8000000000100008},
{"HostAccess", 0xC000000000400000}
};
const fs_perm_t fs_permissions_int[MAX_FS_PERM_int] = {
{"BisCache", 0x8000000000000080},
{"EraseMmc", 0x8000000000000080},
{"GameCardCertificate", 0x8000000000000010},
{"GameCardIdSet", 0x8000000000000010},
{"GameCardDriver", 0x8000000000000200},
{"GameCardAsic", 0x8000000000000200},
{"SaveDataCreate", 0x8000000000002020},
{"SaveDataDelete0", 0x8000000000000060},
{"SystemSaveDataCreate0", 0x8000000000000028},
{"SystemSaveDataCreate1", 0x8000000000000020},
{"SaveDataDelete1", 0x8000000000004028},
{"SaveDataIterators0", 0x8000000000000060},
{"SaveDataIterators1", 0x8000000000004020},
{"SaveThumbnails", 0x8000000000020000},
{"PosixTime", 0x8000000000000400},
{"SaveDataExtraData", 0x8000000000004060},
{"GlobalMode", 0x8000000000080000},
{"SpeedEmulation", 0x8000000000080000},
{"(NULL)", 0},
{"PaddingFiles", 0xC000000000800000},
{"SaveData_Debug", 0xC000000001000000},
{"SaveData_SystemManagement", 0xC000000002000000},
{"Unknown (0x16)", 0x8000000004000000},
{"Unknown (0x17)", 0x8000000008000000},
{"Unknown (0x18)", 0x8000000010000000},
{"Unknown (0x19)", 0x8000000000000800},
{"Unknown (0x1A)", 0x8000000000004020}
};
char *npdm_get_proc_category(npdm_t *npdm) {
switch (npdm->process_category) {
case 0:
return "Regular Title";
case 1:
return "Kernel Built-In";
default:
return "Unknown";
}
}
char *kac_get_app_type(uint32_t app_type) {
switch (app_type) {
case 0:
return "System Module";
case 1:
return "Application";
case 2:
return "Applet";
default:
return "Unknown";
}
}
void kac_add_mmio(kac_t *kac, kac_mmio_t *mmio) {
/* Perform an ordered insertion. */
if (kac->mmio == NULL || mmio->address < kac->mmio->address) {
mmio->next = kac->mmio;
kac->mmio = mmio;
} else {
kac_mmio_t *ins_mmio = kac->mmio;
while (ins_mmio != NULL) {
if (ins_mmio->address < mmio->address) {
if (ins_mmio->next != NULL) {
if (ins_mmio->next->address > mmio->address) {
mmio->next = ins_mmio->next;
ins_mmio->next = mmio;
break;
}
} else {
ins_mmio->next = mmio;
break;
}
}
if (ins_mmio->next == NULL) {
ins_mmio->next = mmio;
break;
}
ins_mmio = ins_mmio->next;
}
}
}
void kac_print(uint32_t *descriptors, uint32_t num_descriptors) {
kac_t kac;
kac_mmio_t *cur_mmio = NULL;
kac_mmio_t *page_mmio = NULL;
kac_irq_t *cur_irq = NULL;
unsigned int syscall_base;
memset(&kac, 0, sizeof(kac));
for (uint32_t i = 0; i < num_descriptors; i++) {
uint32_t desc = descriptors[i];
if (desc == 0xFFFFFFFF) {
continue;
}
unsigned int low_bits = 0;
while (desc & 1) {
desc >>= 1;
low_bits++;
}
desc >>= 1;
switch (low_bits) {
case 3: /* Kernel flags. */
kac.has_kern_flags = 1;
kac.highest_thread_prio = desc & 0x3F;
desc >>= 6;
kac.lowest_thread_prio = desc & 0x3F;
desc >>= 6;
kac.lowest_cpu_id = desc & 0xFF;
desc >>= 8;
kac.highest_cpu_id = desc & 0xFF;
break;
case 4: /* Syscall mask. */
syscall_base = (desc >> 24) * 0x18;
for (unsigned int sc = 0; sc < 0x18; sc++) {
kac.svcs_allowed[syscall_base+sc] = desc & 1;
desc >>= 1;
}
break;
case 6: /* Map IO/Normal. */
if (cur_mmio == NULL) {
cur_mmio = calloc(1, sizeof(kac_mmio_t));
if (cur_mmio == NULL) {
fprintf(stderr, "Failed to allocate MMIO descriptor!\n");
exit(EXIT_FAILURE);
}
cur_mmio->address = (desc & 0xFFFFFF) << 12;
cur_mmio->is_ro = desc >> 24;
} else {
cur_mmio->size = (desc & 0xFFFFFF) << 12;
cur_mmio->is_norm = desc >> 24;
kac_add_mmio(&kac, cur_mmio);
cur_mmio = NULL;
}
break;
case 7: /* Map Normal Page. */
page_mmio = calloc(1, sizeof(kac_mmio_t));
if (page_mmio == NULL) {
fprintf(stderr, "Failed to allocate MMIO descriptor!\n");
exit(EXIT_FAILURE);
}
page_mmio->address = desc << 12;
page_mmio->size = 0x1000;
page_mmio->is_ro = 0;
page_mmio->is_norm = 0;
page_mmio->next = NULL;
kac_add_mmio(&kac, page_mmio);
page_mmio = NULL;
break;
case 11: /* IRQ Pair. */
cur_irq = calloc(1, sizeof(kac_irq_t));
if (cur_irq == NULL) {
fprintf(stderr, "Failed to allocate IRQ descriptor!\n");
exit(EXIT_FAILURE);
}
cur_irq->irq0 = desc & 0x3FF;
cur_irq->irq1 = (desc >> 10) & 0x3FF;
if (kac.irqs == NULL) {
kac.irqs = cur_irq;
} else {
kac_irq_t *tail_irq = kac.irqs;
while (tail_irq->next != NULL) {
tail_irq = tail_irq->next;
}
tail_irq->next = cur_irq;
}
cur_irq = NULL;
break;
case 13: /* App Type. */
kac.has_app_type = 1;
kac.application_type = desc & 7;
break;
case 14: /* Kernel Release Version. */
kac.has_kern_ver = 1;
kac.kernel_release_version = desc;
break;
case 15: /* Handle Table Size. */
kac.has_handle_table_size = 1;
kac.handle_table_size = desc;
break;
case 16: /* Debug Flags. */
kac.has_debug_flags = 1;
kac.allow_debug = desc & 1;
kac.force_debug = (desc >> 1) & 1;
break;
}
}
if (kac.has_kern_flags) {
printf(" Lowest Allowed Priority: %"PRId8"\n", kac.lowest_thread_prio);
printf(" Highest Allowed Priority: %"PRId8"\n", kac.highest_thread_prio);
printf(" Lowest Allowed CPU ID: %"PRId8"\n", kac.lowest_cpu_id);
printf(" Highest Allowed CPU ID: %"PRId8"\n", kac.highest_cpu_id);
}
int first_svc = 1;
for (unsigned int i = 0; i < 0x80; i++) {
if (kac.svcs_allowed[i]) {
printf(first_svc ? " Allowed SVCs: %-35s (0x%02"PRIx8")\n" : " %-35s (0x%02"PRIx8")\n", svc_names[i], i);
first_svc = 0;
}
}
int first_mmio = 1;
if (kac.mmio != NULL) {
kac_mmio_t *cur_mmio;
while (kac.mmio != NULL) {
cur_mmio = kac.mmio;
printf(first_mmio ? " Mapped IO: " : " ");
first_mmio = 0;
printf("(%09"PRIx64"-%09"PRIx64", %s, %s)\n", cur_mmio->address, cur_mmio->address + cur_mmio->size, cur_mmio->is_ro ? "RO" : "RW", cur_mmio->is_norm ? "Normal" : "IO");
kac.mmio = kac.mmio->next;
free(cur_mmio);
}
}
if (kac.irqs != NULL) {
printf(" Mapped Interrupts: ");
int num_irqs = 0;
while (kac.irqs != NULL) {
cur_irq = kac.irqs;
if (cur_irq->irq0 != 0x3FF) {
if (num_irqs % 8 == 0) {
if (num_irqs) printf("\n ");
} else {
printf(", ");
}
printf("0x%03"PRIx32, cur_irq->irq0);
num_irqs++;
}
if (cur_irq->irq1 != 0x3FF) {
if (num_irqs % 8 == 0) {
if (num_irqs) printf("\n ");
} else {
printf(", ");
}
printf("0x%03"PRIx32, cur_irq->irq1);
num_irqs++;
}
kac.irqs = kac.irqs->next;
free(cur_irq);
}
printf("\n");
}
if (kac.has_app_type) {
printf(" Application Type: %s\n", kac_get_app_type(kac.application_type));
}
if (kac.has_handle_table_size) {
printf(" Handle Table Size: %"PRId32"\n", kac.handle_table_size);
}
if (kac.has_debug_flags) {
printf(" Allow Debug: %s\n", kac.allow_debug ? "YES" : "NO");
printf(" Force Debug: %s\n", kac.force_debug ? "YES" : "NO");
}
}
/* Modified from https://stackoverflow.com/questions/23457305/compare-strings-with-wildcard */
int match(const char *pattern, const char *candidate, int p, int c) {
if (pattern[p] == '\0') {
return candidate[c] == '\0';
} else if (pattern[p] == '*') {
for (; candidate[c] != '\0'; c++) {
if (match(pattern, candidate, p+1, c))
return 1;
}
return match(pattern, candidate, p+1, c);
} else {
return match(pattern, candidate, p+1, c+1);
}
}
int sac_matches(sac_entry_t *lst, char *service) {
sac_entry_t *cur = lst;
while (cur != NULL) {
if (match(cur->service, service, 0, 0)) return 1;
cur = cur->next;
}
return 0;
}
void sac_print(char *acid_sac, uint32_t acid_size, char *aci0_sac, uint32_t aci0_size) {
/* Parse the ACID sac. */
sac_entry_t *acid_accesses = NULL;
sac_entry_t *acid_hosts = NULL;
sac_entry_t *cur_entry = NULL;
sac_entry_t *temp = NULL;
uint32_t ofs = 0;
uint32_t service_len;
char ctrl;
while (ofs < acid_size) {
ctrl = acid_sac[ofs++];
service_len = (ctrl & 0xF) + 1;
cur_entry = calloc(1, sizeof(sac_entry_t));
cur_entry->valid = 1;
strncpy(cur_entry->service, &acid_sac[ofs], service_len);
if (ctrl & 0x80 && acid_hosts == NULL) {
acid_hosts = cur_entry;
} else if (!(ctrl & 0x80) && acid_accesses == NULL) {
acid_accesses = cur_entry;
} else {
if (ctrl & 0x80) {
temp = acid_hosts;
} else {
temp = acid_accesses;
}
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = cur_entry;
}
cur_entry = NULL;
ofs += service_len;
}
/* The ACID sac restricts the ACI0 sac... */
sac_entry_t *aci0_accesses = NULL;
sac_entry_t *aci0_hosts = NULL;
ofs = 0;
while (ofs < aci0_size) {
ctrl = aci0_sac[ofs++];
service_len = (ctrl & 0xF) + 1;
cur_entry = calloc(1, sizeof(sac_entry_t));
strncpy(cur_entry->service, &aci0_sac[ofs], service_len);
if (ctrl & 0x80) {
cur_entry->valid = sac_matches(acid_hosts, cur_entry->service);
} else {
cur_entry->valid = sac_matches(acid_accesses, cur_entry->service);
}
if (ctrl & 0x80 && aci0_hosts == NULL) {
aci0_hosts = cur_entry;
} else if (!(ctrl & 0x80) && aci0_accesses == NULL) {
aci0_accesses = cur_entry;
} else {
if (ctrl & 0x80) {
temp = aci0_hosts;
} else {
temp = aci0_accesses;
}
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = cur_entry;
}
cur_entry = NULL;
ofs += service_len;
}
int first = 1;
while (aci0_hosts != NULL) {
printf(first ? " Hosts: %-16s%s\n" : " %-16s%s\n", aci0_hosts->service, aci0_hosts->valid ? "" : "(Invalid)");
temp = aci0_hosts;
aci0_hosts = aci0_hosts->next;
free(temp);
first = 0;
}
first = 1;
while (aci0_accesses != NULL) {
printf(first ? " Accesses: %-16s%s\n" : " %-16s%s\n", aci0_accesses->service, aci0_accesses->valid ? "" : "(Invalid)");
temp = aci0_accesses;
aci0_accesses = aci0_accesses->next;
free(temp);
first = 0;
}
while (acid_hosts != NULL) {
temp = acid_hosts;
acid_hosts = acid_hosts->next;
free(temp);
}
while (acid_accesses != NULL) {
temp = acid_accesses;
acid_accesses = acid_accesses->next;
free(temp);
}
}
void fac_print(fac_t *fac, fah_t *fah) {
if (fac->version == fah->version) {
printf(" Version: %"PRId8"\n", fac->version);
} else {
printf(" Control Version: %"PRId8"\n", fac->version);
printf(" Header Version: %"PRId8"\n", fah->version);
}
uint64_t perms = fac->perms & fah->perms;
printf(" Raw Permissions: 0x%016"PRIx64"\n", perms);
printf(" RW Permissions: ");
for (unsigned int i = 0; i < MAX_FS_PERM_RW; i++) {
if (fs_permissions_rw[i].mask & perms) {
if (fs_permissions_rw[i].mask & (perms & FS_PERM_MASK_NODEBUG)) {
printf("%s\n ", fs_permissions_rw[i].name);
} else {
printf("%-32s [DEBUG ONLY]\n ", fs_permissions_rw[i].name);
}
}
}
printf("\n");
printf(" intean Permissions: ");
for (unsigned int i = 0; i < MAX_FS_PERM_int; i++) {
if (fs_permissions_int[i].mask & perms) {
if (fs_permissions_int[i].mask & (perms & FS_PERM_MASK_NODEBUG)) {
printf("%s\n ", fs_permissions_int[i].name);
} else {
printf("%-32s [DEBUG ONLY]\n ", fs_permissions_int[i].name);
}
}
}
printf("\n");
}
void npdm_print(npdm_t *npdm, ncatool_ctx_t *tool_ctx) {
printf("NPDM:\n");
print_magic(" Magic: ", npdm->magic);
printf(" MMU Flags: %"PRIx8"\n", npdm->mmu_flags);
printf(" Main Thread Priority: %"PRId8"\n", npdm->main_thread_prio);
printf(" Default CPU ID: %"PRIx8"\n", npdm->default_cpuid);
printf(" Process Category: %s\n", npdm_get_proc_category(npdm));
printf(" Main Thread Stack Size: 0x%"PRIx32"\n", npdm->main_stack_size);
printf(" Title Name: %s\n", npdm->title_name);
npdm_acid_t *acid = npdm_get_acid(npdm);
npdm_aci0_t *aci0 = npdm_get_aci0(npdm);
printf(" ACID:\n");
print_magic(" Magic: ", acid->magic);
if (tool_ctx->action & ACTION_VERIFY) {
if (rsa2048_pss_verify(acid->modulus, acid->size, acid->signature, tool_ctx->settings.keyset.acid_fixed_key_modulus)) {
memdump(stdout, " Signature (GOOD): ", &acid->signature, 0x100);
} else {
memdump(stdout, " Signature (FAIL): ", &acid->signature, 0x100);
}
} else {
memdump(stdout, " Signature: ", &acid->signature, 0x100);
}
memdump(stdout, " Header Modulus: ", &acid->modulus, 0x100);
printf(" Is Retail: %"PRId32"\n", acid->is_retail);
printf(" Title ID Range: %016"PRIx64"-%016"PRIx64"\n", acid->title_id_range_min, acid->title_id_range_max);
printf(" ACI0:\n");
print_magic(" Magic: ", aci0->magic);
printf(" Title ID: %016"PRIx64"\n", aci0->title_id);
/* Kernel access control. */
uint32_t *acid_kac = (uint32_t *)((char *)acid + acid->kac_offset);
uint32_t *aci0_kac = (uint32_t *)((char *)aci0 + aci0->kac_offset);
if (acid->kac_size == aci0->kac_size && memcmp(acid_kac, aci0_kac, acid->kac_size) == 0) {
/* Shared KAC. */
printf(" Kernel Access Control:\n");
kac_print(acid_kac, acid->kac_size/sizeof(uint32_t));
} else {
/* Different KAC. */
printf(" ACID Kernel Access Control:\n");
kac_print(acid_kac, acid->kac_size/sizeof(uint32_t));
printf(" ACI0 Kernel Access Control:\n");
kac_print(aci0_kac, aci0->kac_size/sizeof(uint32_t));
}
/* Service access control. */
char *acid_sac = ((char *)acid + acid->sac_offset);
char *aci0_sac = ((char *)aci0 + aci0->sac_offset);
printf(" Service Access Control:\n");
sac_print(acid_sac, acid->sac_size, aci0_sac, aci0->sac_size);
/* FS access control. */
fac_t *fac = (fac_t *)((char *)acid + acid->fac_offset);
fah_t *fah = (fah_t *)((char *)aci0 + aci0->fah_offset);
printf(" Filesystem Access Control:\n");
fac_print(fac, fah);
}

137
npdm.h Normal file
View file

@ -0,0 +1,137 @@
#ifndef NCATOOL_NPDM_H
#define NCATOOL_NPDM_H
#include "types.h"
#include "utils.h"
#include "settings.h"
#define MAGIC_META 0x4154454D
#define MAGIC_ACID 0x44494341
#define MAGIC_ACI0 0x30494341
typedef struct kac_mmio {
uint64_t address;
uint64_t size;
int is_ro;
int is_norm;
struct kac_mmio *next;
} kac_mmio_t;
typedef struct kac_irq {
uint32_t irq0;
uint32_t irq1;
struct kac_irq *next;
} kac_irq_t;
typedef struct {
int has_kern_flags;
uint32_t lowest_thread_prio;
uint32_t highest_thread_prio;
uint32_t lowest_cpu_id;
uint32_t highest_cpu_id;
uint8_t svcs_allowed[0x80];
kac_mmio_t *mmio;
kac_irq_t *irqs;
int has_app_type;
uint32_t application_type;
int has_kern_ver;
uint32_t kernel_release_version;
int has_handle_table_size;
uint32_t handle_table_size;
int has_debug_flags;
int allow_debug;
int force_debug;
} kac_t;
typedef struct sac_entry {
char service[0x11];
int valid;
struct sac_entry *next;
} sac_entry_t;
/* FAC, FAH need to be tightly packed. */
#pragma pack(push, 1)
typedef struct {
uint32_t version;
uint64_t perms;
uint8_t _0xC[0x20];
} fac_t;
#pragma pack(pop)
#pragma pack(push, 1)
typedef struct {
uint32_t version;
uint64_t perms;
uint32_t _0xC;
uint32_t _0x10;
uint32_t _0x14;
uint32_t _0x18;
} fah_t;
#pragma pack(pop)
typedef struct {
char *name;
uint64_t mask;
} fs_perm_t;
typedef struct {
uint32_t magic;
uint8_t _0x4[0xC];
uint64_t title_id;
uint64_t _0x18;
uint32_t fah_offset;
uint32_t fah_size;
uint32_t sac_offset;
uint32_t sac_size;
uint32_t kac_offset;
uint32_t kac_size;
uint64_t padding;
} npdm_aci0_t;
typedef struct {
uint8_t signature[0x100];
uint8_t modulus[0x100];
uint32_t magic;
uint32_t size;
uint32_t _0x204;
uint32_t is_retail;
uint64_t title_id_range_min;
uint64_t title_id_range_max;
uint32_t fac_offset;
uint32_t fac_size;
uint32_t sac_offset;
uint32_t sac_size;
uint32_t kac_offset;
uint32_t kac_size;
uint64_t padding;
} npdm_acid_t;
typedef struct {
uint32_t magic;
uint32_t _0x4;
uint32_t _0x8;
uint8_t mmu_flags;
uint8_t _0xD;
uint8_t main_thread_prio;
uint8_t default_cpuid;
uint64_t _0x10;
uint32_t process_category;
uint32_t main_stack_size;
char title_name[0x50];
uint32_t aci0_offset;
uint32_t aci0_size;
uint32_t acid_offset;
uint32_t acid_size;
} npdm_t;
static inline npdm_acid_t *npdm_get_acid(npdm_t *npdm) {
return (npdm_acid_t *)((char *)npdm + npdm->acid_offset);
}
static inline npdm_aci0_t *npdm_get_aci0(npdm_t *npdm) {
return (npdm_aci0_t *)((char *)npdm + npdm->aci0_offset);
}
void npdm_print(npdm_t *npdm, ncatool_ctx_t *tool_ctx);
#endif

41
pfs0.h Normal file
View file

@ -0,0 +1,41 @@
#ifndef NCATOOL_PFS0_H
#define NCATOOL_PFS0_H
#include "types.h"
#include "utils.h"
#include "npdm.h"
#define MAGIC_PFS0 0x30534650
typedef struct {
uint32_t magic;
uint32_t num_files;
uint32_t string_table_size;
uint32_t reserved;
} pfs0_header_t;
typedef struct {
uint64_t offset;
uint64_t size;
uint32_t string_table_offset;
uint32_t reserved;
} pfs0_file_entry_t;
static inline pfs0_file_entry_t *pfs0_get_file_entry(pfs0_header_t *hdr, uint32_t i) {
if (i >= hdr->num_files) return NULL;
return (pfs0_file_entry_t *)((char *)(hdr) + sizeof(*hdr) + i * sizeof(pfs0_file_entry_t));
}
static inline char *pfs0_get_string_table(pfs0_header_t *hdr) {
return (char *)(hdr) + sizeof(*hdr) + hdr->num_files * sizeof(pfs0_file_entry_t);
}
static inline uint64_t pfs0_get_header_size(pfs0_header_t *hdr) {
return sizeof(*hdr) + hdr->num_files * sizeof(pfs0_file_entry_t) + hdr->string_table_size;
}
static inline char *pfs0_get_file_name(pfs0_header_t *hdr, uint32_t i) {
return pfs0_get_string_table(hdr) + pfs0_get_file_entry(hdr, i)->string_table_offset;
}
#endif

244
pki.h Normal file
View file

@ -0,0 +1,244 @@
#ifndef NCATOOL_PKI_H
#define NCATOOL_PKI_H
#include <string.h>
#include "types.h"
#include "settings.h"
#define ZEROES_KEY {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
#define ZEROES_XTS_KEY {0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00}
#define ZEROES_KAEKS {ZEROES_KEY, ZEROES_KEY, ZEROES_KEY}
/* TODO: Design consideration -- should I store the master keys and derive */
/* the titlekeks and the body kaeks? Worth considering... */
const nca_keyset_t nca_keys_retail = {
ZEROES_XTS_KEY, /* Header key */
{
ZEROES_KEY, /* Titlekek 00 */
ZEROES_KEY, /* Titlekek 01 */
ZEROES_KEY, /* Titlekek 02 */
ZEROES_KEY, /* Titlekek 03 */
ZEROES_KEY, /* Titlekek 04 */
ZEROES_KEY, /* Titlekek 05 */
ZEROES_KEY, /* Titlekek 06 */
ZEROES_KEY, /* Titlekek 07 */
ZEROES_KEY, /* Titlekek 08 */
ZEROES_KEY, /* Titlekek 09 */
ZEROES_KEY, /* Titlekek 10 */
ZEROES_KEY, /* Titlekek 11 */
ZEROES_KEY, /* Titlekek 12 */
ZEROES_KEY, /* Titlekek 13 */
ZEROES_KEY, /* Titlekek 14 */
ZEROES_KEY, /* Titlekek 15 */
ZEROES_KEY, /* Titlekek 16 */
ZEROES_KEY, /* Titlekek 17 */
ZEROES_KEY, /* Titlekek 18 */
ZEROES_KEY, /* Titlekek 19 */
ZEROES_KEY, /* Titlekek 20 */
ZEROES_KEY, /* Titlekek 21 */
ZEROES_KEY, /* Titlekek 22 */
ZEROES_KEY, /* Titlekek 23 */
ZEROES_KEY, /* Titlekek 24 */
ZEROES_KEY, /* Titlekek 25 */
ZEROES_KEY, /* Titlekek 26 */
ZEROES_KEY, /* Titlekek 27 */
ZEROES_KEY, /* Titlekek 28 */
ZEROES_KEY, /* Titlekek 29 */
ZEROES_KEY, /* Titlekek 30 */
ZEROES_KEY /* Titlekek 31 */
},
{
ZEROES_KAEKS, /* Key Area Encryption Keyset 00 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 01 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 02 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 03 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 04 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 05 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 06 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 07 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 08 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 09 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 10 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 11 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 12 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 13 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 14 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 15 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 16 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 17 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 18 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 19 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 20 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 21 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 22 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 23 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 24 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 25 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 26 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 27 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 28 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 29 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 30 */
ZEROES_KAEKS /* Key Area Encryption Keyset 31 */
},
{ /* Fixed RSA key used to validate NCA signature 0. */
0xBF, 0xBE, 0x40, 0x6C, 0xF4, 0xA7, 0x80, 0xE9, 0xF0, 0x7D, 0x0C, 0x99, 0x61, 0x1D, 0x77, 0x2F,
0x96, 0xBC, 0x4B, 0x9E, 0x58, 0x38, 0x1B, 0x03, 0xAB, 0xB1, 0x75, 0x49, 0x9F, 0x2B, 0x4D, 0x58,
0x34, 0xB0, 0x05, 0xA3, 0x75, 0x22, 0xBE, 0x1A, 0x3F, 0x03, 0x73, 0xAC, 0x70, 0x68, 0xD1, 0x16,
0xB9, 0x04, 0x46, 0x5E, 0xB7, 0x07, 0x91, 0x2F, 0x07, 0x8B, 0x26, 0xDE, 0xF6, 0x00, 0x07, 0xB2,
0xB4, 0x51, 0xF8, 0x0D, 0x0A, 0x5E, 0x58, 0xAD, 0xEB, 0xBC, 0x9A, 0xD6, 0x49, 0xB9, 0x64, 0xEF,
0xA7, 0x82, 0xB5, 0xCF, 0x6D, 0x70, 0x13, 0xB0, 0x0F, 0x85, 0xF6, 0xA9, 0x08, 0xAA, 0x4D, 0x67,
0x66, 0x87, 0xFA, 0x89, 0xFF, 0x75, 0x90, 0x18, 0x1E, 0x6B, 0x3D, 0xE9, 0x8A, 0x68, 0xC9, 0x26,
0x04, 0xD9, 0x80, 0xCE, 0x3F, 0x5E, 0x92, 0xCE, 0x01, 0xFF, 0x06, 0x3B, 0xF2, 0xC1, 0xA9, 0x0C,
0xCE, 0x02, 0x6F, 0x16, 0xBC, 0x92, 0x42, 0x0A, 0x41, 0x64, 0xCD, 0x52, 0xB6, 0x34, 0x4D, 0xAE,
0xC0, 0x2E, 0xDE, 0xA4, 0xDF, 0x27, 0x68, 0x3C, 0xC1, 0xA0, 0x60, 0xAD, 0x43, 0xF3, 0xFC, 0x86,
0xC1, 0x3E, 0x6C, 0x46, 0xF7, 0x7C, 0x29, 0x9F, 0xFA, 0xFD, 0xF0, 0xE3, 0xCE, 0x64, 0xE7, 0x35,
0xF2, 0xF6, 0x56, 0x56, 0x6F, 0x6D, 0xF1, 0xE2, 0x42, 0xB0, 0x83, 0x40, 0xA5, 0xC3, 0x20, 0x2B,
0xCC, 0x9A, 0xAE, 0xCA, 0xED, 0x4D, 0x70, 0x30, 0xA8, 0x70, 0x1C, 0x70, 0xFD, 0x13, 0x63, 0x29,
0x02, 0x79, 0xEA, 0xD2, 0xA7, 0xAF, 0x35, 0x28, 0x32, 0x1C, 0x7B, 0xE6, 0x2F, 0x1A, 0xAA, 0x40,
0x7E, 0x32, 0x8C, 0x27, 0x42, 0xFE, 0x82, 0x78, 0xEC, 0x0D, 0xEB, 0xE6, 0x83, 0x4B, 0x6D, 0x81,
0x04, 0x40, 0x1A, 0x9E, 0x9A, 0x67, 0xF6, 0x72, 0x29, 0xFA, 0x04, 0xF0, 0x9D, 0xE4, 0xF4, 0x03
},
{ /* Fixed RSA key used to validate ACID signatures. */
0xDD, 0xC8, 0xDD, 0xF2, 0x4E, 0x6D, 0xF0, 0xCA, 0x9E, 0xC7, 0x5D, 0xC7, 0x7B, 0xAD, 0xFE, 0x7D,
0x23, 0x89, 0x69, 0xB6, 0xF2, 0x06, 0xA2, 0x02, 0x88, 0xE1, 0x55, 0x91, 0xAB, 0xCB, 0x4D, 0x50,
0x2E, 0xFC, 0x9D, 0x94, 0x76, 0xD6, 0x4C, 0xD8, 0xFF, 0x10, 0xFA, 0x5E, 0x93, 0x0A, 0xB4, 0x57,
0xAC, 0x51, 0xC7, 0x16, 0x66, 0xF4, 0x1A, 0x54, 0xC2, 0xC5, 0x04, 0x3D, 0x1B, 0xFE, 0x30, 0x20,
0x8A, 0xAC, 0x6F, 0x6F, 0xF5, 0xC7, 0xB6, 0x68, 0xB8, 0xC9, 0x40, 0x6B, 0x42, 0xAD, 0x11, 0x21,
0xE7, 0x8B, 0xE9, 0x75, 0x01, 0x86, 0xE4, 0x48, 0x9B, 0x0A, 0x0A, 0xF8, 0x7F, 0xE8, 0x87, 0xF2,
0x82, 0x01, 0xE6, 0xA3, 0x0F, 0xE4, 0x66, 0xAE, 0x83, 0x3F, 0x4E, 0x9F, 0x5E, 0x01, 0x30, 0xA4,
0x00, 0xB9, 0x9A, 0xAE, 0x5F, 0x03, 0xCC, 0x18, 0x60, 0xE5, 0xEF, 0x3B, 0x5E, 0x15, 0x16, 0xFE,
0x1C, 0x82, 0x78, 0xB5, 0x2F, 0x47, 0x7C, 0x06, 0x66, 0x88, 0x5D, 0x35, 0xA2, 0x67, 0x20, 0x10,
0xE7, 0x6C, 0x43, 0x68, 0xD3, 0xE4, 0x5A, 0x68, 0x2A, 0x5A, 0xE2, 0x6D, 0x73, 0xB0, 0x31, 0x53,
0x1C, 0x20, 0x09, 0x44, 0xF5, 0x1A, 0x9D, 0x22, 0xBE, 0x12, 0xA1, 0x77, 0x11, 0xE2, 0xA1, 0xCD,
0x40, 0x9A, 0xA2, 0x8B, 0x60, 0x9B, 0xEF, 0xA0, 0xD3, 0x48, 0x63, 0xA2, 0xF8, 0xA3, 0x2C, 0x08,
0x56, 0x52, 0x2E, 0x60, 0x19, 0x67, 0x5A, 0xA7, 0x9F, 0xDC, 0x3F, 0x3F, 0x69, 0x2B, 0x31, 0x6A,
0xB7, 0x88, 0x4A, 0x14, 0x84, 0x80, 0x33, 0x3C, 0x9D, 0x44, 0xB7, 0x3F, 0x4C, 0xE1, 0x75, 0xEA,
0x37, 0xEA, 0xE8, 0x1E, 0x7C, 0x77, 0xB7, 0xC6, 0x1A, 0xA2, 0xF0, 0x9F, 0x10, 0x61, 0xCD, 0x7B,
0x5B, 0x32, 0x4C, 0x37, 0xEF, 0xB1, 0x71, 0x68, 0x53, 0x0A, 0xED, 0x51, 0x7D, 0x35, 0x22, 0xFD
}
};
const nca_keyset_t nca_keys_dev = {
ZEROES_XTS_KEY, /* Header key */
{
ZEROES_KEY, /* Titlekek 00 */
ZEROES_KEY, /* Titlekek 01 */
ZEROES_KEY, /* Titlekek 02 */
ZEROES_KEY, /* Titlekek 03 */
ZEROES_KEY, /* Titlekek 04 */
ZEROES_KEY, /* Titlekek 05 */
ZEROES_KEY, /* Titlekek 06 */
ZEROES_KEY, /* Titlekek 07 */
ZEROES_KEY, /* Titlekek 08 */
ZEROES_KEY, /* Titlekek 09 */
ZEROES_KEY, /* Titlekek 10 */
ZEROES_KEY, /* Titlekek 11 */
ZEROES_KEY, /* Titlekek 12 */
ZEROES_KEY, /* Titlekek 13 */
ZEROES_KEY, /* Titlekek 14 */
ZEROES_KEY, /* Titlekek 15 */
ZEROES_KEY, /* Titlekek 16 */
ZEROES_KEY, /* Titlekek 17 */
ZEROES_KEY, /* Titlekek 18 */
ZEROES_KEY, /* Titlekek 19 */
ZEROES_KEY, /* Titlekek 20 */
ZEROES_KEY, /* Titlekek 21 */
ZEROES_KEY, /* Titlekek 22 */
ZEROES_KEY, /* Titlekek 23 */
ZEROES_KEY, /* Titlekek 24 */
ZEROES_KEY, /* Titlekek 25 */
ZEROES_KEY, /* Titlekek 26 */
ZEROES_KEY, /* Titlekek 27 */
ZEROES_KEY, /* Titlekek 28 */
ZEROES_KEY, /* Titlekek 29 */
ZEROES_KEY, /* Titlekek 30 */
ZEROES_KEY /* Titlekek 31 */
},
{
ZEROES_KAEKS, /* Key Area Encryption Keyset 00 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 01 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 02 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 03 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 04 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 05 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 06 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 07 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 08 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 09 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 10 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 11 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 12 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 13 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 14 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 15 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 16 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 17 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 18 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 19 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 20 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 21 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 22 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 23 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 24 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 25 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 26 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 27 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 28 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 29 */
ZEROES_KAEKS, /* Key Area Encryption Keyset 30 */
ZEROES_KAEKS /* Key Area Encryption Keyset 31 */
},
{
0xD8, 0xF1, 0x18, 0xEF, 0x32, 0x72, 0x4C, 0xA7, 0x47, 0x4C, 0xB9, 0xEA, 0xB3, 0x04, 0xA8, 0xA4,
0xAC, 0x99, 0x08, 0x08, 0x04, 0xBF, 0x68, 0x57, 0xB8, 0x43, 0x94, 0x2B, 0xC7, 0xB9, 0x66, 0x49,
0x85, 0xE5, 0x8A, 0x9B, 0xC1, 0x00, 0x9A, 0x6A, 0x8D, 0xD0, 0xEF, 0xCE, 0xFF, 0x86, 0xC8, 0x5C,
0x5D, 0xE9, 0x53, 0x7B, 0x19, 0x2A, 0xA8, 0xC0, 0x22, 0xD1, 0xF3, 0x22, 0x0A, 0x50, 0xF2, 0x2B,
0x65, 0x05, 0x1B, 0x9E, 0xEC, 0x61, 0xB5, 0x63, 0xA3, 0x6F, 0x3B, 0xBA, 0x63, 0x3A, 0x53, 0xF4,
0x49, 0x2F, 0xCF, 0x03, 0xCC, 0xD7, 0x50, 0x82, 0x1B, 0x29, 0x4F, 0x08, 0xDE, 0x1B, 0x6D, 0x47,
0x4F, 0xA8, 0xB6, 0x6A, 0x26, 0xA0, 0x83, 0x3F, 0x1A, 0xAF, 0x83, 0x8F, 0x0E, 0x17, 0x3F, 0xFE,
0x44, 0x1C, 0x56, 0x94, 0x2E, 0x49, 0x83, 0x83, 0x03, 0xE9, 0xB6, 0xAD, 0xD5, 0xDE, 0xE3, 0x2D,
0xA1, 0xD9, 0x66, 0x20, 0x5D, 0x1F, 0x5E, 0x96, 0x5D, 0x5B, 0x55, 0x0D, 0xD4, 0xB4, 0x77, 0x6E,
0xAE, 0x1B, 0x69, 0xF3, 0xA6, 0x61, 0x0E, 0x51, 0x62, 0x39, 0x28, 0x63, 0x75, 0x76, 0xBF, 0xB0,
0xD2, 0x22, 0xEF, 0x98, 0x25, 0x02, 0x05, 0xC0, 0xD7, 0x6A, 0x06, 0x2C, 0xA5, 0xD8, 0x5A, 0x9D,
0x7A, 0xA4, 0x21, 0x55, 0x9F, 0xF9, 0x3E, 0xBF, 0x16, 0xF6, 0x07, 0xC2, 0xB9, 0x6E, 0x87, 0x9E,
0xB5, 0x1C, 0xBE, 0x97, 0xFA, 0x82, 0x7E, 0xED, 0x30, 0xD4, 0x66, 0x3F, 0xDE, 0xD8, 0x1B, 0x4B,
0x15, 0xD9, 0xFB, 0x2F, 0x50, 0xF0, 0x9D, 0x1D, 0x52, 0x4C, 0x1C, 0x4D, 0x8D, 0xAE, 0x85, 0x1E,
0xEA, 0x7F, 0x86, 0xF3, 0x0B, 0x7B, 0x87, 0x81, 0x98, 0x23, 0x80, 0x63, 0x4F, 0x2F, 0xB0, 0x62,
0xCC, 0x6E, 0xD2, 0x46, 0x13, 0x65, 0x2B, 0xD6, 0x44, 0x33, 0x59, 0xB5, 0x8F, 0xB9, 0x4A, 0xA9
},
{ /* Fixed RSA key used to validate ACID signatures. */
0xD6, 0x34, 0xA5, 0x78, 0x6C, 0x68, 0xCE, 0x5A, 0xC2, 0x37, 0x17, 0xF3, 0x82, 0x45, 0xC6, 0x89,
0xE1, 0x2D, 0x06, 0x67, 0xBF, 0xB4, 0x06, 0x19, 0x55, 0x6B, 0x27, 0x66, 0x0C, 0xA4, 0xB5, 0x87,
0x81, 0x25, 0xF4, 0x30, 0xBC, 0x53, 0x08, 0x68, 0xA2, 0x48, 0x49, 0x8C, 0x3F, 0x38, 0x40, 0x9C,
0xC4, 0x26, 0xF4, 0x79, 0xE2, 0xA1, 0x85, 0xF5, 0x5C, 0x7F, 0x58, 0xBA, 0xA6, 0x1C, 0xA0, 0x8B,
0x84, 0x16, 0x14, 0x6F, 0x85, 0xD9, 0x7C, 0xE1, 0x3C, 0x67, 0x22, 0x1E, 0xFB, 0xD8, 0xA7, 0xA5,
0x9A, 0xBF, 0xEC, 0x0E, 0xCF, 0x96, 0x7E, 0x85, 0xC2, 0x1D, 0x49, 0x5D, 0x54, 0x26, 0xCB, 0x32,
0x7C, 0xF6, 0xBB, 0x58, 0x03, 0x80, 0x2B, 0x5D, 0xF7, 0xFB, 0xD1, 0x9D, 0xC7, 0xC6, 0x2E, 0x53,
0xC0, 0x6F, 0x39, 0x2C, 0x1F, 0xA9, 0x92, 0xF2, 0x4D, 0x7D, 0x4E, 0x74, 0xFF, 0xE4, 0xEF, 0xE4,
0x7C, 0x3D, 0x34, 0x2A, 0x71, 0xA4, 0x97, 0x59, 0xFF, 0x4F, 0xA2, 0xF4, 0x66, 0x78, 0xD8, 0xBA,
0x99, 0xE3, 0xE6, 0xDB, 0x54, 0xB9, 0xE9, 0x54, 0xA1, 0x70, 0xFC, 0x05, 0x1F, 0x11, 0x67, 0x4B,
0x26, 0x8C, 0x0C, 0x3E, 0x03, 0xD2, 0xA3, 0x55, 0x5C, 0x7D, 0xC0, 0x5D, 0x9D, 0xFF, 0x13, 0x2F,
0xFD, 0x19, 0xBF, 0xED, 0x44, 0xC3, 0x8C, 0xA7, 0x28, 0xCB, 0xE5, 0xE0, 0xB1, 0xA7, 0x9C, 0x33,
0x8D, 0xB8, 0x6E, 0xDE, 0x87, 0x18, 0x22, 0x60, 0xC4, 0xAE, 0xF2, 0x87, 0x9F, 0xCE, 0x09, 0x5C,
0xB5, 0x99, 0xA5, 0x9F, 0x49, 0xF2, 0xD7, 0x58, 0xFA, 0xF9, 0xC0, 0x25, 0x7D, 0xD6, 0xCB, 0xF3,
0xD8, 0x6C, 0xA2, 0x69, 0x91, 0x68, 0x73, 0xB1, 0x94, 0x6F, 0xA3, 0xF3, 0xB9, 0x7D, 0xF8, 0xE0,
0x72, 0x9E, 0x93, 0x7B, 0x7A, 0xA2, 0x57, 0x60, 0xB7, 0x5B, 0xA9, 0x84, 0xAE, 0x64, 0x88, 0x69
}
};
static inline void pki_initialize_keyset(nca_keyset_t *keyset, keyset_variant_t variant) {
switch (variant) {
case KEYSET_DEV:
memcpy(keyset, &nca_keys_dev, sizeof(*keyset));
break;
case KEYSET_RETAIL:
memcpy(keyset, &nca_keys_retail, sizeof(*keyset));
break;
default:
memset(keyset, 0, sizeof(*keyset));
break;
}
}
#endif

89
rsa.c Normal file
View file

@ -0,0 +1,89 @@
#include <stdlib.h>
#include "rsa.h"
#include "sha.h"
#include "utils.h"
#include "types.h"
#define RSA_2048_BYTES 0x100
#define RSA_2048_BITS (RSA_2048_BYTES*8)
void init_mpi_from_buffer(gcry_mpi_t *mpi, const void *data, size_t len) {
/* Create new mpi, len * 8 bits. */
*mpi = gcry_mpi_new(len * 8);
if (gcry_mpi_scan(mpi, GCRYMPI_FMT_USG, data, len, NULL) != 0) {
FATAL_ERROR("Unable to load mpi!");
}
}
/* Perform an RSA-PSS verify operation on data, with signature and N. */
int rsa2048_pss_verify(const void *data, size_t len, const unsigned char *signature, const unsigned char *modulus) {
/* Do gcrypt exponentiation. */
gcry_mpi_t signature_mpi;
gcry_mpi_t modulus_mpi;
gcry_mpi_t e_mpi;
gcry_mpi_t message_mpi = gcry_mpi_new(RSA_2048_BITS);
unsigned char m_buf[RSA_2048_BYTES];
unsigned char h_buf[0x24];
const unsigned char E[3] = {1, 0, 1};
init_mpi_from_buffer(&e_mpi, E, 3);
init_mpi_from_buffer(&signature_mpi, signature, RSA_2048_BYTES);
init_mpi_from_buffer(&modulus_mpi, modulus, RSA_2048_BYTES);
gcry_mpi_powm(message_mpi, signature_mpi, e_mpi, modulus_mpi);
size_t sz;
if (gcry_mpi_print(GCRYMPI_FMT_USG, m_buf, RSA_2048_BYTES, &sz, message_mpi) != 0) {
FATAL_ERROR("Failed to export exponentiated RSA message!");
}
if (sz != RSA_2048_BYTES) { /* Ensure message is correct length. */
return false;
}
gcry_mpi_release(signature_mpi);
gcry_mpi_release(modulus_mpi);
gcry_mpi_release(e_mpi);
gcry_mpi_release(message_mpi);
/* libgcrypt requires knowledge of the salt to do automated PSS verification as far as I can tell. */
/* This is not an option in our case... */
if (m_buf[RSA_2048_BYTES-1] != 0xBC) {
return false;
}
memset(h_buf, 0, 0x24);
memcpy(h_buf, m_buf + RSA_2048_BYTES - 0x20 - 0x1, 0x20);
/* Decrypt maskedDB. Should MGF1 be its own function? */
unsigned char seed = 0;
unsigned char mgf1_buf[0x20];
for (unsigned int ofs = 0; ofs < RSA_2048_BYTES - 0x20 - 1; ofs += 0x20) {
h_buf[0x23] = seed++;
sha_hash_buffer(mgf1_buf, h_buf, 0x24);
for (unsigned int i = ofs; i < ofs + 0x20 && i < RSA_2048_BYTES - 0x20 - 1; i++) {
m_buf[i] ^= mgf1_buf[i - ofs];
}
}
m_buf[0] &= 0x7F; /* Constant lmask for rsa-2048-pss. */
/* Validate DB. */
for (unsigned int i = 0; i < RSA_2048_BYTES - 0x20 - 0x20 - 1 - 1; i++) {
if (m_buf[i] != 0) {
return false;
}
}
if (m_buf[RSA_2048_BYTES - 0x20 - 0x20 - 1 - 1] != 1) {
return false;
}
/* Check hash correctness. */
unsigned char validate_buf[8 + 0x20 + 0x20];
unsigned char validate_hash[0x20];
memset(validate_buf, 0, 0x48);
sha_hash_buffer(&validate_buf[8], data, len);
memcpy(&validate_buf[0x28], &m_buf[RSA_2048_BYTES - 0x20 - 0x20 - 1], 0x20);
sha_hash_buffer(validate_hash, validate_buf, 0x48);
return memcmp(h_buf, validate_hash, 0x20) == 0;
}

9
rsa.h Normal file
View file

@ -0,0 +1,9 @@
#ifndef NCATOOL_RSA_H
#define NCATOOL_RSA_H
#define GCRYPT_NO_DEPRECATED
#include <gcrypt.h>
int rsa2048_pss_verify(const void *data, size_t len, const unsigned char *signature, const unsigned char *modulus);
#endif

59
settings.h Normal file
View file

@ -0,0 +1,59 @@
#ifndef NCATOOL_SETTINGS_H
#define NCATOOL_SETTINGS_H
#include <stdio.h>
#include "types.h"
#include "filepath.h"
typedef enum {
KEYSET_DEV,
KEYSET_RETAIL
} keyset_variant_t;
typedef struct {
unsigned char header_key[0x20];
unsigned char titlekeks[0x20][0x10];
unsigned char key_area_keys[0x20][3][0x10];
unsigned char nca_hdr_fixed_key_modulus[0x100];
unsigned char acid_fixed_key_modulus[0x100];
} nca_keyset_t;
typedef struct {
int enabled;
filepath_t path;
} override_filepath_t;
typedef struct {
nca_keyset_t keyset;
int has_titlekey;
unsigned char titlekey[0x10];
unsigned char dec_titlekey[0x10];
int has_contentkey;
unsigned char contentkey[0x10];
filepath_t section_paths[4];
filepath_t section_dir_paths[4];
override_filepath_t exefs_path;
override_filepath_t exefs_dir_path;
override_filepath_t romfs_path;
override_filepath_t romfs_dir_path;
} ncatool_settings_t;
enum ncatool_file_type
{
NCA
};
#define ACTION_INFO (1<<0)
#define ACTION_EXTRACT (1<<1)
#define ACTION_VERIFY (1<<2)
#define ACTION_RAW (1<<3)
#define ACTION_LISTROMFS (1<<4)
typedef struct {
enum ncatool_file_type file_type;
FILE *file;
ncatool_settings_t settings;
uint32_t action;
} ncatool_ctx_t;
#endif

40
sha.c Normal file
View file

@ -0,0 +1,40 @@
#include <stdlib.h>
#include <stdio.h>
#include "sha.h"
#include "types.h"
#include "utils.h"
/* Allocate new context. */
sha_ctx_t *new_sha_ctx(void) {
sha_ctx_t *ctx;
if ((ctx = malloc(sizeof(*ctx))) == NULL) {
FATAL_ERROR("Failed to allocate sha_ctx_t!");
}
if (gcry_md_open(&ctx->digest, GCRY_MD_SHA256, 0) != 0) {
FATAL_ERROR("Failed to open sha_ctx_t!");
}
return ctx;
}
/* Update digest with new data. */
void sha_update(sha_ctx_t *ctx, const void *data, size_t l) {
gcry_md_write(ctx->digest, data, l);
}
/* Read hash from context. */
void sha_get_hash(sha_ctx_t *ctx, unsigned char *hash) {
memcpy(hash, gcry_md_read(ctx->digest, 0), 0x20);
}
/* Free context object. */
void sha_free(sha_ctx_t *ctx) {
gcry_md_close(ctx->digest);
free(ctx);
}
void sha_hash_buffer(unsigned char *digest, const void *data, size_t l) {
gcry_md_hash_buffer(GCRY_MD_SHA256, digest, data, l);
}

21
sha.h Normal file
View file

@ -0,0 +1,21 @@
#ifndef NCATOOL_SHA_H
#define NCATOOL_SHA_H
#define GCRYPT_NO_DEPRECATED
#include <gcrypt.h>
/* Define structs. */
typedef struct {
gcry_md_hd_t digest; /* gcrypt context for this hasher. */
} sha_ctx_t;
/* Function prototypes. */
sha_ctx_t *new_sha_ctx(void);
void sha_update(sha_ctx_t *ctx, const void *data, size_t l);
void sha_get_hash(sha_ctx_t *ctx, unsigned char *hash);
void sha_free(sha_ctx_t *ctx);
void sha_hash_buffer(unsigned char *hash, const void *data, size_t l);
#endif

19
types.h Normal file
View file

@ -0,0 +1,19 @@
#ifndef NCATOOL_TYPES_H
#define NCATOOL_TYPES_H
#include <errno.h>
#include <inttypes.h>
#include <stdbool.h>
#include <stddef.h>
typedef uint8_t byte;
typedef enum {
VALIDITY_UNCHECKED = 0,
VALIDITY_INVALID,
VALIDITY_VALID
} validity_t;
#define GET_VALIDITY_STR(validity) ((validity == VALIDITY_VALID) ? "GOOD" : "FAIL")
#endif

57
utils.c Normal file
View file

@ -0,0 +1,57 @@
#include <stdio.h>
#include <string.h>
#include <sys/stat.h>
#ifdef _WIN32
#include <direct.h>
#endif
#include "utils.h"
uint32_t align(uint32_t offset, uint32_t alignment) {
uint32_t mask = ~(alignment-1);
return (offset + (alignment-1)) & mask;
}
uint64_t align64(uint64_t offset, uint64_t alignment) {
uint64_t mask = ~(uint64_t)(alignment-1);
return (offset + (alignment-1)) & mask;
}
/* Print a magic number. */
void print_magic(const char *prefix, uint32_t magic) {
printf("%s%c%c%c%c\n", prefix, (char)((magic >> 0) & 0xFF), (char)((magic >> 8) & 0xFF), (char)((magic >> 16) & 0xFF), (char)((magic >> 24) & 0xFF));
}
/* Taken mostly from ctrtool. */
void memdump(FILE *f, const char *prefix, const void *data, size_t size) {
uint8_t *p = (uint8_t *)data;
unsigned int prefix_len = strlen(prefix);
size_t offset = 0;
int first = 1;
while (size) {
unsigned int max = 32;
if (max > size) {
max = size;
}
if (first) {
fprintf(f, "%s", prefix);
first = 0;
} else {
fprintf(f, "%*s", prefix_len, "");
}
for (unsigned int i = 0; i < max; i++) {
fprintf(f, "%02X", p[offset++]);
}
fprintf(f, "\n");
size -= max;
}
}

57
utils.h Normal file
View file

@ -0,0 +1,57 @@
#ifndef NCATOOL_UTILS_H
#define NCATOOL_UTILS_H
#include <stdio.h>
#include <stdlib.h>
#include "types.h"
#ifdef _WIN32
#define PATH_SEPERATOR '\\'
#else
#define PATH_SEPERATOR '/'
#endif
#define MEDIA_SIZE 0x200
/* On the switch, paths are limited to 0x300. Limit them to 0x400 - 1 on PC. */
/* MAX_PATH is previously defined in "windef.h" on WIN32. */
#ifndef MAX_PATH
#define MAX_PATH 1023
#endif
#define FATAL_ERROR(msg) do {\
fprintf(stderr, "Error: %s\n", msg);\
exit(EXIT_FAILURE);\
} while (0)
uint32_t align(uint32_t offset, uint32_t alignment);
uint64_t align64(uint64_t offset, uint64_t alignment);
void print_magic(const char *prefix, uint32_t magic);
void memdump(FILE *f, const char *prefix, const void *data, size_t size);
uint64_t _fsize(const char *filename);
#ifdef _MSC_VER
inline int fseeko64(FILE *__stream, long long __off, int __whence)
{
return _fseeki64(__stream, __off, __whence);
}
#elif __APPLE__ || __CYGWIN__
// OS X file I/O is 64bit
#define fseeko64 fseek
#elif __linux__ || __WIN32
extern int fseeko64 (FILE *__stream, __off64_t __off, int __whence);
#else
/* fseeko is guaranteed by POSIX, hopefully the OS made their off_t definition 64-bit;
* known sane on FreeBSD and OpenBSD.
*/
#define fseeko64 fseeko
#endif
static inline uint64_t media_to_real(uint64_t media) {
return MEDIA_SIZE * media;
}
#endif

7
version.h Normal file
View file

@ -0,0 +1,7 @@
#ifndef NCATOOL_VERSION_H
#define NCATOOL_VERSION_H
/* Will become 1.0 when BKTR is implemented and I've done some refactoring... */
#define NCATOOL_VERSION "0.8"
#endif