Difference between revisions of "User:Magicus/Magicus's Tools/Parse-channel.c"
Jump to navigation
Jump to search
(Creation with code and categories) |
|||
Line 9: | Line 9: | ||
// This file is based on tachtig.c, which is | // This file is based on tachtig.c, which is | ||
// Copyright 2007,2008 Segher Boessenkool <segher@kernel.crashing.org> | // Copyright 2007,2008 Segher Boessenkool <segher@kernel.crashing.org> | ||
+ | // | ||
// Licensed under the terms of the GNU GPL, version 2 | // Licensed under the terms of the GNU GPL, version 2 | ||
// http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt | // http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt | ||
+ | |||
+ | // Version 1.0 Initial release | ||
+ | // Version 1.1 Fixing IV-bug for part B. Adding support for decompressing part B; | ||
+ | // thanks Arcnor <arcnorj@yahoo.com> for the basic LZ77 code! | ||
+ | |||
#include <string.h> | #include <string.h> | ||
Line 45: | Line 51: | ||
u8 data[0x624]; | u8 data[0x624]; | ||
} partA_header_t; | } partA_header_t; | ||
+ | |||
+ | typedef struct { | ||
+ | u32 imd5_tag; // 0x494D4435 "IMD5"; | ||
+ | u32 size; // size of the rest of part B, starting from next field. | ||
+ | u8 zeroes[8]; | ||
+ | u8 crypto[16]; | ||
+ | u32 lz77_tag; // 0x4C5A3737 "LZ77" | ||
+ | u32 unknown; // padding to 0x40? | ||
+ | } partB_header_t; | ||
typedef struct { | typedef struct { | ||
Line 90: | Line 105: | ||
{ | { | ||
partA_header_t header; | partA_header_t header; | ||
+ | u8 partA_iv[16]; | ||
u8 md5_file[16]; | u8 md5_file[16]; | ||
u8 md5_calc[16]; | u8 md5_calc[16]; | ||
Line 95: | Line 111: | ||
fread(&header, 1, sizeof header, fp); | fread(&header, 1, sizeof header, fp); | ||
− | aes_cbc_dec(sd_key, | + | // Use a private copy of the sd_iv since we need it again |
+ | // and it will be overwritten otherwise. | ||
+ | memcpy(partA_iv, sd_iv, 16); | ||
+ | aes_cbc_dec(sd_key, partA_iv, (u8*) &header, sizeof header, (u8*) &header); | ||
memcpy(md5_file, header.md5, 16); | memcpy(md5_file, header.md5, 16); | ||
Line 111: | Line 130: | ||
partB_size = be32((u8*) &header.partB_size); | partB_size = be32((u8*) &header.partB_size); | ||
− | |||
write_part(&header, sizeof(header), "01_header"); | write_part(&header, sizeof(header), "01_header"); | ||
+ | } | ||
+ | |||
+ | static u8* decompress_lz77(u8 *data, size_t data_size, size_t* decompressed_size) | ||
+ | { | ||
+ | u8 *data_end; | ||
+ | u8 *decompressed_data; | ||
+ | size_t unpacked_size; | ||
+ | u8 *in_ptr; | ||
+ | u8 *out_ptr; | ||
+ | u8 *out_end; | ||
+ | |||
+ | in_ptr = data; | ||
+ | data_end = data + data_size; | ||
+ | |||
+ | // Assume this for now and grow when needed | ||
+ | unpacked_size = data_size; | ||
+ | |||
+ | decompressed_data = malloc(unpacked_size); | ||
+ | out_end = decompressed_data + unpacked_size; | ||
+ | |||
+ | out_ptr = decompressed_data; | ||
+ | |||
+ | while (in_ptr < data_end) { | ||
+ | int bit; | ||
+ | u8 bitmask = *in_ptr; | ||
+ | |||
+ | in_ptr++; | ||
+ | for (bit = 0x80; bit != 0; bit >>= 1) { | ||
+ | if (bitmask & bit) { | ||
+ | // Next section is compressed | ||
+ | u8 rep_length; | ||
+ | u16 rep_offset; | ||
+ | |||
+ | rep_length = (*in_ptr >> 4) + 3; | ||
+ | rep_offset = *in_ptr & 0x0f; | ||
+ | in_ptr++; | ||
+ | rep_offset = *in_ptr | (rep_offset << 8); | ||
+ | in_ptr++; | ||
+ | if (out_ptr-decompressed_data < rep_offset) { | ||
+ | ERROR("Inconsistency in LZ77 encoding"); | ||
+ | } | ||
+ | |||
+ | for ( ; rep_length > 0; rep_length--) { | ||
+ | *out_ptr = out_ptr[-rep_offset-1]; | ||
+ | out_ptr++; | ||
+ | if (out_ptr >= out_end) { | ||
+ | // Need to grow buffer | ||
+ | decompressed_data = realloc(decompressed_data, unpacked_size*2); | ||
+ | out_ptr = decompressed_data + unpacked_size; | ||
+ | unpacked_size *= 2; | ||
+ | out_end = decompressed_data + unpacked_size; | ||
+ | } | ||
+ | } | ||
+ | } else { | ||
+ | // Just copy byte | ||
+ | *out_ptr = *in_ptr; | ||
+ | out_ptr++; | ||
+ | if (out_ptr >= out_end) { | ||
+ | // Need to grow buffer | ||
+ | decompressed_data = realloc(decompressed_data, unpacked_size*2); | ||
+ | out_ptr = decompressed_data + unpacked_size; | ||
+ | unpacked_size *= 2; | ||
+ | out_end = decompressed_data + unpacked_size; | ||
+ | } | ||
+ | in_ptr++; | ||
+ | } | ||
+ | } | ||
+ | } | ||
+ | |||
+ | *decompressed_size = (out_ptr - decompressed_data); | ||
+ | return decompressed_data; | ||
} | } | ||
Line 119: | Line 208: | ||
{ | { | ||
u8 *data; | u8 *data; | ||
+ | u8 *decompressed_data; | ||
size_t rounded_size; | size_t rounded_size; | ||
+ | size_t decompressed_size; | ||
+ | partB_header_t* header; | ||
+ | u32 tag; | ||
+ | u32 unknown; | ||
+ | u32 size; | ||
rounded_size = (partB_size + 63) & ~63; | rounded_size = (partB_size + 63) & ~63; | ||
Line 128: | Line 223: | ||
aes_cbc_dec(sd_key, sd_iv, data, rounded_size, data); | aes_cbc_dec(sd_key, sd_iv, data, rounded_size, data); | ||
− | + | header = (partB_header_t*) data; | |
+ | |||
+ | tag = be32((u8*) &header->imd5_tag); | ||
+ | if (tag != 0x494D4435) { | ||
+ | ERROR("No IMD5 tag"); | ||
+ | } | ||
+ | size = be32((u8*) &header->size); | ||
+ | if (size != partB_size - 32) { | ||
+ | ERROR("Size mismatch"); | ||
+ | } | ||
+ | |||
+ | tag = be32((u8*) &header->lz77_tag); | ||
+ | if (tag != 0x4C5A3737) { | ||
+ | ERROR("No LZ77 tag"); | ||
+ | } | ||
+ | |||
+ | unknown = be32((u8*) &header->unknown); | ||
+ | printf("Part B unknown field: %x\n", unknown); | ||
+ | |||
+ | write_part(data, sizeof(partB_header_t), "02a_banner_header"); | ||
+ | |||
+ | decompressed_data = decompress_lz77(data + sizeof(partB_header_t), partB_size - sizeof(partB_header_t), &decompressed_size); | ||
+ | write_part(decompressed_data, decompressed_size, "02b_banner_decompressed"); | ||
− | + | free(data); | |
+ | // free(decompressed_data); | ||
} | } | ||
Line 146: | Line 264: | ||
fprintf(stderr, "NG id: %08x\n", be32(header + 8)); | fprintf(stderr, "NG id: %08x\n", be32(header + 8)); | ||
− | + | write_part(header, sizeof(header), "03_bk_header"); | |
} | } | ||
Revision as of 20:15, 1 March 2008
// parse-channel.c // Compile with: // gcc -g -DLARGE_FILES -D_FILE_OFFSET_BITS=64 -Wall -W -O2 -c -o parse-channel.o parse-channel.c // gcc -g -lcrypto parse-channel.o tools.o bn.o ec.o -o parse-channel // The other files are from segher's git repository, created by his Makefile. // Copyright 2008 Magicus <magicus@gmail.com> // This file is based on tachtig.c, which is // Copyright 2007,2008 Segher Boessenkool <segher@kernel.crashing.org> // // Licensed under the terms of the GNU GPL, version 2 // http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt // Version 1.0 Initial release // Version 1.1 Fixing IV-bug for part B. Adding support for decompressing part B; // thanks Arcnor <arcnorj@yahoo.com> for the basic LZ77 code! #include <string.h> #include <stdlib.h> #include <stdio.h> #include "tools.h" #define ERROR(s) do { fprintf(stderr, s "\n"); exit(1); } while (0) // FIXME: this should really move to tools.c u16 be16(u8 *p) { return (p[0] << 8) | p[1]; } static u8 sd_key[16]; static u8 sd_iv[16]; static u8 md5_blanker[16]; static FILE *fp; static char gamename[5]; static size_t partB_size; static u16 num_contents; static size_t* content_sizes; typedef struct { u32 title_id_01_code; u32 title_id_02_name; u32 partB_size; u8 md5[0x10]; u8 data[0x624]; } partA_header_t; typedef struct { u32 imd5_tag; // 0x494D4435 "IMD5"; u32 size; // size of the rest of part B, starting from next field. u8 zeroes[8]; u8 crypto[16]; u32 lz77_tag; // 0x4C5A3737 "LZ77" u32 unknown; // padding to 0x40? } partB_header_t; typedef struct { u32 sig_type; u8 sig[256]; u8 fill1[60]; u8 issuer[64]; // Root-CA%08x-CP%08x u8 version; u8 ca_crl_version; u8 signer_crl_version; u8 fill2; u64 sys_version; u64 title_id; u32 title_type; u16 group_id; // publisher u8 reserved[62]; u32 access_rights; u16 title_version; u16 num_contents; u16 boot_index; u16 fill3; } tmd_t; typedef struct { u32 cid; // content id u16 index; // # number of the file u16 type; u64 size; u8 hash [20]; // SHA1 hash content } content_record_t; static void write_part(void* data, size_t size, char* name) { FILE *out; char filename[128]; snprintf(filename, sizeof(filename), "%s_%s.bin", gamename, name); filename[128] = '\0'; out = fopen(filename, "wb"); fwrite(data, 1, size, out); fclose(out); } static void do_partA_header(void) { partA_header_t header; u8 partA_iv[16]; u8 md5_file[16]; u8 md5_calc[16]; fread(&header, 1, sizeof header, fp); // Use a private copy of the sd_iv since we need it again // and it will be overwritten otherwise. memcpy(partA_iv, sd_iv, 16); aes_cbc_dec(sd_key, partA_iv, (u8*) &header, sizeof header, (u8*) &header); memcpy(md5_file, header.md5, 16); memcpy(header.md5, md5_blanker, 16); md5((u8*) &header, sizeof header, md5_calc); if (memcmp(md5_file, md5_calc, 0x10)) { ERROR("MD5 mismatch"); } // Get the four-letter code of the game, for file naming purposes. strncpy(gamename, (char*) &header.title_id_02_name, 4); gamename[5] = '\0'; printf("Game code is: %s\n", gamename); partB_size = be32((u8*) &header.partB_size); write_part(&header, sizeof(header), "01_header"); } static u8* decompress_lz77(u8 *data, size_t data_size, size_t* decompressed_size) { u8 *data_end; u8 *decompressed_data; size_t unpacked_size; u8 *in_ptr; u8 *out_ptr; u8 *out_end; in_ptr = data; data_end = data + data_size; // Assume this for now and grow when needed unpacked_size = data_size; decompressed_data = malloc(unpacked_size); out_end = decompressed_data + unpacked_size; out_ptr = decompressed_data; while (in_ptr < data_end) { int bit; u8 bitmask = *in_ptr; in_ptr++; for (bit = 0x80; bit != 0; bit >>= 1) { if (bitmask & bit) { // Next section is compressed u8 rep_length; u16 rep_offset; rep_length = (*in_ptr >> 4) + 3; rep_offset = *in_ptr & 0x0f; in_ptr++; rep_offset = *in_ptr | (rep_offset << 8); in_ptr++; if (out_ptr-decompressed_data < rep_offset) { ERROR("Inconsistency in LZ77 encoding"); } for ( ; rep_length > 0; rep_length--) { *out_ptr = out_ptr[-rep_offset-1]; out_ptr++; if (out_ptr >= out_end) { // Need to grow buffer decompressed_data = realloc(decompressed_data, unpacked_size*2); out_ptr = decompressed_data + unpacked_size; unpacked_size *= 2; out_end = decompressed_data + unpacked_size; } } } else { // Just copy byte *out_ptr = *in_ptr; out_ptr++; if (out_ptr >= out_end) { // Need to grow buffer decompressed_data = realloc(decompressed_data, unpacked_size*2); out_ptr = decompressed_data + unpacked_size; unpacked_size *= 2; out_end = decompressed_data + unpacked_size; } in_ptr++; } } } *decompressed_size = (out_ptr - decompressed_data); return decompressed_data; } static void do_partB_gameinfo(void) { u8 *data; u8 *decompressed_data; size_t rounded_size; size_t decompressed_size; partB_header_t* header; u32 tag; u32 unknown; u32 size; rounded_size = (partB_size + 63) & ~63; data = malloc(rounded_size); fread(data, 1, rounded_size, fp); aes_cbc_dec(sd_key, sd_iv, data, rounded_size, data); header = (partB_header_t*) data; tag = be32((u8*) &header->imd5_tag); if (tag != 0x494D4435) { ERROR("No IMD5 tag"); } size = be32((u8*) &header->size); if (size != partB_size - 32) { ERROR("Size mismatch"); } tag = be32((u8*) &header->lz77_tag); if (tag != 0x4C5A3737) { ERROR("No LZ77 tag"); } unknown = be32((u8*) &header->unknown); printf("Part B unknown field: %x\n", unknown); write_part(data, sizeof(partB_header_t), "02a_banner_header"); decompressed_data = decompress_lz77(data + sizeof(partB_header_t), partB_size - sizeof(partB_header_t), &decompressed_size); write_part(decompressed_data, decompressed_size, "02b_banner_decompressed"); free(data); // free(decompressed_data); } static void do_partC_Bk_header(void) { u8 header[0x80]; fread(header, 1, sizeof header, fp); if (be32(header + 4) != 0x426b0001) ERROR("no Bk header"); if (be32(header) != 0x70) ERROR("wrong Bk header size"); fprintf(stderr, "NG id: %08x\n", be32(header + 8)); write_part(header, sizeof(header), "03_bk_header"); } static void do_partD_tmd(void) { tmd_t tmd; u8* data; size_t tmd_size; int i; content_record_t* rec; fread(&tmd, 1, sizeof tmd, fp); num_contents = be16((u8*) &tmd.num_contents); printf("Number of content files: %d\n", num_contents); // Now we can read the rest of the tmd. tmd_size = sizeof(tmd) + num_contents*sizeof(content_record_t); tmd_size = (tmd_size + 63) & ~63; data = malloc(tmd_size); memcpy(data, &tmd, sizeof(tmd)); fread(&data[sizeof(tmd)], 1, tmd_size-sizeof(tmd), fp); write_part(data, tmd_size, "04_tmd"); content_sizes = calloc(1, sizeof (size_t) * num_contents); rec = (content_record_t*) &data[sizeof(tmd)]; for (i = 0; i < num_contents; i++, rec++) { u16 type = be16((u8*) &rec->type); if (!(type & 0x8000)) { content_sizes[i] = (size_t)be64((u8*) &rec->size); } } } static void do_partE_contents(void) { int i; for (i=0; i < num_contents; i++) { if (content_sizes[i] != 0) { char name[128]; u8 *data; size_t rounded_size = (content_sizes[i] + 63) & ~63; data = malloc(rounded_size); fread(data, 1, rounded_size, fp); snprintf(name, 128, "05_content_%02d", i); printf("Writing included content index %d of size: %x\n", i, content_sizes[i]); write_part(data, rounded_size, name); free (data); } } } static void do_partF_cert(void) { u8 cert[0x340]; fread(cert, 1, sizeof cert, fp); write_part(cert, sizeof(cert), "06_cert"); } int main(int argc, char **argv) { if (argc != 2) { ERROR("Usage: parse-channel <file>"); } get_key("sd-key", sd_key, 16); get_key("sd-iv", sd_iv, 16); get_key("md5-blanker", md5_blanker, 16); fp = fopen(argv[1], "rb"); do_partA_header(); do_partB_gameinfo(); do_partC_Bk_header(); do_partD_tmd(); do_partE_contents(); do_partF_cert(); fclose(fp); return 0; }