From 9b13c5cd7c226e161f5171f7a918aea3675e7193 Mon Sep 17 00:00:00 2001 From: Michael Scire Date: Mon, 23 Jul 2018 23:18:48 -0700 Subject: [PATCH] Add support for KIP1 decompression. --- kip.c | 116 +++++++++++++++++++++++++++++++++++++++++++++++------ main.c | 13 +++++- settings.h | 4 +- 3 files changed, 118 insertions(+), 15 deletions(-) diff --git a/kip.c b/kip.c index 80765f2..9aa425b 100644 --- a/kip.c +++ b/kip.c @@ -129,6 +129,81 @@ const char *kip1_get_json(kip1_ctx_t *ctx) { return output_str; } +void kip1_blz_uncompress(void *hdr_end) { + uint32_t addl_size = ((uint32_t *)hdr_end)[-1]; + uint32_t header_size = ((uint32_t *)hdr_end)[-2]; + uint32_t cmp_and_hdr_size = ((uint32_t *)hdr_end)[-3]; + + unsigned char *cmp_start = (unsigned char *)(((uintptr_t)hdr_end) - cmp_and_hdr_size); + uint32_t cmp_ofs = cmp_and_hdr_size - header_size; + uint32_t out_ofs = cmp_and_hdr_size + addl_size; + + while (out_ofs) { + unsigned char control = cmp_start[--cmp_ofs]; + for (unsigned int i = 0; i < 8; i++) { + if (control & 0x80) { + if (cmp_ofs < 2) { + fprintf(stderr, "KIP1 decompression out of bounds!\n"); + exit(EXIT_FAILURE); + } + cmp_ofs -= 2; + uint16_t seg_val = ((unsigned int)cmp_start[cmp_ofs+1] << 8) | cmp_start[cmp_ofs]; + uint32_t seg_size = ((seg_val >> 12) & 0xF) + 3; + uint32_t seg_ofs = (seg_val & 0x0FFF) + 3; + if (out_ofs < seg_size) { + /* Kernel restricts segment copy to stay in bounds. */ + seg_size = out_ofs; + } + out_ofs -= seg_size; + + for (unsigned int j = 0; j < seg_size; j++) { + cmp_start[out_ofs + j] = cmp_start[out_ofs + j + seg_ofs]; + } + } else { + /* Copy directly. */ + if (cmp_ofs < 1) { + fprintf(stderr, "KIP1 decompression out of bounds!\n"); + exit(EXIT_FAILURE); + } + cmp_start[--out_ofs] = cmp_start[--cmp_ofs]; + } + control <<= 1; + if (out_ofs == 0) { + return; + } + } + } +} + +void *kip1_uncompress(kip1_ctx_t *ctx, uint64_t *size) { + /* Make new header with correct sizes, fixed flags. */ + kip1_header_t new_header = *ctx->header; + for (unsigned int i = 0; i < 3; i++) { + new_header.section_headers[i].compressed_size = new_header.section_headers[i].out_size; + } + new_header.flags &= 0xF8; + + *size = kip1_get_size_from_header(&new_header); + unsigned char *new_kip = calloc(1, *size); + if (new_kip == NULL) { + fprintf(stderr, "Failed to allocate uncompressed KIP1!\n"); + exit(EXIT_FAILURE); + } + *((kip1_header_t *)new_kip) = new_header; + + uint64_t new_offset = 0x100; + uint64_t old_offset = 0x100; + for (unsigned int i = 0; i < 3; i++) { + // Copy in section data */ + memcpy(new_kip + new_offset, (unsigned char *)ctx->header + old_offset, ctx->header->section_headers[i].compressed_size); + kip1_blz_uncompress(new_kip + new_offset + ctx->header->section_headers[i].compressed_size); + new_offset += ctx->header->section_headers[i].out_size; + old_offset += ctx->header->section_headers[i].compressed_size; + } + + return new_kip; +} + void kip1_process(kip1_ctx_t *ctx) { /* Read *just* safe amount. */ kip1_header_t raw_header; @@ -146,7 +221,7 @@ void kip1_process(kip1_ctx_t *ctx) { 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"); + fprintf(stderr, "Failed to allocate KIP1!\n"); exit(EXIT_FAILURE); } @@ -188,19 +263,34 @@ void kip1_print(kip1_ctx_t *ctx, int suppress) { } void kip1_save(kip1_ctx_t *ctx) { - /* Do nothing. */ filepath_t *json_path = &ctx->tool_ctx->settings.npdm_json_path; - if (ctx->tool_ctx->file_type == FILETYPE_KIP1 && json_path->valid == VALIDITY_VALID) { - FILE *f_json = os_fopen(json_path->os_path, OS_MODE_WRITE); - if (f_json == NULL) { - fprintf(stderr, "Failed to open %s!\n", json_path->char_path); - return; + filepath_t *uncmp_path = &ctx->tool_ctx->settings.uncompressed_path; + if (ctx->tool_ctx->file_type == FILETYPE_KIP1) { + if (json_path->valid == VALIDITY_VALID) { + FILE *f_json = os_fopen(json_path->os_path, OS_MODE_WRITE); + if (f_json == NULL) { + fprintf(stderr, "Failed to open %s!\n", json_path->char_path); + return; + } + const char *json = kip1_get_json(ctx); + if (fwrite(json, 1, strlen(json), f_json) != strlen(json)) { + fprintf(stderr, "Failed to write JSON file!\n"); + exit(EXIT_FAILURE); + } + fclose(f_json); + } else if (uncmp_path->valid == VALIDITY_VALID) { + FILE *f_uncmp = os_fopen(uncmp_path->os_path, OS_MODE_WRITE); + if (f_uncmp == NULL) { + fprintf(stderr, "Failed to open %s!\n", uncmp_path->char_path); + return; + } + uint64_t sz = 0; + void *uncmp = kip1_uncompress(ctx, &sz); + if (fwrite(uncmp, 1, sz, f_uncmp) != sz) { + fprintf(stderr, "Failed to write uncompressed kip!\n"); + exit(EXIT_FAILURE); + } + fclose(f_uncmp); } - const char *json = kip1_get_json(ctx); - if (fwrite(json, 1, strlen(json), f_json) != strlen(json)) { - fprintf(stderr, "Failed to write JSON file!\n"); - exit(EXIT_FAILURE); - } - fclose(f_json); } } \ No newline at end of file diff --git a/main.c b/main.c index d3f6b78..7ef4510 100644 --- a/main.c +++ b/main.c @@ -54,8 +54,13 @@ static void usage(void) { " --basenca Set Base NCA to use with update partitions.\n" " --basefake Use a fake Base RomFS with update partitions (all reads will return 0xCC).\n" " --onlyupdated Ignore non-updated files in update partitions.\n" - "NPDM/KIP1 options:\n" + "NPDM options:\n" " --json=file Specify file path for saving JSON representation of program permissions to.\n" + "KIP1 options:\n" + " --json=file Specify file path for saving JSON representation of program permissions to.\n" + " --uncompressed=f Specify file path for saving uncompressed KIP1.\n" + "NSO0 options:\n" + " --uncompressed=f Specify file path for saving uncompressed NSO0.\n" "PFS0 options:\n" " --pfs0dir=dir Specify PFS0 directory path.\n" " --outdir=dir Specify PFS0 directory path. Overrides previous path, if present.\n" @@ -171,6 +176,7 @@ int main(int argc, char **argv) { {"tseckey", 1, NULL, 36}, {"json", 1, NULL, 37}, {"saveini1json", 0, NULL, 38}, + {"uncompressed", 1, NULL, 39}, {NULL, 0, NULL, 0}, }; @@ -222,6 +228,8 @@ int main(int argc, char **argv) { nca_ctx.tool_ctx->file_type = FILETYPE_INI1; } else if (!strcmp(optarg, "kip1") || !strcmp(optarg, "kip")) { nca_ctx.tool_ctx->file_type = FILETYPE_KIP1; + } else if (!strcmp(optarg, "nso0") || !strcmp(optarg, "nso")) { + nca_ctx.tool_ctx->file_type = FILETYPE_NSO0; } else if (!strcmp(optarg, "nax0") || !strcmp(optarg, "nax")) { nca_ctx.tool_ctx->file_type = FILETYPE_NAX0; } else if (!strcmp(optarg, "keygen") || !strcmp(optarg, "keys") || !strcmp(optarg, "boot0") || !strcmp(optarg, "boot")) { @@ -373,6 +381,9 @@ int main(int argc, char **argv) { case 38: tool_ctx.action |= ACTION_SAVEINIJSON; break; + case 39: + filepath_set(&nca_ctx.tool_ctx->settings.uncompressed_path, optarg); + break; default: usage(); return EXIT_FAILURE; diff --git a/settings.h b/settings.h index 7fe5052..f2525fe 100644 --- a/settings.h +++ b/settings.h @@ -89,6 +89,7 @@ typedef struct { filepath_t pk21_dir_path; filepath_t ini1_dir_path; filepath_t plaintext_path; + filepath_t uncompressed_path; filepath_t rootpt_dir_path; filepath_t update_dir_path; filepath_t normal_dir_path; @@ -113,6 +114,7 @@ enum hactool_file_type FILETYPE_PACKAGE2, FILETYPE_INI1, FILETYPE_KIP1, + FILETYPE_NSO0, FILETYPE_NAX0, FILETYPE_BOOT0 }; @@ -125,7 +127,7 @@ enum hactool_file_type #define ACTION_DEV (1<<5) #define ACTION_EXTRACTINI1 (1<<6) #define ACTION_ONLYUPDATEDROMFS (1<<7) -#define ACTION_SAVEINIJSON (1<<0) +#define ACTION_SAVEINIJSON (1<<8) struct nca_ctx; /* This will get re-defined by nca.h. */