usbloadergx/source/libs/libwbfs/wiidisc.c

365 lines
10 KiB
C
Raw Normal View History

// 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-24 02:48:03 +02:00
void aes_set_key(u8 *key);
void aes_decrypt(u8 *iv, u8 *inbuf, u8 *outbuf, unsigned long long len);
2010-09-24 02:48:03 +02:00
static void _decrypt_title_key(u8 *tik, u8 *title_key)
{
2010-09-24 02:48:03 +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 );
2010-09-24 02:48:03 +02:00
aes_set_key(common_key);
//_aes_cbc_dec(common_key, iv, tik + 0x01bf, 16, title_key);
2010-09-24 02:48:03 +02:00
aes_decrypt(iv, tik + 0x01bf, title_key, 16);
}
2010-09-24 02:48:03 +02:00
static u32 _be32(const u8 *p)
{
2010-09-24 02:48:03 +02:00
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
}
2010-09-24 02:48:03 +02:00
static void disc_read(wiidisc_t *d, u32 offset, u8 *data, u32 len)
{
2010-09-24 02:48:03 +02:00
if (data)
{
int ret = 0;
2010-09-24 02:48:03 +02:00
if (len == 0) return;
ret = d->read(d->fp, offset, len, data);
if (ret)
wbfs_fatal( "error reading disc" );
}
2010-09-24 02:48:03 +02:00
if (d->sector_usage_table)
{
u32 blockno = offset >> 13;
do
{
d->sector_usage_table[blockno] = 1;
blockno += 1;
2010-09-24 02:48:03 +02:00
if (len > 0x8000) len -= 0x8000;
} while (len > 0x8000);
}
}
2010-09-24 02:48:03 +02:00
static void partition_raw_read(wiidisc_t *d, u32 offset, u8 *data, u32 len)
{
2010-09-24 02:48:03 +02:00
disc_read(d, d->partition_raw_offset + offset, data, len);
}
2010-09-24 02:48:03 +02:00
static void partition_read_block(wiidisc_t *d, u32 blockno, u8 *block)
{
u8*raw = d->tmp_buffer;
u8 iv[16];
u32 offset;
2010-09-24 02:48:03 +02:00
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
2010-09-24 02:48:03 +02:00
memcpy(iv, raw + 0x3d0, 16);
aes_set_key(d->disc_key);
aes_decrypt(iv, raw + 0x400, block, 0x7c00);
}
2010-09-24 02:48:03 +02:00
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;
2010-09-24 02:48:03 +02:00
if (fake && d->sector_usage_table == 0) return;
2010-09-24 02:48:03 +02:00
while (len)
{
2010-09-24 02:48:03 +02:00
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)
{
2010-09-24 02:48:03 +02:00
partition_read_block(d, offset / (0x7c00 >> 2), block);
wbfs_memcpy( data, block + ( offset_in_block << 2 ), len_in_block );
}
2010-09-24 02:48:03 +02:00
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;
}
}
2010-09-24 02:48:03 +02:00
static u32 do_fst(wiidisc_t *d, u8 *fst, const char *names, u32 i)
{
u32 offset;
u32 size;
const char *name;
u32 j;
2010-09-24 02:48:03 +02:00
name = names + (_be32(fst + 12 * i) & 0x00ffffff);
size = _be32(fst + 12 * i + 8);
2010-09-24 02:48:03 +02:00
if (i == 0)
{
2010-09-24 02:48:03 +02:00
for (j = 1; j < size && !d->extracted_buffer;)
{
2010-09-24 02:48:03 +02:00
j = do_fst(d, fst, names, j);
}
return size;
}
//printf("name %s\n",name);
2010-09-24 02:48:03 +02:00
if (fst[12 * i])
{
2010-09-24 02:48:03 +02:00
for (j = i + 1; j < size && !d->extracted_buffer;)
j = do_fst(d, fst, names, j);
return size;
}
else
{
2010-09-24 02:48:03 +02:00
offset = _be32(fst + 12 * i + 4);
2010-09-24 02:48:03 +02:00
if (d->extract_pathname && strcasecmp(name, d->extract_pathname) == 0)
{
d->extracted_buffer = wbfs_ioalloc( size );
d->extracted_size = size;
2010-09-24 02:48:03 +02:00
partition_read(d, offset, d->extracted_buffer, size, 0);
}
2010-09-24 02:48:03 +02:00
else partition_read(d, offset, 0, size, 1);
return i + 1;
}
}
2010-09-24 02:48:03 +02:00
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;
2010-09-24 02:48:03 +02:00
partition_read(d, 0, b, 0x480, 0);
2010-09-24 02:48:03 +02:00
dol_offset = _be32(b + 0x0420);
fst_offset = _be32(b + 0x0424);
fst_size = _be32(b + 0x0428) << 2;
apl_offset = 0x2440 >> 2;
2010-09-24 02:48:03 +02:00
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
2010-09-24 02:48:03 +02:00
if (apl_size) partition_read(d, apl_offset, 0, apl_size, 1);
partition_read(d, dol_offset, 0, (fst_offset - dol_offset) << 2, 1);
2010-09-24 02:48:03 +02:00
if (fst_size)
{
fst = wbfs_ioalloc( fst_size );
2010-09-24 02:48:03 +02:00
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 && strcmp(d->extract_pathname, "FST") == 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;
}
2010-09-24 02:48:03 +02:00
if (12 * n_files <= fst_size)
{
2010-09-24 02:48:03 +02:00
if (n_files > 1) do_fst(d, fst, (char *) fst + 12 * n_files, 0);
}
2010-09-24 02:48:03 +02:00
if (fst != d->extracted_buffer) wbfs_iofree( fst );
}
wbfs_iofree( b );
wbfs_iofree( apl_header );
}
2010-09-24 02:48:03 +02:00
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
2010-09-24 02:48:03 +02:00
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 );
2010-09-24 02:48:03 +02:00
if (tmd == 0)
wbfs_fatal( "malloc tmd" );
partition_raw_read(d, tmd_offset, tmd, tmd_size);
if(d->extract_pathname && strcmp(d->extract_pathname, "TMD") == 0 && !d->extracted_buffer)
{
d->extracted_buffer = tmd;
d->extracted_size = 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 );
if(tmd != d->extracted_buffer)
wbfs_iofree( tmd );
2010-09-24 02:48:03 +02:00
do_files(d);
}
2010-09-24 02:48:03 +02:00
static int test_parition_skip(u32 partition_type, partition_selector_t part_sel)
{
2010-09-24 02:48:03 +02:00
switch (part_sel)
{
case ALL_PARTITIONS:
return 0;
case REMOVE_UPDATE_PARTITION:
2010-09-24 02:48:03 +02:00
return (partition_type == 1);
case ONLY_GAME_PARTITION:
2010-09-24 02:48:03 +02:00
return (partition_type != 0);
default:
2010-09-24 02:48:03 +02:00
return (partition_type != part_sel);
}
}
2010-09-24 02:48:03 +02:00
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;
2010-09-24 02:48:03 +02:00
disc_read(d, 0, b, 0x100);
magic = _be32(b + 24);
if (magic != 0x5D1C9EA3)
{
*Fixed display of partition size on WBFS partitions with a different wbfs sector size than 512bytes. *Made the ProgressWindow for game installation more accurate *Added displaying newly installed games (marked as new) on favorite list, so you don't have to change to full list when installing new games. (Thanks Cyan for the patch) *Lot's a small fixes *Added WDM Menu on game start. You can set it in the alternative DOL option (one new option there). The menu lists all DOLs on the disc and if a wdm file is provided in the WDM path (configurable in the settings) than the dol parameter and dol replacement name will be taken from the wdm. The DOLs that are not listed in the WDM but exist on the DISC will be listed at the end of the list. *Added avoid of multiple app cleanup when game fails to boot *Changed libfat to use FS info sector on FAT32 partitions. This speeds up the free space information getting to instant. For that the FS info sector has to have correct values. The values of all partitions where homebrews were writing to are currently incorrect because the official libfat does not support FS info sector (i submited a patch) (Windows does write it correct though). That is why there needs to be a synchronization of the FS info sector for partitions used with homebrews. For this purpose a new setting was added in the Loader Settings. You can synchronize all your FAT32 partitions on the USB with it once and you are done (if you don't write to that partition with current homebrews). After that you can enable free space display and it will be instant like on WBFS/NTFS/EXT partitions.
2011-01-16 14:12:07 +01:00
wbfs_iofree( b );
wbfs_error( "not a wii disc" );
2010-09-24 02:48:03 +02:00
return;
}
2010-09-24 02:48:03 +02:00
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++)
{
2010-09-24 02:48:03 +02:00
partition_offset[i] = _be32(b + 8 * i);
partition_type[i] = _be32(b + 8 * i + 4);
}
2010-09-24 02:48:03 +02:00
for (i = 0; i < n_partitions; i++)
{
d->partition_raw_offset = partition_offset[i];
2010-09-24 02:48:03 +02:00
if (!test_parition_skip(partition_type[i], d->part_sel)) do_partition(d);
}
wbfs_iofree( b );
}
2010-09-24 02:48:03 +02:00
wiidisc_t *wd_open_disc(read_wiidisc_callback_t read, void*fp)
{
wiidisc_t *d = wbfs_malloc( sizeof( wiidisc_t ) );
2010-09-24 02:48:03 +02:00
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;
}
2010-09-24 02:48:03 +02:00
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.
2010-09-24 02:48:03 +02:00
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;
2010-09-24 02:48:03 +02:00
do_disc(d);
d->extract_pathname = 0;
d->part_sel = ALL_PARTITIONS;
retval = d->extracted_buffer;
return retval;
}
2010-09-24 02:48:03 +02:00
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;
2010-09-24 02:48:03 +02:00
do_disc(d);
d->part_sel = ALL_PARTITIONS;
d->sector_usage_table = 0;
}
2010-09-24 02:48:03 +02:00
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;
2010-09-24 02:48:03 +02:00
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." );
2010-09-24 02:48:03 +02:00
b += (_be32(b + 4) - (0x40000 >> 2)) * 4;
j = 0;
2010-09-24 02:48:03 +02:00
for (i = 0; i < n_partitions; i++)
{
2010-09-24 02:48:03 +02:00
partition_offset = _be32(b + 8 * i);
partition_type = _be32(b + 8 * i + 4);
if (!test_parition_skip(partition_type, selector))
{
2010-09-24 02:48:03 +02:00
b32 = (u32*) (b + 8 * j);
b32[0] = wbfs_htonl( partition_offset );
b32[1] = wbfs_htonl( partition_type );
j++;
}
}
2010-09-24 02:48:03 +02:00
b32 = (u32*) (partition_table);
*b32 = wbfs_htonl( j );
}