usbloadergx/source/libwbfs/wiidisc.c
dimok321 a4f3d16a56 *Added support for all the currently new IOS stuff. Stole a few modules/plugins from WiiFlow here :P. (Tested Hermes 222 v5.1 base 38 and Waninkoko 249 base 57)
*Added support for older Hermes IOS too (rev 2 and rev 3)
*Changed Alternative Dol Browser to get the filelist (fst) with functions from libwbfs (like banner sounds) instead of from an emulated disk. This should fix all the weird crashes with it.
2010-10-01 15:01:19 +00:00

377 lines
10 KiB
C

// 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"
void aes_set_key(u8 *key);
void aes_decrypt(u8 *iv, u8 *inbuf, u8 *outbuf, unsigned long long len);
static u8 get_fst = 0;
static void _decrypt_title_key(u8 *tik, u8 *title_key)
{
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);
}
static u32 _be32(const u8 *p)
{
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}
static void disc_read(wiidisc_t *d, u32 offset, u8 *data, u32 len)
{
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
{
d->sector_usage_table[blockno] = 1;
blockno += 1;
if (len > 0x8000) len -= 0x8000;
} while (len > 0x8000);
}
}
static void partition_raw_read(wiidisc_t *d, u32 offset, u8 *data, u32 len)
{
disc_read(d, d->partition_raw_offset + offset, data, len);
}
static void partition_read_block(wiidisc_t *d, u32 blockno, u8 *block)
{
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);
}
static void partition_read(wiidisc_t *d, u32 offset, u8 *data, u32 len, int fake)
{
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;
}
}
static u32 do_fst(wiidisc_t *d, u8 *fst, const char *names, u32 i)
{
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;
}
}
static void do_files(wiidisc_t*d)
{
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(get_fst && !d->extracted_buffer)
{
d->extracted_buffer = malloc(fst_size);
memcpy(d->extracted_buffer, fst, fst_size);
}
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);
}
if (fst != d->extracted_buffer) wbfs_iofree( fst );
}
wbfs_iofree( b );
wbfs_iofree( apl_header );
}
static void do_partition(wiidisc_t*d)
{
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);
}
static int test_parition_skip(u32 partition_type, partition_selector_t part_sel)
{
switch (part_sel)
{
case ALL_PARTITIONS:
return 0;
case REMOVE_UPDATE_PARTITION:
return (partition_type == 1);
case ONLY_GAME_PARTITION:
return (partition_type != 0);
default:
return (partition_type != part_sel);
}
}
static void do_disc(wiidisc_t*d)
{
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 );
}
wiidisc_t *wd_open_disc(read_wiidisc_callback_t read, void*fp)
{
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;
}
void wd_close_disc(wiidisc_t *d)
{
wbfs_iofree( d->tmp_buffer );
wbfs_free( d->tmp_buffer2 );
wbfs_free( d );
}
// returns a buffer allocated with wbfs_ioalloc() or NULL if not found of alloc error
// XXX pathname not implemented. files are extracted by their name.
// first file found with that name is returned.
u8 * wd_extract_file(wiidisc_t *d, partition_selector_t partition_type, char *pathname)
{
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;
}
u8 * wd_get_fst(wiidisc_t *d, partition_selector_t partition_type)
{
get_fst = 1;
u8 *retval = 0;
d->extract_pathname = 0;
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;
}
void wd_build_disc_usage(wiidisc_t *d, partition_selector_t selector, u8* usage_table)
{
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;
}
void wd_fix_partition_table(wiidisc_t *d, partition_selector_t selector, u8* partition_table)
{
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++;
}
}
b32 = (u32*) (partition_table);
*b32 = wbfs_htonl( j );
}