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"
|
|
|
|
|
|
|
|
void aes_set_key(u8 *key);
|
|
|
|
void aes_decrypt(u8 *iv, u8 *inbuf, u8 *outbuf, unsigned long long len);
|
|
|
|
|
2010-02-09 07:33:18 +01:00
|
|
|
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);
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
2010-02-09 07:33:18 +01:00
|
|
|
static u32 _be32(const u8 *p) {
|
|
|
|
return (p[0] << 24) | (p[1] << 16) | (p[2] << 8) | p[3];
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-02-09 07:33:18 +01:00
|
|
|
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);
|
|
|
|
}
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-02-09 07:33:18 +01:00
|
|
|
static void partition_raw_read(wiidisc_t *d,u32 offset, u8 *data, u32 len) {
|
|
|
|
disc_read(d, d->partition_raw_offset + offset, data, len);
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-02-09 07:33:18 +01:00
|
|
|
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);
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-02-09 07:33:18 +01: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;
|
|
|
|
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-02-09 07:33:18 +01:00
|
|
|
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;
|
|
|
|
}
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-02-09 07:33:18 +01: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;
|
|
|
|
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
|
|
|
|
partition_read(d,apl_offset, 0, apl_size,1);
|
|
|
|
partition_read(d,dol_offset, 0, (fst_offset - dol_offset)<<2,1);
|
|
|
|
|
|
|
|
|
|
|
|
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 (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-02-09 07:33:18 +01: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
|
|
|
|
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-02-09 07:33:18 +01:00
|
|
|
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);
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-02-09 07:33:18 +01:00
|
|
|
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;
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
2010-02-09 07:33:18 +01:00
|
|
|
void wd_close_disc(wiidisc_t *d) {
|
|
|
|
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-02-09 07:33:18 +01: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-02-09 07:33:18 +01: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;
|
|
|
|
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-02-09 07:33:18 +01: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;
|
|
|
|
do_disc(d);
|
|
|
|
d->part_sel = ALL_PARTITIONS;
|
|
|
|
d->sector_usage_table = 0;
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|
2010-02-09 07:33:18 +01: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;
|
|
|
|
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-02-09 07:33:18 +01:00
|
|
|
}
|
|
|
|
b32 = (u32*)(partition_table);
|
|
|
|
*b32 = wbfs_htonl(j);
|
2009-05-03 20:53:31 +02:00
|
|
|
}
|
|
|
|
|