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

From WiiBrew
Jump to: navigation, search

Put this file in the same directory as a compiled version of Segher's Wii.git tools, since it depends on them.

// parse-opening.c
// Compile with:
// gcc -g -DLARGE_FILES -D_FILE_OFFSET_BITS=64 -Wall -W -O2   -c -o parse-opening.o parse-opening.c
// gcc -g -lcrypto  parse-opening.o tools.o bn.o ec.o   -o parse-opening
// The other files are from segher's git repository, created by his Makefile.

// Copyright 2008 Magicus <magicus@gmail.com>
//
// 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

#define _GNU_SOURCE 

#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

#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 FILE *fp;

static char *outdir;

typedef struct {
    u8 zeroes[0x40];
    u32 imet; // "IMET"
    u8 zero_six_zero_three[8];  // fixed, unknown purpose
    u32 sizes[3];
    u32 flag1;
    u16 name_jp[0x2a]; // might be empty
    u16 name_en[0x2a];
    u16 name_de[0x2a];
    u16 name_fr[0x2a];
    u16 name_es[0x2a];
    u16 name_it[0x2a];
    u16 name_nl[0x2a];
    u8 zeroes_2[0x348];
    u8 crypto[0x10];
} imet_data_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 md5[16];
      u32 payload_tag; // 0x4C5A3737 "LZ77" if this is lz77
      u32 payload_data;
}  imd5_header_t;

typedef struct 
{
  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
{
  u32 tag; // 0x55AA382D "U.8-"
  u32 rootnode_offset; // offset to root_node, always 0x20.
  u32 header_size; // size of header from root_node to end of string table.
  u32 data_offset; // offset to data -- this is rootnode_offset + header_size, aligned to 0x40.
  u8 zeroes[16];
} U8_archive_header;

static void write_file(void* data, size_t size, char* name)
{
	FILE *out;
	out = fopen(name, "wb");
	fwrite(data, 1, size, out);
	fclose(out);	
}

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 write_imd5_lz77(u8* data, size_t size, char* outname)
{
	imd5_header_t* header = (imd5_header_t*) data;
	u32 tag;
	u32 size_in_imd5;
  u8 md5_calc[16];
	u8 *decompressed_data;
	size_t decompressed_size;
	
	tag = be32((u8*) &header->imd5_tag);
	if (tag != 0x494D4435) {
	  ERROR("No IMD5 tag");
	}
	
	md5(data+32, size-32, md5_calc);
	if (memcmp(&header->md5, md5_calc, 0x10)) {
		ERROR("MD5 mismatch");
  }

	size_in_imd5 = be32((u8*) &header->size);
	if (size_in_imd5 != size - 32) {
	  ERROR("Size mismatch");
	}

	tag = be32((u8*) &header->payload_tag);
	if (tag == 0x4C5A3737) {
	  // "LZ77" - uncompress
    decompressed_data = decompress_lz77(data + sizeof(imd5_header_t), size - sizeof(imd5_header_t), &decompressed_size);
    write_file(decompressed_data, decompressed_size, outname);
    printf(", uncompressed %d bytes, md5 ok", decompressed_size);
    
    free(decompressed_data);
	} else {
	  write_file(&header->payload_tag, size-32, outname);
	  printf(", md5 ok");
	}
}

static void do_U8_archive(void)
{
  U8_archive_header header;
  U8_node root_node;
	u32 tag;
	u32 num_nodes;
	U8_node* nodes;
	u8* string_table;
	size_t rest_size;
	unsigned int i;
	u32 data_offset;
	u32 current_offset;
	u16 dir_stack[16];
	int dir_index = 0;

	fread(&header, 1, sizeof header, fp);
	tag = be32((u8*) &header.tag);
	if (tag != 0x55AA382D) {
	  ERROR("No U8 tag");
	}

	fread(&root_node, 1, sizeof(root_node), fp);
	num_nodes = be32((u8*) &root_node.size) - 1;
	printf("Number of files: %d\n", num_nodes);
	
	nodes = malloc(sizeof(U8_node) * (num_nodes));
	fread(nodes, 1, num_nodes * sizeof(U8_node), fp);
	
	data_offset = be32((u8*) &header.data_offset);
	rest_size = data_offset - sizeof(header) - (num_nodes+1)*sizeof(U8_node);

	string_table = malloc(rest_size);
	fread(string_table, 1, rest_size, fp);
  current_offset = data_offset;

	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 my_data_offset = be32((u8*)&node->data_offset);
    u32 size = be32((u8*)&node->size);
    char* name = (char*) &string_table[name_offset];
    u8* file_data;
    
    if (type == 0x0100) {
      // Directory
      mkdir(name, 0777);
      chdir(name);
      dir_stack[++dir_index] = size;
      printf("%*s%s/\n", dir_index, "", name);
    } else {
      // Normal file
      u8 padding[32];

      if (type != 0x0000) {
         ERROR("Unknown type");
      }

      if (current_offset < my_data_offset) {
        int diff = my_data_offset - current_offset;

        if (diff > 32) {
          ERROR("Archive inconsistency, too much padding");
        }
        fread(padding, 1, diff, fp);
        current_offset += diff;
      }

      file_data = malloc(size);
      fread(file_data, 1, size, fp);
      printf("%*s %s (%d bytes", dir_index, "", name, size);
      write_imd5_lz77(file_data, size, name);
      printf(")\n");
      current_offset += size;
    }

    while (dir_stack[dir_index] == i+2 && dir_index > 0) {
      chdir("..");
      dir_index--;
    }
	}
}

static void do_imet_header(void)
{
  imet_data_t header;

	fread(&header, 1, sizeof header, fp);

  write_file(&header, sizeof(header), "header.imet");
}

int main(int argc, char **argv)
{
  char outdir_name[128];

  if (argc == 3) {
    outdir = argv[2];
  } else if (argc != 2) {
    ERROR("Usage: parse-opening <file> [<outdir>]");
  } else {

    snprintf(outdir_name, sizeof(outdir_name), "%s.out", basename(argv[1]));
    outdir_name[127] = '\0';
    outdir = outdir_name;
  }

  printf("Extracting files to %s.\n", outdir);

	fp = fopen(argv[1], "rb");
	
  mkdir(outdir, 0777);
  chdir(outdir);
  
  do_imet_header();
  do_U8_archive();

	fclose(fp);

	return 0;
}

See Also

LZ77