In memory of Ben “bushing” Byer, who passed away on Monday, February 8th, 2016.

User:Magicus/Magicus's Tools/Parse-channel.c

From WiiBrew
Jump to navigation Jump to search
The printable version is no longer supported and may have rendering errors. Please update your browser bookmarks and please use the default browser print function instead.

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.es> 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)

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;
}

See Also

LZ77