Line 1: |
Line 1: |
| + | '''Put this file in the same directory as a compiled version of [[Segher's Wii.git]] tools, since it depends on them.''' |
| + | |
| + | : Sorry for the beta status, it's 2 am and I need to get some sleep ;-) but I thought it was better I released what I've done so far. Feel free to improve it. [[User:Magicus|Magicus]] 17:00, 1 March 2008 (PST) |
| + | |
| <pre> | | <pre> |
− | // parse-channel.c | + | // parse-u8.c |
| // Compile with: | | // Compile with: |
− | // gcc -g -DLARGE_FILES -D_FILE_OFFSET_BITS=64 -Wall -W -O2 -c -o parse-channel.o parse-channel.c | + | // gcc -g -DLARGE_FILES -D_FILE_OFFSET_BITS=64 -Wall -W -O2 -c -o parse-u8.o parse-u8.c |
− | // gcc -g -lcrypto parse-channel.o tools.o bn.o ec.o -o parse-channel | + | // gcc -g -lcrypto parse-u8.o tools.o bn.o ec.o -o parse-u8 |
| // The other files are from segher's git repository, created by his Makefile. | | // The other files are from segher's git repository, created by his Makefile. |
| | | |
| // Copyright 2008 Magicus <magicus@gmail.com> | | // 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 | | // 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 0.5 Beta version, just lists contents, doesn't yet extract files. |
− | // 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 <sys/stat.h> |
| + | #include <sys/types.h> |
| + | |
| #include <string.h> | | #include <string.h> |
| #include <stdlib.h> | | #include <stdlib.h> |
| #include <stdio.h> | | #include <stdio.h> |
| + | #include <unistd.h> |
| | | |
| #include "tools.h" | | #include "tools.h" |
Line 31: |
Line 34: |
| return (p[0] << 8) | p[1]; | | 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 FILE *fp; |
| | | |
− | static char gamename[5]; | + | static char *outdir = "OUTDIR"; |
| | | |
− | static size_t partB_size;
| + | typedef struct |
− | static u16 num_contents;
| + | { |
− | static size_t* content_sizes;
| + | u16 type; |
| + | u16 name_offset; |
| + | u32 data_offset; // == absolut offset från U.8- headerns början |
| + | u32 size; // last included file num for directories |
| + | } U8_node; |
| | | |
− | typedef struct { | + | typedef struct |
− | u32 title_id_01_code;
| + | { |
− | u32 title_id_02_name;
| + | u32 tag; // 0x55AA382D "U.8-" |
− | u32 partB_size;
| + | u32 rootnode_offset; // offset to root_node, always 0x20. |
− | u8 md5[0x10];
| + | u32 header_size; // size of header from root_node to end of string table. |
− | u8 data[0x624];
| + | u32 data_offset; // offset to data -- this is rootnode_offset + header_size, aligned to 0x40. |
− | } partA_header_t;
| + | u8 zeroes[16]; |
− | | + | } U8_archive_header; |
− | 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) | | static void write_part(void* data, size_t size, char* name) |
| { | | { |
Line 96: |
Line 63: |
| | | |
| snprintf(filename, sizeof(filename), "%s_%s.bin", gamename, name); | | snprintf(filename, sizeof(filename), "%s_%s.bin", gamename, name); |
− | filename[128] = '\0'; | + | filename[127] = '\0'; |
| out = fopen(filename, "wb"); | | out = fopen(filename, "wb"); |
| fwrite(data, 1, size, out); | | fwrite(data, 1, size, out); |
| fclose(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);
| + | static void do_U8_archive(void) |
− | | |
− | // 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_archive_header header; |
− | u8 *decompressed_data;
| + | U8_node root_node; |
− | 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 tag; |
− | u32 unknown; | + | u32 num_nodes; |
− | u32 size; | + | U8_node* nodes; |
| + | u8* string_table; |
| + | size_t rest_size; |
| + | unsigned int i; |
| + | u32 data_offset; |
| | | |
− | rounded_size = (partB_size + 63) & ~63; | + | fread(&header, 1, sizeof header, fp); |
− | | + | tag = be32((u8*) &header.tag); |
− | data = malloc(rounded_size);
| + | if (tag != 0x55AA382D) { |
− | fread(data, 1, rounded_size, fp);
| + | ERROR("No U8 tag"); |
− | | |
− | 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); | + | fread(&root_node, 1, sizeof(root_node), fp); |
− | if (tag != 0x4C5A3737) {
| + | num_nodes = be32((u8*) &root_node.size) - 1; |
− | ERROR("No LZ77 tag");
| + | printf("Number of files: %d\n", num_nodes); |
− | }
| |
− | | |
− | 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++) {
| + | nodes = malloc(sizeof(U8_node) * (num_nodes)); |
− | if (content_sizes[i] != 0) {
| + | fread(nodes, 1, num_nodes * sizeof(U8_node), fp); |
− | 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);
| + | data_offset = be32((u8*) &header.data_offset); |
− | printf("Writing included content index %d of size: %x\n", i, content_sizes[i]);
| + | rest_size = data_offset - sizeof(header) - num_nodes*sizeof(U8_node); |
− |
| |
− | write_part(data, rounded_size, name);
| |
| | | |
− | free (data);
| + | string_table = malloc(rest_size); |
| + | fread(string_table, 1, rest_size, fp); |
| + | |
| + | for (i = 0; i < num_nodes; i++) { |
| + | U8_node* node = &nodes[i]; |
| + | u16 type = be16((u8*)&node->type); |
| + | u16 name_offset = be16((u8*)&node->name_offset); |
| + | u32 data_offset = be32((u8*)&node->data_offset); |
| + | u32 size = be32((u8*)&node->size); |
| + | char* name = (char*) &string_table[name_offset]; |
| + | char* premarker; |
| + | char* postmarker; |
| + | |
| + | if (type == 0x0100) { |
| + | premarker = ""; |
| + | postmarker = "/"; |
| + | } else { |
| + | premarker = " "; |
| + | postmarker = ""; |
| } | | } |
| + | |
| + | printf("%s%s%s (%d) offset: %x\n", premarker, name, postmarker, size, data_offset); |
| } | | } |
| } | | } |
− |
| |
− | 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) | | int main(int argc, char **argv) |
| { | | { |
− | if (argc != 2) { | + | if (argc == 3) { |
− | ERROR("Usage: parse-channel <file>"); | + | outdir = argv[2]; |
| + | } else if (argc != 2) { |
| + | ERROR("Usage: parse-u8 <file> [<outdir>]"); |
| } | | } |
− |
| |
− | 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"); | | fp = fopen(argv[1], "rb"); |
| | | |
− | do_partA_header();
| + | // mkdir(outdir, 0777); |
− | do_partB_gameinfo();
| + | // chdir(outdir); |
− | do_partC_Bk_header();
| + | |
− | do_partD_tmd();
| + | do_U8_archive(); |
− | do_partE_contents();
| |
− | do_partF_cert();
| |
| | | |
| fclose(fp); | | fclose(fp); |