mirror of
https://github.com/SciresM/hactool
synced 2024-11-14 00:17:14 +00:00
673 lines
28 KiB
C
673 lines
28 KiB
C
#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"
|
|
#include "xci.h"
|
|
#include "nax0.h"
|
|
#include "extkeys.h"
|
|
#include "packages.h"
|
|
|
|
static char *prog_name = "hactool";
|
|
|
|
/* Print usage. Taken largely from ctrtool. */
|
|
static void usage(void) {
|
|
fprintf(stderr,
|
|
"hactool (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"
|
|
" -k, --keyset Load keys from an external file.\n"
|
|
" -t, --intype=type Specify input file type [nca, xci, pfs0, romfs, hfs0, npdm, pk11, pk21, ini1, kip1, nax0, keygen]\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"
|
|
" --plaintext=file Specify file path for saving a decrypted copy of the NCA.\n"
|
|
" --header=file Specify Header file path.\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"
|
|
" --baseromfs Set Base RomFS to use with update partitions.\n"
|
|
" --basenca Set Base NCA to use with update partitions.\n"
|
|
" --basefake Use a fake Base RomFS with update partitions (all reads will return 0xCC).\n"
|
|
" --onlyupdated Ignore non-updated files in update partitions.\n"
|
|
"NPDM/KIP1 options:\n"
|
|
" --json=file Specify file path for saving JSON representation of program permissions to.\n"
|
|
"PFS0 options:\n"
|
|
" --pfs0dir=dir Specify PFS0 directory path.\n"
|
|
" --outdir=dir Specify PFS0 directory path. Overrides previous path, if present.\n"
|
|
" --exefsdir=dir Specify PFS0 directory path. Overrides previous paths, if present for ExeFS PFS0.\n"
|
|
"RomFS options:\n"
|
|
" --romfsdir=dir Specify RomFS directory path.\n"
|
|
" --outdir=dir Specify RomFS directory path. Overrides previous path, if present.\n"
|
|
" --listromfs List files in RomFS.\n"
|
|
"HFS0 options:\n"
|
|
" --hfs0dir=dir Specify HFS0 directory path.\n"
|
|
" --outdir=dir Specify HFS0 directory path. Overrides previous path, if present.\n"
|
|
" --exefsdir=dir Specify HFS0 directory path. Overrides previous paths, if present.\n"
|
|
"XCI options:\n"
|
|
" --rootdir=dir Specify XCI root HFS0 directory path.\n"
|
|
" --updatedir=dir Specify XCI update HFS0 directory path.\n"
|
|
" --normaldir=dir Specify XCI normal HFS0 directory path.\n"
|
|
" --securedir=dir Specify XCI secure HFS0 directory path.\n"
|
|
" --logodir=dir Specify XCI logo HFS0 directory path.\n"
|
|
" --outdir=dir Specify XCI directory path. Overrides previous paths, if present.\n"
|
|
"Package1 options:\n"
|
|
" --package1dir=dir Specify Package1 directory path.\n"
|
|
" --outdir=dir Specify Package1 directory path. Overrides previous path, if present.\n"
|
|
"Package2 options:\n"
|
|
" --package2dir=dir Specify Package2 directory path.\n"
|
|
" --outdir=dir Specify Package2 directory path. Overrides previous path, if present.\n"
|
|
" --extractini1 Enable INI1 extraction to default directory (redundant with --ini1dir set).\n"
|
|
" --ini1dir=dir Specify INI1 directory path. Overrides default path, if present.\n"
|
|
"INI1 options:\n"
|
|
" --ini1dir=dir Specify INI1 directory path.\n"
|
|
" --outdir=dir Specify INI1 directory path. Overrides previous path, if present.\n"
|
|
" --saveini1json Enable generation of JSON descriptors for all INI1 members.\n"
|
|
"NAX0 options:\n"
|
|
" --sdseed=seed Set console unique seed for SD card NAX0 encryption.\n"
|
|
" --sdpath=path Set relative path for NAX0 key derivation (ex: /registered/000000FF/cafebabecafebabecafebabecafebabe.nca).\n"
|
|
"Key Derivation options:\n"
|
|
" --sbk=key Set console unique Secure Boot Key for key derivation.\n"
|
|
" --tseckey=key Set console unique TSEC Key for key derivation.\n"
|
|
"\n", __TIME__, __DATE__, prog_name);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
int main(int argc, char **argv) {
|
|
hactool_ctx_t tool_ctx;
|
|
hactool_ctx_t base_ctx; /* Context for base NCA, if used. */
|
|
nca_ctx_t nca_ctx;
|
|
char input_name[0x200];
|
|
filepath_t keypath;
|
|
|
|
prog_name = (argc < 1) ? "hactool" : argv[0];
|
|
|
|
nca_init(&nca_ctx);
|
|
memset(&tool_ctx, 0, sizeof(tool_ctx));
|
|
memset(&base_ctx, 0, sizeof(base_ctx));
|
|
memset(input_name, 0, sizeof(input_name));
|
|
filepath_init(&keypath);
|
|
nca_ctx.tool_ctx = &tool_ctx;
|
|
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_NCA;
|
|
base_ctx.file_type = FILETYPE_NCA;
|
|
|
|
nca_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'},
|
|
{"intype", 1, NULL, 't'},
|
|
{"keyset", 1, NULL, 'k'},
|
|
{"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},
|
|
{"baseromfs", 1, NULL, 15},
|
|
{"basenca", 1, NULL, 16},
|
|
{"outdir", 1, NULL, 17},
|
|
{"plaintext", 1, NULL, 18},
|
|
{"header", 1, NULL, 19},
|
|
{"pfs0dir", 1, NULL, 20},
|
|
{"hfs0dir", 1, NULL, 21},
|
|
{"rootdir", 1, NULL, 22},
|
|
{"updatedir", 1, NULL, 23},
|
|
{"normaldir", 1, NULL, 24},
|
|
{"securedir", 1, NULL, 25},
|
|
{"logodir", 1, NULL, 26},
|
|
{"package1dir", 1, NULL, 27},
|
|
{"package2dir", 1, NULL, 28},
|
|
{"ini1dir", 1, NULL, 29},
|
|
{"extractini1", 0, NULL, 30},
|
|
{"basefake", 0, NULL, 31},
|
|
{"onlyupdated", 0, NULL, 32},
|
|
{"sdseed", 1, NULL, 33},
|
|
{"sdpath", 1, NULL, 34},
|
|
{"sbk", 1, NULL, 35},
|
|
{"tseckey", 1, NULL, 36},
|
|
{"json", 1, NULL, 37},
|
|
{"saveini1json", 0, NULL, 38},
|
|
{NULL, 0, NULL, 0},
|
|
};
|
|
|
|
c = getopt_long(argc, argv, "dryxt:ik:", long_options, &option_index);
|
|
if (c == -1)
|
|
break;
|
|
|
|
switch (c)
|
|
{
|
|
case 'i':
|
|
nca_ctx.tool_ctx->action |= ACTION_INFO;
|
|
break;
|
|
case 'x':
|
|
nca_ctx.tool_ctx->action |= ACTION_EXTRACT;
|
|
break;
|
|
case 'y':
|
|
nca_ctx.tool_ctx->action |= ACTION_VERIFY;
|
|
break;
|
|
case 'r':
|
|
nca_ctx.tool_ctx->action |= ACTION_RAW;
|
|
break;
|
|
case 'd':
|
|
pki_initialize_keyset(&tool_ctx.settings.keyset, KEYSET_DEV);
|
|
nca_ctx.tool_ctx->action |= ACTION_DEV;
|
|
break;
|
|
case 'k':
|
|
filepath_set(&keypath, optarg);
|
|
break;
|
|
case 't':
|
|
if (!strcmp(optarg, "nca")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_NCA;
|
|
} else if (!strcmp(optarg, "pfs0") || !strcmp(optarg, "exefs")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_PFS0;
|
|
} else if (!strcmp(optarg, "romfs")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_ROMFS;
|
|
} else if (!strcmp(optarg, "nca0_romfs") || !strcmp(optarg, "nca0romfs") || !strcmp(optarg, "betaromfs") || !strcmp(optarg, "beta_romfs")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_NCA0_ROMFS;
|
|
} else if (!strcmp(optarg, "hfs0")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_HFS0;
|
|
} else if (!strcmp(optarg, "xci") || !strcmp(optarg, "gamecard") || !strcmp(optarg, "gc")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_XCI;
|
|
} else if (!strcmp(optarg, "npdm") || !strcmp(optarg, "meta")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_NPDM;
|
|
} else if (!strcmp(optarg, "package1") || !strcmp(optarg, "pk11")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_PACKAGE1;
|
|
} else if (!strcmp(optarg, "package2") || !strcmp(optarg, "pk21")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_PACKAGE2;
|
|
} else if (!strcmp(optarg, "ini1")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_INI1;
|
|
} else if (!strcmp(optarg, "kip1") || !strcmp(optarg, "kip")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_KIP1;
|
|
} else if (!strcmp(optarg, "nax0") || !strcmp(optarg, "nax")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_NAX0;
|
|
} else if (!strcmp(optarg, "keygen") || !strcmp(optarg, "keys") || !strcmp(optarg, "boot0") || !strcmp(optarg, "boot")) {
|
|
nca_ctx.tool_ctx->file_type = FILETYPE_BOOT0;
|
|
}
|
|
break;
|
|
case 0: filepath_set(&nca_ctx.tool_ctx->settings.section_paths[0], optarg); break;
|
|
case 1: filepath_set(&nca_ctx.tool_ctx->settings.section_paths[1], optarg); break;
|
|
case 2: filepath_set(&nca_ctx.tool_ctx->settings.section_paths[2], optarg); break;
|
|
case 3: filepath_set(&nca_ctx.tool_ctx->settings.section_paths[3], optarg); break;
|
|
case 4: filepath_set(&nca_ctx.tool_ctx->settings.section_dir_paths[0], optarg); break;
|
|
case 5: filepath_set(&nca_ctx.tool_ctx->settings.section_dir_paths[1], optarg); break;
|
|
case 6: filepath_set(&nca_ctx.tool_ctx->settings.section_dir_paths[2], optarg); break;
|
|
case 7: filepath_set(&nca_ctx.tool_ctx->settings.section_dir_paths[3], optarg); break;
|
|
case 8:
|
|
nca_ctx.tool_ctx->settings.exefs_path.enabled = 1;
|
|
filepath_set(&nca_ctx.tool_ctx->settings.exefs_path.path, optarg);
|
|
break;
|
|
case 9:
|
|
nca_ctx.tool_ctx->settings.romfs_path.enabled = 1;
|
|
filepath_set(&nca_ctx.tool_ctx->settings.romfs_path.path, optarg);
|
|
break;
|
|
case 10:
|
|
nca_ctx.tool_ctx->settings.exefs_dir_path.enabled = 1;
|
|
filepath_set(&nca_ctx.tool_ctx->settings.exefs_dir_path.path, optarg);
|
|
break;
|
|
case 11:
|
|
nca_ctx.tool_ctx->settings.romfs_dir_path.enabled = 1;
|
|
filepath_set(&nca_ctx.tool_ctx->settings.romfs_dir_path.path, optarg);
|
|
break;
|
|
case 12:
|
|
parse_hex_key(nca_ctx.tool_ctx->settings.titlekey, optarg, 16);
|
|
nca_ctx.tool_ctx->settings.has_titlekey = 1;
|
|
break;
|
|
case 13:
|
|
parse_hex_key(nca_ctx.tool_ctx->settings.contentkey, optarg, 16);
|
|
nca_ctx.tool_ctx->settings.has_contentkey = 1;
|
|
break;
|
|
case 14:
|
|
nca_ctx.tool_ctx->action |= ACTION_LISTROMFS;
|
|
break;
|
|
case 15:
|
|
if (nca_ctx.tool_ctx->base_file != NULL) {
|
|
usage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
if ((nca_ctx.tool_ctx->base_file = fopen(optarg, "rb")) == NULL) {
|
|
fprintf(stderr, "unable to open %s: %s\n", optarg, strerror(errno));
|
|
return EXIT_FAILURE;
|
|
}
|
|
nca_ctx.tool_ctx->base_file_type = BASEFILE_ROMFS;
|
|
break;
|
|
case 16:
|
|
if (nca_ctx.tool_ctx->base_file != NULL) {
|
|
usage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
if ((nca_ctx.tool_ctx->base_file = fopen(optarg, "rb")) == NULL) {
|
|
fprintf(stderr, "unable to open %s: %s\n", optarg, strerror(errno));
|
|
return EXIT_FAILURE;
|
|
}
|
|
nca_ctx.tool_ctx->base_file_type = BASEFILE_NCA;
|
|
nca_ctx.tool_ctx->base_nca_ctx = malloc(sizeof(*nca_ctx.tool_ctx->base_nca_ctx));
|
|
if (nca_ctx.tool_ctx->base_nca_ctx == NULL) {
|
|
fprintf(stderr, "Failed to allocate base NCA context!\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
nca_init(nca_ctx.tool_ctx->base_nca_ctx);
|
|
base_ctx.file = nca_ctx.tool_ctx->base_file;
|
|
nca_ctx.tool_ctx->base_nca_ctx->file = base_ctx.file;
|
|
break;
|
|
case 17:
|
|
tool_ctx.settings.out_dir_path.enabled = 1;
|
|
filepath_set(&tool_ctx.settings.out_dir_path.path, optarg);
|
|
break;
|
|
case 18:
|
|
filepath_set(&nca_ctx.tool_ctx->settings.plaintext_path, optarg);
|
|
break;
|
|
case 19:
|
|
filepath_set(&nca_ctx.tool_ctx->settings.header_path, optarg);
|
|
break;
|
|
case 20:
|
|
filepath_set(&tool_ctx.settings.pfs0_dir_path, optarg);
|
|
break;
|
|
case 21:
|
|
filepath_set(&tool_ctx.settings.hfs0_dir_path, optarg);
|
|
break;
|
|
case 22:
|
|
filepath_set(&tool_ctx.settings.rootpt_dir_path, optarg);
|
|
break;
|
|
case 23:
|
|
filepath_set(&tool_ctx.settings.update_dir_path, optarg);
|
|
break;
|
|
case 24:
|
|
filepath_set(&tool_ctx.settings.normal_dir_path, optarg);
|
|
break;
|
|
case 25:
|
|
filepath_set(&tool_ctx.settings.secure_dir_path, optarg);
|
|
break;
|
|
case 26:
|
|
filepath_set(&tool_ctx.settings.logo_dir_path, optarg);
|
|
break;
|
|
case 27:
|
|
filepath_set(&tool_ctx.settings.pk11_dir_path, optarg);
|
|
break;
|
|
case 28:
|
|
filepath_set(&tool_ctx.settings.pk21_dir_path, optarg);
|
|
break;
|
|
case 29:
|
|
filepath_set(&tool_ctx.settings.ini1_dir_path, optarg);
|
|
break;
|
|
case 30:
|
|
tool_ctx.action |= ACTION_EXTRACTINI1;
|
|
break;
|
|
case 31:
|
|
if (nca_ctx.tool_ctx->base_file != NULL) {
|
|
usage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
nca_ctx.tool_ctx->base_file_type = BASEFILE_FAKE;
|
|
nca_ctx.tool_ctx->base_file++; /* Guarantees base_file != NULL. I'm so sorry. */
|
|
break;
|
|
case 32:
|
|
tool_ctx.action |= ACTION_ONLYUPDATEDROMFS;
|
|
break;
|
|
case 33:
|
|
parse_hex_key(nca_ctx.tool_ctx->settings.sdseed, optarg, 16);
|
|
nca_ctx.tool_ctx->settings.has_sdseed = 1;
|
|
for (unsigned int key = 0; key < 2; key++) {
|
|
for (unsigned int i = 0; i < 0x20; i++) {
|
|
tool_ctx.settings.keyset.sd_card_key_sources[key][i] ^= tool_ctx.settings.sdseed[i & 0xF];
|
|
}
|
|
}
|
|
pki_derive_keys(&tool_ctx.settings.keyset);
|
|
break;
|
|
case 34:
|
|
filepath_set(&tool_ctx.settings.nax0_sd_path, optarg);
|
|
break;
|
|
case 35:
|
|
parse_hex_key(nca_ctx.tool_ctx->settings.keygen_sbk, optarg, 16);
|
|
break;
|
|
case 36:
|
|
parse_hex_key(nca_ctx.tool_ctx->settings.keygen_tsec, optarg, 16);
|
|
break;
|
|
case 37:
|
|
filepath_set(&tool_ctx.settings.npdm_json_path, optarg);
|
|
break;
|
|
case 38:
|
|
tool_ctx.action |= ACTION_SAVEINIJSON;
|
|
break;
|
|
default:
|
|
usage();
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
/* Try to populate default keyfile. */
|
|
/* Use $HOME/.switch/prod.keys if it exists */
|
|
char *home = getenv("HOME");
|
|
if (home == NULL)
|
|
home = getenv("USERPROFILE");
|
|
if (keypath.valid == VALIDITY_INVALID) {
|
|
if (home != NULL) {
|
|
filepath_set(&keypath, home);
|
|
filepath_append(&keypath, ".switch");
|
|
filepath_append(&keypath, "%s.keys", (tool_ctx.action & ACTION_DEV) ? "dev" : "prod");
|
|
}
|
|
}
|
|
|
|
/* Load external keys, if relevant. */
|
|
FILE *keyfile = NULL;
|
|
if (keypath.valid == VALIDITY_VALID) {
|
|
keyfile = os_fopen(keypath.os_path, OS_MODE_READ);
|
|
}
|
|
|
|
/* If $HOME/.switch/prod.keys don't exist, try using $XDG_CONFIG_HOME */
|
|
if (keyfile == NULL) {
|
|
char *xdgconfig = getenv("XDG_CONFIG_HOME");
|
|
if (xdgconfig != NULL)
|
|
filepath_set(&keypath, xdgconfig);
|
|
else if (home != NULL) {
|
|
filepath_set(&keypath, home);
|
|
filepath_append(&keypath, ".config");
|
|
}
|
|
/* Keypath contains xdg config. Add switch/%s.keys */
|
|
filepath_append(&keypath, "switch");
|
|
filepath_append(&keypath, "%s.keys", (tool_ctx.action & ACTION_DEV) ? "dev" : "prod");
|
|
}
|
|
|
|
if (keyfile == NULL && keypath.valid == VALIDITY_VALID) {
|
|
keyfile = os_fopen(keypath.os_path, OS_MODE_READ);
|
|
}
|
|
|
|
if (keyfile != NULL) {
|
|
extkeys_initialize_keyset(&tool_ctx.settings.keyset, keyfile);
|
|
if (tool_ctx.settings.has_sdseed) {
|
|
for (unsigned int key = 0; key < 2; key++) {
|
|
for (unsigned int i = 0; i < 0x20; i++) {
|
|
tool_ctx.settings.keyset.sd_card_key_sources[key][i] ^= tool_ctx.settings.sdseed[i & 0xF];
|
|
}
|
|
}
|
|
}
|
|
pki_derive_keys(&tool_ctx.settings.keyset);
|
|
fclose(keyfile);
|
|
}
|
|
|
|
if (optind == argc - 1) {
|
|
/* Copy input file. */
|
|
strncpy(input_name, argv[optind], sizeof(input_name));
|
|
} else if (tool_ctx.file_type != FILETYPE_BOOT0 && ((optind < argc) || (argc == 1))) {
|
|
usage();
|
|
}
|
|
|
|
/* Special case NAX0. */
|
|
if (tool_ctx.file_type == FILETYPE_NAX0) {
|
|
nax0_ctx_t nax_ctx;
|
|
memset(&nax_ctx, 0, sizeof(nax_ctx));
|
|
filepath_set(&nax_ctx.base_path, input_name);
|
|
nax_ctx.tool_ctx = &tool_ctx;
|
|
nax0_process(&nax_ctx);
|
|
|
|
if (nax_ctx.aes_ctx) {
|
|
free_aes_ctx(nax_ctx.aes_ctx);
|
|
}
|
|
if (nax_ctx.num_files) {
|
|
for (unsigned int i = 0; i < nax_ctx.num_files; i++) {
|
|
fclose(nax_ctx.files[i]);
|
|
}
|
|
}
|
|
if (nax_ctx.files) {
|
|
free(nax_ctx.files);
|
|
}
|
|
printf("Done!\n");
|
|
return EXIT_SUCCESS;
|
|
}
|
|
|
|
if ((tool_ctx.file = fopen(input_name, "rb")) == NULL && tool_ctx.file_type != FILETYPE_BOOT0) {
|
|
fprintf(stderr, "unable to open %s: %s\n", input_name, strerror(errno));
|
|
return EXIT_FAILURE;
|
|
}
|
|
|
|
switch (tool_ctx.file_type) {
|
|
case FILETYPE_NCA: {
|
|
if (nca_ctx.tool_ctx->base_nca_ctx != NULL) {
|
|
memcpy(&base_ctx.settings.keyset, &tool_ctx.settings.keyset, sizeof(nca_keyset_t));
|
|
nca_ctx.tool_ctx->base_nca_ctx->tool_ctx = &base_ctx;
|
|
nca_process(nca_ctx.tool_ctx->base_nca_ctx);
|
|
int found_romfs = 0;
|
|
for (unsigned int i = 0; i < 4; i++) {
|
|
if (nca_ctx.tool_ctx->base_nca_ctx->section_contexts[i].is_present && nca_ctx.tool_ctx->base_nca_ctx->section_contexts[i].type == ROMFS) {
|
|
found_romfs = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (found_romfs == 0) {
|
|
fprintf(stderr, "Unable to locate RomFS in base NCA!\n");
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
|
|
nca_ctx.file = tool_ctx.file;
|
|
nca_process(&nca_ctx);
|
|
nca_free_section_contexts(&nca_ctx);
|
|
|
|
if (nca_ctx.tool_ctx->base_file_type == BASEFILE_FAKE) {
|
|
nca_ctx.tool_ctx->base_file = NULL;
|
|
}
|
|
|
|
if (nca_ctx.tool_ctx->base_file != NULL) {
|
|
fclose(nca_ctx.tool_ctx->base_file);
|
|
if (nca_ctx.tool_ctx->base_file_type == BASEFILE_NCA) {
|
|
nca_free_section_contexts(nca_ctx.tool_ctx->base_nca_ctx);
|
|
free(nca_ctx.tool_ctx->base_nca_ctx);
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
case FILETYPE_PFS0: {
|
|
pfs0_ctx_t pfs0_ctx;
|
|
memset(&pfs0_ctx, 0, sizeof(pfs0_ctx));
|
|
pfs0_ctx.file = tool_ctx.file;
|
|
pfs0_ctx.tool_ctx = &tool_ctx;
|
|
pfs0_process(&pfs0_ctx);
|
|
if (pfs0_ctx.header) {
|
|
free(pfs0_ctx.header);
|
|
}
|
|
if (pfs0_ctx.npdm) {
|
|
free(pfs0_ctx.npdm);
|
|
}
|
|
break;
|
|
}
|
|
case FILETYPE_ROMFS: {
|
|
romfs_ctx_t romfs_ctx;
|
|
memset(&romfs_ctx, 0, sizeof(romfs_ctx));
|
|
romfs_ctx.file = tool_ctx.file;
|
|
romfs_ctx.tool_ctx = &tool_ctx;
|
|
romfs_process(&romfs_ctx);
|
|
if (romfs_ctx.files) {
|
|
free(romfs_ctx.files);
|
|
}
|
|
if (romfs_ctx.directories) {
|
|
free(romfs_ctx.directories);
|
|
}
|
|
break;
|
|
}
|
|
case FILETYPE_NCA0_ROMFS: {
|
|
nca0_romfs_ctx_t romfs_ctx;
|
|
memset(&romfs_ctx, 0, sizeof(romfs_ctx));
|
|
romfs_ctx.file = tool_ctx.file;
|
|
romfs_ctx.tool_ctx = &tool_ctx;
|
|
nca0_romfs_process(&romfs_ctx);
|
|
if (romfs_ctx.files) {
|
|
free(romfs_ctx.files);
|
|
}
|
|
if (romfs_ctx.directories) {
|
|
free(romfs_ctx.directories);
|
|
}
|
|
break;
|
|
}
|
|
case FILETYPE_NPDM: {
|
|
npdm_t raw_hdr;
|
|
memset(&raw_hdr, 0, sizeof(raw_hdr));
|
|
if (fread(&raw_hdr, 1, sizeof(raw_hdr), tool_ctx.file) != sizeof(raw_hdr)) {
|
|
fprintf(stderr, "Failed to read NPDM header!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (raw_hdr.magic != MAGIC_META) {
|
|
fprintf(stderr, "NPDM seems corrupt!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
uint64_t npdm_size = raw_hdr.aci0_size + raw_hdr.aci0_offset;
|
|
if (raw_hdr.acid_offset + raw_hdr.acid_size > npdm_size) {
|
|
npdm_size = raw_hdr.acid_offset + raw_hdr.acid_size;
|
|
}
|
|
fseeko64(tool_ctx.file, 0, SEEK_SET);
|
|
npdm_t *npdm = malloc(npdm_size);
|
|
if (npdm == NULL) {
|
|
fprintf(stderr, "Failed to allocate NPDM!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (fread(npdm, 1, npdm_size, tool_ctx.file) != npdm_size) {
|
|
fprintf(stderr, "Failed to read NPDM!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
npdm_process(npdm, &tool_ctx);
|
|
break;
|
|
}
|
|
case FILETYPE_HFS0: {
|
|
hfs0_ctx_t hfs0_ctx;
|
|
memset(&hfs0_ctx, 0, sizeof(hfs0_ctx));
|
|
hfs0_ctx.file = tool_ctx.file;
|
|
hfs0_ctx.tool_ctx = &tool_ctx;
|
|
hfs0_process(&hfs0_ctx);
|
|
if (hfs0_ctx.header) {
|
|
free(hfs0_ctx.header);
|
|
}
|
|
break;
|
|
}
|
|
case FILETYPE_PACKAGE1: {
|
|
pk11_ctx_t pk11_ctx;
|
|
memset(&pk11_ctx, 0, sizeof(pk11_ctx));
|
|
pk11_ctx.file = tool_ctx.file;
|
|
pk11_ctx.tool_ctx = &tool_ctx;
|
|
pk11_process(&pk11_ctx);
|
|
if (pk11_ctx.pk11) {
|
|
free(pk11_ctx.pk11);
|
|
}
|
|
break;
|
|
}
|
|
case FILETYPE_PACKAGE2: {
|
|
pk21_ctx_t pk21_ctx;
|
|
memset(&pk21_ctx, 0, sizeof(pk21_ctx));
|
|
pk21_ctx.file = tool_ctx.file;
|
|
pk21_ctx.tool_ctx = &tool_ctx;
|
|
pk21_process(&pk21_ctx);
|
|
if (pk21_ctx.sections) {
|
|
free(pk21_ctx.sections);
|
|
}
|
|
break;
|
|
}
|
|
case FILETYPE_INI1: {
|
|
ini1_ctx_t ini1_ctx;
|
|
memset(&ini1_ctx, 0, sizeof(ini1_ctx));
|
|
ini1_ctx.file = tool_ctx.file;
|
|
ini1_ctx.tool_ctx = &tool_ctx;
|
|
ini1_process(&ini1_ctx);
|
|
if (ini1_ctx.header) {
|
|
free(ini1_ctx.header);
|
|
}
|
|
break;
|
|
}
|
|
case FILETYPE_KIP1: {
|
|
kip1_ctx_t kip1_ctx;
|
|
memset(&kip1_ctx, 0, sizeof(kip1_ctx));
|
|
kip1_ctx.file = tool_ctx.file;
|
|
kip1_ctx.tool_ctx = &tool_ctx;
|
|
kip1_process(&kip1_ctx);
|
|
if (kip1_ctx.header) {
|
|
free(kip1_ctx.header);
|
|
}
|
|
break;
|
|
}
|
|
case FILETYPE_XCI: {
|
|
xci_ctx_t xci_ctx;
|
|
memset(&xci_ctx, 0, sizeof(xci_ctx));
|
|
xci_ctx.file = tool_ctx.file;
|
|
xci_ctx.tool_ctx = &tool_ctx;
|
|
xci_process(&xci_ctx);
|
|
break;
|
|
}
|
|
case FILETYPE_BOOT0: {
|
|
nca_keyset_t new_keyset;
|
|
memcpy(&new_keyset, &tool_ctx.settings.keyset, sizeof(new_keyset));
|
|
for (unsigned int i = 0; i < 0x10; i++) {
|
|
if (tool_ctx.settings.keygen_sbk[i] != 0) {
|
|
memcpy(new_keyset.secure_boot_key, tool_ctx.settings.keygen_sbk, 0x10);
|
|
}
|
|
}
|
|
for (unsigned int i = 0; i < 0x10; i++) {
|
|
if (tool_ctx.settings.keygen_tsec[i] != 0) {
|
|
memcpy(new_keyset.tsec_key, tool_ctx.settings.keygen_tsec, 0x10);
|
|
}
|
|
}
|
|
for (unsigned int i = 0; tool_ctx.file != NULL && i < 0x20; i++) {
|
|
fseek(tool_ctx.file, 0x180000 + 0x200 * i, SEEK_SET);
|
|
if (fread(&new_keyset.encrypted_keyblobs[i], sizeof(new_keyset.encrypted_keyblobs[i]), 1, tool_ctx.file) != 1) {
|
|
fprintf(stderr, "Error: Failed to read encrypted_keyblob_%02x from boot0!\n", i);
|
|
return EXIT_FAILURE;
|
|
}
|
|
}
|
|
printf("Deriving keys...\n");
|
|
pki_derive_keys(&new_keyset);
|
|
printf("--\n");
|
|
printf("All derivable keys (using loaded sources):\n\n");
|
|
pki_print_keys(&new_keyset);
|
|
break;
|
|
}
|
|
default: {
|
|
fprintf(stderr, "Unknown File Type!\n\n");
|
|
usage();
|
|
}
|
|
}
|
|
|
|
if (tool_ctx.file != NULL) {
|
|
fclose(tool_ctx.file);
|
|
}
|
|
printf("Done!\n");
|
|
|
|
return EXIT_SUCCESS;
|
|
}
|