/**************************************************************************** * USB Loader GX Team * openingbnr * * Extract opening.bnr/banner.bin/sound.bin/icon.bin * * Copyright 2008 Magicus * 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 #include #include #include #include #include #include #include "libfat/fat.h" #include "MD5.h" #include "banner.h" #include "openingbnr.h" #include "../ramdisk/ramdisk.h" #include "../listfiles.h" u16 be16( const u8 *p ) { return ( p[0] << 8 ) | p[1]; } u32 be32( const u8 *p ) { return ( p[0] << 24 ) | ( p[1] << 16 ) | ( p[2] << 8 ) | p[3]; } u64 be64( const u8 *p ) { return ( ( u64 )be32( p ) << 32 ) | be32( p + 4 ); } u64 be34( const u8 *p ) { return 4 * ( u64 )be32( p ); } void wbe16( u8 *p, u16 x ) { p[0] = x >> 8; p[1] = x; } void wbe32( u8 *p, u32 x ) { wbe16( p, x >> 16 ); wbe16( p + 2, x ); } void wbe64( u8 *p, u64 x ) { wbe32( p, x >> 32 ); wbe32( p + 4, x ); } void md5( u8 *data, u32 len, u8 *hash ) { MD5( hash, data, len ); } 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 int write_file( void* data, size_t size, char* name ) { size_t written = 0; FILE *out; out = fopen( name, "wb" ); if ( out ) { written = fwrite( data, 1, size, out ); fclose( out ); } return ( written == size ) ? 1 : -1; } 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 ) { return NULL; } 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 int 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 ) { return -4; } md5( data + 32, size - 32, md5_calc ); if ( memcmp( &header->md5, md5_calc, 0x10 ) ) { return -5; } size_in_imd5 = be32( ( u8* ) & header->size ); if ( size_in_imd5 != size - 32 ) { return -6; } 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 ); if ( decompressed_data == NULL ) return -7; 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"); } return 0; } static int do_U8_archive( FILE *fp ) { 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 ) { return -1; } 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 ) { free( string_table ); return -2; } if ( current_offset < my_data_offset ) { int diff = my_data_offset - current_offset; if ( diff > 32 ) { free( string_table ); return -3; } 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); int result; result = write_imd5_lz77( file_data, size, name ); if ( result < 0 ) { free( string_table ); return result; } //printf(")\n"); current_offset += size; } while ( dir_stack[dir_index] == i + 2 && dir_index > 0 ) { chdir( ".." ); dir_index--; } } free( string_table ); return 0; } static void do_imet_header( FILE *fp ) { imet_data_t header; fread( &header, 1, sizeof header, fp ); write_file( &header, sizeof( header ), "header.imet" ); } void do_U8_archivebanner( FILE *fp ) { 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; u16 dir_stack[16]; int dir_index = 0; fread( &header, 1, sizeof header, fp ); tag = be32( ( u8* ) & header.tag ); if ( tag != 0x55AA382D ) { //printf("No U8 tag"); exit( 0 ); } 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 ); 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 if ( type != 0x0000 ) { printf( "Unknown type" ); exit( 0 ); } fseek( fp, my_data_offset, SEEK_SET ); file_data = malloc( size ); fread( file_data, 1, size, fp ); write_file( file_data, size, name ); free( file_data ); //printf("%*s %s (%d bytes)\n", dir_index, "", name, size); } while ( dir_stack[dir_index] == i + 2 && dir_index > 0 ) { chdir( ".." ); dir_index--; } } free( string_table ); } int extractbnrfile( const char * filepath, const char * destpath ) { int ret = -1; FILE *fp = fopen( filepath, "rb" ); if ( fp ) { subfoldercreate( destpath ); chdir( destpath ); do_imet_header( fp ); ret = do_U8_archive( fp ); fclose( fp ); } return ret; } int unpackBin( const char * filename, const char * outdir ) { FILE *fp = fopen( filename, "rb" );; if ( fp ) { subfoldercreate( outdir ); chdir( outdir ); do_U8_archivebanner( fp ); fclose( fp ); return 1; } return 0; } #define TMP_PATH(s) "BANNER:/dump"s //#define TMP_PATH(s) "SD:/dump"s int unpackBanner( const u8 *gameid, int what, const char *outdir ) { char path[256]; if ( !ramdiskMount( "BANNER", NULL ) ) return -1; subfoldercreate( TMP_PATH( "/" ) ); s32 ret = dump_banner( gameid, TMP_PATH( "/opening.bnr" ) ); if ( ret != 1 ) { ret = -1; goto error2; } ret = extractbnrfile( TMP_PATH( "/opening.bnr" ), TMP_PATH( "/" ) ); if ( ret != 0 ) { ret = -1; goto error2; } if ( what & UNPACK_BANNER_BIN ) { snprintf( path, sizeof( path ), "%sbanner/", outdir ); ret = unpackBin( TMP_PATH( "/meta/banner.bin" ), path ); if ( ret != 1 ) { ret = -1; goto error2; } } if ( what & UNPACK_ICON_BIN ) { snprintf( path, sizeof( path ), "%sicon/", outdir ); ret = unpackBin( TMP_PATH( "/meta/icon.bin" ), path ); if ( ret != 1 ) { ret = -1; goto error2; } } if ( what & UNPACK_SOUND_BIN ) { snprintf( path, sizeof( path ), "%ssound.bin", outdir ); FILE *fp = fopen( TMP_PATH( "/meta/sound.bin" ), "rb" ); if ( fp ) { size_t size; u8 *data; fseek( fp, 0, SEEK_END ); size = ftell( fp ); if ( !size ) { ret = -1; goto error; } fseek( fp, 0, SEEK_SET ); data = ( u8 * )malloc( size ); if ( !data ) { ret = -1; goto error; } if ( fread( data, 1, size, fp ) != size ) { ret = -1; goto error; } ret = write_file( data, size, path ); } error: fclose( fp ); } ramdiskUnmount( "BANNER" ); error2: if ( ret < 0 ) return ret; return 1; }