Fix XTS/NCA2 support. Add NCA0 support.

This commit is contained in:
Michael Scire 2018-07-20 02:57:28 -07:00
parent 94d55a936b
commit 2e87aa8a73
14 changed files with 730 additions and 74 deletions

View file

@ -13,7 +13,7 @@ all:
.c.o:
$(CC) $(INCLUDE) -c $(CFLAGS) -o $@ $<
hactool: sha.o aes.o extkeys.o rsa.o npdm.o bktr.o kip.o packages.o pki.o pfs0.o hfs0.o romfs.o utils.o nax0.o nca.o xci.o main.o filepath.o ConvertUTF.o cJSON.o
hactool: sha.o aes.o extkeys.o rsa.o npdm.o bktr.o kip.o packages.o pki.o pfs0.o hfs0.o nca0_romfs.o romfs.o utils.o nax0.o nca.o xci.o main.o filepath.o ConvertUTF.o cJSON.o
$(CC) -o $@ $^ $(LDFLAGS) -L $(LIBDIR)
aes.o: aes.h types.h
@ -44,6 +44,8 @@ npdm.o: npdm.c cJSON.h types.h
romfs.o: ivfc.h types.h
nca0_romfs.o: nca0_romfs.h ivfc.h types.h
rsa.o: rsa.h sha.h types.h
sha.o: sha.h types.h

View file

