2009-05-03 20:53:31 +02:00
|
|
|
// Copyright 2009 Kwiirk based on negentig.c:
|
|
|
|
// 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
|
|
|
|
|
|
|
|
#include "wiidisc.h"
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
void aes_set_key( u8 *key );
|
|
|
|
void aes_decrypt( u8 *iv, u8 *inbuf, u8 *outbuf, unsigned long long len );
|
2009-05-03 20:53:31 +02:00
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
static void _decrypt_title_key( u8 *tik, u8 *title_key )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
u8 common_key[16] =
|
|
|
|
{
|
|
|
|
0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, 0x48, 0xd9, 0xc5, 0x45,
|
|
|
|
0x73, 0x81, 0xaa, 0xf7
|
|
|
|
};;
|
|
|
|
u8 iv[16];
|
|
|
|
|
|
|
|
wbfs_memset( iv, 0, sizeof iv );
|
|
|
|
wbfs_memcpy( iv, tik + 0x01dc, 8 );
|
|
|
|
aes_set_key( common_key );
|
|
|
|
//_aes_cbc_dec(common_key, iv, tik + 0x01bf, 16, title_key);
|
|
|
|
aes_decrypt( iv, tik + 0x01bf, title_key, 16 );
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
2010-09-19 01:16:05 +02:00
|
|
|
static u32 _be32( const u8 *p )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
return ( p[0] << 24 ) | ( p[1] << 16 ) | ( p[2] << 8 ) | p[3];
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
static void disc_read( wiidisc_t *d, u32 offset, u8 *data, u32 len )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
if ( data )
|
|
|
|
{
|
|
|
|
int ret = 0;
|
|
|
|
if ( len == 0 )
|
|
|
|
return ;
|
|
|
|
ret = d->read( d->fp, offset, len, data );
|
|
|
|
if ( ret )
|
|
|
|
wbfs_fatal( "error reading disc" );
|
|
|
|
}
|
|
|
|
if ( d->sector_usage_table )
|
|
|
|
{
|
|
|
|
u32 blockno = offset >> 13;
|
|
|
|
do
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
d->sector_usage_table[blockno] = 1;
|
|
|
|
blockno += 1;
|
|
|
|
if ( len > 0x8000 )
|
|
|
|
len -= 0x8000;
|
2010-02-09 11:59:55 +01:00
|
|
|
}
|
2010-09-19 01:16:05 +02:00
|
|
|
while ( len > 0x8000 );
|
|
|
|
}
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
static void partition_raw_read( wiidisc_t *d, u32 offset, u8 *data, u32 len )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
disc_read( d, d->partition_raw_offset + offset, data, len );
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
static void partition_read_block( wiidisc_t *d, u32 blockno, u8 *block )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
u8*raw = d->tmp_buffer;
|
|
|
|
u8 iv[16];
|
|
|
|
u32 offset;
|
|
|
|
if ( d->sector_usage_table )
|
|
|
|
d->sector_usage_table[d->partition_block+blockno] = 1;
|
|
|
|
offset = d->partition_data_offset + ( ( 0x8000 >> 2 ) * blockno );
|
|
|
|
partition_raw_read( d, offset, raw, 0x8000 );
|
|
|
|
|
|
|
|
// decrypt data
|
|
|
|
memcpy( iv, raw + 0x3d0, 16 );
|
|
|
|
aes_set_key( d->disc_key );
|
|
|
|
aes_decrypt( iv, raw + 0x400, block, 0x7c00 );
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
static void partition_read( wiidisc_t *d, u32 offset, u8 *data, u32 len, int fake )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
u8 *block = d->tmp_buffer2;
|
|
|
|
u32 offset_in_block;
|
|
|
|
u32 len_in_block;
|
|
|
|
if ( fake && d->sector_usage_table == 0 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
while ( len )
|
|
|
|
{
|
|
|
|
offset_in_block = offset % ( 0x7c00 >> 2 );
|
|
|
|
len_in_block = 0x7c00 - ( offset_in_block << 2 );
|
|
|
|
if ( len_in_block > len )
|
|
|
|
len_in_block = len;
|
|
|
|
if ( !fake )
|
|
|
|
{
|
|
|
|
partition_read_block( d, offset / ( 0x7c00 >> 2 ), block );
|
|
|
|
wbfs_memcpy( data, block + ( offset_in_block << 2 ), len_in_block );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
d->sector_usage_table[d->partition_block+( offset/( 0x7c00>>2 ) )] = 1;
|
|
|
|
data += len_in_block;
|
|
|
|
offset += len_in_block >> 2;
|
|
|
|
len -= len_in_block;
|
|
|
|
}
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
static u32 do_fst( wiidisc_t *d, u8 *fst, const char *names, u32 i )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
u32 offset;
|
|
|
|
u32 size;
|
|
|
|
const char *name;
|
|
|
|
u32 j;
|
|
|
|
|
|
|
|
name = names + ( _be32( fst + 12 * i ) & 0x00ffffff );
|
|
|
|
size = _be32( fst + 12 * i + 8 );
|
|
|
|
|
|
|
|
if ( i == 0 )
|
|
|
|
{
|
|
|
|
for ( j = 1; j < size && !d->extracted_buffer; )
|
|
|
|
{
|
|
|
|
j = do_fst( d, fst, names, j );
|
|
|
|
}
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
//printf("name %s\n",name);
|
|
|
|
|
|
|
|
if ( fst[12*i] )
|
|
|
|
{
|
|
|
|
|
|
|
|
for ( j = i + 1; j < size && !d->extracted_buffer; )
|
|
|
|
j = do_fst( d, fst, names, j );
|
|
|
|
|
|
|
|
return size;
|
|
|
|
}
|
|
|
|
else
|
|
|
|
{
|
|
|
|
offset = _be32( fst + 12 * i + 4 );
|
|
|
|
if ( d->extract_pathname && strcasecmp( name, d->extract_pathname ) == 0 )
|
|
|
|
{
|
|
|
|
d->extracted_buffer = wbfs_ioalloc( size );
|
|
|
|
d->extracted_size = size;
|
|
|
|
partition_read( d, offset, d->extracted_buffer, size, 0 );
|
|
|
|
}
|
|
|
|
else
|
|
|
|
partition_read( d, offset, 0, size, 1 );
|
|
|
|
return i + 1;
|
|
|
|
}
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
static void do_files( wiidisc_t*d )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
u8 *b = wbfs_ioalloc( 0x480 ); // XXX: determine actual header size
|
|
|
|
u32 dol_offset;
|
|
|
|
u32 fst_offset;
|
|
|
|
u32 fst_size;
|
|
|
|
u32 apl_offset;
|
|
|
|
u32 apl_size;
|
|
|
|
u8 *apl_header = wbfs_ioalloc( 0x20 );
|
|
|
|
u8 *fst;
|
|
|
|
u32 n_files;
|
|
|
|
partition_read( d, 0, b, 0x480, 0 );
|
|
|
|
|
|
|
|
dol_offset = _be32( b + 0x0420 );
|
|
|
|
fst_offset = _be32( b + 0x0424 );
|
|
|
|
fst_size = _be32( b + 0x0428 ) << 2;
|
|
|
|
|
|
|
|
apl_offset = 0x2440 >> 2;
|
|
|
|
partition_read( d, apl_offset, apl_header, 0x20, 0 );
|
|
|
|
apl_size = 0x20 + _be32( apl_header + 0x14 ) + _be32( apl_header + 0x18 );
|
|
|
|
// fake read dol and partition
|
|
|
|
if ( apl_size )
|
|
|
|
partition_read( d, apl_offset, 0, apl_size, 1 );
|
|
|
|
partition_read( d, dol_offset, 0, ( fst_offset - dol_offset ) << 2, 1 );
|
|
|
|
|
|
|
|
|
|
|
|
if ( fst_size )
|
|
|
|
{
|
|
|
|
fst = wbfs_ioalloc( fst_size );
|
|
|
|
if ( fst == 0 )
|
|
|
|
wbfs_fatal( "malloc fst" );
|
|
|
|
partition_read( d, fst_offset, fst, fst_size, 0 );
|
|
|
|
n_files = _be32( fst + 8 );
|
|
|
|
|
|
|
|
if ( d->extract_pathname && *d->extract_pathname == 0 )
|
|
|
|
{
|
|
|
|
// if empty pathname requested return fst
|
|
|
|
d->extracted_buffer = fst;
|
|
|
|
d->extracted_size = fst_size;
|
|
|
|
d->extract_pathname = NULL;
|
|
|
|
// skip do_fst if only fst requested
|
|
|
|
n_files = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( 12*n_files <= fst_size )
|
|
|
|
{
|
|
|
|
if ( n_files > 1 )
|
|
|
|
do_fst( d, fst, ( char * )fst + 12*n_files, 0 );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
wbfs_iofree( b );
|
|
|
|
wbfs_iofree( apl_header );
|
|
|
|
if ( fst != d->extracted_buffer )
|
|
|
|
wbfs_iofree( fst );
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
static void do_partition( wiidisc_t*d )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
u8 *tik = wbfs_ioalloc( 0x2a4 );
|
|
|
|
u8 *b = wbfs_ioalloc( 0x1c );
|
|
|
|
u64 tmd_offset;
|
|
|
|
u32 tmd_size;
|
|
|
|
u8 *tmd;
|
|
|
|
u64 cert_offset;
|
|
|
|
u32 cert_size;
|
|
|
|
u8 *cert;
|
|
|
|
u64 h3_offset;
|
|
|
|
|
|
|
|
// read ticket, and read some offsets and sizes
|
|
|
|
partition_raw_read( d, 0, tik, 0x2a4 );
|
|
|
|
partition_raw_read( d, 0x2a4 >> 2, b, 0x1c );
|
|
|
|
|
|
|
|
tmd_size = _be32( b );
|
|
|
|
tmd_offset = _be32( b + 4 );
|
|
|
|
cert_size = _be32( b + 8 );
|
|
|
|
cert_offset = _be32( b + 0x0c );
|
|
|
|
h3_offset = _be32( b + 0x10 );
|
|
|
|
d->partition_data_offset = _be32( b + 0x14 );
|
|
|
|
d->partition_block = ( d->partition_raw_offset + d->partition_data_offset ) >> 13;
|
|
|
|
tmd = wbfs_ioalloc( tmd_size );
|
|
|
|
if ( tmd == 0 )
|
|
|
|
wbfs_fatal( "malloc tmd" );
|
|
|
|
partition_raw_read( d, tmd_offset, tmd, tmd_size );
|
|
|
|
|
|
|
|
cert = wbfs_ioalloc( cert_size );
|
|
|
|
if ( cert == 0 )
|
|
|
|
wbfs_fatal( "malloc cert" );
|
|
|
|
partition_raw_read( d, cert_offset, cert, cert_size );
|
|
|
|
|
|
|
|
|
|
|
|
_decrypt_title_key( tik, d->disc_key );
|
|
|
|
|
|
|
|
partition_raw_read( d, h3_offset, 0, 0x18000 );
|
|
|
|
wbfs_iofree( b );
|
|
|
|
wbfs_iofree( tik );
|
|
|
|
wbfs_iofree( cert );
|
|
|
|
wbfs_iofree( tmd );
|
|
|
|
|
|
|
|
do_files( d );
|
2009-05-03 20:53:31 +02:00
|
|
|
|
|
|
|
}
|
2010-09-19 01:16:05 +02:00
|
|
|
static int test_parition_skip( u32 partition_type, partition_selector_t part_sel )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
switch ( part_sel )
|
|
|
|
{
|
2010-02-09 11:59:55 +01:00
|
|
|
case ALL_PARTITIONS:
|
2010-09-19 01:16:05 +02:00
|
|
|
return 0;
|
2010-02-09 11:59:55 +01:00
|
|
|
case REMOVE_UPDATE_PARTITION:
|
2010-09-19 01:16:05 +02:00
|
|
|
return ( partition_type == 1 );
|
2010-02-09 11:59:55 +01:00
|
|
|
case ONLY_GAME_PARTITION:
|
2010-09-19 01:16:05 +02:00
|
|
|
return ( partition_type != 0 );
|
2010-02-09 11:59:55 +01:00
|
|
|
default:
|
2010-09-19 01:16:05 +02:00
|
|
|
return ( partition_type != part_sel );
|
|
|
|
}
|
|
|
|
}
|
|
|
|
static void do_disc( wiidisc_t*d )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
u8 *b = wbfs_ioalloc( 0x100 );
|
|
|
|
u64 partition_offset[32]; // XXX: don't know the real maximum
|
|
|
|
u64 partition_type[32]; // XXX: don't know the real maximum
|
|
|
|
u32 n_partitions;
|
|
|
|
u32 magic;
|
|
|
|
u32 i;
|
|
|
|
disc_read( d, 0, b, 0x100 );
|
|
|
|
magic = _be32( b + 24 );
|
|
|
|
if ( magic != 0x5D1C9EA3 )
|
|
|
|
{
|
|
|
|
wbfs_error( "not a wii disc" );
|
|
|
|
return ;
|
|
|
|
}
|
|
|
|
disc_read( d, 0x40000 >> 2, b, 0x100 );
|
|
|
|
n_partitions = _be32( b );
|
|
|
|
disc_read( d, _be32( b + 4 ), b, 0x100 );
|
|
|
|
for ( i = 0; i < n_partitions; i++ )
|
|
|
|
{
|
|
|
|
partition_offset[i] = _be32( b + 8 * i );
|
|
|
|
partition_type[i] = _be32( b + 8 * i + 4 );
|
|
|
|
}
|
|
|
|
for ( i = 0; i < n_partitions; i++ )
|
|
|
|
{
|
|
|
|
d->partition_raw_offset = partition_offset[i];
|
|
|
|
if ( !test_parition_skip( partition_type[i], d->part_sel ) )
|
|
|
|
do_partition( d );
|
|
|
|
}
|
|
|
|
wbfs_iofree( b );
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
wiidisc_t *wd_open_disc( read_wiidisc_callback_t read, void*fp )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
wiidisc_t *d = wbfs_malloc( sizeof( wiidisc_t ) );
|
|
|
|
if ( !d )
|
|
|
|
return 0;
|
|
|
|
wbfs_memset( d, 0, sizeof( wiidisc_t ) );
|
|
|
|
d->read = read;
|
|
|
|
d->fp = fp;
|
|
|
|
d->part_sel = ALL_PARTITIONS;
|
|
|
|
d->tmp_buffer = wbfs_ioalloc( 0x8000 );
|
|
|
|
d->tmp_buffer2 = wbfs_malloc( 0x8000 );
|
|
|
|
|
|
|
|
return d;
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
2010-09-19 01:16:05 +02:00
|
|
|
void wd_close_disc( wiidisc_t *d )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
wbfs_iofree( d->tmp_buffer );
|
|
|
|
wbfs_free( d->tmp_buffer2 );
|
|
|
|
wbfs_free( d );
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
// returns a buffer allocated with wbfs_ioalloc() or NULL if not found of alloc error
|
2010-09-19 01:16:05 +02:00
|
|
|
// XXX pathname not implemented. files are extracted by their name.
|
2009-05-03 20:53:31 +02:00
|
|
|
// first file found with that name is returned.
|
2010-09-19 01:16:05 +02:00
|
|
|
u8 * wd_extract_file( wiidisc_t *d, partition_selector_t partition_type, char *pathname )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
u8 *retval = 0;
|
|
|
|
d->extract_pathname = pathname;
|
|
|
|
d->extracted_buffer = 0;
|
|
|
|
d->part_sel = partition_type;
|
|
|
|
do_disc( d );
|
|
|
|
d->extract_pathname = 0;
|
|
|
|
d->part_sel = ALL_PARTITIONS;
|
|
|
|
retval = d->extracted_buffer;
|
|
|
|
d->extracted_buffer = 0;
|
|
|
|
return retval;
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
void wd_build_disc_usage( wiidisc_t *d, partition_selector_t selector, u8* usage_table )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
d->sector_usage_table = usage_table;
|
|
|
|
wbfs_memset( usage_table, 0, 143432*2 );
|
|
|
|
d->part_sel = selector;
|
|
|
|
do_disc( d );
|
|
|
|
d->part_sel = ALL_PARTITIONS;
|
|
|
|
d->sector_usage_table = 0;
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-09-19 01:16:05 +02:00
|
|
|
void wd_fix_partition_table( wiidisc_t *d, partition_selector_t selector, u8* partition_table )
|
2010-02-09 11:59:55 +01:00
|
|
|
{
|
2010-09-19 01:16:05 +02:00
|
|
|
u8 *b = partition_table;
|
|
|
|
u32 partition_offset;
|
|
|
|
u32 partition_type;
|
|
|
|
u32 n_partitions, i, j;
|
|
|
|
u32 *b32;
|
|
|
|
if ( selector == ALL_PARTITIONS )
|
|
|
|
return;
|
|
|
|
n_partitions = _be32( b );
|
|
|
|
if ( _be32( b + 4 ) - ( 0x40000 >> 2 ) > 0x50 )
|
|
|
|
wbfs_fatal( "cannot modify this partition table. Please report the bug." );
|
|
|
|
|
|
|
|
b += ( _be32( b + 4 ) - ( 0x40000 >> 2 ) ) * 4;
|
|
|
|
j = 0;
|
|
|
|
for ( i = 0; i < n_partitions; i++ )
|
|
|
|
{
|
|
|
|
partition_offset = _be32( b + 8 * i );
|
|
|
|
partition_type = _be32( b + 8 * i + 4 );
|
|
|
|
if ( !test_parition_skip( partition_type, selector ) )
|
|
|
|
{
|
|
|
|
b32 = ( u32* )( b + 8 * j );
|
|
|
|
b32[0] = wbfs_htonl( partition_offset );
|
|
|
|
b32[1] = wbfs_htonl( partition_type );
|
|
|
|
j++;
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
2010-09-19 01:16:05 +02:00
|
|
|
}
|
|
|
|
b32 = ( u32* )( partition_table );
|
|
|
|
*b32 = wbfs_htonl( j );
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|