mirror of
https://github.com/SciresM/hactool
synced 2025-02-16 11:18:24 +00:00
Fix XTS/NCA2 support. Add NCA0 support.
This commit is contained in:
parent
94d55a936b
commit
2e87aa8a73
14 changed files with 730 additions and 74 deletions
4
Makefile
4
Makefile
|
@ -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
|
||||
|
|
|
@ -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
1
ivfc.h
|
@ -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
16
main.c
|
@ -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
387
nca.c
|
@ -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(§or_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, §or_buf, §or_buf, 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);
|
||||
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(§or_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, §or_buf, §or_buf, 0x200, ctx->sector_num, 0x200);
|
||||
memcpy((char *)buffer + ofs, §or_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, §or_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
24
nca.h
|
@ -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
143
nca0_romfs.c
Normal 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
53
nca0_romfs.h
Normal 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
63
pki.c
|
@ -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
6
pki.h
|
@ -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
|
3
romfs.c
3
romfs.c
|
@ -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
96
rsa.c
|
@ -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
1
rsa.h
|
@ -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
|
||||
|
|
|
@ -93,6 +93,7 @@ enum hactool_file_type
|
|||
FILETYPE_NCA,
|
||||
FILETYPE_PFS0,
|
||||
FILETYPE_ROMFS,
|
||||
FILETYPE_NCA0_ROMFS,
|
||||
FILETYPE_HFS0,
|
||||
FILETYPE_XCI,
|
||||
FILETYPE_NPDM,
|
||||
|
|
Loading…
Add table
Reference in a new issue