mirror of
https://github.com/SciresM/hactool.git
synced 2024-11-23 12:39:45 +00:00
1373 lines
60 KiB
C
1373 lines
60 KiB
C
#include <stdlib.h>
|
|
#include "nca.h"
|
|
#include "aes.h"
|
|
#include "sha.h"
|
|
#include "rsa.h"
|
|
#include "utils.h"
|
|
#include "filepath.h"
|
|
|
|
/* Initialize the context. */
|
|
void nca_init(nca_ctx_t *ctx) {
|
|
memset(ctx, 0, sizeof(*ctx));
|
|
}
|
|
|
|
/* Updates the CTR for an offset. */
|
|
void nca_update_ctr(unsigned char *ctr, uint64_t ofs) {
|
|
ofs >>= 4;
|
|
for (unsigned int j = 0; j < 0x8; j++) {
|
|
ctr[0x10-j-1] = (unsigned char)(ofs & 0xFF);
|
|
ofs >>= 8;
|
|
}
|
|
}
|
|
|
|
/* Updates the CTR for a bktr offset. */
|
|
void nca_update_bktr_ctr(unsigned char *ctr, uint32_t ctr_val, uint64_t ofs) {
|
|
ofs >>= 4;
|
|
for (unsigned int j = 0; j < 0x8; j++) {
|
|
ctr[0x10-j-1] = (unsigned char)(ofs & 0xFF);
|
|
ofs >>= 8;
|
|
}
|
|
for (unsigned int j = 0; j < 4; j++) {
|
|
ctr[0x8-j-1] = (unsigned char)(ctr_val & 0xFF);
|
|
ctr_val >>= 8;
|
|
}
|
|
}
|
|
|
|
/* Seek to an offset within a section. */
|
|
void nca_section_fseek(nca_section_ctx_t *ctx, uint64_t offset) {
|
|
if (ctx->is_decrypted) {
|
|
fseeko64(ctx->file, (ctx->offset + offset), SEEK_SET);
|
|
ctx->cur_seek = (ctx->offset + offset);
|
|
} else if (ctx->header->crypt_type == CRYPT_XTS) {
|
|
fseeko64(ctx->file, (ctx->offset + offset) & ~0x1FF, SEEK_SET);
|
|
ctx->cur_seek = (ctx->offset + offset) & ~0x1FF;
|
|
ctx->sector_num = offset / 0x200;
|
|
ctx->sector_ofs = offset & 0x1FF;
|
|
} else if (ctx->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;
|
|
if (ctx->tool_ctx->base_file == NULL && ctx->physical_reads == 0) { /* Without base romfs, reads will be physical. */
|
|
ctx->bktr_ctx.bktr_seek = offset;
|
|
} else { /* Let's do the complicated thing. */
|
|
bktr_relocation_entry_t *reloc = bktr_get_relocation(ctx->bktr_ctx.relocation_block, offset);
|
|
uint64_t section_ofs = offset - reloc->virt_offset + reloc->phys_offset;
|
|
if (reloc->is_patch) {
|
|
/* Seeked within the patch romfs. */
|
|
ctx->bktr_ctx.bktr_seek = section_ofs;
|
|
} else {
|
|
/* Seeked within the base romfs. */
|
|
ctx->bktr_ctx.base_seek = section_ofs;
|
|
}
|
|
}
|
|
} else if (ctx->header->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);
|
|
ctx->sector_ofs = offset & 0xF;
|
|
}
|
|
}
|
|
|
|
size_t nca_bktr_section_physical_fread(nca_section_ctx_t *ctx, void *buffer, size_t count) {
|
|
size_t read = 0; /* XXX */
|
|
size_t size = 1;
|
|
char block_buf[0x10];
|
|
|
|
if (ctx->is_decrypted) {
|
|
fseeko64(ctx->file, (ctx->offset + ctx->bktr_ctx.bktr_seek), SEEK_SET);
|
|
read = fread(buffer, size, count, ctx->file);
|
|
nca_section_fseek(ctx, ctx->bktr_ctx.virtual_seek + read);
|
|
return read;
|
|
}
|
|
|
|
bktr_subsection_entry_t *subsec = bktr_get_subsection(ctx->bktr_ctx.subsection_block, ctx->bktr_ctx.bktr_seek);
|
|
nca_update_bktr_ctr(ctx->ctr, subsec->ctr_val, ctx->bktr_ctx.bktr_seek + ctx->offset);
|
|
fseeko64(ctx->file, (ctx->offset + ctx->bktr_ctx.bktr_seek) & ~0xF, SEEK_SET);
|
|
uint32_t block_ofs;
|
|
bktr_subsection_entry_t *next_subsec = subsec + 1;
|
|
if (ctx->bktr_ctx.bktr_seek + count <= next_subsec->offset) {
|
|
/* Easy path, reading *only* within the subsection. */
|
|
if ((block_ofs = ctx->bktr_ctx.bktr_seek & 0xF) != 0) {
|
|
if ((read = fread(block_buf, 1, 0x10, ctx->file)) != 0x10) {
|
|
return 0;
|
|
}
|
|
aes_setiv(ctx->aes, ctx->ctr, 0x10);
|
|
aes_decrypt(ctx->aes, block_buf, block_buf, 0x10);
|
|
if (count + block_ofs < 0x10) {
|
|
memcpy(buffer, block_buf + ctx->sector_ofs, count);
|
|
nca_section_fseek(ctx, ctx->bktr_ctx.virtual_seek + count);
|
|
return count;
|
|
}
|
|
memcpy(buffer, block_buf + block_ofs, 0x10 - block_ofs);
|
|
uint32_t read_in_block = 0x10 - block_ofs;
|
|
nca_section_fseek(ctx, ctx->bktr_ctx.virtual_seek - block_ofs + 0x10);
|
|
return read_in_block + nca_section_fread(ctx, (char *)buffer + read_in_block, count - read_in_block);
|
|
}
|
|
if ((read = fread(buffer, 1, count, ctx->file)) != count) {
|
|
return 0;
|
|
}
|
|
aes_setiv(ctx->aes, ctx->ctr, 16);
|
|
aes_decrypt(ctx->aes, buffer, buffer, count);
|
|
nca_section_fseek(ctx, ctx->bktr_ctx.virtual_seek + count);
|
|
} else {
|
|
/* Sad path. */
|
|
uint64_t within_subsection = next_subsec->offset - ctx->bktr_ctx.bktr_seek;
|
|
if ((read = nca_section_fread(ctx, buffer, within_subsection)) != within_subsection) {
|
|
return 0;
|
|
}
|
|
read += nca_section_fread(ctx, (char *)buffer + within_subsection, count - within_subsection);
|
|
if (read != count) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return read;
|
|
}
|
|
|
|
size_t nca_section_fread(nca_section_ctx_t *ctx, void *buffer, size_t count) {
|
|
size_t read = 0; /* XXX */
|
|
size_t size = 1;
|
|
char block_buf[0x10];
|
|
|
|
if (ctx->is_decrypted && ctx->type != BKTR) {
|
|
read = fread(buffer, size, count, ctx->file);
|
|
return read;
|
|
}
|
|
|
|
if (ctx->header->crypt_type == CRYPT_XTS) { /* AES-XTS requires special handling... */
|
|
unsigned char sector_buf[0x200];
|
|
if ((read = fread(§or_buf, size, 0x200, ctx->file)) != 0x200) {
|
|
return 0;
|
|
}
|
|
aes_xts_decrypt(ctx->aes, §or_buf, §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);
|
|
ctx->sector_num++;
|
|
ctx->sector_ofs = 0;
|
|
size_t remaining = count - (0x200 - ctx->sector_ofs);
|
|
size_t ofs = (0x200 - ctx->sector_ofs);
|
|
if (remaining & ~0x1FF) { /* Read intermediate sectors. */
|
|
if ((read = fread((char *)buffer + ofs, size, (remaining & ~0x1FF), ctx->file)) != (remaining & ~0x1FF)) {
|
|
return ofs;
|
|
}
|
|
|
|
aes_xts_decrypt(ctx->aes, (char *)buffer + ofs, (char *)buffer + ofs, remaining & ~0x1FF, ctx->sector_num, 0x200);
|
|
ctx->sector_num += remaining / 0x200;
|
|
ofs += remaining & ~0x1FF;
|
|
remaining &= 0x1FF;
|
|
}
|
|
if (remaining) { /* Read last sector. */
|
|
if ((read = fread(§or_buf, size, 0x200, ctx->file)) != 0x200) {
|
|
return ofs;
|
|
}
|
|
aes_xts_decrypt(ctx->aes, §or_buf, §or_buf, 0x200, ctx->sector_num, 0x200);
|
|
memcpy((char *)buffer + ofs, §or_buf, remaining);
|
|
ctx->sector_ofs = remaining;
|
|
read = count;
|
|
}
|
|
} else {
|
|
memcpy(buffer, §or_buf + ctx->sector_ofs, count);
|
|
ctx->sector_num += (ctx->sector_ofs + count) / 0x200;
|
|
ctx->sector_ofs += count;
|
|
ctx->sector_ofs &= 0x1FF;
|
|
}
|
|
} else {
|
|
/* Perform decryption, if necessary. */
|
|
/* AES-CTR. */
|
|
if (ctx->header->crypt_type == CRYPT_CTR || (ctx->header->crypt_type == CRYPT_BKTR && ctx->bktr_ctx.subsection_block == NULL))
|
|
{
|
|
if (ctx->sector_ofs) {
|
|
if ((read = fread(block_buf, 1, 0x10, ctx->file)) != 0x10) {
|
|
return 0;
|
|
}
|
|
aes_setiv(ctx->aes, ctx->ctr, 0x10);
|
|
aes_decrypt(ctx->aes, block_buf, block_buf, 0x10);
|
|
if (count + ctx->sector_ofs < 0x10) {
|
|
memcpy(buffer, block_buf + ctx->sector_ofs, count);
|
|
ctx->sector_ofs += count;
|
|
nca_section_fseek(ctx, ctx->cur_seek - ctx->offset);
|
|
return count;
|
|
}
|
|
memcpy(buffer, block_buf + ctx->sector_ofs, 0x10 - ctx->sector_ofs);
|
|
uint32_t read_in_block = 0x10 - ctx->sector_ofs;
|
|
nca_section_fseek(ctx, ctx->cur_seek - ctx->offset + 0x10);
|
|
return read_in_block + nca_section_fread(ctx, (char *)buffer + read_in_block, count - read_in_block);
|
|
}
|
|
if ((read = fread(buffer, 1, count, ctx->file)) != count) {
|
|
return 0;
|
|
}
|
|
aes_setiv(ctx->aes, ctx->ctr, 16);
|
|
aes_decrypt(ctx->aes, buffer, buffer, count);
|
|
nca_section_fseek(ctx, ctx->cur_seek - ctx->offset + count);
|
|
} else if (ctx->header->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);
|
|
bktr_relocation_entry_t *next_reloc = reloc + 1;
|
|
uint64_t virt_seek = ctx->bktr_ctx.virtual_seek;
|
|
if (ctx->bktr_ctx.virtual_seek + count <= next_reloc->virt_offset) {
|
|
/* Easy path: We're reading *only* within the current relocation. */
|
|
if (reloc->is_patch) {
|
|
read = nca_bktr_section_physical_fread(ctx, buffer, count);
|
|
} else {
|
|
/* Nice and easy read from the base rom. */
|
|
if (ctx->tool_ctx->base_file_type == BASEFILE_ROMFS) {
|
|
fseeko64(ctx->tool_ctx->base_file, ctx->bktr_ctx.base_seek, SEEK_SET);
|
|
if ((read = fread(buffer, 1, count, ctx->tool_ctx->base_file)) != count) {
|
|
return 0;
|
|
}
|
|
} else if (ctx->tool_ctx->base_file_type == BASEFILE_NCA) {
|
|
nca_ctx_t *base_ctx = ctx->tool_ctx->base_nca_ctx;
|
|
unsigned int romfs_section_num;
|
|
for (romfs_section_num = 0; romfs_section_num < 4; romfs_section_num++) {
|
|
if (base_ctx->section_contexts[romfs_section_num].type == ROMFS) {
|
|
break;
|
|
}
|
|
}
|
|
nca_section_fseek(&base_ctx->section_contexts[romfs_section_num], ctx->bktr_ctx.base_seek);
|
|
if ((read = nca_section_fread(&base_ctx->section_contexts[romfs_section_num], buffer, count)) != count) {
|
|
fprintf(stderr, "Failed to read from Base NCA RomFS!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
} else if (ctx->tool_ctx->base_file_type == BASEFILE_FAKE) {
|
|
/* Fake reads. */
|
|
memset(buffer, 0xCC, count);
|
|
read = count;
|
|
} else {
|
|
fprintf(stderr, "Unknown Base File Type!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
} else {
|
|
uint64_t within_relocation = next_reloc->virt_offset - ctx->bktr_ctx.virtual_seek;
|
|
if ((read = nca_section_fread(ctx, buffer, within_relocation)) != within_relocation) {
|
|
return 0;
|
|
}
|
|
nca_section_fseek(ctx, virt_seek + within_relocation);
|
|
read += nca_section_fread(ctx, (char *)buffer + within_relocation, count - within_relocation);
|
|
if (read != count) {
|
|
return 0;
|
|
}
|
|
}
|
|
nca_section_fseek(ctx, virt_seek + count);
|
|
} else {
|
|
read = nca_bktr_section_physical_fread(ctx, buffer, count);
|
|
}
|
|
}
|
|
}
|
|
return read;
|
|
}
|
|
|
|
void nca_free_section_contexts(nca_ctx_t *ctx) {
|
|
for (unsigned int i = 0; i < 4; i++) {
|
|
if (ctx->section_contexts[i].is_present) {
|
|
if (ctx->section_contexts[i].aes) {
|
|
free_aes_ctx(ctx->section_contexts[i].aes);
|
|
}
|
|
if (ctx->section_contexts[i].pfs0_ctx.is_exefs) {
|
|
free(ctx->section_contexts[i].pfs0_ctx.npdm);
|
|
} else if (ctx->section_contexts[i].type == ROMFS) {
|
|
if (ctx->section_contexts[i].romfs_ctx.directories) {
|
|
free(ctx->section_contexts[i].romfs_ctx.directories);
|
|
}
|
|
if (ctx->section_contexts[i].romfs_ctx.files) {
|
|
free(ctx->section_contexts[i].romfs_ctx.files);
|
|
}
|
|
} 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);
|
|
}
|
|
if (ctx->section_contexts[i].bktr_ctx.relocation_block) {
|
|
free(ctx->section_contexts[i].bktr_ctx.relocation_block);
|
|
}
|
|
if (ctx->section_contexts[i].bktr_ctx.directories) {
|
|
free(ctx->section_contexts[i].bktr_ctx.directories);
|
|
}
|
|
if (ctx->section_contexts[i].bktr_ctx.files) {
|
|
free(ctx->section_contexts[i].bktr_ctx.files);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void nca_save(nca_ctx_t *ctx) {
|
|
/* Save header. */
|
|
filepath_t *header_path = &ctx->tool_ctx->settings.header_path;
|
|
|
|
if (header_path->valid == VALIDITY_VALID) {
|
|
printf("Saving Header to %s...\n", header_path->char_path);
|
|
FILE *f_hdr = os_fopen(header_path->os_path, OS_MODE_WRITE);
|
|
|
|
if (f_hdr != NULL) {
|
|
fwrite(&ctx->header, 1, 0xC00, f_hdr);
|
|
fclose(f_hdr);
|
|
} else {
|
|
fprintf(stderr, "Failed to open %s!\n", header_path->char_path);
|
|
}
|
|
}
|
|
|
|
|
|
for (unsigned int i = 0; i < 4; i++) {
|
|
if (ctx->section_contexts[i].is_present) {
|
|
/* printf("Saving section %"PRId32"...\n", i); */
|
|
nca_save_section(&ctx->section_contexts[i]);
|
|
printf("\n");
|
|
}
|
|
}
|
|
|
|
/* Save Decrypted NCA. */
|
|
filepath_t *dec_path = &ctx->tool_ctx->settings.dec_nca_path;
|
|
|
|
if (dec_path->valid == VALIDITY_VALID) {
|
|
printf("Saving Decrypted NCA to %s...\n", dec_path->char_path);
|
|
FILE *f_dec = os_fopen(dec_path->os_path, OS_MODE_WRITE);
|
|
|
|
if (f_dec != NULL) {
|
|
if (fwrite(&ctx->header, 1, 0xC00, f_dec) != 0xC00) {
|
|
fprintf(stderr, "Failed to write header!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
unsigned char *buf = malloc(0x400000);
|
|
if (buf == NULL) {
|
|
fprintf(stderr, "Failed to allocate file-save buffer!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
for (unsigned int i = 0; i < 4; i++) {
|
|
if (ctx->section_contexts[i].is_present) {
|
|
fseeko64(f_dec, ctx->section_contexts[i].offset, SEEK_SET);
|
|
ctx->section_contexts[i].physical_reads = 1;
|
|
|
|
uint64_t read_size = 0x400000; /* 4 MB buffer. */
|
|
memset(buf, 0xCC, read_size); /* Debug in case I fuck this up somehow... */
|
|
uint64_t ofs = 0;
|
|
uint64_t end_ofs = ofs + ctx->section_contexts[i].size;
|
|
nca_section_fseek(&ctx->section_contexts[i], ofs);
|
|
while (ofs < end_ofs) {
|
|
if (ofs + read_size >= end_ofs) read_size = end_ofs - ofs;
|
|
if (nca_section_fread(&ctx->section_contexts[i], buf, read_size) != read_size) {
|
|
fprintf(stderr, "Failed to read file!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (fwrite(buf, 1, read_size, f_dec) != read_size) {
|
|
fprintf(stderr, "Failed to write file!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
ofs += read_size;
|
|
}
|
|
|
|
ctx->section_contexts[i].physical_reads = 0;
|
|
}
|
|
}
|
|
|
|
fclose(f_dec);
|
|
|
|
free(buf);
|
|
} else {
|
|
fprintf(stderr, "Failed to open %s!\n", dec_path->char_path);
|
|
}
|
|
}
|
|
}
|
|
|
|
void nca_process(nca_ctx_t *ctx) {
|
|
/* First things first, decrypt header. */
|
|
if (!nca_decrypt_header(ctx)) {
|
|
fprintf(stderr, "Invalid NCA header! Are keys correct?\n");
|
|
return;
|
|
}
|
|
|
|
if (rsa2048_pss_verify(&ctx->header.magic, 0x200, ctx->header.fixed_key_sig, ctx->tool_ctx->settings.keyset.nca_hdr_fixed_key_modulus)) {
|
|
ctx->fixed_sig_validity = VALIDITY_VALID;
|
|
} else {
|
|
ctx->fixed_sig_validity = VALIDITY_INVALID;
|
|
}
|
|
|
|
/* Sort out crypto type. */
|
|
ctx->crypto_type = ctx->header.crypto_type;
|
|
if (ctx->header.crypto_type2 > ctx->header.crypto_type)
|
|
ctx->crypto_type = ctx->header.crypto_type2;
|
|
|
|
if (ctx->crypto_type)
|
|
ctx->crypto_type--; /* 0, 1 are both master key 0. */
|
|
|
|
/* Rights ID. */
|
|
for (unsigned int i = 0; i < 0x10; i++) {
|
|
if (ctx->header.rights_id[i] != 0) {
|
|
ctx->has_rights_id = 1;
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Decrypt key area if required. */
|
|
if (!ctx->has_rights_id) {
|
|
nca_decrypt_key_area(ctx);
|
|
} else {
|
|
/* Decrypt title key. */
|
|
if (ctx->tool_ctx->settings.has_titlekey) {
|
|
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.titlekeks[ctx->crypto_type], 16, AES_MODE_ECB);
|
|
aes_decrypt(aes_ctx, ctx->tool_ctx->settings.dec_titlekey, ctx->tool_ctx->settings.titlekey, 0x10);
|
|
free_aes_ctx(aes_ctx);
|
|
}
|
|
}
|
|
|
|
/* Parse sections. */
|
|
for (unsigned int i = 0; i < 4; i++) {
|
|
if (ctx->header.section_entries[i].media_start_offset) { /* Section exists. */
|
|
ctx->section_contexts[i].is_present = 1;
|
|
ctx->section_contexts[i].is_decrypted = ctx->is_decrypted;
|
|
ctx->section_contexts[i].tool_ctx = ctx->tool_ctx;
|
|
ctx->section_contexts[i].file = ctx->file;
|
|
ctx->section_contexts[i].section_num = i;
|
|
ctx->section_contexts[i].offset = media_to_real(ctx->header.section_entries[i].media_start_offset);
|
|
ctx->section_contexts[i].size = media_to_real(ctx->header.section_entries[i].media_end_offset) - ctx->section_contexts[i].offset;
|
|
ctx->section_contexts[i].header = &ctx->header.fs_headers[i];
|
|
if (ctx->section_contexts[i].header->partition_type == PARTITION_PFS0 && ctx->section_contexts[i].header->fs_type == FS_TYPE_PFS0) {
|
|
ctx->section_contexts[i].type = PFS0;
|
|
ctx->section_contexts[i].pfs0_ctx.superblock = &ctx->section_contexts[i].header->pfs0_superblock;
|
|
} else if (ctx->section_contexts[i].header->partition_type == PARTITION_ROMFS && ctx->section_contexts[i].header->fs_type == FS_TYPE_ROMFS) {
|
|
if (ctx->section_contexts[i].header->crypt_type == CRYPT_BKTR) {
|
|
ctx->section_contexts[i].type = BKTR;
|
|
ctx->section_contexts[i].bktr_ctx.superblock = &ctx->section_contexts[i].header->bktr_superblock;
|
|
} else {
|
|
ctx->section_contexts[i].type = ROMFS;
|
|
ctx->section_contexts[i].romfs_ctx.superblock = &ctx->section_contexts[i].header->romfs_superblock;
|
|
}
|
|
} else {
|
|
ctx->section_contexts[i].type = INVALID;
|
|
}
|
|
uint64_t ofs = ctx->section_contexts[i].offset >> 4;
|
|
for (unsigned int j = 0; j < 0x8; j++) {
|
|
ctx->section_contexts[i].ctr[j] = ctx->section_contexts[i].header->section_ctr[0x8-j-1];
|
|
ctx->section_contexts[i].ctr[0x10-j-1] = (unsigned char)(ofs & 0xFF);
|
|
ofs >>= 8;
|
|
}
|
|
ctx->section_contexts[i].sector_num = 0;
|
|
ctx->section_contexts[i].sector_ofs = 0;
|
|
|
|
if (ctx->section_contexts[i].header->crypt_type == CRYPT_NONE) {
|
|
ctx->section_contexts[i].is_decrypted = 1;
|
|
}
|
|
|
|
if (ctx->tool_ctx->settings.has_contentkey) {
|
|
ctx->section_contexts[i].aes = new_aes_ctx(ctx->tool_ctx->settings.contentkey, 16, AES_MODE_CTR);
|
|
} else {
|
|
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) {
|
|
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);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ctx->tool_ctx->action & ACTION_VERIFY) {
|
|
printf("Verifying section %"PRId32"...\n", i);
|
|
}
|
|
|
|
switch (ctx->section_contexts[i].type) {
|
|
case PFS0:
|
|
nca_process_pfs0_section(&ctx->section_contexts[i]);
|
|
/* Verify NPDM sig now, if we can... */
|
|
if (ctx->section_contexts[i].pfs0_ctx.is_exefs) {
|
|
ctx->npdm = ctx->section_contexts[i].pfs0_ctx.npdm;
|
|
if (rsa2048_pss_verify(&ctx->header.magic, 0x200, ctx->header.npdm_key_sig, npdm_get_acid(ctx->npdm)->modulus)) {
|
|
ctx->npdm_sig_validity = VALIDITY_VALID;
|
|
} else {
|
|
ctx->npdm_sig_validity = VALIDITY_INVALID;
|
|
}
|
|
}
|
|
break;
|
|
case ROMFS:
|
|
nca_process_ivfc_section(&ctx->section_contexts[i]);
|
|
break;
|
|
case BKTR:
|
|
nca_process_bktr_section(&ctx->section_contexts[i]);
|
|
break;
|
|
case INVALID:
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (ctx->tool_ctx->action & ACTION_INFO) {
|
|
nca_print(ctx);
|
|
}
|
|
|
|
if (ctx->tool_ctx->action & ACTION_EXTRACT) {
|
|
nca_save(ctx);
|
|
}
|
|
}
|
|
|
|
/* Decrypt NCA header. */
|
|
int nca_decrypt_header(nca_ctx_t *ctx) {
|
|
fseeko64(ctx->file, 0, SEEK_SET);
|
|
if (fread(&ctx->header, 1, 0xC00, ctx->file) != 0xC00) {
|
|
fprintf(stderr, "Failed to read NCA header!\n");
|
|
return 0;
|
|
}
|
|
|
|
/* Try to support decrypted NCA headers. */
|
|
if (ctx->header.magic == MAGIC_NCA3) {
|
|
if (ctx->header._0x340[0] == 0 && !memcmp(ctx->header._0x340, ctx->header._0x340 + 1, 0xBF)) {
|
|
ctx->is_decrypted = 1;
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
ctx->is_decrypted = 0;
|
|
|
|
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.header_key, 32, AES_MODE_XTS);
|
|
aes_xts_decrypt(aes_ctx, &ctx->header, &ctx->header, 0xC00, 0, 0x200);
|
|
free_aes_ctx(aes_ctx);
|
|
return ctx->header.magic == MAGIC_NCA3;
|
|
}
|
|
|
|
/* Decrypt key area. */
|
|
void nca_decrypt_key_area(nca_ctx_t *ctx) {
|
|
aes_ctx_t *aes_ctx = new_aes_ctx(ctx->tool_ctx->settings.keyset.key_area_keys[ctx->crypto_type][ctx->header.kaek_ind], 16, AES_MODE_ECB);
|
|
aes_decrypt(aes_ctx, ctx->decrypted_keys, ctx->header.encrypted_keys, 0x40);
|
|
free_aes_ctx(aes_ctx);
|
|
}
|
|
|
|
|
|
char *nca_get_distribution_type(nca_ctx_t *ctx) {
|
|
switch (ctx->header.distribution) {
|
|
case 0:
|
|
return "Download";
|
|
case 1:
|
|
return "Gamecard";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
char *nca_get_content_type(nca_ctx_t *ctx) {
|
|
switch (ctx->header.content_type) {
|
|
case 0:
|
|
return "Program";
|
|
case 1:
|
|
return "Meta";
|
|
case 2:
|
|
return "Control";
|
|
case 3:
|
|
return "Manual";
|
|
case 4:
|
|
return "Data";
|
|
default:
|
|
return "Unknown";
|
|
}
|
|
}
|
|
|
|
char *nca_get_encryption_type(nca_ctx_t *ctx) {
|
|
if (ctx->has_rights_id) {
|
|
return "Titlekey crypto";
|
|
} else {
|
|
return "Standard crypto";
|
|
}
|
|
}
|
|
|
|
void nca_print_key_area(nca_ctx_t *ctx) {
|
|
printf("Key Area (Encrypted):\n");
|
|
for (unsigned int i = 0; i < 0x4; i++) {
|
|
printf(" Key %"PRId32" (Encrypted): ", i);
|
|
memdump(stdout, "", &ctx->header.encrypted_keys[i], 0x10);
|
|
}
|
|
printf("Key Area (Decrypted):\n");
|
|
for (unsigned int i = 0; i < 0x4; i++) {
|
|
printf(" Key %"PRId32" (Decrypted): ", i);
|
|
memdump(stdout, "", &ctx->decrypted_keys[i], 0x10);
|
|
}
|
|
}
|
|
|
|
char *nca_get_section_type(nca_section_ctx_t *meta) {
|
|
switch (meta->type) {
|
|
case PFS0: {
|
|
if (meta->pfs0_ctx.is_exefs) return "ExeFS";
|
|
return "PFS0";
|
|
}
|
|
case ROMFS: return "RomFS";
|
|
case BKTR: return "Patch RomFS";
|
|
case INVALID:
|
|
default:
|
|
return "Unknown/Invalid";
|
|
}
|
|
}
|
|
|
|
|
|
void nca_print_sections(nca_ctx_t *ctx) {
|
|
printf("Sections:\n");
|
|
for (unsigned int i = 0; i < 4; i++) {
|
|
if (ctx->section_contexts[i].is_present) { /* Section exists. */
|
|
printf(" Section %"PRId32":\n", i);
|
|
printf(" Offset: 0x%012"PRIx64"\n", ctx->section_contexts[i].offset);
|
|
printf(" Size: 0x%012"PRIx64"\n", ctx->section_contexts[i].size);
|
|
printf(" Partition Type: %s\n", nca_get_section_type(&ctx->section_contexts[i]));
|
|
memdump(stdout, " Section CTR: ", &ctx->section_contexts[i].ctr, 16);
|
|
switch (ctx->section_contexts[i].type) {
|
|
case PFS0: {
|
|
nca_print_pfs0_section(&ctx->section_contexts[i]);
|
|
break;
|
|
}
|
|
case ROMFS: {
|
|
nca_print_ivfc_section(&ctx->section_contexts[i]);
|
|
break;
|
|
}
|
|
case BKTR: {
|
|
nca_print_bktr_section(&ctx->section_contexts[i]);
|
|
break;
|
|
}
|
|
case INVALID:
|
|
default: {
|
|
printf(" Unknown/invalid superblock!");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* Print out information about the NCA. */
|
|
void nca_print(nca_ctx_t *ctx) {
|
|
printf("\nNCA:\n");
|
|
print_magic("Magic: ", ctx->header.magic);
|
|
|
|
if (ctx->tool_ctx->action & ACTION_VERIFY && ctx->fixed_sig_validity != VALIDITY_UNCHECKED) {
|
|
if (ctx->fixed_sig_validity == VALIDITY_VALID) {
|
|
memdump(stdout, "Fixed-Key Signature (GOOD): ", &ctx->header.fixed_key_sig, 0x100);
|
|
} else {
|
|
memdump(stdout, "Fixed-Key Signature (FAIL): ", &ctx->header.fixed_key_sig, 0x100);
|
|
}
|
|
} else {
|
|
memdump(stdout, "Fixed-Key Signature: ", &ctx->header.fixed_key_sig, 0x100);
|
|
}
|
|
if (ctx->tool_ctx->action & ACTION_VERIFY && ctx->npdm_sig_validity != VALIDITY_UNCHECKED) {
|
|
if (ctx->npdm_sig_validity == VALIDITY_VALID) {
|
|
memdump(stdout, "NPDM Signature (GOOD): ", &ctx->header.npdm_key_sig, 0x100);
|
|
} else {
|
|
memdump(stdout, "NPDM Signature (FAIL): ", &ctx->header.npdm_key_sig, 0x100);
|
|
}
|
|
} else {
|
|
memdump(stdout, "NPDM Signature: ", &ctx->header.npdm_key_sig, 0x100);
|
|
}
|
|
printf("Content Size: 0x%012"PRIx64"\n", ctx->header.nca_size);
|
|
printf("Title ID: %016"PRIx64"\n", ctx->header.title_id);
|
|
printf("SDK Version: %"PRId8".%"PRId8".%"PRId8".%"PRId8"\n", ctx->header.sdk_major, ctx->header.sdk_minor, ctx->header.sdk_micro, ctx->header.sdk_revision);
|
|
printf("Distribution type: %s\n", nca_get_distribution_type(ctx));
|
|
printf("Content Type: %s\n", nca_get_content_type(ctx));
|
|
printf("Master Key Revision: %"PRIx8" (%s)\n", ctx->crypto_type, get_key_revision_summary(ctx->crypto_type));
|
|
printf("Encryption Type: %s\n", nca_get_encryption_type(ctx));
|
|
|
|
if (ctx->has_rights_id) {
|
|
memdump(stdout, "Rights ID: ", &ctx->header.rights_id, 0x10);
|
|
if (ctx->tool_ctx->settings.has_titlekey) {
|
|
memdump(stdout, "Titlekey (Encrypted) ", ctx->tool_ctx->settings.titlekey, 0x10);
|
|
memdump(stdout, "Titlekey (Decrypted) ", ctx->tool_ctx->settings.dec_titlekey, 0x10);
|
|
}
|
|
} else {
|
|
printf("Key Area Encryption Key: %"PRIx8"\n", ctx->header.kaek_ind);
|
|
nca_print_key_area(ctx);
|
|
}
|
|
|
|
if (ctx->npdm) {
|
|
npdm_print(ctx->npdm, ctx->tool_ctx);
|
|
}
|
|
|
|
nca_print_sections(ctx);
|
|
|
|
printf("\n");
|
|
}
|
|
|
|
validity_t nca_section_check_external_hash_table(nca_section_ctx_t *ctx, unsigned char *hash_table, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block) {
|
|
if (block_size == 0) {
|
|
/* Block size of 0 is always invalid. */
|
|
return VALIDITY_INVALID;
|
|
}
|
|
unsigned char cur_hash[0x20];
|
|
uint64_t read_size = block_size;
|
|
unsigned char *block = malloc(block_size);
|
|
if (block == NULL) {
|
|
fprintf(stderr, "Failed to allocate hash block!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
validity_t result = VALIDITY_VALID;
|
|
unsigned char *cur_hash_table_entry = hash_table;
|
|
for (uint64_t ofs = 0; ofs < data_len; ofs += read_size) {
|
|
nca_section_fseek(ctx, ofs + data_ofs);
|
|
if (ofs + read_size > data_len) {
|
|
/* Last block... */
|
|
memset(block, 0, read_size);
|
|
read_size = data_len - ofs;
|
|
}
|
|
|
|
uint64_t r = nca_section_fread(ctx, block, read_size);
|
|
if (r != read_size) {
|
|
fprintf(stderr, "%012"PRIx64" %012"PRIx64" %08"PRIx64"\n", ofs, data_len, r);
|
|
fprintf(stderr, "%d %d\n", ctx->is_decrypted, ctx->section_num);
|
|
fprintf(stderr, "Failed to read section!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
sha256_hash_buffer(cur_hash, block, full_block ? block_size : read_size);
|
|
if (memcmp(cur_hash, cur_hash_table_entry, 0x20) != 0) {
|
|
result = VALIDITY_INVALID;
|
|
break;
|
|
}
|
|
cur_hash_table_entry += 0x20;
|
|
}
|
|
free(block);
|
|
|
|
return result;
|
|
|
|
}
|
|
|
|
validity_t nca_section_check_hash_table(nca_section_ctx_t *ctx, uint64_t hash_ofs, uint64_t data_ofs, uint64_t data_len, uint64_t block_size, int full_block) {
|
|
if (block_size == 0) {
|
|
/* Block size of 0 is always invalid. */
|
|
return VALIDITY_INVALID;
|
|
}
|
|
uint64_t hash_table_size = data_len / block_size;
|
|
if (data_len % block_size) hash_table_size++;
|
|
hash_table_size *= 0x20;
|
|
unsigned char *hash_table = malloc(hash_table_size);
|
|
if (hash_table == NULL) {
|
|
fprintf(stderr, "Failed to allocate hash table!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
nca_section_fseek(ctx, hash_ofs);
|
|
if (nca_section_fread(ctx, hash_table, hash_table_size) != hash_table_size) {
|
|
fprintf(stderr, "Failed to read section!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
validity_t result = nca_section_check_external_hash_table(ctx, hash_table, data_ofs, data_len, block_size, full_block);
|
|
|
|
free(hash_table);
|
|
|
|
return result;
|
|
}
|
|
|
|
void nca_save_pfs0_file(nca_section_ctx_t *ctx, uint32_t i, filepath_t *dirpath) {
|
|
if (i >= ctx->pfs0_ctx.header->num_files) {
|
|
fprintf(stderr, "Could not save file %"PRId32"!\n", i);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
pfs0_file_entry_t *cur_file = pfs0_get_file_entry(ctx->pfs0_ctx.header, i);
|
|
if (cur_file->size >= ctx->size) {
|
|
fprintf(stderr, "File %"PRId32" too big in PFS0 (section %"PRId32")!\n", i, ctx->section_num);
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (strlen(pfs0_get_file_name(ctx->pfs0_ctx.header, i)) >= MAX_PATH - strlen(dirpath->char_path) - 1) {
|
|
fprintf(stderr, "Filename too long in PFS0!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
filepath_t filepath;
|
|
filepath_copy(&filepath, dirpath);
|
|
filepath_append(&filepath, "%s", pfs0_get_file_name(ctx->pfs0_ctx.header, i));
|
|
|
|
printf("Saving %s to %s...\n", pfs0_get_file_name(ctx->pfs0_ctx.header, i), filepath.char_path);
|
|
uint64_t ofs = ctx->pfs0_ctx.superblock->pfs0_offset + pfs0_get_header_size(ctx->pfs0_ctx.header) + cur_file->offset;
|
|
nca_save_section_file(ctx, ofs, cur_file->size, &filepath);
|
|
}
|
|
|
|
|
|
void nca_process_pfs0_section(nca_section_ctx_t *ctx) {
|
|
pfs0_superblock_t *sb = ctx->pfs0_ctx.superblock;
|
|
ctx->superblock_hash_validity = nca_section_check_external_hash_table(ctx, sb->master_hash, sb->hash_table_offset, sb->hash_table_size, sb->hash_table_size, 0);
|
|
if (ctx->tool_ctx->action & ACTION_VERIFY) {
|
|
/* Verify actual PFS0... */
|
|
ctx->pfs0_ctx.hash_table_validity = nca_section_check_hash_table(ctx, sb->hash_table_offset, sb->pfs0_offset, sb->pfs0_size, sb->block_size, 0);
|
|
}
|
|
|
|
if (ctx->superblock_hash_validity != VALIDITY_VALID) return;
|
|
|
|
/* Read *just* safe amount. */
|
|
pfs0_header_t raw_header;
|
|
nca_section_fseek(ctx, sb->pfs0_offset);
|
|
if (nca_section_fread(ctx, &raw_header, sizeof(raw_header)) != sizeof(raw_header)) {
|
|
fprintf(stderr, "Failed to read PFS0 header!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
uint64_t header_size = pfs0_get_header_size(&raw_header);
|
|
ctx->pfs0_ctx.header = malloc(header_size);
|
|
if (ctx->pfs0_ctx.header == NULL) {
|
|
fprintf(stderr, "Failed to get PFS0 header size!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
nca_section_fseek(ctx, sb->pfs0_offset);
|
|
if (nca_section_fread(ctx, ctx->pfs0_ctx.header, header_size) != header_size) {
|
|
fprintf(stderr, "Failed to read PFS0 header!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
for (unsigned int i = 0; i < ctx->pfs0_ctx.header->num_files; i++) {
|
|
pfs0_file_entry_t *cur_file = pfs0_get_file_entry(ctx->pfs0_ctx.header, i);
|
|
if (strcmp(pfs0_get_file_name(ctx->pfs0_ctx.header, i), "main.npdm") == 0) {
|
|
/* We might have found the exefs... */
|
|
if (cur_file->size >= sb->pfs0_size) {
|
|
fprintf(stderr, "NPDM too big!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ctx->pfs0_ctx.npdm = malloc(cur_file->size);
|
|
if (ctx->pfs0_ctx.npdm == NULL) {
|
|
fprintf(stderr, "Failed to allocate NPDM!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
nca_section_fseek(ctx, sb->pfs0_offset + pfs0_get_header_size(ctx->pfs0_ctx.header) + cur_file->offset);
|
|
if (nca_section_fread(ctx, ctx->pfs0_ctx.npdm, cur_file->size) != cur_file->size) {
|
|
fprintf(stderr, "Failed to read NPDM!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
if (ctx->pfs0_ctx.npdm->magic == MAGIC_META) {
|
|
ctx->pfs0_ctx.is_exefs = 1;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
void nca_process_ivfc_section(nca_section_ctx_t *ctx) {
|
|
romfs_superblock_t *sb = ctx->romfs_ctx.superblock;
|
|
for (unsigned int i = 0; i < IVFC_MAX_LEVEL; i++) {
|
|
/* Load in the current level's header data. */
|
|
ivfc_level_ctx_t *cur_level = &ctx->romfs_ctx.ivfc_levels[i];
|
|
cur_level->data_offset = sb->ivfc_header.level_headers[i].logical_offset;
|
|
cur_level->data_size = sb->ivfc_header.level_headers[i].hash_data_size;
|
|
cur_level->hash_block_size = 1 << sb->ivfc_header.level_headers[i].block_size;
|
|
|
|
if (i != 0) {
|
|
/* Hash table is previous level's data. */
|
|
cur_level->hash_offset = ctx->romfs_ctx.ivfc_levels[i-1].data_offset;
|
|
} else {
|
|
/* Hash table is the superblock hash. Always check the superblock hash. */
|
|
ctx->superblock_hash_validity = nca_section_check_external_hash_table(ctx, sb->ivfc_header.master_hash, cur_level->data_offset, cur_level->data_size, cur_level->hash_block_size, 1);
|
|
cur_level->hash_validity = ctx->superblock_hash_validity;
|
|
}
|
|
if (ctx->tool_ctx->action & ACTION_VERIFY && i != 0) {
|
|
/* Actually check the table. */
|
|
printf(" Verifying IVFC Level %"PRId32"...\n", i);
|
|
cur_level->hash_validity = nca_section_check_hash_table(ctx, cur_level->hash_offset, cur_level->data_offset, cur_level->data_size, cur_level->hash_block_size, 1);
|
|
}
|
|
}
|
|
|
|
ctx->romfs_ctx.romfs_offset = ctx->romfs_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_offset;
|
|
nca_section_fseek(ctx, ctx->romfs_ctx.romfs_offset);
|
|
if (nca_section_fread(ctx, &ctx->romfs_ctx.header, sizeof(romfs_hdr_t)) != sizeof(romfs_hdr_t)) {
|
|
fprintf(stderr, "Failed to read RomFS header!\n");
|
|
}
|
|
|
|
if ((ctx->tool_ctx->action & (ACTION_EXTRACT | ACTION_LISTROMFS)) && ctx->romfs_ctx.header.header_size == ROMFS_HEADER_SIZE) {
|
|
/* Pre-load the file/data entry caches. */
|
|
ctx->romfs_ctx.directories = calloc(1, ctx->romfs_ctx.header.dir_meta_table_size);
|
|
if (ctx->romfs_ctx.directories == NULL) {
|
|
fprintf(stderr, "Failed to allocate RomFS directory cache!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Switch RomFS has actual entries at table offset + 4 for no good reason. */
|
|
nca_section_fseek(ctx, ctx->romfs_ctx.romfs_offset + ctx->romfs_ctx.header.dir_meta_table_offset + 4);
|
|
if (nca_section_fread(ctx, ctx->romfs_ctx.directories, ctx->romfs_ctx.header.dir_meta_table_size) != ctx->romfs_ctx.header.dir_meta_table_size) {
|
|
fprintf(stderr, "Failed to read RomFS directory cache!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
ctx->romfs_ctx.files = calloc(1, ctx->romfs_ctx.header.file_meta_table_size);
|
|
if (ctx->romfs_ctx.files == NULL) {
|
|
fprintf(stderr, "Failed to allocate RomFS file cache!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
nca_section_fseek(ctx, ctx->romfs_ctx.romfs_offset + ctx->romfs_ctx.header.file_meta_table_offset);
|
|
if (nca_section_fread(ctx, ctx->romfs_ctx.files, ctx->romfs_ctx.header.file_meta_table_size) != ctx->romfs_ctx.header.file_meta_table_size) {
|
|
fprintf(stderr, "Failed to read RomFS file cache!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
|
|
void nca_process_bktr_section(nca_section_ctx_t *ctx) {
|
|
bktr_superblock_t *sb = ctx->bktr_ctx.superblock;
|
|
/* Validate magics. */
|
|
if (sb->relocation_header.magic == MAGIC_BKTR && sb->subsection_header.magic == MAGIC_BKTR) {
|
|
if (sb->relocation_header.offset + sb->relocation_header.size != sb->subsection_header.offset ||
|
|
sb->subsection_header.offset + sb->subsection_header.size != ctx->size) {
|
|
fprintf(stderr, "Invalid BKTR layout!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
/* Allocate space for an extra (fake) relocation entry, to simplify our logic. */
|
|
void *relocs = calloc(1, sb->relocation_header.size + sizeof(bktr_relocation_entry_t));
|
|
if (relocs == NULL) {
|
|
fprintf(stderr, "Failed to allocate relocation header!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
/* Allocate space for an extra (fake) subsection entry, to simplify our logic. */
|
|
void *subs = calloc(1, sb->subsection_header.size + 2 * sizeof(bktr_subsection_entry_t));
|
|
if (subs == NULL) {
|
|
fprintf(stderr, "Failed to allocate subsection header!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
nca_section_fseek(ctx, sb->relocation_header.offset);
|
|
if (nca_section_fread(ctx, relocs, sb->relocation_header.size) != sb->relocation_header.size) {
|
|
fprintf(stderr, "Failed to read relocation header!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
nca_section_fseek(ctx, sb->subsection_header.offset);
|
|
if (nca_section_fread(ctx, subs, sb->subsection_header.size) != sb->subsection_header.size) {
|
|
fprintf(stderr, "Failed to read subsection header!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* NOTE: Setting these variables changes the way fseek/fread work! */
|
|
ctx->bktr_ctx.relocation_block = relocs;
|
|
ctx->bktr_ctx.subsection_block = subs;
|
|
|
|
if (ctx->bktr_ctx.subsection_block->bktr_entry_offset != sb->subsection_header.offset) {
|
|
free(relocs);
|
|
free(subs);
|
|
ctx->bktr_ctx.relocation_block = NULL;
|
|
ctx->bktr_ctx.subsection_block = NULL;
|
|
ctx->superblock_hash_validity = VALIDITY_INVALID;
|
|
return;
|
|
}
|
|
|
|
/* This simplifies logic greatly... */
|
|
ctx->bktr_ctx.relocation_block->entries[ctx->bktr_ctx.relocation_block->num_entries].virt_offset = ctx->bktr_ctx.relocation_block->patch_romfs_size;
|
|
ctx->bktr_ctx.subsection_block->entries[ctx->bktr_ctx.subsection_block->num_entries].offset = sb->relocation_header.offset;
|
|
ctx->bktr_ctx.subsection_block->entries[ctx->bktr_ctx.subsection_block->num_entries].ctr_val = ctx->header->section_ctr_low;
|
|
ctx->bktr_ctx.subsection_block->entries[ctx->bktr_ctx.subsection_block->num_entries + 1].offset = ctx->size;
|
|
ctx->bktr_ctx.subsection_block->entries[ctx->bktr_ctx.subsection_block->num_entries + 1].ctr_val = 0;
|
|
|
|
|
|
/* Now parse out the romfs stuff. */
|
|
for (unsigned int i = 0; i < IVFC_MAX_LEVEL; i++) {
|
|
/* Load in the current level's header data. */
|
|
ivfc_level_ctx_t *cur_level = &ctx->bktr_ctx.ivfc_levels[i];
|
|
cur_level->data_offset = sb->ivfc_header.level_headers[i].logical_offset;
|
|
cur_level->data_size = sb->ivfc_header.level_headers[i].hash_data_size;
|
|
cur_level->hash_block_size = 1 << sb->ivfc_header.level_headers[i].block_size;
|
|
|
|
if (i != 0) {
|
|
/* Hash table is previous level's data. */
|
|
cur_level->hash_offset = ctx->bktr_ctx.ivfc_levels[i-1].data_offset;
|
|
} else if (ctx->tool_ctx->base_file != NULL) {
|
|
/* Hash table is the superblock hash. Always check the superblock hash. */
|
|
ctx->superblock_hash_validity = nca_section_check_external_hash_table(ctx, sb->ivfc_header.master_hash, cur_level->data_offset, cur_level->data_size, cur_level->hash_block_size, 1);
|
|
cur_level->hash_validity = ctx->superblock_hash_validity;
|
|
}
|
|
if (ctx->tool_ctx->action & ACTION_VERIFY && i != 0) {
|
|
/* Actually check the table. */
|
|
printf(" Verifying IVFC Level %"PRId32"...\n", i);
|
|
cur_level->hash_validity = nca_section_check_hash_table(ctx, cur_level->hash_offset, cur_level->data_offset, cur_level->data_size, cur_level->hash_block_size, 1);
|
|
}
|
|
}
|
|
|
|
ctx->bktr_ctx.romfs_offset = ctx->bktr_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_offset;
|
|
if (ctx->tool_ctx->base_file != NULL) {
|
|
nca_section_fseek(ctx, ctx->bktr_ctx.romfs_offset);
|
|
if (nca_section_fread(ctx, &ctx->bktr_ctx.header, sizeof(romfs_hdr_t)) != sizeof(romfs_hdr_t)) {
|
|
fprintf(stderr, "Failed to read BKTR Virtual RomFS header!\n");
|
|
}
|
|
|
|
if ((ctx->tool_ctx->action & (ACTION_EXTRACT | ACTION_LISTROMFS)) && ctx->bktr_ctx.header.header_size == ROMFS_HEADER_SIZE) {
|
|
/* Pre-load the file/data entry caches. */
|
|
ctx->bktr_ctx.directories = calloc(1, ctx->bktr_ctx.header.dir_meta_table_size);
|
|
if (ctx->bktr_ctx.directories == NULL) {
|
|
fprintf(stderr, "Failed to allocate RomFS directory cache!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
|
|
/* Switch RomFS has actual entries at table offset + 4 for no good reason. */
|
|
nca_section_fseek(ctx, ctx->bktr_ctx.romfs_offset + ctx->bktr_ctx.header.dir_meta_table_offset + 4);
|
|
if (nca_section_fread(ctx, ctx->bktr_ctx.directories, ctx->bktr_ctx.header.dir_meta_table_size) != ctx->bktr_ctx.header.dir_meta_table_size) {
|
|
fprintf(stderr, "Failed to read RomFS directory cache!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
ctx->bktr_ctx.files = calloc(1, ctx->bktr_ctx.header.file_meta_table_size);
|
|
if (ctx->bktr_ctx.files == NULL) {
|
|
fprintf(stderr, "Failed to allocate RomFS file cache!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
nca_section_fseek(ctx, ctx->bktr_ctx.romfs_offset + ctx->bktr_ctx.header.file_meta_table_offset);
|
|
if (nca_section_fread(ctx, ctx->bktr_ctx.files, ctx->bktr_ctx.header.file_meta_table_size) != ctx->bktr_ctx.header.file_meta_table_size) {
|
|
fprintf(stderr, "Failed to read RomFS file cache!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void nca_print_pfs0_section(nca_section_ctx_t *ctx) {
|
|
if (ctx->tool_ctx->action & ACTION_VERIFY) {
|
|
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
|
|
memdump(stdout, " Superblock Hash (GOOD): ", &ctx->pfs0_ctx.superblock->master_hash, 0x20);
|
|
} else {
|
|
memdump(stdout, " Superblock Hash (FAIL): ", &ctx->pfs0_ctx.superblock->master_hash, 0x20);
|
|
}
|
|
printf(" Hash Table (%s):\n", GET_VALIDITY_STR(ctx->pfs0_ctx.hash_table_validity));
|
|
} else {
|
|
memdump(stdout, " Superblock Hash: ", &ctx->pfs0_ctx.superblock->master_hash, 0x20);
|
|
printf(" Hash Table:\n");
|
|
}
|
|
printf(" Offset: %012"PRIx64"\n", ctx->pfs0_ctx.superblock->hash_table_offset);
|
|
printf(" Size: %012"PRIx64"\n", ctx->pfs0_ctx.superblock->hash_table_size);
|
|
printf(" Block Size: 0x%"PRIx32"\n", ctx->pfs0_ctx.superblock->block_size);
|
|
printf(" PFS0 Offset: %012"PRIx64"\n", ctx->pfs0_ctx.superblock->pfs0_offset);
|
|
printf(" PFS0 Size: %012"PRIx64"\n", ctx->pfs0_ctx.superblock->pfs0_size);
|
|
}
|
|
|
|
void nca_print_ivfc_section(nca_section_ctx_t *ctx) {
|
|
if (ctx->tool_ctx->action & ACTION_VERIFY) {
|
|
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
|
|
memdump(stdout, " Superblock Hash (GOOD): ", &ctx->romfs_ctx.superblock->ivfc_header.master_hash, 0x20);
|
|
} else {
|
|
memdump(stdout, " Superblock Hash (FAIL): ", &ctx->romfs_ctx.superblock->ivfc_header.master_hash, 0x20);
|
|
}
|
|
} else {
|
|
memdump(stdout, " Superblock Hash: ", &ctx->romfs_ctx.superblock->ivfc_header.master_hash, 0x20);
|
|
}
|
|
print_magic(" Magic: ", ctx->romfs_ctx.superblock->ivfc_header.magic);
|
|
printf(" ID: %08"PRIx32"\n", ctx->romfs_ctx.superblock->ivfc_header.id);
|
|
for (unsigned int i = 0; i < IVFC_MAX_LEVEL; i++) {
|
|
if (ctx->tool_ctx->action & ACTION_VERIFY) {
|
|
printf(" Level %"PRId32" (%s):\n", i, GET_VALIDITY_STR(ctx->romfs_ctx.ivfc_levels[i].hash_validity));
|
|
} else {
|
|
printf(" Level %"PRId32":\n", i);
|
|
}
|
|
printf(" Data Offset: 0x%012"PRIx64"\n", ctx->romfs_ctx.ivfc_levels[i].data_offset);
|
|
printf(" Data Size: 0x%012"PRIx64"\n", ctx->romfs_ctx.ivfc_levels[i].data_size);
|
|
if (i != 0) printf(" Hash Offset: 0x%012"PRIx64"\n", ctx->romfs_ctx.ivfc_levels[i].hash_offset);
|
|
printf(" Hash Block Size: 0x%08"PRIx32"\n", ctx->romfs_ctx.ivfc_levels[i].hash_block_size);
|
|
}
|
|
}
|
|
|
|
void nca_print_bktr_section(nca_section_ctx_t *ctx) {
|
|
if (ctx->bktr_ctx.subsection_block == NULL) {
|
|
printf(" BKTR section seems to be corrupted.\n");
|
|
return;
|
|
}
|
|
int did_verify = (ctx->tool_ctx->action & ACTION_VERIFY) && (ctx->tool_ctx->base_file != NULL);
|
|
if (did_verify ) {
|
|
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
|
|
memdump(stdout, " Superblock Hash (GOOD): ", &ctx->bktr_ctx.superblock->ivfc_header.master_hash, 0x20);
|
|
} else {
|
|
memdump(stdout, " Superblock Hash (FAIL): ", &ctx->bktr_ctx.superblock->ivfc_header.master_hash, 0x20);
|
|
}
|
|
} else {
|
|
memdump(stdout, " Superblock Hash: ", &ctx->bktr_ctx.superblock->ivfc_header.master_hash, 0x20);
|
|
}
|
|
print_magic(" Magic: ", ctx->bktr_ctx.superblock->ivfc_header.magic);
|
|
printf(" ID: %08"PRIx32"\n", ctx->bktr_ctx.superblock->ivfc_header.id);
|
|
for (unsigned int i = 0; i < IVFC_MAX_LEVEL; i++) {
|
|
if (did_verify) {
|
|
printf(" Level %"PRId32" (%s):\n", i, GET_VALIDITY_STR(ctx->bktr_ctx.ivfc_levels[i].hash_validity));
|
|
} else {
|
|
printf(" Level %"PRId32":\n", i);
|
|
}
|
|
printf(" Data Offset: 0x%012"PRIx64"\n", ctx->bktr_ctx.ivfc_levels[i].data_offset);
|
|
printf(" Data Size: 0x%012"PRIx64"\n", ctx->bktr_ctx.ivfc_levels[i].data_size);
|
|
if (i != 0) printf(" Hash Offset: 0x%012"PRIx64"\n", ctx->bktr_ctx.ivfc_levels[i].hash_offset);
|
|
printf(" Hash Block Size: 0x%08"PRIx32"\n", ctx->bktr_ctx.ivfc_levels[i].hash_block_size);
|
|
}
|
|
}
|
|
|
|
void nca_save_section_file(nca_section_ctx_t *ctx, uint64_t ofs, uint64_t total_size, filepath_t *filepath) {
|
|
FILE *f_out = os_fopen(filepath->os_path, OS_MODE_WRITE);
|
|
|
|
if (f_out == NULL) {
|
|
fprintf(stderr, "Failed to open %s!\n", filepath->char_path);
|
|
return;
|
|
}
|
|
|
|
uint64_t read_size = 0x400000; /* 4 MB buffer. */
|
|
unsigned char *buf = malloc(read_size);
|
|
if (buf == NULL) {
|
|
fprintf(stderr, "Failed to allocate file-save buffer!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
memset(buf, 0xCC, read_size); /* Debug in case I fuck this up somehow... */
|
|
uint64_t end_ofs = ofs + total_size;
|
|
nca_section_fseek(ctx, ofs);
|
|
while (ofs < end_ofs) {
|
|
if (ofs + read_size >= end_ofs) read_size = end_ofs - ofs;
|
|
if (nca_section_fread(ctx, buf, read_size) != read_size) {
|
|
fprintf(stderr, "Failed to read file!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
if (fwrite(buf, 1, read_size, f_out) != read_size) {
|
|
fprintf(stderr, "Failed to write file!\n");
|
|
exit(EXIT_FAILURE);
|
|
}
|
|
ofs += read_size;
|
|
}
|
|
|
|
fclose(f_out);
|
|
|
|
free(buf);
|
|
}
|
|
|
|
void nca_save_section(nca_section_ctx_t *ctx) {
|
|
/* Save raw section file... */
|
|
uint64_t offset = 0;
|
|
uint64_t size = ctx->size;
|
|
if (!(ctx->tool_ctx->action & ACTION_RAW)) {
|
|
switch (ctx->type) {
|
|
case PFS0:
|
|
offset = ctx->pfs0_ctx.superblock->pfs0_offset;
|
|
size = ctx->pfs0_ctx.superblock->pfs0_size;
|
|
break;
|
|
case ROMFS:
|
|
offset = ctx->romfs_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_offset;
|
|
size = ctx->romfs_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_size;
|
|
break;
|
|
case BKTR:
|
|
if (ctx->tool_ctx->base_file != NULL) {
|
|
offset = ctx->bktr_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_offset;
|
|
size = ctx->bktr_ctx.ivfc_levels[IVFC_MAX_LEVEL - 1].data_size;
|
|
}
|
|
break;
|
|
case INVALID:
|
|
break;
|
|
}
|
|
} else if (ctx->type == BKTR && ctx->bktr_ctx.subsection_block != NULL && ctx->tool_ctx->base_file != NULL) {
|
|
size = ctx->bktr_ctx.relocation_block->patch_romfs_size;
|
|
}
|
|
|
|
/* Extract to file. */
|
|
filepath_t *secpath = &ctx->tool_ctx->settings.section_paths[ctx->section_num];
|
|
|
|
/* Handle overrides. */
|
|
if (ctx->type == PFS0 && ctx->pfs0_ctx.is_exefs && ctx->tool_ctx->settings.exefs_path.enabled && ctx->tool_ctx->settings.exefs_path.path.valid == VALIDITY_VALID) {
|
|
secpath = &ctx->tool_ctx->settings.exefs_path.path;
|
|
} else if (ctx->type == ROMFS && ctx->tool_ctx->settings.romfs_path.enabled && ctx->tool_ctx->settings.romfs_path.path.valid == VALIDITY_VALID) {
|
|
secpath = &ctx->tool_ctx->settings.romfs_path.path;
|
|
}
|
|
if (secpath != NULL && secpath->valid == VALIDITY_VALID) {
|
|
printf("Saving Section %"PRId32" to %s...\n", ctx->section_num, secpath->char_path);
|
|
printf("Size: %012"PRIx64"\n", size);
|
|
nca_save_section_file(ctx, offset, size, secpath);
|
|
}
|
|
|
|
switch (ctx->type) {
|
|
case PFS0:
|
|
nca_save_pfs0_section(ctx);
|
|
break;
|
|
case ROMFS:
|
|
nca_save_ivfc_section(ctx);
|
|
break;
|
|
case BKTR:
|
|
if (ctx->tool_ctx->base_file == NULL) {
|
|
fprintf(stderr, "Note: cannot save BKTR section without base romfs.\n");
|
|
break;
|
|
}
|
|
nca_save_bktr_section(ctx);
|
|
break;
|
|
case INVALID:
|
|
break;
|
|
}
|
|
}
|
|
|
|
void nca_save_pfs0_section(nca_section_ctx_t *ctx) {
|
|
if (ctx->superblock_hash_validity == VALIDITY_VALID && ctx->pfs0_ctx.header->magic == MAGIC_PFS0) {
|
|
/* Extract to directory. */
|
|
filepath_t *dirpath = NULL;
|
|
if (ctx->pfs0_ctx.is_exefs && ctx->tool_ctx->settings.exefs_dir_path.enabled) {
|
|
dirpath = &ctx->tool_ctx->settings.exefs_dir_path.path;
|
|
}
|
|
if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) {
|
|
dirpath = &ctx->tool_ctx->settings.section_dir_paths[ctx->section_num];
|
|
}
|
|
if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) {
|
|
os_makedir(dirpath->os_path);
|
|
for (uint32_t i = 0; i < ctx->pfs0_ctx.header->num_files; i++) {
|
|
nca_save_pfs0_file(ctx, i, dirpath);
|
|
}
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Error: section %"PRId32" is corrupted!\n", ctx->section_num);
|
|
return;
|
|
}
|
|
}
|
|
|
|
/* RomFS functions... */
|
|
int nca_is_romfs_file_updated(nca_section_ctx_t *ctx, uint64_t file_offset, uint64_t file_size) {
|
|
/* All files in a Base RomFS are "updated". */
|
|
if (ctx->type == ROMFS) {
|
|
return 1;
|
|
}
|
|
|
|
bktr_relocation_entry_t *first_reloc = bktr_get_relocation(ctx->bktr_ctx.relocation_block, file_offset);
|
|
bktr_relocation_entry_t *last_reloc = first_reloc;
|
|
while (last_reloc->virt_offset < file_offset + file_size) {
|
|
last_reloc++;
|
|
}
|
|
|
|
for (bktr_relocation_entry_t *cur_reloc = first_reloc; cur_reloc < last_reloc; cur_reloc++) {
|
|
if (cur_reloc->is_patch) {
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int nca_visit_romfs_file(nca_section_ctx_t *ctx, uint32_t file_offset, filepath_t *dir_path) {
|
|
romfs_fentry_t *entry;
|
|
if (ctx->type == ROMFS) {
|
|
entry = romfs_get_fentry(ctx->romfs_ctx.files, file_offset);
|
|
} else {
|
|
entry = romfs_get_fentry(ctx->bktr_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;
|
|
if (ctx->type == ROMFS) {
|
|
phys_offset = ctx->romfs_ctx.romfs_offset + ctx->romfs_ctx.header.data_offset + entry->offset;
|
|
} else {
|
|
phys_offset = ctx->bktr_ctx.romfs_offset + ctx->bktr_ctx.header.data_offset + entry->offset;
|
|
}
|
|
if ((ctx->tool_ctx->action & ACTION_ONLYUPDATEDROMFS) == 0 || nca_is_romfs_file_updated(ctx, phys_offset, entry->size)) {
|
|
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);
|
|
}
|
|
} else {
|
|
found_file = 0;
|
|
}
|
|
|
|
free(cur_path);
|
|
|
|
if (entry->sibling != ROMFS_ENTRY_EMPTY) {
|
|
return found_file | nca_visit_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) {
|
|
entry = romfs_get_direntry(ctx->romfs_ctx.directories, dir_offset);
|
|
} else {
|
|
entry = romfs_get_direntry(ctx->bktr_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_romfs_file(ctx, entry->file, cur_path);
|
|
}
|
|
if (entry->child != ROMFS_ENTRY_EMPTY) {
|
|
any_files |= nca_visit_romfs_dir(ctx, entry->child, cur_path);
|
|
}
|
|
|
|
if (any_files == 0 && ctx->type == BKTR && (ctx->tool_ctx->action & ACTION_ONLYUPDATEDROMFS)) {
|
|
os_rmdir(cur_path->os_path);
|
|
}
|
|
|
|
|
|
if (entry->sibling != ROMFS_ENTRY_EMPTY) {
|
|
nca_visit_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) {
|
|
if (ctx->romfs_ctx.header.header_size == ROMFS_HEADER_SIZE) {
|
|
if (ctx->tool_ctx->action & ACTION_LISTROMFS) {
|
|
filepath_t fakepath;
|
|
filepath_init(&fakepath);
|
|
filepath_set(&fakepath, "");
|
|
|
|
nca_visit_romfs_dir(ctx, 0, &fakepath);
|
|
} else {
|
|
filepath_t *dirpath = NULL;
|
|
if (ctx->tool_ctx->settings.romfs_dir_path.enabled) {
|
|
dirpath = &ctx->tool_ctx->settings.romfs_dir_path.path;
|
|
}
|
|
if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) {
|
|
dirpath = &ctx->tool_ctx->settings.section_dir_paths[ctx->section_num];
|
|
}
|
|
if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) {
|
|
os_makedir(dirpath->os_path);
|
|
nca_visit_romfs_dir(ctx, 0, dirpath);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "Error: section %"PRId32" is corrupted!\n", ctx->section_num);
|
|
}
|
|
|
|
void nca_save_bktr_section(nca_section_ctx_t *ctx) {
|
|
if (ctx->superblock_hash_validity == VALIDITY_VALID) {
|
|
if (ctx->bktr_ctx.header.header_size == ROMFS_HEADER_SIZE) {
|
|
if (ctx->tool_ctx->action & ACTION_LISTROMFS) {
|
|
filepath_t fakepath;
|
|
filepath_init(&fakepath);
|
|
filepath_set(&fakepath, "");
|
|
|
|
nca_visit_romfs_dir(ctx, 0, &fakepath);
|
|
} else {
|
|
filepath_t *dirpath = NULL;
|
|
if (ctx->tool_ctx->settings.romfs_dir_path.enabled) {
|
|
dirpath = &ctx->tool_ctx->settings.romfs_dir_path.path;
|
|
}
|
|
if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) {
|
|
dirpath = &ctx->tool_ctx->settings.section_dir_paths[ctx->section_num];
|
|
}
|
|
if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) {
|
|
os_makedir(dirpath->os_path);
|
|
nca_visit_romfs_dir(ctx, 0, dirpath);
|
|
}
|
|
}
|
|
|
|
return;
|
|
}
|
|
}
|
|
|
|
fprintf(stderr, "Error: section %"PRId32" is corrupted!\n", ctx->section_num);
|
|
}
|