mirror of
https://github.com/SciresM/hactool
synced 2024-11-24 21:13:07 +00:00
First Public commit.
This commit is contained in:
commit
e2c9c1116f
26 changed files with 3289 additions and 0 deletions
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal 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
15
LICENSE
Normal 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
45
Makefile
Normal 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
51
README.md
Normal 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
112
aes.c
Normal 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
24
aes.h
Normal 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
3
config.mk.template
Normal file
|
@ -0,0 +1,3 @@
|
|||
CC=gcc
|
||||
CFLAGS=-O2 -Wall -Wextra -pedantic -std=gnu11 -fPIC
|
||||
LDFLAGS= -lgcrypt
|
110
filepath.c
Normal file
110
filepath.c
Normal 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
45
filepath.h
Normal 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
82
ivfc.h
Normal 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
212
main.c
Normal 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
937
nca.c
Normal 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(§or_buf, size, 0x200, ctx->file)) != 0x200) {
|
||||
return 0;
|
||||
}
|
||||
aes_xts_decrypt(ctx->aes, §or_buf, NULL, 0x200, ctx->sector_num, 0x200);
|
||||
if (count > 0x200 - ctx->sector_ofs) { /* We're leaving the sector... */
|
||||
memcpy(buffer, §or_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(§or_buf, size, 0x200, ctx->file)) != 0x200) {
|
||||
return ofs;
|
||||
}
|
||||
aes_xts_decrypt(ctx->aes, §or_buf, NULL, 0x200, ctx->sector_num, 0x200);
|
||||
memcpy((char *)buffer + ofs, §or_buf, remaining);
|
||||
ctx->sector_ofs = remaining;
|
||||
read = count;
|
||||
}
|
||||
} else {
|
||||
memcpy(buffer, §or_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
205
nca.h
Normal 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
657
npdm.c
Normal 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
137
npdm.h
Normal 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
41
pfs0.h
Normal 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
244
pki.h
Normal 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
89
rsa.c
Normal 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
9
rsa.h
Normal 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
59
settings.h
Normal 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
40
sha.c
Normal 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
21
sha.h
Normal 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
19
types.h
Normal 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
57
utils.c
Normal 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
57
utils.h
Normal 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
7
version.h
Normal 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
|
Loading…
Reference in a new issue