@ -1,6 +1,7 @@
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include "pki.h"
#include "extkeys.h"
/**
@ -236,6 +237,11 @@ void extkeys_initialize_keyset(nca_keyset_t *keyset, FILE *f) {
} else if (strcmp(key, "tsec_key") == 0) {
parse_hex_key(keyset->tsec_key, value, sizeof(keyset->tsec_key));
matched_key = 1;
} else if (strcmp(key, "beta_nca0_exponent") == 0) {
unsigned char exponent[0x100] = {0};
parse_hex_key(exponent, value, sizeof(exponent));
pki_set_beta_nca0_exponent(exponent);
matched_key = 1;
} else {
char test_name[0x100];
memset(test_name, 0, sizeof(100));

1
ivfc.h
View file

@ -54,6 +54,7 @@ typedef struct {
} romfs_hdr_t;
typedef struct {
uint32_t parent;
uint32_t sibling;
uint32_t child;
uint32_t file;

16
main.c
View file

@ -205,6 +205,8 @@ int main(int argc, char **argv) {
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")) {
@ -524,6 +526,20 @@ int main(int argc, char **argv) {
}
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));

387
nca.c
View file

@ -1,6 +1,7 @@
#include <stdlib.h>
#include "nca.h"
#include "aes.h"
#include "pki.h"
#include "sha.h"
#include "rsa.h"
#include "utils.h"
@ -38,11 +39,16 @@ 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->crypt_type == CRYPT_XTS) {
fseeko64(ctx->file, (ctx->offset + offset) & ~ctx->sector_mask, SEEK_SET);
ctx->cur_seek = (ctx->offset + offset) & ~ctx->sector_mask;
ctx->sector_num = offset / ctx->sector_size;
ctx->sector_ofs = offset & ctx->sector_mask;
} else if (ctx->crypt_type == CRYPT_NCA0) {
fseeko64(ctx->file, (ctx->offset + offset) & ~ctx->sector_mask, SEEK_SET);
ctx->cur_seek = ((ctx->offset + offset - 0x400ULL) & ~ctx->sector_mask) + 0x400ULL;
ctx->sector_num = (ctx->offset + offset - 0x400ULL) / ctx->sector_size;
ctx->sector_ofs = (ctx->offset + offset - 0x400ULL) & ctx->sector_mask;
} else if (ctx->type == BKTR && ctx->bktr_ctx.subsection_block != NULL) {
/* No better way to do this than to make all BKTR seeking virtual. */
ctx->bktr_ctx.virtual_seek = offset;
@ -59,7 +65,7 @@ void nca_section_fseek(nca_section_ctx_t *ctx, uint64_t offset) {
ctx->bktr_ctx.base_seek = section_ofs;
}
}
} else if (ctx->header->crypt_type != CRYPT_NONE) { /* CTR, and BKTR until subsections are read. */
} else if (ctx->crypt_type != CRYPT_NONE) { /* CTR, and BKTR until subsections are read. */
fseeko64(ctx->file, (ctx->offset + offset) & ~0xF, SEEK_SET);
ctx->cur_seek = (ctx->offset + offset) & ~0xF;
nca_update_ctr(ctx->ctr, ctx->offset + offset);
@ -133,47 +139,54 @@ size_t nca_section_fread(nca_section_ctx_t *ctx, void *buffer, size_t count) {
return read;
}
if (ctx->header->crypt_type == CRYPT_XTS) { /* AES-XTS requires special handling... */
unsigned char sector_buf[0x200];
if ((read = fread(&sector_buf, size, 0x200, ctx->file)) != 0x200) {
if (ctx->crypt_type == CRYPT_XTS || ctx->crypt_type == CRYPT_NCA0) { /* AES-XTS requires special handling... */
unsigned char *sector_buf = malloc(ctx->sector_size);
if ((read = fread(sector_buf, size, ctx->sector_size, ctx->file)) != ctx->sector_size) {
free(sector_buf);
return 0;
}
aes_xts_decrypt(ctx->aes, &sector_buf, &sector_buf, 0x200, ctx->sector_num, 0x200);
if (count > 0x200 - ctx->sector_ofs) { /* We're leaving the sector... */
memcpy(buffer, &sector_buf + ctx->sector_ofs, 0x200 - ctx->sector_ofs);
aes_xts_decrypt(ctx->aes, sector_buf, sector_buf, ctx->sector_size, ctx->sector_num, ctx->sector_size);
if (count > ctx->sector_size - ctx->sector_ofs) { /* We're leaving the sector... */
memcpy(buffer, sector_buf + ctx->sector_ofs, ctx->sector_size - ctx->sector_ofs);
size_t remaining = count - (ctx->sector_size - ctx->sector_ofs);
size_t ofs = (ctx->sector_size - 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)) {
if (remaining & ~ctx->sector_mask) { /* Read intermediate sectors. */
uint64_t addl;
if ((addl = fread((char *)buffer + ofs, size, (remaining & ~ctx->sector_mask), ctx->file)) != (remaining & ~ctx->sector_mask)) {
free(sector_buf);
return ofs;
}
aes_xts_decrypt(ctx->aes, (char *)buffer + ofs, (char *)buffer + ofs, remaining & ~0x1FF, ctx->sector_num, 0x200);
ctx->sector_num += remaining / 0x200;
ofs += remaining & ~0x1FF;
remaining &= 0x1FF;
aes_xts_decrypt(ctx->aes, (char *)buffer + ofs, (char *)buffer + ofs, remaining & ~ctx->sector_mask, ctx->sector_num, ctx->sector_size);
ctx->sector_num += remaining / ctx->sector_size;
ofs += remaining & ~ctx->sector_mask;
remaining &= ctx->sector_mask;
read += addl;
}
if (remaining) { /* Read last sector. */
if ((read = fread(&sector_buf, size, 0x200, ctx->file)) != 0x200) {
if ((read = fread(sector_buf, size, ctx->sector_size, ctx->file)) != ctx->sector_size) {
free(sector_buf);
return ofs;
}
aes_xts_decrypt(ctx->aes, &sector_buf, &sector_buf, 0x200, ctx->sector_num, 0x200);
memcpy((char *)buffer + ofs, &sector_buf, remaining);
aes_xts_decrypt(ctx->aes, sector_buf, sector_buf, ctx->sector_size, ctx->sector_num, ctx->sector_size);
memcpy((char *)buffer + ofs, sector_buf, remaining);
ctx->sector_ofs = remaining;
read = count;
}
} else {
memcpy(buffer, &sector_buf + ctx->sector_ofs, count);
ctx->sector_num += (ctx->sector_ofs + count) / 0x200;
memcpy(buffer, sector_buf + ctx->sector_ofs, count);
ctx->sector_num += (ctx->sector_ofs + count) / ctx->sector_size;
ctx->sector_ofs += count;
ctx->sector_ofs &= 0x1FF;
ctx->sector_ofs &= ctx->sector_mask;
read = count;
}
free(sector_buf);
} else {
/* Perform decryption, if necessary. */
/* AES-CTR. */
if (ctx->header->crypt_type == CRYPT_CTR || (ctx->header->crypt_type == CRYPT_BKTR && ctx->bktr_ctx.subsection_block == NULL))
if (ctx->crypt_type == CRYPT_CTR || (ctx->crypt_type == CRYPT_BKTR && ctx->bktr_ctx.subsection_block == NULL))
{
if (ctx->sector_ofs) {
if ((read = fread(block_buf, 1, 0x10, ctx->file)) != 0x10) {
@ -193,12 +206,12 @@ size_t nca_section_fread(nca_section_ctx_t *ctx, void *buffer, size_t count) {
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;
return 0;
}
aes_setiv(ctx->aes, ctx->ctr, 16);
aes_decrypt(ctx->aes, buffer, buffer, count);
nca_section_fseek(ctx, ctx->cur_seek - ctx->offset + count);
} else if (ctx->header->crypt_type == CRYPT_BKTR) { /* Spooky BKTR AES-CTR. */
} else if (ctx->crypt_type == CRYPT_BKTR) { /* Spooky BKTR AES-CTR. */
/* Are we doing virtual reads, or physical reads? */
if (ctx->tool_ctx->base_file != NULL && ctx->physical_reads == 0) {
bktr_relocation_entry_t *reloc = bktr_get_relocation(ctx->bktr_ctx.relocation_block, ctx->bktr_ctx.virtual_seek);
@ -263,7 +276,7 @@ void nca_free_section_contexts(nca_ctx_t *ctx) {
if (ctx->section_contexts[i].aes) {
free_aes_ctx(ctx->section_contexts[i].aes);
}
if (ctx->section_contexts[i].pfs0_ctx.is_exefs) {
if (ctx->section_contexts[i].type == PFS0 && 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) {
@ -272,6 +285,13 @@ void nca_free_section_contexts(nca_ctx_t *ctx) {
if (ctx->section_contexts[i].romfs_ctx.files) {
free(ctx->section_contexts[i].romfs_ctx.files);
}
} else if (ctx->section_contexts[i].type == NCA0_ROMFS) {
if (ctx->section_contexts[i].nca0_romfs_ctx.directories) {
free(ctx->section_contexts[i].nca0_romfs_ctx.directories);
}
if (ctx->section_contexts[i].nca0_romfs_ctx.files) {
free(ctx->section_contexts[i].nca0_romfs_ctx.files);
}
} else if (ctx->section_contexts[i].type == BKTR) {
if (ctx->section_contexts[i].bktr_ctx.subsection_block) {
free(ctx->section_contexts[i].bktr_ctx.subsection_block);
@ -413,7 +433,7 @@ void nca_process(nca_ctx_t *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_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;
@ -421,17 +441,24 @@ void nca_process(nca_ctx_t *ctx) {
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];
ctx->section_contexts[i].crypt_type = ctx->section_contexts[i].header->crypt_type;
if (ctx->format_version == NCAVERSION_NCA0 || ctx->format_version == NCAVERSION_NCA0_BETA) {
ctx->section_contexts[i].crypt_type = CRYPT_NCA0;
}
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) {
if (ctx->section_contexts[i].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 if (ctx->section_contexts[i].header->partition_type == PARTITION_ROMFS && ctx->section_contexts[i].header->fs_type == FS_TYPE_PFS0 && (ctx->format_version == NCAVERSION_NCA0 || ctx->format_version == NCAVERSION_NCA0_BETA)) {
ctx->section_contexts[i].type = NCA0_ROMFS;
ctx->section_contexts[i].nca0_romfs_ctx.superblock = &ctx->section_contexts[i].header->nca0_romfs_superblock;
} else {
ctx->section_contexts[i].type = INVALID;
}
@ -444,7 +471,7 @@ void nca_process(nca_ctx_t *ctx) {
ctx->section_contexts[i].sector_num = 0;
ctx->section_contexts[i].sector_ofs = 0;
if (ctx->section_contexts[i].header->crypt_type == CRYPT_NONE) {
if (ctx->section_contexts[i].crypt_type == CRYPT_NONE) {
ctx->section_contexts[i].is_decrypted = 1;
}
@ -454,10 +481,14 @@ void nca_process(nca_ctx_t *ctx) {
if (ctx->has_rights_id) {
ctx->section_contexts[i].aes = new_aes_ctx(ctx->tool_ctx->settings.dec_titlekey, 16, AES_MODE_CTR);
} else {
if (ctx->section_contexts[i].header->crypt_type == CRYPT_CTR || ctx->section_contexts[i].header->crypt_type == CRYPT_BKTR) {
if (ctx->section_contexts[i].crypt_type == CRYPT_CTR || ctx->section_contexts[i].crypt_type == CRYPT_BKTR) {
ctx->section_contexts[i].aes = new_aes_ctx(ctx->decrypted_keys[2], 16, AES_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, AES_MODE_XTS);
} else if (ctx->section_contexts[i].crypt_type == CRYPT_XTS || ctx->section_contexts[i].crypt_type == CRYPT_NCA0) {
ctx->section_contexts[i].aes = new_aes_ctx(ctx->decrypted_keys, 32, AES_MODE_XTS);
ctx->section_contexts[i].sector_size = 0x200ULL;
}
if (ctx->section_contexts[i].sector_size) {
ctx->section_contexts[i].sector_mask = ctx->section_contexts[i].sector_size - 1ULL;
}
}
}
@ -481,6 +512,9 @@ void nca_process(nca_ctx_t *ctx) {
case ROMFS:
nca_process_ivfc_section(&ctx->section_contexts[i]);
break;
case NCA0_ROMFS:
nca_process_nca0_romfs_section(&ctx->section_contexts[i]);
break;
case BKTR:
nca_process_bktr_section(&ctx->section_contexts[i]);
break;
@ -512,6 +546,11 @@ int nca_decrypt_header(nca_ctx_t *ctx) {
if (ctx->header.magic == MAGIC_NCA3 || ctx->header.magic == MAGIC_NCA2) {
if (ctx->header._0x340[0] == 0 && !memcmp(ctx->header._0x340, ctx->header._0x340 + 1, 0xBF)) {
ctx->is_decrypted = 1;
if (ctx->header.magic == MAGIC_NCA3) {
ctx->format_version = NCAVERSION_NCA3;
} else {
ctx->format_version = NCAVERSION_NCA2;
}
return 1;
}
}
@ -522,30 +561,65 @@ int nca_decrypt_header(nca_ctx_t *ctx) {
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.header_key, 32, AES_MODE_XTS);
aes_xts_decrypt(aes_ctx, &dec_header, &ctx->header, 0x400, 0, 0x200);
aes_ctx_t *hdr_aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.header_key, 32, AES_MODE_XTS);
aes_xts_decrypt(hdr_aes_ctx, &dec_header, &ctx->header, 0x400, 0, 0x200);
if (dec_header.magic == MAGIC_NCA3) {
aes_xts_decrypt(aes_ctx, &dec_header, &ctx->header, 0xC00, 0, 0x200);
ctx->format_version = NCAVERSION_NCA3;
aes_xts_decrypt(hdr_aes_ctx, &dec_header, &ctx->header, 0xC00, 0, 0x200);
ctx->header = dec_header;
} else if (dec_header.magic == MAGIC_NCA2) {
ctx->format_version = NCAVERSION_NCA2;
for (unsigned int i = 0; i < 4; i++) {
if (dec_header.fs_headers[i]._0x148[0] != 0 || memcmp(dec_header.fs_headers[i]._0x148, dec_header.fs_headers[i]._0x148 + 1, 0xB7)) {
aes_xts_decrypt(aes_ctx, &dec_header.fs_headers[i], &ctx->header.fs_headers[i], 0x200, 0, 0x200);
aes_xts_decrypt(hdr_aes_ctx, &dec_header.fs_headers[i], &ctx->header.fs_headers[i], 0x200, 0, 0x200);
} else {
memset(&dec_header.fs_headers[i], 0, sizeof(nca_fs_header_t));
}
}
ctx->header = dec_header;
} else if (dec_header.magic == MAGIC_NCA0) {
memset(ctx->decrypted_keys, 0, 0x40);
unsigned char out_keydata[0x100];
size_t out_len = 0;
if (rsa2048_oaep_decrypt_verify(out_keydata, sizeof(out_keydata), (const unsigned char *)dec_header.encrypted_keys, pki_get_beta_nca0_modulus(), pki_get_beta_nca0_exponent(), 0x100, pki_get_beta_nca0_label_hash(), &out_len)) {
if (out_len >= 0x20) {
memcpy(ctx->decrypted_keys, out_keydata, 0x20);
ctx->format_version = NCAVERSION_NCA0_BETA;
}
} else {
ctx->format_version = NCAVERSION_NCA0;
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.key_area_keys[ctx->crypto_type][dec_header.kaek_ind], 16, AES_MODE_ECB);
aes_decrypt(aes_ctx, ctx->decrypted_keys, dec_header.encrypted_keys, 0x20);
free_aes_ctx(aes_ctx);
}
if (ctx->format_version != NCAVERSION_UNKNOWN) {
memset(dec_header.fs_headers, 0, sizeof(dec_header.fs_headers));
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->decrypted_keys, 32, AES_MODE_XTS);
for (unsigned int i = 0; i < 4; i++) {
if (dec_header.section_entries[i].media_start_offset) { /* Section exists. */
uint64_t offset = media_to_real(dec_header.section_entries[i].media_start_offset);
fseeko64(ctx->tool_ctx->file, offset, SEEK_SET);
if (fread(&dec_header.fs_headers[i], sizeof(dec_header.fs_headers[i]), 1, ctx->tool_ctx->file) != 1) {
fprintf(stderr, "Failed to read NCA0 FS header at %" PRIx64"!\n", offset);
exit(EXIT_FAILURE);
}
aes_xts_decrypt(aes_ctx, &dec_header.fs_headers[i], &dec_header.fs_headers[i], sizeof(dec_header.fs_headers[i]), (offset - 0x400ULL) >> 9ULL, 0x200);
}
}
free_aes_ctx(aes_ctx);
ctx->header = dec_header;
}
}
free_aes_ctx(aes_ctx);
return ctx->header.magic == MAGIC_NCA3 || ctx->header.magic == MAGIC_NCA2;
free_aes_ctx(hdr_aes_ctx);
return ctx->format_version != NCAVERSION_UNKNOWN;
}
/* Decrypt key area. */
void nca_decrypt_key_area(nca_ctx_t *ctx) {
if (ctx->format_version == NCAVERSION_NCA0_BETA || ctx->format_version == NCAVERSION_NCA0) return;
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.key_area_keys[ctx->crypto_type][ctx->header.kaek_ind], 16, AES_MODE_ECB);
aes_decrypt(aes_ctx, ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40);
free_aes_ctx(aes_ctx);
@ -589,15 +663,36 @@ char *nca_get_encryption_type(nca_ctx_t *ctx) {
}
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);
if (ctx->format_version == NCAVERSION_NCA0_BETA) {
printf("Key Area (Encrypted):\n");
memdump(stdout, "Key (RSA-OAEP Encrypted): ", &ctx->header.encrypted_keys, 0x100);
printf("Key Area (Decrypted):\n");
for (unsigned int i = 0; i < 0x2; i++) {
printf(" Key %"PRId32" (Decrypted): ", i);
memdump(stdout, "", &ctx->decrypted_keys[i], 0x10);
}
} else if (ctx->format_version == NCAVERSION_NCA0) {
printf("Key Area (Encrypted):\n");
for (unsigned int i = 0; i < 0x2; 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 < 0x2; i++) {
printf(" Key %"PRId32" (Decrypted): ", i);
memdump(stdout, "", &ctx->decrypted_keys[i], 0x10);
}
} else {
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);
}
}
}
@ -607,6 +702,7 @@ char *nca_get_section_type(nca_section_ctx_t *meta) {
if (meta->pfs0_ctx.is_exefs) return "ExeFS";
return "PFS0";
}
case NCA0_ROMFS: return "NCA0 RomFS";
case ROMFS: return "RomFS";
case BKTR: return "Patch RomFS";
case INVALID:
@ -624,7 +720,9 @@ void nca_print_sections(nca_ctx_t *ctx) {
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);
if (!(ctx->format_version == NCAVERSION_NCA0 || ctx->format_version == NCAVERSION_NCA0_BETA)) {
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]);
@ -634,6 +732,10 @@ void nca_print_sections(nca_ctx_t *ctx) {
nca_print_ivfc_section(&ctx->section_contexts[i]);
break;
}
case NCA0_ROMFS: {
nca_print_nca0_romfs_section(&ctx->section_contexts[i]);
break;
}
case BKTR: {
nca_print_bktr_section(&ctx->section_contexts[i]);
break;
@ -722,10 +824,7 @@ validity_t nca_section_check_external_hash_table(nca_section_ctx_t *ctx, unsigne
read_size = data_len - ofs;
}
uint64_t r = nca_section_fread(ctx, block, read_size);
if (r != read_size) {
fprintf(stderr, "%012"PRIx64" %012"PRIx64" %08"PRIx64"\n", ofs, data_len, r);
fprintf(stderr, "%d %d\n", ctx->is_decrypted, ctx->section_num);
if (nca_section_fread(ctx, block, read_size) != read_size) {
fprintf(stderr, "Failed to read section!\n");
exit(EXIT_FAILURE);
}
@ -891,8 +990,7 @@ void nca_process_ivfc_section(nca_section_ctx_t *ctx) {
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);
nca_section_fseek(ctx, ctx->romfs_ctx.romfs_offset + ctx->romfs_ctx.header.dir_meta_table_offset);
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");
exit(EXIT_FAILURE);
@ -911,6 +1009,50 @@ void nca_process_ivfc_section(nca_section_ctx_t *ctx) {
}
}
void nca_process_nca0_romfs_section(nca_section_ctx_t *ctx) {
nca0_romfs_superblock_t *sb = ctx->nca0_romfs_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 ROMFS... */
ctx->nca0_romfs_ctx.hash_table_validity = nca_section_check_hash_table(ctx, sb->hash_table_offset, sb->romfs_offset, sb->romfs_size, sb->block_size, 0);
}
if (ctx->superblock_hash_validity != VALIDITY_VALID) return;
ctx->nca0_romfs_ctx.romfs_offset = sb->romfs_offset;
nca_section_fseek(ctx, ctx->nca0_romfs_ctx.romfs_offset);
if (nca_section_fread(ctx, &ctx->nca0_romfs_ctx.header, sizeof(nca0_romfs_hdr_t)) != sizeof(nca0_romfs_hdr_t)) {
fprintf(stderr, "Failed to read NCA0 RomFS header!\n");
}
if ((ctx->tool_ctx->action & (ACTION_EXTRACT | ACTION_LISTROMFS)) && ctx->nca0_romfs_ctx.header.header_size == NCA0_ROMFS_HEADER_SIZE) {
/* Pre-load the file/data entry caches. */
ctx->nca0_romfs_ctx.directories = calloc(1, ctx->nca0_romfs_ctx.header.dir_meta_table_size);
if (ctx->nca0_romfs_ctx.directories == NULL) {
fprintf(stderr, "Failed to allocate NCA0 RomFS directory cache!\n");
exit(EXIT_FAILURE);
}
nca_section_fseek(ctx, ctx->nca0_romfs_ctx.romfs_offset + ctx->nca0_romfs_ctx.header.dir_meta_table_offset);
if (nca_section_fread(ctx, ctx->nca0_romfs_ctx.directories, ctx->nca0_romfs_ctx.header.dir_meta_table_size) != ctx->nca0_romfs_ctx.header.dir_meta_table_size) {
fprintf(stderr, "Failed to read NCA0 RomFS directory cache!\n");
exit(EXIT_FAILURE);
}
ctx->nca0_romfs_ctx.files = calloc(1, ctx->nca0_romfs_ctx.header.file_meta_table_size);
if (ctx->nca0_romfs_ctx.files == NULL) {
fprintf(stderr, "Failed to allocate NCA0 RomFS file cache!\n");
exit(EXIT_FAILURE);
}
nca_section_fseek(ctx, ctx->nca0_romfs_ctx.romfs_offset + ctx->nca0_romfs_ctx.header.file_meta_table_offset);
if (nca_section_fread(ctx, ctx->nca0_romfs_ctx.files, ctx->nca0_romfs_ctx.header.file_meta_table_size) != ctx->nca0_romfs_ctx.header.file_meta_table_size) {
fprintf(stderr, "Failed to read NCA0 RomFS file cache!\n");
exit(EXIT_FAILURE);
}
}
}
void nca_process_bktr_section(nca_section_ctx_t *ctx) {
bktr_superblock_t *sb = ctx->bktr_ctx.superblock;
/* Validate magics. */
@ -1084,6 +1226,25 @@ void nca_print_ivfc_section(nca_section_ctx_t *ctx) {
}
}
void nca_print_nca0_romfs_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->nca0_romfs_ctx.superblock->master_hash, 0x20);
} else {
memdump(stdout, " Superblock Hash (FAIL): ", &ctx->nca0_romfs_ctx.superblock->master_hash, 0x20);
}
printf(" Hash Table (%s):\n", GET_VALIDITY_STR(ctx->nca0_romfs_ctx.hash_table_validity));
} else {
memdump(stdout, " Superblock Hash: ", &ctx->nca0_romfs_ctx.superblock->master_hash, 0x20);
printf(" Hash Table:\n");
}
printf(" Offset: %012"PRIx64"\n", ctx->nca0_romfs_ctx.superblock->hash_table_offset);
printf(" Size: %012"PRIx64"\n", ctx->nca0_romfs_ctx.superblock->hash_table_size);
printf(" Block Size: 0x%"PRIx32"\n", ctx->nca0_romfs_ctx.superblock->block_size);
printf(" RomFS Offset: %012"PRIx64"\n", ctx->nca0_romfs_ctx.superblock->romfs_offset);
printf(" RomFS Size: %012"PRIx64"\n", ctx->nca0_romfs_ctx.superblock->romfs_size);
}
void nca_print_bktr_section(nca_section_ctx_t *ctx) {
if (ctx->bktr_ctx.subsection_block == NULL) {
printf(" BKTR section seems to be corrupted.\n");
@ -1130,8 +1291,8 @@ void nca_save_section_file(nca_section_ctx_t *ctx, uint64_t ofs, uint64_t total_
}
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) {
nca_section_fseek(ctx, 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");
@ -1163,6 +1324,10 @@ void nca_save_section(nca_section_ctx_t *ctx) {
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 NCA0_ROMFS:
offset = ctx->nca0_romfs_ctx.superblock->romfs_offset;
size = ctx->nca0_romfs_ctx.superblock->romfs_size;
break;
case BKTR:
if (ctx->tool_ctx->base_file != NULL) {
offset = ctx->bktr_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_offset;
@ -1182,7 +1347,7 @@ void nca_save_section(nca_section_ctx_t *ctx) {
/* 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) {
} else if ((ctx->type == ROMFS || ctx->type == NCA0_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) {
@ -1198,6 +1363,9 @@ void nca_save_section(nca_section_ctx_t *ctx) {
case ROMFS:
nca_save_ivfc_section(ctx);
break;
case NCA0_ROMFS:
nca_save_nca0_romfs_section(ctx);
break;
case BKTR:
if (ctx->tool_ctx->base_file == NULL) {
fprintf(stderr, "Note: cannot save BKTR section without base romfs.\n");
@ -1301,6 +1469,40 @@ int nca_visit_romfs_file(nca_section_ctx_t *ctx, uint32_t file_offset, filepath_
return found_file;
}
int nca_visit_nca0_romfs_file(nca_section_ctx_t *ctx, uint32_t file_offset, filepath_t *dir_path) {
romfs_fentry_t *entry = romfs_get_fentry(ctx->nca0_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);
}
int found_file = 1;
/* If we're extracting... */
uint64_t phys_offset = ctx->nca0_romfs_ctx.romfs_offset + ctx->nca0_romfs_ctx.header.data_offset + entry->offset;
if ((ctx->tool_ctx->action & ACTION_LISTROMFS) == 0) {
printf("Saving %s...\n", cur_path->char_path);
nca_save_section_file(ctx, phys_offset, entry->size, cur_path);
} else {
printf("rom:%s\n", cur_path->char_path);
}
free(cur_path);
if (entry->sibling != ROMFS_ENTRY_EMPTY) {
return found_file | nca_visit_nca0_romfs_file(ctx, entry->sibling, dir_path);
}
return found_file;
}
int nca_visit_romfs_dir(nca_section_ctx_t *ctx, uint32_t dir_offset, filepath_t *parent_path) {
romfs_direntry_t *entry;
if (ctx->type == ROMFS) {
@ -1346,6 +1548,40 @@ int nca_visit_romfs_dir(nca_section_ctx_t *ctx, uint32_t dir_offset, filepath_t
return any_files;
}
int nca_visit_nca0_romfs_dir(nca_section_ctx_t *ctx, uint32_t dir_offset, filepath_t *parent_path) {
romfs_direntry_t *entry = romfs_get_direntry(ctx->nca0_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);
}
int any_files = 0;
if (entry->file != ROMFS_ENTRY_EMPTY) {
any_files |= nca_visit_nca0_romfs_file(ctx, entry->file, cur_path);
}
if (entry->child != ROMFS_ENTRY_EMPTY) {
any_files |= nca_visit_nca0_romfs_file(ctx, entry->child, cur_path);
}
if (entry->sibling != ROMFS_ENTRY_EMPTY) {
nca_visit_nca0_romfs_dir(ctx, entry->sibling, parent_path);
}
free(cur_path);
return any_files;
}
void nca_save_ivfc_section(nca_section_ctx_t *ctx) {
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
@ -1377,6 +1613,37 @@ void nca_save_ivfc_section(nca_section_ctx_t *ctx) {
fprintf(stderr, "Error: section %"PRId32" is corrupted!\n", ctx->section_num);
}
void nca_save_nca0_romfs_section(nca_section_ctx_t *ctx) {
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
if (ctx->nca0_romfs_ctx.header.header_size == NCA0_ROMFS_HEADER_SIZE) {
if (ctx->tool_ctx->action & ACTION_LISTROMFS) {
filepath_t fakepath;
filepath_init(&fakepath);
filepath_set(&fakepath, "");
nca_visit_nca0_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_nca0_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) {
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
if (ctx->bktr_ctx.header.header_size == ROMFS_HEADER_SIZE) {

24
nca.h
View file

@ -9,10 +9,12 @@
#include "npdm.h"
#include "pfs0.h"
#include "ivfc.h"
#include "nca0_romfs.h"
#include "bktr.h"
#define MAGIC_NCA3 0x3341434E /* "NCA3" */
#define MAGIC_NCA2 0x3241434E /* "NCA2" */
#define MAGIC_NCA0 0x3041434E /* "NCA0" */
typedef struct {
uint32_t media_start_offset;
@ -57,7 +59,8 @@ typedef enum {
CRYPT_NONE = 1,
CRYPT_XTS = 2,
CRYPT_CTR = 3,
CRYPT_BKTR = 4
CRYPT_BKTR = 4,
CRYPT_NCA0 = MAGIC_NCA0
} section_crypt_type_t;
/* NCA FS header. */
@ -71,6 +74,7 @@ typedef struct {
union { /* FS-specific superblock. Size = 0x138. */
pfs0_superblock_t pfs0_superblock;
romfs_superblock_t romfs_superblock;
nca0_romfs_superblock_t nca0_romfs_superblock;
bktr_superblock_t bktr_superblock;
};
union {
@ -118,9 +122,19 @@ enum nca_section_type {
PFS0,
ROMFS,
BKTR,
NCA0_ROMFS,
INVALID
};
enum nca_version {
NCAVERSION_UNKNOWN = 0,
NCAVERSION_NCA0_BETA,
NCAVERSION_NCA0,
/* NCAVERSION_NCA1, // Does this exist? */
NCAVERSION_NCA2,
NCAVERSION_NCA3
};
typedef struct {
int is_present;
enum nca_section_type type;
@ -130,11 +144,14 @@ typedef struct {
uint32_t section_num;
nca_fs_header_t *header;
int is_decrypted;
uint64_t sector_size;
uint64_t sector_mask;
aes_ctx_t *aes; /* AES context for the section. */
hactool_ctx_t *tool_ctx;
union {
pfs0_ctx_t pfs0_ctx;
romfs_ctx_t romfs_ctx;
nca0_romfs_ctx_t nca0_romfs_ctx;
bktr_section_ctx_t bktr_ctx;
};
validity_t superblock_hash_validity;
@ -143,6 +160,7 @@ typedef struct {
size_t sector_num;
uint32_t sector_ofs;
int physical_reads; /* Should reads be forced physical? */
section_crypt_type_t crypt_type;
} nca_section_ctx_t;
typedef struct nca_ctx {
@ -151,6 +169,7 @@ typedef struct nca_ctx {
unsigned char crypto_type;
int has_rights_id;
int is_decrypted;
enum nca_version format_version;
validity_t fixed_sig_validity;
validity_t npdm_sig_validity;
hactool_ctx_t *tool_ctx;
@ -177,15 +196,18 @@ void nca_save_section_file(nca_section_ctx_t *ctx, uint64_t ofs, uint64_t total_
/* 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_nca0_romfs_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_nca0_romfs_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_nca0_romfs_section(nca_section_ctx_t *ctx);
void nca_save_bktr_section(nca_section_ctx_t *ctx);
#endif

143
nca0_romfs.c Normal file
View file

@ -0,0 +1,143 @@
#include <stdio.h>
#include "types.h"
#include "utils.h"
#include "nca0_romfs.h"
/* NCA0 RomFS functions... */
void nca0_romfs_visit_file(nca0_romfs_ctx_t *ctx, uint32_t file_offset, filepath_t *dir_path) {
romfs_fentry_t *entry = romfs_get_fentry(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);
save_file_section(ctx->file, ctx->romfs_offset + 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) {
nca0_romfs_visit_file(ctx, entry->sibling, dir_path);
}
}
void nca0_romfs_visit_dir(nca0_romfs_ctx_t *ctx, uint32_t dir_offset, filepath_t *parent_path) {
romfs_direntry_t *entry = romfs_get_direntry(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) {
nca0_romfs_visit_file(ctx, entry->file, cur_path);
}
if (entry->child != ROMFS_ENTRY_EMPTY) {
nca0_romfs_visit_dir(ctx, entry->child, cur_path);
}
if (entry->sibling != ROMFS_ENTRY_EMPTY) {
nca0_romfs_visit_dir(ctx, entry->sibling, parent_path);
}
free(cur_path);
}
void nca0_romfs_process(nca0_romfs_ctx_t *ctx) {
ctx->romfs_offset = 0;
fseeko64(ctx->file, ctx->romfs_offset, SEEK_SET);
if (fread(&ctx->header, 1, sizeof(nca0_romfs_hdr_t), ctx->file) != sizeof(nca0_romfs_hdr_t)) {
fprintf(stderr, "Failed to read NCA0 RomFS header!\n");
return;
}
if ((ctx->tool_ctx->action & (ACTION_EXTRACT | ACTION_LISTROMFS)) && ctx->header.header_size == NCA0_ROMFS_HEADER_SIZE) {
/* Pre-load the file/data entry caches. */
ctx->directories = calloc(1, ctx->header.dir_meta_table_size);
if (ctx->directories == NULL) {
fprintf(stderr, "Failed to allocate NCA0 RomFS directory cache!\n");
exit(EXIT_FAILURE);
}
fseeko64(ctx->file, ctx->romfs_offset + ctx->header.dir_meta_table_offset, SEEK_SET);
if (fread(ctx->directories, 1, ctx->header.dir_meta_table_size, ctx->file) != ctx->header.dir_meta_table_size) {
fprintf(stderr, "Failed to read NCA0 RomFS directory cache!\n");
exit(EXIT_FAILURE);
}
ctx->files = calloc(1, ctx->header.file_meta_table_size);
if (ctx->files == NULL) {
fprintf(stderr, "Failed to allocate NCA0 RomFS file cache!\n");
exit(EXIT_FAILURE);
}
fseeko64(ctx->file, ctx->romfs_offset + ctx->header.file_meta_table_offset, SEEK_SET);
if (fread(ctx->files, 1, ctx->header.file_meta_table_size, ctx->file) != ctx->header.file_meta_table_size) {
fprintf(stderr, "Failed to read NCA0 RomFS file cache!\n");
exit(EXIT_FAILURE);
}
} else {
fprintf(stderr, "NCA0 RomFS is corrupt?\n");
return;
}
/* If there's ever anything meaningful to print about RomFS, uncomment and implement.
*
* if (ctx->tool_ctx->action & ACTION_INFO) {
* nca0_romfs_print(ctx);
* }
*/
if (ctx->tool_ctx->action & ACTION_EXTRACT) {
nca0_romfs_save(ctx);
}
}
void nca0_romfs_save(nca0_romfs_ctx_t *ctx) {
if (ctx->tool_ctx->action & ACTION_LISTROMFS) {
filepath_t fakepath;
filepath_init(&fakepath);
filepath_set(&fakepath, "");
nca0_romfs_visit_dir(ctx, 0, &fakepath);
} else {
/* Extract to directory. */
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) && (ctx->tool_ctx->file_type == FILETYPE_NCA0_ROMFS && ctx->tool_ctx->settings.out_dir_path.enabled)) {
dirpath = &ctx->tool_ctx->settings.out_dir_path.path;
}
if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) {
os_makedir(dirpath->os_path);
nca0_romfs_visit_dir(ctx, 0, dirpath);
}
}
}
void nca0_romfs_print(nca0_romfs_ctx_t *ctx) {
/* Is there anything meaningful to print here? */
fprintf(stderr, "Error: NCA0 RomFS printing not implemented.\n");
}

53
nca0_romfs.h Normal file
View file

@ -0,0 +1,53 @@
#ifndef HACTOOL_NCA0_ROMFS_H
#define HACTOOL_NCA0_ROMFS_H
#include "types.h"
#include "utils.h"
#include "ivfc.h"
#include "settings.h"
/* RomFS structs. */
#define NCA0_ROMFS_HEADER_SIZE 0x00000028
typedef struct {
uint32_t header_size;
uint32_t dir_hash_table_offset;
uint32_t dir_hash_table_size;
uint32_t dir_meta_table_offset;
uint32_t dir_meta_table_size;
uint32_t file_hash_table_offset;
uint32_t file_hash_table_size;
uint32_t file_meta_table_offset;
uint32_t file_meta_table_size;
uint32_t data_offset;
} nca0_romfs_hdr_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 romfs_offset;
uint64_t romfs_size;
uint8_t _0x48[0xF0];
} nca0_romfs_superblock_t;
typedef struct {
nca0_romfs_superblock_t *superblock;
FILE *file;
hactool_ctx_t *tool_ctx;
validity_t superblock_hash_validity;
validity_t hash_table_validity;
uint64_t romfs_offset;
nca0_romfs_hdr_t header;
romfs_direntry_t *directories;
romfs_fentry_t *files;
} nca0_romfs_ctx_t;
void nca0_romfs_process(nca0_romfs_ctx_t *ctx);
void nca0_romfs_save(nca0_romfs_ctx_t *ctx);
void nca0_romfs_print(nca0_romfs_ctx_t *ctx);
#endif

63
pki.c
View file

@ -3,6 +3,69 @@
#include "aes.h"
#include "pki.h"
/* Keydata for very early beta NCA0 archives' RSA-OAEP. */
const unsigned char beta_nca0_modulus[0x100] = {
0xAD, 0x58, 0xEE, 0x97, 0xF9, 0x47, 0x90, 0x7D, 0xF9, 0x29, 0x5F, 0x1F, 0x39, 0x68, 0xEE, 0x49,
0x4C, 0x1E, 0x8D, 0x84, 0x91, 0x31, 0x5D, 0xE5, 0x96, 0x27, 0xB2, 0xB3, 0x59, 0x7B, 0xDE, 0xFD,
0xB7, 0xEB, 0x40, 0xA1, 0xE7, 0xEB, 0xDC, 0x60, 0xD0, 0x3D, 0xC5, 0x50, 0x92, 0xAD, 0x3D, 0xC4,
0x8C, 0x17, 0xD2, 0x37, 0x66, 0xE3, 0xF7, 0x14, 0x34, 0x38, 0x6B, 0xA7, 0x2B, 0x21, 0x10, 0x9B,
0x73, 0x49, 0x15, 0xD9, 0x2A, 0x90, 0x86, 0x76, 0x81, 0x6A, 0x10, 0xBD, 0x74, 0xC4, 0x20, 0x55,
0x25, 0xA8, 0x02, 0xC5, 0xA0, 0x34, 0x36, 0x7B, 0x66, 0x47, 0x2C, 0x7E, 0x47, 0x82, 0xA5, 0xD4,
0xA3, 0x42, 0x45, 0xE8, 0xFD, 0x65, 0x72, 0x48, 0xA1, 0xB0, 0x44, 0x10, 0xEF, 0xAC, 0x1D, 0x0F,
0xB5, 0x12, 0x19, 0xA8, 0x41, 0x0B, 0x76, 0x3B, 0xBC, 0xF1, 0x4A, 0x10, 0x46, 0x22, 0xB8, 0xF1,
0xBC, 0x21, 0x81, 0x69, 0x9B, 0x63, 0x6F, 0xD7, 0xB9, 0x60, 0x2A, 0x9A, 0xE5, 0x2C, 0x47, 0x72,
0x59, 0x65, 0xA2, 0x21, 0x60, 0xC4, 0xFC, 0xB0, 0xD7, 0x6F, 0x42, 0xC9, 0x0C, 0xF5, 0x76, 0x7D,
0xF2, 0x5C, 0xE0, 0x80, 0x0F, 0xEE, 0x45, 0x7E, 0x4E, 0x3A, 0x8D, 0x9C, 0x5B, 0x5B, 0xD9, 0xD1,
0x43, 0x94, 0x2C, 0xC7, 0x2E, 0xB9, 0x4A, 0xE5, 0x3E, 0x15, 0xDD, 0x43, 0x00, 0xF7, 0x78, 0xE7,
0x7C, 0x39, 0xB0, 0x4D, 0xC5, 0xD1, 0x1C, 0xF2, 0xB4, 0x7A, 0x2A, 0xEA, 0x0A, 0x8E, 0xB9, 0x13,
0xB4, 0x4F, 0xD7, 0x5B, 0x4D, 0x7B, 0x43, 0xB0, 0x3A, 0x9A, 0x60, 0x22, 0x47, 0x91, 0x78, 0xC7,
0x10, 0x64, 0xE0, 0x2C, 0x69, 0xD1, 0x66, 0x3C, 0x42, 0x2E, 0xEF, 0x19, 0x21, 0x89, 0x8E, 0xE1,
0xB0, 0xB4, 0xD0, 0x17, 0xA1, 0x0F, 0x73, 0x98, 0x5A, 0xF6, 0xEE, 0xC0, 0x2F, 0x9E, 0xCE, 0xC5
};
unsigned char beta_nca0_exponent[0x100] = {
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,
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,
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,
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,
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,
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,
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,
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, 0x01
};
const unsigned char beta_nca0_label_hash[0x20] = {
0xE3, 0xB0, 0xC4, 0x42, 0x98, 0xFC, 0x1C, 0x14, 0x9A, 0xFB, 0xF4, 0xC8, 0x99, 0x6F, 0xB9, 0x24,
0x27, 0xAE, 0x41, 0xE4, 0x64, 0x9B, 0x93, 0x4C, 0xA4, 0x95, 0x99, 0x1B, 0x78, 0x52, 0xB8, 0x55
};
const unsigned char *pki_get_beta_nca0_modulus(void) {
return beta_nca0_modulus;
}
void pki_set_beta_nca0_exponent(void *exponent) {
memcpy(beta_nca0_exponent, exponent, sizeof(beta_nca0_exponent));
}
const unsigned char *pki_get_beta_nca0_exponent(void) {
return beta_nca0_exponent;
}
const unsigned char *pki_get_beta_nca0_label_hash(void) {
return beta_nca0_label_hash;
}
const nca_keyset_t nca_keys_retail = {
ZEROES_KEY, /* Secure Boot Key (CONSOLE UNIQUE) */
ZEROES_KEY, /* TSEC Key (CONSOLE UNIQUE) */

6
pki.h
View file

@ -14,4 +14,10 @@ void pki_derive_keys(nca_keyset_t *keyset);
void pki_print_keys(nca_keyset_t *keyset);
void pki_initialize_keyset(nca_keyset_t *keyset, keyset_variant_t variant);
/* Beta NCA0 helpers */
const unsigned char *pki_get_beta_nca0_modulus(void);
void pki_set_beta_nca0_exponent(void *exponent);
const unsigned char *pki_get_beta_nca0_exponent(void);
const unsigned char *pki_get_beta_nca0_label_hash(void);
#endif

View file

@ -79,8 +79,7 @@ void romfs_process(romfs_ctx_t *ctx) {
exit(EXIT_FAILURE);
}
/* Switch RomFS has actual entries at table offset + 4 for no good reason. */
fseeko64(ctx->file, ctx->romfs_offset + ctx->header.dir_meta_table_offset + 4, SEEK_SET);
fseeko64(ctx->file, ctx->romfs_offset + ctx->header.dir_meta_table_offset, SEEK_SET);
if (fread(ctx->directories, 1, ctx->header.dir_meta_table_size, ctx->file) != ctx->header.dir_meta_table_size) {
fprintf(stderr, "Failed to read RomFS directory cache!\n");
exit(EXIT_FAILURE);

96
rsa.c
View file

@ -8,6 +8,26 @@
#define RSA_2048_BYTES 0x100
#define RSA_2048_BITS (RSA_2048_BYTES*8)
static void calculate_mgf1_and_xor(unsigned char *data, size_t data_size, const void *h_src, size_t h_src_size) {
unsigned char h_buf[RSA_2048_BYTES] = {0};
memcpy(h_buf, h_src, h_src_size);
unsigned char mgf1_buf[0x20];
size_t ofs = 0;
unsigned int seed = 0;
while (ofs < data_size) {
for (unsigned int i = 0; i < sizeof(seed); i++) {
h_buf[h_src_size + 3 - i] = (seed >> (8 * i)) & 0xFF;
}
sha256_hash_buffer(mgf1_buf, h_buf, h_src_size + 4);
for (unsigned int i = ofs; i < data_size && i < ofs + 0x20; i++) {
data[i] ^= mgf1_buf[i - ofs];
}
seed++;
ofs += 0x20;
}
}
/* 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) {
mbedtls_mpi signature_mpi;
@ -47,16 +67,8 @@ int rsa2048_pss_verify(const void *data, size_t len, const unsigned char *signat
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++;
sha256_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];
}
}
/* Decrypt maskedDB. */
calculate_mgf1_and_xor(m_buf, RSA_2048_BYTES - 0x20 - 1, h_buf, 0x20);
m_buf[0] &= 0x7F; /* Constant lmask for rsa-2048-pss. */
@ -132,4 +144,68 @@ int rsa2048_pkcs1_verify(const void *data, size_t len, const unsigned char *sign
sha256_hash_buffer(h_buf, data, len);
return memcmp(pkcs1_hash_prefix, m_buf, 0xE0) == 0 && memcmp(&m_buf[0xE0], h_buf, 0x20) == 0;
}
/* Perform an RSA-OAEP decryption (and verification) on data, with Signature and N. */
int rsa2048_oaep_decrypt_verify(void *out, size_t max_out_len, const unsigned char *signature, const unsigned char *modulus, const unsigned char *exponent, size_t exponent_len, const unsigned char *label_hash, size_t *out_len) {
mbedtls_mpi signature_mpi;
mbedtls_mpi modulus_mpi;
mbedtls_mpi exp_mpi;
mbedtls_mpi message_mpi;
mbedtls_mpi_init(&signature_mpi);
mbedtls_mpi_init(&modulus_mpi);
mbedtls_mpi_init(&exp_mpi);
mbedtls_mpi_init(&message_mpi);
mbedtls_mpi_lset(&message_mpi, RSA_2048_BITS);
unsigned char m_buf[RSA_2048_BYTES];
mbedtls_mpi_read_binary(&exp_mpi, exponent, exponent_len);
mbedtls_mpi_read_binary(&signature_mpi, signature, RSA_2048_BYTES);
mbedtls_mpi_read_binary(&modulus_mpi, modulus, RSA_2048_BYTES);
mbedtls_mpi_exp_mod(&message_mpi, &signature_mpi, &exp_mpi, &modulus_mpi, NULL);
if (mbedtls_mpi_write_binary(&message_mpi, m_buf, RSA_2048_BYTES) != 0) {
FATAL_ERROR("Failed to export exponentiated RSA message!");
}
mbedtls_mpi_free(&signature_mpi);
mbedtls_mpi_free(&modulus_mpi);
mbedtls_mpi_free(&exp_mpi);
mbedtls_mpi_free(&message_mpi);
/* There's no automated PSS verification as far as I can tell. */
if (m_buf[0] != 0x00) {
return false;
}
/* Unmask salt. */
calculate_mgf1_and_xor(m_buf + 1, 0x20, m_buf + 0x21, RSA_2048_BYTES - 0x20 - 1);
/* Unmask DB. */
calculate_mgf1_and_xor(m_buf + 0x21, RSA_2048_BYTES - 0x20 - 1, m_buf + 1, 0x20);
/* Validate label hash */
const unsigned char *db = m_buf + 0x21;
if (memcmp(db, label_hash, 0x20) != 0) {
return false;
}
/* Validate message prefix. */
const unsigned char *data = db + 0x20;
size_t remaining = RSA_2048_BYTES - 0x20 - 1 - 0x20;
while (*data == 0 && remaining) {
data++;
remaining--;
}
if (remaining == 0 || *data++ != 1) {
return false;
}
remaining--;
*out_len = remaining;
if (remaining > max_out_len) {
remaining = max_out_len;
}
memcpy(out, data, remaining);
return true;
}

1
rsa.h
View file

@ -5,5 +5,6 @@
int rsa2048_pss_verify(const void *data, size_t len, const unsigned char *signature, const unsigned char *modulus);
int rsa2048_pkcs1_verify(const void *data, size_t len, const unsigned char *signature, const unsigned char *modulus);
int rsa2048_oaep_decrypt_verify(void *out, size_t max_out_len, const unsigned char *signature, const unsigned char *modulus, const unsigned char *exponent, size_t exponent_len, const unsigned char *label_hash, size_t *out_len);
#endif

View file

@ -93,6 +93,7 @@ enum hactool_file_type
FILETYPE_NCA,
FILETYPE_PFS0,
FILETYPE_ROMFS,
FILETYPE_NCA0_ROMFS,
FILETYPE_HFS0,
FILETYPE_XCI,
FILETYPE_NPDM,