Difference between revisions of "User:Magicus/Magicus's Tools/Parse-channel.c"

From WiiBrew
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.'''
 
: 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-u8.c
+
// parse-channel.c
 
// Compile with:
 
// Compile with:
// gcc -g -DLARGE_FILES -D_FILE_OFFSET_BITS=64 -Wall -W -O2  -c -o parse-u8.o parse-u8.c
+
// gcc -g -DLARGE_FILES -D_FILE_OFFSET_BITS=64 -Wall -W -O2  -c -o parse-channel.o parse-channel.c
// gcc -g -lcrypto  parse-u8.o tools.o bn.o ec.o  -o parse-u8
+
// 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.5 Beta version, just lists contents, doesn't yet extract files.
+
// 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 <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 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 *outdir = "OUTDIR";
+
static char gamename[5];
  
typedef struct  
+
static size_t partB_size;
{
+
static u16 num_contents;
  u16 type;
+
static size_t* content_sizes;
  u16 name_offset;
+
 
  u32 data_offset; // == absolut offset från U.8- headerns början
+
typedef struct {
  u32 size; // last included file num for directories
+
      u32 title_id_01_code;
} U8_node;
+
      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 tag; // 0x55AA382D "U.8-"
+
   u16 index; // # number of the file
   u32 rootnode_offset; // offset to root_node, always 0x20.
+
   u16  type;
   u32 header_size; // size of header from root_node to end of string table.
+
   u64 size;
   u32 data_offset; // offset to data -- this is rootnode_offset + header_size, aligned to 0x40.
+
   u8 hash [20]; //  SHA1 hash content
   u8 zeroes[16];
+
} content_record_t;
} U8_archive_header;
 
  
/*
 
 
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[127] = '\0';
+
   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 do_U8_archive(void)
+
*decompressed_size = (out_ptr - decompressed_data);
 +
return decompressed_data;
 +
}
 +
 
 +
static void do_partB_gameinfo(void)
 
{
 
{
  U8_archive_header header;
+
u8 *data;
  U8_node root_node;
+
u8 *decompressed_data;
 +
size_t rounded_size;
 +
size_t decompressed_size;
 +
partB_header_t* header;
 
u32 tag;
 
u32 tag;
u32 num_nodes;
+
u32 unknown;
U8_node* nodes;
+
u32 size;
u8* string_table;
+
 
size_t rest_size;
+
rounded_size = (partB_size + 63) & ~63;
unsigned int i;
+
 
u32 data_offset;
+
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");
 +
}
  
fread(&header, 1, sizeof header, fp);
+
tag = be32((u8*) &header->lz77_tag);
tag = be32((u8*) &header.tag);
+
if (tag != 0x4C5A3737) {
if (tag != 0x55AA382D) {
+
  ERROR("No LZ77 tag");
  ERROR("No U8 tag");
 
 
}
 
}
  
fread(&root_node, 1, sizeof(root_node), fp);
+
unknown = be32((u8*) &header->unknown);
num_nodes = be32((u8*) &root_node.size) - 1;
+
printf("Part B unknown field: %x\n", unknown);
printf("Number of files: %d\n", num_nodes);
+
 
 +
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;
 
 
nodes = malloc(sizeof(U8_node) * (num_nodes));
+
  for (i=0; i < num_contents; i++) {
fread(nodes, 1, num_nodes * sizeof(U8_node), fp);
+
  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);
 
 
data_offset = be32((u8*) &header.data_offset);
+
      snprintf(name, 128, "05_content_%02d", i);
rest_size = data_offset - sizeof(header) - num_nodes*sizeof(U8_node);
+
      printf("Writing included content index %d of size: %x\n", i, content_sizes[i]);
 +
     
 +
      write_part(data, rounded_size, name);
  
string_table = malloc(rest_size);
+
      free (data);
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 == 3) {
+
   if (argc != 2) {
    outdir = argv[2];
+
     ERROR("Usage: parse-channel <file>");
  } 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");
  
//  mkdir(outdir, 0777);
+
  do_partA_header();
//  chdir(outdir);
+
do_partB_gameinfo();
 
+
do_partC_Bk_header();
  do_U8_archive();
+
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;
}