diff --git a/Makefile b/Makefile index 8ba8c0b..5c1694a 100644 --- a/Makefile +++ b/Makefile @@ -17,7 +17,7 @@ all: .c.o: $(CC) $(INCLUDE) -c $(CFLAGS) -o $@ $< -hactool: sha.o aes.o rsa.o npdm.o bktr.o pki.o pfs0.o utils.o nca.o main.o filepath.o +hactool: sha.o aes.o rsa.o npdm.o bktr.o pki.o pfs0.o romfs.o utils.o nca.o main.o filepath.o $(CC) -o $@ $^ $(LDFLAGS) -L $(LIBDIR) aes.o: aes.h types.h @@ -36,6 +36,8 @@ nca.o: nca.h aes.h sha.h rsa.h bktr.h filepath.h types.h npdm.o: npdm.c types.h +romfs.o: ivfc.h types.h + rsa.o: rsa.h sha.h types.h sha.o: sha.h types.h diff --git a/ivfc.h b/ivfc.h index d726578..46ea64b 100644 --- a/ivfc.h +++ b/ivfc.h @@ -2,6 +2,9 @@ #define HACTOOL_IVFC_H #include "types.h" +#include "utils.h" +#include "settings.h" + #define IVFC_HEADER_SIZE 0xE0 #define IVFC_MAX_LEVEL 6 @@ -69,6 +72,23 @@ typedef struct { char name[]; } romfs_fentry_t; +typedef struct { + ivfc_hdr_t ivfc_header; + uint8_t _0xE0[0x58]; +} romfs_superblock_t; + +typedef struct { + romfs_superblock_t *superblock; + FILE *file; + hactool_ctx_t *tool_ctx; + validity_t superblock_hash_validity; + ivfc_level_ctx_t ivfc_levels[IVFC_MAX_LEVEL]; + uint64_t romfs_offset; + romfs_hdr_t header; + romfs_direntry_t *directories; + romfs_fentry_t *files; +} romfs_ctx_t; + #define ROMFS_ENTRY_EMPTY 0xFFFFFFFF static inline romfs_direntry_t *romfs_get_direntry(romfs_direntry_t *directories, uint32_t offset) { @@ -79,4 +99,9 @@ static inline romfs_fentry_t *romfs_get_fentry(romfs_fentry_t *files, uint32_t o return (romfs_fentry_t *)((char *)files + offset); } +void romfs_process(romfs_ctx_t *ctx); +void romfs_save(romfs_ctx_t *ctx); +void romfs_print(romfs_ctx_t *ctx); + + #endif diff --git a/main.c b/main.c index e5aa546..343313e 100644 --- a/main.c +++ b/main.c @@ -26,7 +26,7 @@ static void usage(void) { " -r, --raw Keep raw data, don't unpack.\n" " -y, --verify Verify hashes and signatures.\n" " -d, --dev Decrypt with development keys instead of retail.\n" - " -t, --intype=type Specify input file type [nca, pfs0]\n" + " -t, --intype=type Specify input file type [nca, pfs0, romfs]\n" " --titlekey=key Set title key for Rights ID crypto titles.\n" " --contentkey=key Set raw key for NCA body decryption.\n" "NCA options:\n" @@ -49,6 +49,9 @@ static void usage(void) { " --basenca Set Base NCA to use with update partitions.\n" "PFS0 options:\n" " --outdir=dir Specify PFS0 directory path.\n" + "RomFS options:\n" + " --romfsdir=dir Specify RomFS directory path.\n" + " --listromfs List files in RomFS.\n" "\n", __TIME__, __DATE__, prog_name); exit(EXIT_FAILURE); } @@ -171,7 +174,22 @@ int main(int argc, char **argv) { nca_ctx.tool_ctx->file_type = FILETYPE_NCA; } else if (!strcmp(optarg, "pfs0") || !strcmp(optarg, "exefs")) { nca_ctx.tool_ctx->file_type = FILETYPE_PFS0; - } + } else if (!strcmp(optarg, "romfs")) { + nca_ctx.tool_ctx->file_type = FILETYPE_ROMFS; + } + /* } else if (!strcmp(optarg, "hfs0")) { + * nca_ctx.tool_ctx->file_type = FILETYPE_HFS0; + * } + * } else if (!strcmp(optarg, "xci") || !strcmp(optarg, "gamecard") || !strcmp(optarg, "gc")) { + * nca_ctx.tool_ctx->file_type = FILETYPE_XCI; + * } + * } else if (!strcmp(optarg, "package2") || !strcmp(optarg, "pk21")) { + * nca_ctx.tool_ctx->file_type = FILETYPE_PACKAGE2; + * } + * } else if (!strcmp(optarg, "package1") || !strcmp(optarg, "pk11")) { + * nca_ctx.tool_ctx->file_type = FILETYPE_PACKAGE1; + * } + */ break; case 0: filepath_set(&nca_ctx.tool_ctx->settings.section_paths[0], optarg); break; @@ -266,51 +284,72 @@ int main(int argc, char **argv) { return EXIT_FAILURE; } - if (tool_ctx.file_type == FILETYPE_NCA) { - if (nca_ctx.tool_ctx->base_nca_ctx != NULL) { - memcpy(&base_ctx.settings.keyset, &tool_ctx.settings.keyset, sizeof(nca_keyset_t)); - nca_ctx.tool_ctx->base_nca_ctx->tool_ctx = &base_ctx; - nca_process(nca_ctx.tool_ctx->base_nca_ctx); - int found_romfs = 0; - for (unsigned int i = 0; i < 4; i++) { - if (nca_ctx.tool_ctx->base_nca_ctx->section_contexts[i].is_present && nca_ctx.tool_ctx->base_nca_ctx->section_contexts[i].type == ROMFS) { - found_romfs = 1; - break; + switch (tool_ctx.file_type) { + case FILETYPE_NCA: { + if (nca_ctx.tool_ctx->base_nca_ctx != NULL) { + memcpy(&base_ctx.settings.keyset, &tool_ctx.settings.keyset, sizeof(nca_keyset_t)); + nca_ctx.tool_ctx->base_nca_ctx->tool_ctx = &base_ctx; + nca_process(nca_ctx.tool_ctx->base_nca_ctx); + int found_romfs = 0; + for (unsigned int i = 0; i < 4; i++) { + if (nca_ctx.tool_ctx->base_nca_ctx->section_contexts[i].is_present && nca_ctx.tool_ctx->base_nca_ctx->section_contexts[i].type == ROMFS) { + found_romfs = 1; + break; + } + } + if (found_romfs == 0) { + fprintf(stderr, "Unable to locate RomFS in base NCA!\n"); + return EXIT_FAILURE; } } - if (found_romfs == 0) { - fprintf(stderr, "Unable to locate RomFS in base NCA!\n"); - return EXIT_FAILURE; - } - } - nca_ctx.file = tool_ctx.file; - nca_process(&nca_ctx); - nca_free_section_contexts(&nca_ctx); - - if (nca_ctx.tool_ctx->base_file != NULL) { - fclose(nca_ctx.tool_ctx->base_file); - if (nca_ctx.tool_ctx->base_file_type == BASEFILE_NCA) { - nca_free_section_contexts(nca_ctx.tool_ctx->base_nca_ctx); - free(nca_ctx.tool_ctx->base_nca_ctx); - } - } - } else if (tool_ctx.file_type == FILETYPE_PFS0) { - pfs0_ctx_t pfs0_ctx; - memset(&pfs0_ctx, 0, sizeof(pfs0_ctx)); - pfs0_ctx.file = tool_ctx.file; - pfs0_ctx.tool_ctx = &tool_ctx; - pfs0_process(&pfs0_ctx); - if (pfs0_ctx.header) { - free(pfs0_ctx.header); + nca_ctx.file = tool_ctx.file; + nca_process(&nca_ctx); + nca_free_section_contexts(&nca_ctx); + + if (nca_ctx.tool_ctx->base_file != NULL) { + fclose(nca_ctx.tool_ctx->base_file); + if (nca_ctx.tool_ctx->base_file_type == BASEFILE_NCA) { + nca_free_section_contexts(nca_ctx.tool_ctx->base_nca_ctx); + free(nca_ctx.tool_ctx->base_nca_ctx); + } + } + break; } - if (pfs0_ctx.npdm) { - free(pfs0_ctx.npdm); + case FILETYPE_PFS0: { + pfs0_ctx_t pfs0_ctx; + memset(&pfs0_ctx, 0, sizeof(pfs0_ctx)); + pfs0_ctx.file = tool_ctx.file; + pfs0_ctx.tool_ctx = &tool_ctx; + pfs0_process(&pfs0_ctx); + if (pfs0_ctx.header) { + free(pfs0_ctx.header); + } + if (pfs0_ctx.npdm) { + free(pfs0_ctx.npdm); + } + break; + } + case FILETYPE_ROMFS: { + romfs_ctx_t romfs_ctx; + memset(&romfs_ctx, 0, sizeof(romfs_ctx)); + romfs_ctx.file = tool_ctx.file; + romfs_ctx.tool_ctx = &tool_ctx; + romfs_process(&romfs_ctx); + if (romfs_ctx.files) { + free(romfs_ctx.files); + } + if (romfs_ctx.directories) { + free(romfs_ctx.directories); + } + break; + } + default: { + fprintf(stderr, "Unknown File Type!\n\n"); + usage(); } } - - if (tool_ctx.file != NULL) { fclose(tool_ctx.file); } diff --git a/nca.h b/nca.h index 3563856..afe1291 100644 --- a/nca.h +++ b/nca.h @@ -19,11 +19,6 @@ typedef struct { uint8_t _0x8[0x8]; /* Padding. */ } nca_section_entry_t; -typedef struct { - ivfc_hdr_t ivfc_header; - uint8_t _0xE0[0x58]; -} romfs_superblock_t; - typedef struct { ivfc_hdr_t ivfc_header; uint8_t _0xE0[0x18]; @@ -31,17 +26,6 @@ typedef struct { bktr_header_t subsection_header; } bktr_superblock_t; -typedef struct { - romfs_superblock_t *superblock; - FILE *file; - validity_t superblock_hash_validity; - ivfc_level_ctx_t ivfc_levels[IVFC_MAX_LEVEL]; - uint64_t romfs_offset; - romfs_hdr_t header; - romfs_direntry_t *directories; - romfs_fentry_t *files; -} romfs_ctx_t; - typedef struct { bktr_superblock_t *superblock; FILE *file; diff --git a/pfs0.c b/pfs0.c index 082517a..fb26f94 100644 --- a/pfs0.c +++ b/pfs0.c @@ -4,7 +4,7 @@ void pfs0_process(pfs0_ctx_t *ctx) { /* Read *just* safe amount. */ pfs0_header_t raw_header; - fseek(ctx->file, 0, SEEK_SET); + fseeko64(ctx->file, 0, SEEK_SET); if (fread(&raw_header, 1, sizeof(raw_header), ctx->file) != sizeof(raw_header)) { fprintf(stderr, "Failed to read PFS0 header!\n"); exit(EXIT_FAILURE); @@ -22,7 +22,7 @@ void pfs0_process(pfs0_ctx_t *ctx) { exit(EXIT_FAILURE); } - fseek(ctx->file, 0, SEEK_SET); + fseeko64(ctx->file, 0, SEEK_SET); if (fread(ctx->header, 1, header_size, ctx->file) != header_size) { fprintf(stderr, "Failed to read PFS0 header!\n"); exit(EXIT_FAILURE); @@ -51,7 +51,7 @@ void pfs0_process(pfs0_ctx_t *ctx) { fprintf(stderr, "Failed to allocate NPDM!\n"); exit(EXIT_FAILURE); } - fseek(ctx->file, pfs0_get_header_size(ctx->header) + cur_file->offset, SEEK_SET); + fseeko64(ctx->file, pfs0_get_header_size(ctx->header) + cur_file->offset, SEEK_SET); if (fread(ctx->npdm, 1, cur_file->size, ctx->file) != cur_file->size) { fprintf(stderr, "Failed to read NPDM!\n"); exit(EXIT_FAILURE); diff --git a/romfs.c b/romfs.c new file mode 100644 index 0000000..330fa9c --- /dev/null +++ b/romfs.c @@ -0,0 +1,138 @@ +#include +#include "types.h" +#include "utils.h" +#include "ivfc.h" + +/* RomFS functions... */ +void romfs_visit_file(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) { + romfs_visit_file(ctx, entry->sibling, dir_path); + } +} + +void romfs_visit_dir(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) { + romfs_visit_file(ctx, entry->file, cur_path); + } + if (entry->child != ROMFS_ENTRY_EMPTY) { + romfs_visit_dir(ctx, entry->child, cur_path); + } + if (entry->sibling != ROMFS_ENTRY_EMPTY) { + romfs_visit_dir(ctx, entry->sibling, parent_path); + } + + free(cur_path); +} + +void romfs_process(romfs_ctx_t *ctx) { + ctx->romfs_offset = 0; + fseeko64(ctx->file, ctx->romfs_offset, SEEK_SET); + if (fread(&ctx->header, 1, sizeof(romfs_hdr_t), ctx->file) != sizeof(romfs_hdr_t)) { + fprintf(stderr, "Failed to read RomFS header!\n"); + return; + } + + if ((ctx->tool_ctx->action & (ACTION_EXTRACT | ACTION_LISTROMFS)) && ctx->header.header_size == 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 RomFS directory cache!\n"); + 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); + 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); + } + + ctx->files = calloc(1, ctx->header.file_meta_table_size); + if (ctx->files == NULL) { + fprintf(stderr, "Failed to allocate 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 RomFS file cache!\n"); + exit(EXIT_FAILURE); + } + } + + /* If there's ever anything meaningful to print about RomFS, uncomment and implement. + * + * if (ctx->tool_ctx->action & ACTION_INFO) { + * romfs_print(ctx); + * } + */ + + if (ctx->tool_ctx->action & ACTION_EXTRACT) { + romfs_save(ctx); + } + +} + +void romfs_save(romfs_ctx_t *ctx) { + if (ctx->tool_ctx->action & ACTION_LISTROMFS) { + filepath_t fakepath; + filepath_init(&fakepath); + filepath_set(&fakepath, ""); + + 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) { + os_makedir(dirpath->os_path); + romfs_visit_dir(ctx, 0, dirpath); + } + } + +} + +void romfs_print(romfs_ctx_t *ctx) { + /* Is there anything meaningful to print here? */ + fprintf(stderr, "Error: RomFS printing not implemented.\n"); +} \ No newline at end of file diff --git a/settings.h b/settings.h index f2bf374..0c4b5bc 100644 --- a/settings.h +++ b/settings.h @@ -57,7 +57,12 @@ typedef struct { enum hactool_file_type { FILETYPE_NCA, - FILETYPE_PFS0 + FILETYPE_PFS0, + FILETYPE_ROMFS, + /* FILETYPE_HFS0, */ + /* FILETYPE_XCI, */ + /* FILETYPE_PACKAGE2, */ + /* FILETYPE_PACKAGE1, */ }; #define ACTION_INFO (1<<0)