Difference between revisions of "User:Magicus/Magicus's Tools/Parse-channel.c"
Jump to navigation
Jump to search
(Replace with the right file (it's late! :)), and add some instructions) |
(I'm *really* tired. Modifying the wrong page. :-/ sorry for the mess...) |
||
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.''' | '''Put this file in the same directory as a compiled version of [[Segher's Wii.git]] tools, since it depends on them.''' | ||
− | |||
− | |||
<pre> | <pre> | ||
− | // parse- | + | // parse-channel.c |
// Compile with: | // Compile with: | ||
− | // gcc -g -DLARGE_FILES -D_FILE_OFFSET_BITS=64 -Wall -W -O2 -c -o parse- | + | // gcc -g -DLARGE_FILES -D_FILE_OFFSET_BITS=64 -Wall -W -O2 -c -o parse-channel.o parse-channel.c |
− | // gcc -g -lcrypto parse- | + | // 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. | // 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 0. | + | // 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> | ||
#include <stdlib.h> | #include <stdlib.h> | ||
#include <stdio.h> | #include <stdio.h> | ||
− | |||
#include "tools.h" | #include "tools.h" | ||
Line 34: | Line 33: | ||
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 | + | static char gamename[5]; |
− | typedef struct | + | 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 | + | typedef struct { |
− | { | + | u32 cid; // content id |
− | u32 | + | u16 index; // # number of the file |
− | + | u16 type; | |
− | + | u64 size; | |
− | + | u8 hash [20]; // SHA1 hash content | |
− | u8 | + | } 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 63: | Line 98: | ||
snprintf(filename, sizeof(filename), "%s_%s.bin", gamename, name); | snprintf(filename, sizeof(filename), "%s_%s.bin", gamename, name); | ||
− | filename[ | + | filename[128] = '\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); | ||
+ | |||
+ | // 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++; | ||
+ | } | ||
+ | } | ||
+ | } | ||
− | static void | + | *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 | + | u32 unknown; |
− | + | u32 size; | |
− | u8* | + | |
− | + | 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); | |
− | tag = be32((u8*) &header | + | if (tag != 0x4C5A3737) { |
− | if (tag != | + | ERROR("No LZ77 tag"); |
− | ERROR("No | ||
} | } | ||
− | + | 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) | int main(int argc, char **argv) | ||
{ | { | ||
− | + | if (argc != 2) { | |
− | + | ERROR("Usage: parse-channel <file>"); | |
− | |||
− | ERROR("Usage: parse- | ||
} | } | ||
+ | |||
+ | 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(); | |
− | + | do_partB_gameinfo(); | |
− | + | do_partC_Bk_header(); | |
− | + | do_partD_tmd(); | |
+ | do_partE_contents(); | ||
+ | do_partF_cert(); | ||
fclose(fp); | fclose(fp); |
Revision as of 03:04, 2 March 2008
Put this file in the same directory as a compiled version of Segher's Wii.git tools, since it depends on them.
// 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; }