diff --git a/Makefile b/Makefile index d62c6ef..1e709bc 100644 --- a/Makefile +++ b/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 packages.o pki.o pfs0.o hfs0.o romfs.o utils.o nca.o xci.o main.o filepath.o ConvertUTF.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 nca.o xci.o main.o filepath.o ConvertUTF.o $(CC) -o $@ $^ $(LDFLAGS) -L $(LIBDIR) aes.o: aes.h types.h @@ -26,9 +26,11 @@ filepath.o: filepath.c types.h hfs0.o: hfs0.h types.h +kip.o: kip.h types.h + main.o: main.c pki.h types.h -packages.o: packages.h aes.h types.h +packages.o: packages.h aes.h kip.h types.h pfs0.o: pfs0.h types.h diff --git a/kip.c b/kip.c new file mode 100644 index 0000000..2e0b287 --- /dev/null +++ b/kip.c @@ -0,0 +1,146 @@ +#include +#include +#include "kip.h" +#include "npdm.h" + +void ini1_process(ini1_ctx_t *ctx) { + /* Read *just* safe amount. */ + ini1_header_t raw_header; + fseeko64(ctx->file, 0, SEEK_SET); + if (fread(&raw_header, 1, sizeof(raw_header), ctx->file) != sizeof(raw_header)) { + fprintf(stderr, "Failed to read INI1 header!\n"); + exit(EXIT_FAILURE); + } + + if (raw_header.magic != MAGIC_INI1 || raw_header.num_processes > INI1_MAX_KIPS) { + printf("Error: INI1 is corrupt!\n"); + exit(EXIT_FAILURE); + } + + ctx->header = malloc(raw_header.size); + if (ctx->header == NULL) { + fprintf(stderr, "Failed to allocate INI1 header!\n"); + exit(EXIT_FAILURE); + } + + fseeko64(ctx->file, 0, SEEK_SET); + if (fread(ctx->header, 1, raw_header.size, ctx->file) != raw_header.size) { + fprintf(stderr, "Failed to read INI1!\n"); + exit(EXIT_FAILURE); + } + + uint64_t offset = 0; + for (unsigned int i = 0; i < ctx->header->num_processes; i++) { + ctx->kips[i].tool_ctx = ctx->tool_ctx; + ctx->kips[i].header = (kip1_header_t *)&ctx->header->kip_data[offset]; + if (ctx->kips[i].header->magic != MAGIC_KIP1) { + fprintf(stderr, "INI1 is corrupted!\n"); + exit(EXIT_FAILURE); + } + offset += kip1_get_size(&ctx->kips[i]); + } + + if (ctx->tool_ctx->action & ACTION_INFO) { + ini1_print(ctx); + } + + if (ctx->tool_ctx->action & ACTION_EXTRACT) { + ini1_save(ctx); + } +} + +void ini1_print(ini1_ctx_t *ctx) { + printf("INI1:\n"); + printf(" Number of Processes: %02"PRIx32"\n", ctx->header->num_processes); + printf(" Size: %08"PRIx32"\n", ctx->header->size); + printf("\n"); + for (unsigned int i = 0; i < ctx->header->num_processes; i++) { + printf("Process %02"PRIx32":\n", i); + kip1_print(&ctx->kips[i], 1); + printf("\n"); + } + printf("\n"); +} + +void ini1_save(ini1_ctx_t *ctx) { + filepath_t *dirpath; + if (ctx->tool_ctx->file_type == FILETYPE_INI1 && ctx->tool_ctx->settings.out_dir_path.enabled) { + dirpath = &ctx->tool_ctx->settings.out_dir_path.path; + } + if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) { + dirpath = &ctx->tool_ctx->settings.ini1_dir_path; + } + if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) { + os_makedir(dirpath->os_path); + for (unsigned int i = 0; i < ctx->header->num_processes; i++) { + char padded_name[0x20]; + memset(&padded_name, 0, sizeof(padded_name)); + memcpy(&padded_name, ctx->kips[i].header->name, sizeof(ctx->kips[i].header->name)); + strcat(padded_name, ".kip1"); + printf("Saving %s to %s/%s...\n", padded_name, dirpath->char_path, padded_name); + save_buffer_to_directory_file(ctx->kips[i].header, kip1_get_size(&ctx->kips[i]), dirpath, padded_name); + } + } +} + +void kip1_process(kip1_ctx_t *ctx) { + /* Read *just* safe amount. */ + kip1_header_t raw_header; + fseeko64(ctx->file, 0, SEEK_SET); + if (fread(&raw_header, 1, sizeof(raw_header), ctx->file) != sizeof(raw_header)) { + fprintf(stderr, "Failed to read KIP1 header!\n"); + exit(EXIT_FAILURE); + } + + if (raw_header.magic != MAGIC_KIP1) { + printf("Error: KIP1 is corrupt!\n"); + exit(EXIT_FAILURE); + } + + uint64_t size = kip1_get_size_from_header(&raw_header); + ctx->header = malloc(size); + if (ctx->header == NULL) { + fprintf(stderr, "Failed to allocate KIP1 header!\n"); + exit(EXIT_FAILURE); + } + + fseeko64(ctx->file, 0, SEEK_SET); + if (fread(ctx->header, 1, size, ctx->file) != size) { + fprintf(stderr, "Failed to read KIP1!\n"); + exit(EXIT_FAILURE); + } + + if (ctx->tool_ctx->action & ACTION_INFO) { + kip1_print(ctx, 0); + } + + if (ctx->tool_ctx->action & ACTION_EXTRACT) { + kip1_save(ctx); + } +} + +void kip1_print(kip1_ctx_t *ctx, int suppress) { + if (!suppress) printf("KIP1:\n"); + printf(" Title ID: %016"PRIx64"\n", ctx->header->title_id); + char padded_name[13]; + memset(&padded_name, 0, sizeof(padded_name)); + memcpy(&padded_name, ctx->header->name, sizeof(ctx->header->name)); + printf(" Name: %s\n", padded_name); + printf(" Process Category: %s\n", npdm_get_proc_category(ctx->header->process_category)); + printf(" Main Thread Priority: %"PRId8"\n", ctx->header->main_thread_priority); + printf(" Default CPU Core: %"PRId8"\n", ctx->header->default_core); + printf(" Is 64 Bit: %s\n", (ctx->header->flags & (1 << 3)) ? "True" : "False"); + printf(" Is Address Space 64 Bit: %s\n", (ctx->header->flags & (1 << 4)) ? "True" : "False"); + printf(" Sections:\n"); + printf(" .text: %08"PRIx32"-%08"PRIx32"\n", ctx->header->section_headers[0].out_offset, ctx->header->section_headers[0].out_offset + align(ctx->header->section_headers[0].out_size, 0x1000)); + printf(" .rodata: %08"PRIx32"-%08"PRIx32"\n", ctx->header->section_headers[1].out_offset, ctx->header->section_headers[1].out_offset + align(ctx->header->section_headers[1].out_size, 0x1000)); + printf(" .rwdata: %08"PRIx32"-%08"PRIx32"\n", ctx->header->section_headers[2].out_offset, ctx->header->section_headers[2].out_offset + align(ctx->header->section_headers[2].out_size, 0x1000)); + printf(" .bss: %08"PRIx32"-%08"PRIx32"\n", ctx->header->section_headers[3].out_offset, ctx->header->section_headers[3].out_offset + align(ctx->header->section_headers[3].out_size, 0x1000)); + printf(" Kernel Access Control:\n"); + kac_print(ctx->header->capabilities, 0x20); + printf("\n"); +} + +void kip1_save(kip1_ctx_t *ctx) { + /* Do nothing. */ +} \ No newline at end of file diff --git a/kip.h b/kip.h new file mode 100644 index 0000000..247eaff --- /dev/null +++ b/kip.h @@ -0,0 +1,72 @@ +#ifndef HACTOOL_KIP_H +#define HACTOOL_KIP_H +#include "types.h" +#include "utils.h" +#include "settings.h" + +#define MAGIC_INI1 0x31494E49 +#define MAGIC_KIP1 0x3150494B +#define INI1_MAX_KIPS 0x50 + +typedef struct { + uint32_t magic; + uint32_t size; + uint32_t num_processes; + uint32_t _0xC; + char kip_data[]; +} ini1_header_t; + +typedef struct { + uint32_t out_offset; + uint32_t out_size; + uint32_t compressed_size; + uint32_t attribute; +} kip_section_header_t; + +typedef struct { + uint32_t magic; + char name[0xC]; + uint64_t title_id; + uint32_t process_category; + uint8_t main_thread_priority; + uint8_t default_core; + uint8_t _0x1E; + uint8_t flags; + kip_section_header_t section_headers[6]; + uint32_t capabilities[0x20]; + unsigned char data[]; +} kip1_header_t; + +typedef struct { + FILE *file; + hactool_ctx_t *tool_ctx; + kip1_header_t *header; +} kip1_ctx_t; + +typedef struct { + FILE *file; + hactool_ctx_t *tool_ctx; + ini1_header_t *header; + kip1_ctx_t kips[INI1_MAX_KIPS]; +} ini1_ctx_t; + +void ini1_process(ini1_ctx_t *ctx); +void ini1_print(ini1_ctx_t *ctx); +void ini1_save(ini1_ctx_t *ctx); + +void kip1_process(kip1_ctx_t *ctx); +void kip1_print(kip1_ctx_t *ctx, int suppress); +void kip1_save(kip1_ctx_t *ctx); + +static inline uint64_t kip1_get_size(kip1_ctx_t *ctx) { + /* Header + .text + .rodata + .rwdata */ + return 0x100 + ctx->header->section_headers[0].compressed_size + ctx->header->section_headers[1].compressed_size + ctx->header->section_headers[2].compressed_size; +} + +static inline uint64_t kip1_get_size_from_header(kip1_header_t *header) { + /* Header + .text + .rodata + .rwdata */ + return 0x100 + header->section_headers[0].compressed_size + header->section_headers[1].compressed_size + header->section_headers[2].compressed_size; +} + + +#endif \ No newline at end of file diff --git a/main.c b/main.c index befae43..d00883f 100644 --- a/main.c +++ b/main.c @@ -30,7 +30,7 @@ static void usage(void) { " -y, --verify Verify hashes and signatures.\n" " -d, --dev Decrypt with development keys instead of retail.\n" " -k, --keyset Load keys from an external file.\n" - " -t, --intype=type Specify input file type [nca, xci, pfs0, romfs, hfs0, npdm, pk11]\n" + " -t, --intype=type Specify input file type [nca, xci, pfs0, romfs, hfs0, npdm, pk11, pk21, ini1, kip1]\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" @@ -72,6 +72,12 @@ static void usage(void) { "Package1 options:\n" " --package1dir=dir Specify Package1 directory path.\n" " --outdir=dir Specify Package1 directory path. Overrides previous path, if present.\n" + "Package2 options:\n" + " --package2dir=dir Specify Package1 directory path.\n" + " --outdir=dir Specify Package1 directory path. Overrides previous path, if present.\n" + "INI1 options:\n" + " --ini1dir=dir Specify Package1 directory path.\n" + " --outdir=dir Specify Package1 directory path. Overrides previous path, if present.\n" "\n", __TIME__, __DATE__, prog_name); exit(EXIT_FAILURE); } @@ -137,6 +143,9 @@ int main(int argc, char **argv) { {"normaldir", 1, NULL, 24}, {"securedir", 1, NULL, 25}, {"package1dir", 1, NULL, 26}, + {"package2dir", 1, NULL, 27}, + {"ini1dir", 1, NULL, 28}, + {"extractini1", 0, NULL, 29}, {NULL, 0, NULL, 0}, }; @@ -182,6 +191,10 @@ int main(int argc, char **argv) { nca_ctx.tool_ctx->file_type = FILETYPE_PACKAGE1; } else if (!strcmp(optarg, "package2") || !strcmp(optarg, "pk21")) { nca_ctx.tool_ctx->file_type = FILETYPE_PACKAGE2; + } else if (!strcmp(optarg, "ini1")) { + nca_ctx.tool_ctx->file_type = FILETYPE_INI1; + } else if (!strcmp(optarg, "kip1") || !strcmp(optarg, "kip")) { + nca_ctx.tool_ctx->file_type = FILETYPE_KIP1; } break; case 0: filepath_set(&nca_ctx.tool_ctx->settings.section_paths[0], optarg); break; @@ -280,6 +293,15 @@ int main(int argc, char **argv) { case 26: filepath_set(&tool_ctx.settings.pk11_dir_path, optarg); break; + case 27: + filepath_set(&tool_ctx.settings.pk21_dir_path, optarg); + break; + case 28: + filepath_set(&tool_ctx.settings.ini1_dir_path, optarg); + break; + case 29: + tool_ctx.action |= ACTION_EXTRACTINI1; + break; default: usage(); return EXIT_FAILURE; @@ -445,6 +467,28 @@ int main(int argc, char **argv) { } break; } + case FILETYPE_INI1: { + ini1_ctx_t ini1_ctx; + memset(&ini1_ctx, 0, sizeof(ini1_ctx)); + ini1_ctx.file = tool_ctx.file; + ini1_ctx.tool_ctx = &tool_ctx; + ini1_process(&ini1_ctx); + if (ini1_ctx.header) { + free(ini1_ctx.header); + } + break; + } + case FILETYPE_KIP1: { + kip1_ctx_t kip1_ctx; + memset(&kip1_ctx, 0, sizeof(kip1_ctx)); + kip1_ctx.file = tool_ctx.file; + kip1_ctx.tool_ctx = &tool_ctx; + kip1_process(&kip1_ctx); + if (kip1_ctx.header) { + free(kip1_ctx.header); + } + break; + } case FILETYPE_XCI: { xci_ctx_t xci_ctx; memset(&xci_ctx, 0, sizeof(xci_ctx)); diff --git a/nca.c b/nca.c index 53297f8..833b98a 100644 --- a/nca.c +++ b/nca.c @@ -625,7 +625,7 @@ void nca_print_sections(nca_ctx_t *ctx) { /* Print out information about the NCA. */ void nca_print(nca_ctx_t *ctx) { printf("\nNCA:\n"); - print_magic("Magic: ", ctx->header.magic); + 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) { diff --git a/npdm.c b/npdm.c index 69a9a4d..3e6b2a9 100644 --- a/npdm.c +++ b/npdm.c @@ -212,8 +212,8 @@ const fs_perm_t fs_permissions_bool[MAX_FS_PERM_BOOL] = { {"Unknown (0x1A)", 0x8000000000004020} }; -char *npdm_get_proc_category(npdm_t *npdm) { - switch (npdm->process_category) { +char *npdm_get_proc_category(int process_category) { + switch (process_category) { case 0: return "Regular Title"; case 1: diff --git a/npdm.h b/npdm.h index 819e3b5..31dd5aa 100644 --- a/npdm.h +++ b/npdm.h @@ -134,4 +134,7 @@ static inline npdm_aci0_t *npdm_get_aci0(npdm_t *npdm) { void npdm_print(npdm_t *npdm, hactool_ctx_t *tool_ctx); +char *npdm_get_proc_category(int process_category); +void kac_print(uint32_t *descriptors, uint32_t num_descriptors); + #endif diff --git a/packages.c b/packages.c index 7245d6c..6dfc800 100644 --- a/packages.c +++ b/packages.c @@ -194,7 +194,20 @@ void pk21_process(pk21_ctx_t *ctx) { offset += ctx->header.section_sizes[i]; } - /* TODO: Parse INI1 */ + ctx->ini1_ctx.tool_ctx = ctx->tool_ctx; + ctx->ini1_ctx.header = (ini1_header_t *)(ctx->sections + ctx->header.section_sizes[0]); + if (ctx->ini1_ctx.header->magic == MAGIC_INI1 && ctx->ini1_ctx.header->num_processes <= INI1_MAX_KIPS) { + offset = 0; + for (unsigned int i = 0; i < ctx->ini1_ctx.header->num_processes; i++) { + ctx->ini1_ctx.kips[i].tool_ctx = ctx->tool_ctx; + ctx->ini1_ctx.kips[i].header = (kip1_header_t *)&ctx->ini1_ctx.header->kip_data[offset]; + if (ctx->ini1_ctx.kips[i].header->magic != MAGIC_KIP1) { + fprintf(stderr, "INI1 is corrupted!\n"); + exit(EXIT_FAILURE); + } + offset += kip1_get_size(&ctx->ini1_ctx.kips[i]); + } + } if (ctx->tool_ctx->action & ACTION_INFO) { pk21_print(ctx); @@ -244,12 +257,49 @@ void pk21_print(pk21_ctx_t *ctx) { printf(" Load Address: %08"PRIx32"\n", ctx->header.section_offsets[i] + 0x80000000); printf(" Size: %08"PRIx32"\n", ctx->header.section_sizes[i]); } - printf("\n"); + ini1_print(&ctx->ini1_ctx); } void pk21_save(pk21_ctx_t *ctx) { - printf("Saving PK21 currently not implemented.\n"); - /* TODO: Save sections + extract INI1 */ + /* Extract to directory. */ + filepath_t *dirpath = NULL; + if (ctx->tool_ctx->file_type == FILETYPE_PACKAGE2 && ctx->tool_ctx->settings.out_dir_path.enabled) { + dirpath = &ctx->tool_ctx->settings.out_dir_path.path; + } + if (dirpath == NULL || dirpath->valid != VALIDITY_VALID) { + dirpath = &ctx->tool_ctx->settings.pk21_dir_path; + } + if (dirpath != NULL && dirpath->valid == VALIDITY_VALID) { + os_makedir(dirpath->os_path); + + /* Save Decrypted.bin */ + printf("Saving decrypted binary to %s/Decrypted.bin\n", dirpath->char_path); + char *decrypted_bin = malloc(ctx->package_size); + if (decrypted_bin == NULL) { + fprintf(stderr, "Failed to allocate buffer!\n"); + exit(EXIT_FAILURE); + } + memcpy(decrypted_bin, &ctx->header, 0x200); + memcpy(decrypted_bin + sizeof(ctx->header), ctx->sections, ctx->package_size - 0x200); + save_buffer_to_directory_file(decrypted_bin, ctx->package_size, dirpath, "Decrypted.bin"); + free(decrypted_bin); + + /* Save Kernel.bin */ + printf("Saving Kernel.bin to %s/Kernel.bin...\n", dirpath->char_path); + save_buffer_to_directory_file(ctx->sections, ctx->header.section_sizes[0], dirpath, "Kernel.bin"); + + /* Save INI1.bin */ + printf("Saving INI1.bin to %s/INI1.bin...\n", dirpath->char_path); + save_buffer_to_directory_file(ctx->sections + ctx->header.section_sizes[0], ctx->header.section_sizes[1], dirpath, "INI1.bin"); + } + if (ctx->ini1_ctx.header != NULL && (ctx->tool_ctx->action & ACTION_EXTRACTINI1 || ctx->tool_ctx->settings.ini1_dir_path.valid == VALIDITY_VALID)) { + filepath_t *ini1_dirpath = &ctx->tool_ctx->settings.ini1_dir_path; + if (ini1_dirpath->valid != VALIDITY_VALID && dirpath != NULL && dirpath->valid == VALIDITY_VALID) { + filepath_copy(ini1_dirpath, dirpath); + filepath_append(ini1_dirpath, "INI1"); + } + ini1_save(&ctx->ini1_ctx); + } } \ No newline at end of file diff --git a/packages.h b/packages.h index 78b8f8a..75b6070 100644 --- a/packages.h +++ b/packages.h @@ -4,6 +4,7 @@ #include "types.h" #include "utils.h" #include "settings.h" +#include "kip.h" #define MAGIC_PK11 0x31314B50 #define MAGIC_PK21 0x31324B50 @@ -87,6 +88,7 @@ typedef struct { validity_t section_validities[4]; unsigned char *sections; pk21_header_t header; + ini1_ctx_t ini1_ctx; } pk21_ctx_t; void pk21_process(pk21_ctx_t *ctx); diff --git a/settings.h b/settings.h index 38911d1..6bbbf2d 100644 --- a/settings.h +++ b/settings.h @@ -57,6 +57,8 @@ typedef struct { filepath_t pfs0_dir_path; filepath_t hfs0_dir_path; filepath_t pk11_dir_path; + filepath_t pk21_dir_path; + filepath_t ini1_dir_path; filepath_t dec_nca_path; filepath_t rootpt_dir_path; filepath_t update_dir_path; @@ -74,7 +76,9 @@ enum hactool_file_type FILETYPE_XCI, FILETYPE_NPDM, FILETYPE_PACKAGE1, - FILETYPE_PACKAGE2 + FILETYPE_PACKAGE2, + FILETYPE_INI1, + FILETYPE_KIP1 }; #define ACTION_INFO (1<<0) @@ -83,6 +87,7 @@ enum hactool_file_type #define ACTION_RAW (1<<3) #define ACTION_LISTROMFS (1<<4) #define ACTION_DEV (1<<5) +#define ACTION_EXTRACTINI1 (1<<6) struct nca_ctx; /* This will get re-defined by nca.h. */