* NUS downloader - add 2 more full updates to the list

* fix bug in creating the jap lists
* change the saveDialog to openFileDialog when opening a wad
* remove default text in the NUS downloader
* nandBin class - add the beginnings of writing...  adding & deleting files & directories apparently is working.  writing data to a file appears to work
* right now, its not writing to the nand.bin, so dont use these functions
* probably some other stuff i forgot
This commit is contained in:
giantpune@gmail.com 2010-12-16 23:37:30 +00:00
parent 6ce5ffc566
commit 8eac10ea9e
9 changed files with 629 additions and 386 deletions

View File

@ -60,6 +60,48 @@ bool NandBin::ExtractToDir( QTreeWidgetItem *item, const QString &path )
return true;
}
QTreeWidgetItem *NandBin::CreateItem( QTreeWidgetItem *parent, const QString &name, quint32 size, quint16 entry, quint32 uid, quint32 gid, quint32 x3, quint8 attr, quint8 wtf)
{
if( !parent )
return NULL;
QStringList text;
QString enStr = QString( "%1" ).arg( entry );
QString sizeStr = QString( "%1" ).arg( size, 0, 16 );
QString uidStr = QString( "%1" ).arg( uid, 8, 16, QChar( '0' ) );
QString gidStr = QString( "%1 (\"%2%3\")" ).arg( gid, 4, 16, QChar( '0' ) )
.arg( QChar( ascii( (char)( (gid >> 8) & 0xff ) ) ) )
.arg( QChar( ascii( (char)( (gid) & 0xff ) ) ) );
QString x3Str = QString( "%1" ).arg( x3, 8, 16, QChar( '0' ) );
QString wtfStr = QString( "%1" ).arg( wtf, 2, 16, QChar( '0' ) );
QString attrStr = QString( "%1 " ).arg( ( attr & 3 ), 2, 16, QChar( '0' ) );
quint8 m = attr;
const char perm[ 3 ] = {'-','r','w'};
for( quint8 i = 0; i < 3; i++ )
{
attrStr += perm[ ( m >> 6 ) & 1 ];
attrStr += perm[ ( m >> 6 ) & 2 ];
attrStr += ' ';
m <<= 2;
}
attrStr += QString( "[%1]" ).arg( attr, 2, 16, QChar( '0' ) );
text << name << enStr << sizeStr << uidStr << gidStr << x3Str << wtfStr << attrStr;
//qDebug() << "adding" << name << en << size << uid << gid << x3 << mode << attr;
QTreeWidgetItem *child = new QTreeWidgetItem( parent, text );
child->setTextAlignment( 1, Qt::AlignRight | Qt::AlignVCenter );//align to the right
child->setTextAlignment( 2, Qt::AlignRight | Qt::AlignVCenter );
child->setTextAlignment( 3, Qt::AlignRight | Qt::AlignVCenter );
child->setTextAlignment( 4, Qt::AlignRight | Qt::AlignVCenter );
child->setTextAlignment( 5, Qt::AlignRight | Qt::AlignVCenter );
child->setTextAlignment( 6, Qt::AlignRight | Qt::AlignVCenter );
//child->setTextAlignment( 7, Qt::AlignRight | Qt::AlignVCenter );
child->setFont( 7, QFont( "Courier New", 10, 5 ) );
return child;
}
bool NandBin::AddChildren( QTreeWidgetItem *parent, quint16 entry )
{
if( entry >= 0x17ff )
@ -78,36 +120,18 @@ bool NandBin::AddChildren( QTreeWidgetItem *parent, quint16 entry )
if( !AddChildren( parent, fst.sib ) )
return false;
}
QStringList text;
QString name = FstName( fst );
QString en = QString( "%1" ).arg( entry );
QString size = QString( "%1" ).arg( fst.size, 0, 16 );
QString uid = QString( "%1" ).arg( fst.uid, 8, 16, QChar( '0' ) );
QString gid = QString( "%1 (\"%2%3\")" ).arg( fst.gid, 4, 16, QChar( '0' ) )
.arg( QChar( ascii( (char)( (fst.gid >> 8) & 0xff ) ) ) )
.arg( QChar( ascii( (char)( (fst.gid) & 0xff ) ) ) );
QString x3 = QString( "%1" ).arg( fst.x3, 8, 16, QChar( '0' ) );
QString mode = QString( "%1" ).arg( fst.mode, 2, 16, QChar( '0' ) );
QString attr = QString( "%1" ).arg( fst.attr, 2, 16, QChar( '0' ) );
text << name << en << size << uid << gid << x3 << mode << attr;
QTreeWidgetItem *child = new QTreeWidgetItem( parent, text );
child->setTextAlignment( 1, Qt::AlignRight | Qt::AlignVCenter );//align to the right
child->setTextAlignment( 2, Qt::AlignRight | Qt::AlignVCenter );
child->setTextAlignment( 3, Qt::AlignRight | Qt::AlignVCenter );
child->setTextAlignment( 4, Qt::AlignRight | Qt::AlignVCenter );
child->setTextAlignment( 5, Qt::AlignRight | Qt::AlignVCenter );
child->setTextAlignment( 6, Qt::AlignRight | Qt::AlignVCenter );
child->setTextAlignment( 7, Qt::AlignRight | Qt::AlignVCenter );
QTreeWidgetItem *child = CreateItem( parent, FstName( fst ), fst.size, entry, fst.uid, fst.gid, fst.x3, fst.attr, fst.wtf );
//set some icons
if( fst.mode )
if( ( fst.attr & 3 ) == 1 )
{
//qDebug() << "is a file";
child->setIcon( 0, keyIcon );
}
else
{
//qDebug() << "is a folder" << (fst.attr & 3);
child->setIcon( 0, groupIcon );
//try to add subfolder contents to the tree
if( fst.sub != 0xffff && !AddChildren( child, fst.sub ) )
@ -137,9 +161,9 @@ bool NandBin::ExtractFST( quint16 entry, const QString &path, bool singleFile )
if( !singleFile && fst.sib != 0xffff && !ExtractFST( fst.sib, path ) )
return false;
switch( fst.mode )
switch( fst.attr & 3 )
{
case 0:
case 2:
if( !ExtractDir( fst, path ) )
return false;
break;
@ -300,8 +324,26 @@ bool NandBin::InitNand( QIcon dirs, QIcon files )
}*/
//GetFile( 369 );
//testing creating new items
/*quint16 n = CreateEntry( "/test1", 0x4444, 0x5555, 2, 1, 2, 0 );
qDebug() << "created item" << n;
n = CreateEntry( "/test1/test2", 0x4444, 0x5555, 1, 1, 2, 3 );
qDebug() << "created item" << n;
if( n )
{
bool dd = SetData( n, QByteArray( 0x3000000, '\x69') );
qDebug() << "added data" << dd;
}*/
/*ShowLostClusters();
bool x= Delete( "/ticket" );
qDebug() << "delete" << x;
ShowLostClusters();*/
//delete root;
//root = new QTreeWidgetItem( QStringList() << nandPath );
//AddChildren( root, 0 );
if( !bootBlocks.SetBlocks( blocks ) )
@ -310,6 +352,39 @@ bool NandBin::InitNand( QIcon dirs, QIcon files )
return true;
}
void NandBin::ShowLostClusters()
{
QList<quint16> u = GetFatsForEntry( 0 );
quint16 ss = fats.size();
qDebug() << "total used clusters" << u.size() << "of" << ss << "total";
quint16 lost = 0;
QList<quint16> ffs;
QList<quint16> frs;
for( quint16 i = 0; i < ss; i++ )
{
if( u.contains( fats.at( i ) ) )//this cluster is really used
continue;
switch( fats.at( i ) )
{
case 0xFFFB:
case 0xFFFC:
case 0xFFFD:
break;
case 0xFFFE:
frs << i;
break;
case 0xFFFF:
ffs << i;
break;
default:
lost++;
qDebug() << hex << i << fats.at( i );
break;
}
}
qDebug() << "found" << lost << "lost clusters\n0xffffs" << hex << ffs.size() << ffs << "\nfree" << frs.size();
}
int NandBin::GetDumpType( quint64 fileSize )
{
quint64 sizes[] = { 536870912, // type 0 | 536870912 == no ecc
@ -464,7 +539,7 @@ fst_t NandBin::GetFST( quint16 entry )
fst_t fst;
if( entry >= 0x17FF )
{
emit SendError( tr( "Tried to get entry above 0x17ff [ 0x%1 ]" ).arg( entry, 0, 16 ) );
emit SendError( tr( "Tried to get entry above 0x17fe [ 0x%1 ]" ).arg( entry, 0, 16 ) );
fst.filename[ 0 ] = '\0';
return fst;
}
@ -485,8 +560,8 @@ fst_t NandBin::GetFST( quint16 entry )
f.seek( loc_fst + loc_entry );
f.read( (char*)&fst.filename, 0xc );
f.read( (char*)&fst.mode, 1 );
f.read( (char*)&fst.attr, 1 );
f.read( (char*)&fst.wtf, 1 );
f.read( (char*)&fst.sub, 2 );
f.read( (char*)&fst.sib, 2 );
if( type && ( entry + 1 ) % 64 == 0 )//bug in other nand.bin extracterizers. the entry for every 64th fst item is inturrupeted by some spare shit
@ -509,7 +584,7 @@ fst_t NandBin::GetFST( quint16 entry )
fst.x3 = qFromBigEndian( fst.x3 );
fst.fst_pos = entry;
fst.mode &= 1;
//fst.mode &= 1;
return fst;
}
@ -668,11 +743,39 @@ const QList<quint16> NandBin::GetFatsForFile( quint16 i )
return ret;
quint16 fat = fst.sub;
//qDebug() << hex << fat;
quint16 j = 0;//just to make sure a broken nand doesnt lead to an endless loop
while ( fat < 0x8000 && fat > 0 && ++j )
{
ret << fat;
fat = GetFAT( fat );
//qDebug() << hex << fat;
}
//qDebug() << hex << ret;
return ret;
}
const QList<quint16> NandBin::GetFatsForEntry( quint16 i )
{
//qDebug() << "NandBin::GetFatsForEntry" << i;
fst_t fst = GetFST( i );
QList<quint16> ret;
if( fst.sib != 0xffff )
{
ret.append( GetFatsForEntry( fst.sib ) );
}
if( ( fst.attr & 3 ) == 1 )
{
ret.append( GetFatsForFile( i ) );
}
else
{
if( fst.sub != 0xffff )
ret.append( GetFatsForEntry( fst.sub ) );
}
return ret;
@ -689,7 +792,7 @@ const QByteArray NandBin::GetData( const QString &path )
if( !item )
return QByteArray();
if( !item->text( 6 ).contains( "1" ) )
if( !item->text( 7 ).startsWith( "01" ) )
{
qDebug() << "NandBin::GetData -> can't get data for a folder" << item->text( 0 );
return QByteArray();
@ -735,6 +838,22 @@ QTreeWidgetItem *NandBin::ItemFromPath( const QString &path )
return item;
}
QTreeWidgetItem *NandBin::GetParent( const QString &path )
{
if( !path.startsWith( "/" ) || !root || !root->childCount() )//invalid entry
return NULL;
if( path.count( "/" ) < 2 )//this will be an entry in the root
return root->child( 0 );
QString parent = path;
if( parent.endsWith( "/" ) )
parent.resize( parent.size() - 1 );
int sl = parent.lastIndexOf( "/" );
parent.resize( sl );
return ItemFromPath( parent );
}
QTreeWidgetItem *NandBin::FindItem( const QString &s, QTreeWidgetItem *parent )
{
int cnt = parent->childCount();
@ -785,6 +904,34 @@ void NandBin::ShowInfo()
<< "\nreserved:" << hex << reserved;
}
QTreeWidgetItem *NandBin::ItemFromEntry( quint16 i, QTreeWidgetItem *parent )
{
return ItemFromEntry( QString( "%1" ).arg( i ), parent );
}
QTreeWidgetItem *NandBin::ItemFromEntry( const QString &i, QTreeWidgetItem *parent )
{
if( !parent )
return NULL;
//qDebug() << "NandBin::ItemFromEntry" << i << parent->text( 0 );
quint32 cnt = parent->childCount();
for( quint32 j = 0; j < cnt; j++ )
{
QTreeWidgetItem *child = parent->child( j );
if( child->text( 1 ) == i )
return child;
//qDebug() << child->text( 2 ) << i;
QTreeWidgetItem *r = ItemFromEntry( i, child );
if( r )
return r;
}
return NULL;
}
bool NandBin::WriteCluster( quint32 pageNo, const QByteArray data, const QByteArray hmac )
{
if( data.size() != 0x4000 )
@ -825,15 +972,7 @@ bool NandBin::WriteDecryptedCluster( quint32 pageNo, const QByteArray data, fst_
hexdump( hmac );
return true;
/*fs_hmac_data(
buffer,
fp->node->uid,
(const unsigned char *)fp->node->name,
fp->idx,
fp->node->dummy,
fp->cluster_idx,
hmac
);*/
AesSetKey( key );
QByteArray encData = AesEncrypt( 0, data );
return WriteCluster( pageNo, encData, hmac );
@ -859,306 +998,358 @@ bool NandBin::WritePage( quint32 pageNo, const QByteArray data )
return f.write( data );
}
/*
structure of blocks 0 - 7
block 0 ( boot 1 )
0 - 0x4320
a bunch of encrypted gibberish.
the rest of the block as all 0s
sha1 of the whole block is
2cdd5affd2e78c537616a119a7a2e1c568e91f22 with boot1b
f01e8aca029ee0cb5287f5055da1a0bed2a533fa with boot1c
<sven> {1, {0x4a, 0x7c, 0x6f, 0x30, 0x38, 0xde, 0xea, 0x7a, 0x07, 0xd3, 0x32, 0x32, 0x02, 0x4b, 0xe9, 0x5a, 0xfb, 0x56, 0xbf, 0x65}},
<sven> {1, {0x2c, 0xdd, 0x5a, 0xff, 0xd2, 0xe7, 0x8c, 0x53, 0x76, 0x16, 0xa1, 0x19, 0xa7, 0xa2, 0xe1, 0xc5, 0x68, 0xe9, 0x1f, 0x22}},
<sven> {0, {0xf0, 0x1e, 0x8a, 0xca, 0x02, 0x9e, 0xe0, 0xcb, 0x52, 0x87, 0xf5, 0x05, 0x5d, 0xa1, 0xa0, 0xbe, 0xd2, 0xa5, 0x33, 0xfa}},
<sven> {0, {0x8d, 0x9e, 0xcf, 0x2f, 0x8f, 0x98, 0xa3, 0xc1, 0x07, 0xf1, 0xe5, 0xe3, 0x6f, 0xf2, 0x4d, 0x57, 0x7e, 0xac, 0x36, 0x08}},
block 1 ( boot2 ) //just guessing at these for now
u32 0x20 //header size
u32 0xf00 //data offset
u32 0xa00 //cert size
u32 0x2a4 //ticket size
u32 0x208 //tmd size
u32[ 3 ] 0s //padding till 0x20
0x20 - 0x9f0
cert
root ca00000001
Root-CA00000001 CP00000004
Root-CA00000001 XS00000003
round up to 0xa20
ticket for boot2
0xcc4
tmd?
0xecc - 0xf00
gibberish padding
0xf00
start of boot2 contents? ( mine is 0x00023E91 bytes in the TMD for v2 )
there is room for 0x1F100 bytes of it left in this block with 0x4D91 leftover
block 2
bunch of gibberish till 0x4da0
probably the rest of the contents of boot2, padded with gibberish
0x4da0
0s till 0x5000
0x5000 - 0x1f800
bunch of 0xffff
maybe this is untouched nand. never been written to
0x1f800 - 0x1f8e4 blockmap?
26F29A401EE684CF0000000201000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000201000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000201000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
0x1f8e4 - 0x20000 ( end of block )
more 0s
block 2 ( looks a lot like block 1 )
wad type header again with the same values
same cert looking doodad again
same ticket again
tmd is different ( zero'd RSA )
1 content, 0x00027B5D bytes
block 4 ( the rest of bootmii )
0 - 0x8a60
gibberish - rest of bootmii content still encrypted
0x8a60 - 0x1f800
0s
0x1f800
26F29A401EE684CF0000000501010100
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000501010100
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000501010100
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
then 0s till 0x20000 ( end of block )
block 5
all 0xff
block 6
identical to block 2 except this only difference is the 0x02 is 0x03
26F29A401EE684CF0000000301000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000301000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000301000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
block 7
identical to block 1
///////////
nand #2 ( no bootmii )
///////////
block 0 ( boot 1c )
gibberish till 0x43f0. then 0s for the rest of the block
block 1
same wad header as before, with the same values
cert doodad and ticket are the same
tmd is v4 and the content is 0x00027BE8 bytes
block 2
0 - 0x8af0
the rest of the content from boot2 ( padded to 0x40 )
0s till 0x9000
0xff till 0x18f00
26F29A401EE684CF0000000201000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000201000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000201000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
block 3 - all 0xff
block 4 - all 0xff
block 5 - all 0xff
block 6 - identical to block 2 except the blockmap ( generation 3 ? )
26F29A401EE684CF0000000301000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000301000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
26F29A401EE684CF0000000301000000
00000000010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
010101010101010101010101
block 7 matches block 1
/////////////
nand #3
/////////////
block 2
26F29A401EE684CF
00000004
01000000000000000101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
block 4
26F29A401EE684CF
0000000F
01010100000000000101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
block 6
26F29A401EE684CF
00000005
01000000000000000101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
26F29A401EE684CF
00000002
01000000000000000101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
26F29A401EE684CF
00000005
01010100000000000101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
01010101010101010101010101010101
*/
quint16 NandBin::CreateNode( const QString &name, quint32 uid, quint16 gid, quint8 attr, quint8 user_perm, quint8 group_perm, quint8 other_perm )
{
attr = attr | ( ( user_perm & 3 ) << 6 ) | ( ( group_perm & 3 ) << 4 ) | ( ( other_perm & 3 ) << 2 );
quint32 i;//TODO: maybe add in some sort of wear-leveling emulation so all new entries arent created in sequential order
for( i = 1; i < 0x17ff; i++ )//cant be entry 0 because that is the root
{
fst_t fst = fsts[ i ];
if( !fst.filename[ 0 ] )//this one doesnt have a filename, it cant be used already
break;
}
if( i == 0x17ff )
{
return 0;
}
QByteArray n = name.toLatin1().data();
n.resize( 12 );
memcpy( &fsts[ i ].filename, n, 12 );
fsts[ i ].attr = attr;
fsts[ i ].wtf = 0;
if( ( attr & 3 ) == 2 )
{
fsts[ i ].sub = 0xffff;
}
else
{
fsts[ i ].sub = 0xfffb;
}
fsts[ i ].sib = 0xffff;
fsts[ i ].size = 0;
fsts[ i ].uid = uid;
fsts[ i ].gid = gid;
fsts[ i ].x3 = 0;
return i;
}
quint16 NandBin::CreateEntry( const QString &path, quint32 uid, quint16 gid, quint8 attr, quint8 user_perm, quint8 group_perm, quint8 other_perm )
{
qDebug() << "NandBin::CreateEntry" << path;
quint16 ret = 0;
QTreeWidgetItem *par = GetParent( path );
if( !par )
{
qDebug() << "NandBin::CreateEntry -> parent doesnt exist for" << path;
return ret;
}
QString name = path;
name.remove( 0, name.lastIndexOf( "/" ) + 1 );
if( name.size() > 12 )
return 0;
//QTreeWidgetItem *cur = NULL;
quint32 cnt = par->childCount();
for( quint32 i = 0; i < cnt; i++ )
{
if( par->child( i )->text( 0 ) == name )
{
qDebug() << "NandBin::CreateEntry ->" << path << "already exists";
return ret;
}
}
bool ok = false;
quint16 parIdx = par->text( 1 ).toInt( &ok );
if( !ok || parIdx > 0x17fe )
return 0;//wtf
//fst_t parFst = fsts[ parIdx ];
if( fsts[ parIdx ].sub == 0xffff )//directory has no entries yet
{
ret = CreateNode( name, uid, gid, attr, user_perm, group_perm, other_perm );
if( !ret )
return 0;
fsts[ parIdx ].sub = ret;
}
else//find the last entry in this directory
{
quint16 entryNo = 0;
for( quint32 i = cnt; i > 0; i-- )
{
entryNo = par->child( i - 1 )->text( 1 ).toInt( &ok );
if( !ok || entryNo > 0x17fe )
return 0;//wtf
if( fsts[ entryNo ].sib == 0xffff )
break;
if( i == 1 )//wtf
return 0;
}
if( !entryNo )//something is busted, none of the child entries is marked as the last one
return 0;
ret = CreateNode( name, uid, gid, attr, user_perm, group_perm, other_perm );
if( !ret )
return 0;
fsts[ entryNo ].sib = ret;
}
QTreeWidgetItem *child = CreateItem( par, name, 0, ret, uid, gid, 0, fsts[ ret ].attr, 0 );
if( attr == 1 )
{
child->setIcon( 0, keyIcon );
}
else
{
child->setIcon( 0, groupIcon );
}
return ret;
}
bool NandBin::Delete( const QString &path )
{
QTreeWidgetItem *i = ItemFromPath( path );
return DeleteItem( i );
}
bool NandBin::DeleteItem( QTreeWidgetItem *item )
{
qDebug() << "NandBin::DeleteItem" << item->text( 0 );
if( !item )
{
qWarning() << "cant delete a null item";
return false;
}
bool ok = false;
quint16 idx = item->text( 1 ).toInt( &ok );//get the index of the entry to remove
if( !ok || idx > 0x17fe )
{
qWarning() << "wtf1";
return false;//wtf
}
//find the entry that is this one's previous sibling
quint16 pId = 0xffff;//this is the index of the item in relation to its parent
quint16 prevSib = 0;
QTreeWidgetItem *par = item->parent();
if( !par )
{
qWarning() << "wtf2";
return false;//trying to delete the root item
}
quint16 parIdx = par->text( 1 ).toInt( &ok );
if( !ok || parIdx > 0x17fe )
{
qWarning() << "wtf1a";
return false;//wtf
}
if( fsts[ parIdx ].sub == idx ) //this is the first item in the folder, point the parent to this items first sibling
fsts[ parIdx ].sub = fsts[ idx ].sib;
else //point the previous entry to the next one
{
quint16 cnt = par->childCount();
for( quint16 i = 0; i < cnt; i++ )
{
//qDebug() << i << par->child( i )->text( 0 ) << pId << prevSib;
quint16 r = par->child( i )->text( 1 ).toInt( &ok );
if( !ok || r > 0x17fe )
{
qWarning() << "wtf3";
return false;//wtf
}
if( fsts[ r ].sib == idx )//this is the one we want
prevSib = r;
if( par->child( i )->text( 0 ) == item->text( 0 ) )
{
pId = i;
//qDebug() << "found the item";
}
if( pId != 0xffff && prevSib )
break;
if( i == cnt - 1 )//not found
{
qWarning() << "wtf4" << pId << prevSib;
return false;
}
}
fsts[ prevSib ].sib = fsts[ idx ].sib;
}
switch( fsts[ idx ].attr & 3 )
{
case 1:
{
int q = 0;
qDebug() << "deleting clusters of" << item->text( 0 ) << idx;
quint16 fat = fsts[ idx ].sub;//delete all this file's clusters
//fats.replace( fat, 0xfffe );
do
{
fats.replace( fat, 0xfffe );
//qDebug() << "fat" << hex << fat;
fat = GetFAT( fat );
//fats.replace( fat, 0xfffe );
//qDebug() << "deleting cluster" << hex << fat << "from table";
q++;
}
while( fat < 0x17ff );
qDebug() << "delete loop done. freed" << q << "clusters";
}
break;
case 2:
{
qDebug() << "deleting children of" << item->text( 0 );
quint32 cnt = item->childCount();//delete all the children of this item
for( quint32 i = cnt - 1; i > 0; i-- )
{
if( !DeleteItem( item->child( i - 1 ) ) )
{
qWarning() << "wtf6";
return false;
}
}
}
break;
}
memset( &fsts[ idx ], 0, sizeof( fst_t ) ); //clear this entry
QTreeWidgetItem *d = par->takeChild( pId );
delete d;
return true;
}
bool NandBin::SetData( const QString &path, const QByteArray data )
{
QTreeWidgetItem *i = ItemFromPath( path );
if( !i )
return false;
bool ok = false;
quint16 idx = i->text( 1 ).toInt( &ok );//find the entry
if( !ok || idx > 0x17fe )
return false;
return SetData( idx, data );
}
bool NandBin::SetData( quint16 idx, const QByteArray data )
{
fst_t fst = fsts[ idx ];
if( ( fst.attr & 3 ) != 1 )
return false;
QList<quint16> fts = GetFatsForFile( idx ); //get the currently used fats and overwrite them. this doesnt serve much purpose, but it seems cleaner
QByteArray pData = PaddedByteArray( data, 0x4000 );//actual data that must be written to the nand
quint32 size = pData.size(); //the actual space used in the nand for this file
quint16 clCnt = size / 0x4000; //the number of clusters needed to hold this file
//if this new data will take more clusters than the old data, create the new ones
if( clCnt > fts.size() )
{
//list all the free clusters
QList<quint16>freeClusters;
for( quint16 i = 0; i < 0x8000; i++ )
{
if( fats.at( i ) == 0xfffe )
freeClusters << i;
}
if( freeClusters.size() < clCnt - fts.size() )
{
qWarning() << "not enough free clusters to write the file";
return false;
}
//setup random number stuff to emulate wear leveling
QTime midnight( 0, 0, 0 );
qsrand( midnight.secsTo( QTime::currentTime() ) );
//now grab the clusters that will be used from the list
qDebug() << "trying to find" << ( clCnt - fts.size() ) << "free clusters";
while( fts.size() < clCnt )
{
if( !freeClusters.size() )//avoid endless loop in case there are some clusters that should be free, but the spare data says theyre bad
return false;
//grab a random cluster from the list
quint16 idx = qrand() % freeClusters.size();
quint16 cl = freeClusters.takeAt( idx ); //remove this number from the list
fts << cl; //add this one to the clusters that will be used to hold the data
quint16 block = cl / 8; //try to find other clusters in the same block
for( quint16 i = block * 8; i < ( ( block + 1 ) * 8 ) && fts.size() < clCnt; i++ )
{
if( cl == i ) //this one is already added to the list
continue;
if( fats.at( i ) == 0xfffe ) //theres more free clusters in this same block, grab them
{
fts << i;
freeClusters.removeAt( freeClusters.indexOf( i, MAX( cl - 8, 0 ) ) );
}
}
//read the spare data to see that the cluster is good - removed for now. but its probably not a bad idea to do this
/*if( type )//if the dump doesnt have spare data, dont try to read it, just assume the cluster is good
{
QByteArray page = GetPage( cl * 8, true );
if( page.isEmpty() )
continue;
QByteArray spr = page.right( 0x40 );
if( !spr.startsWith( 0xff ) )
{
qWarning() << "page" << hex << ( cl * 8 ) << "is bad??";
continue;
}
}*/
}
}
//qDebug() << "about to writing shit" << clCnt << fts.size();
//qDebug() << "file will be on clusters" << hex << fts;
for( quint32 i = 0; i < clCnt; i++ )
{
QByteArray cluster = pData.mid( i * 0x4000, 0x4000 );
if( !WriteDecryptedCluster( ( fts.at( i ) * 8 ), cluster, fst, i ) )
return false;
}
//qDebug() << "done writing shit, fix the fats now" << clCnt << fts.size();
//all the data has been written, now make sure the fats are correct
fsts[ idx ].sub = fts.at( 0 );
for( quint16 i = 0; i < clCnt - 1; i++ )
{
//qDebug() << "replacing fat" << hex << fts.at( 0 );
fats.replace( fts.at( 0 ), fts.at( 1 ) );
fts.takeFirst();
}
//qDebug() << "loop is done";
fats.replace( fts.at( 0 ), 0xfffb );//last cluster in chain
fts.takeFirst();
//qDebug() << "fixed the last one" << hex << fts;
// if the new data uses less clusters than the previous data, mark the extra ones as free
while( !fts.isEmpty() )
{
fats.replace( fts.at( 0 ), 0xfffe );
fts.takeFirst();
}
//qDebug() << "2nd loop is done";
fsts[ idx ].size = data.size();
QTreeWidgetItem *i = ItemFromEntry( idx, root );
if( !i )
return false;
i->setText( 2, QString( "%1" ).arg( data.size(), 0, 16 ) );
return true;
}

View File

@ -7,8 +7,8 @@
struct fst_t
{
quint8 filename[ 0xc ];
quint8 mode;
quint8 attr;
quint8 wtf;
quint16 sub;
quint16 sib;
quint32 size;
@ -20,6 +20,8 @@ struct fst_t
// class to deal with an encrypted wii nand dump
// basic usage... create an object, set a path, call InitNand. then you can get the detailed list of entries with GetTree()
// extract files with GetFile()
//! so far, all functions for writing to the nand are just dummies. i have only run a few test with them, but now actually used them to write to a nand
//! dont try to use them yet
//once InitNand() is called, you can get the contents of the nand in a nice QTreeWidgetItem* with GetTree()
class NandBin : public QObject
@ -87,12 +89,33 @@ public:
//get the fats for a given file
const QList<quint16> GetFatsForFile( quint16 i );
//recurse folders and files and get all fats used for them
//! this is probably a more expensive function than you want to use
//! it was added only to aid in checking for bugs and lost clusters
const QList<quint16> GetFatsForEntry( quint16 i );
//use the above function to search and display lost clusters
void ShowLostClusters();
const Blocks0to7 BootBlocks(){ return bootBlocks; }
const QList<Boot2Info> Boot2Infos();
quint8 Boot1Version();
QByteArray GetPage( quint32 pageNo, bool withEcc = false );
//create new entry
//returns the index of the entry on success, or 0 on error
quint16 CreateEntry( const QString &path, quint32 uid, quint16 gid, quint8 attr, quint8 user_perm, quint8 group_perm, quint8 other_perm );
//delete a file/folder
bool Delete( const QString &path );
//sets the data for a given file ( overwrites existing data )
bool SetData( quint16 idx, const QByteArray data );
//overloads the above function
bool SetData( const QString &path, const QByteArray data );
private:
QByteArray key;
@ -133,6 +156,8 @@ private:
bool ExtractDir( fst_t fst, QString parent );
bool ExtractFile( fst_t fst, QString parent );
QTreeWidgetItem *CreateItem( QTreeWidgetItem *parent, const QString &name, quint32 size, quint16 entry, quint32 uid, quint32 gid, quint32 x3, quint8 attr, quint8 wtf);
QTreeWidgetItem *root;
@ -147,6 +172,15 @@ private:
bool WriteDecryptedCluster( quint32 pageNo, const QByteArray data, fst_t fst, quint16 idx );
bool WritePage( quint32 pageNo, const QByteArray data );
quint16 CreateNode( const QString &name, quint32 uid, quint16 gid, quint8 attr, quint8 user_perm, quint8 group_perm, quint8 other_perm );
bool DeleteItem( QTreeWidgetItem *item );
//find a parent entry for a path to be created - "/title/00000001" should give the entry for "/title"
QTreeWidgetItem *GetParent( const QString &path );
QTreeWidgetItem *ItemFromEntry( quint16 i, QTreeWidgetItem *parent = NULL );
QTreeWidgetItem *ItemFromEntry( const QString &i, QTreeWidgetItem *parent = NULL );
signals:
//connect to these to receive messages from this object
void SendError( QString );

View File

@ -185,7 +185,7 @@ void fs_hmac_data( const unsigned char *data, quint32 uid, const unsigned char *
QByteArray NandSpare::Get_hmac_data( const QByteArray cluster, quint32 uid, const unsigned char *name, quint32 entry_n, quint32 x3, quint16 blk )
{
qDebug() << "NandSpare::Get_hmac_data" << hex << cluster.size() << uid << QString( (const char*)name, 12 ) << entry_n << x3 << blk;
//qDebug() << "NandSpare::Get_hmac_data" << hex << cluster.size() << uid << QString( (const char*)name ) << entry_n << x3 << blk;
if( hmacKey.size() != 0x14 || cluster.size() != 0x4000 )
return QByteArray();

View File

@ -539,6 +539,7 @@ bool NusDownloader::GetUpdate( const QString & upd, bool decrypt )
if( s == "2.1e" ) titles = List21e();
else if( s == "3.0e" ) titles = List30e();
else if( s == "3.1e" ) titles = List31e();
else if( s == "3.3e" ) titles = List33e();
else if( s == "3.4e" ) titles = List34e();
else if( s == "4.0e" ) titles = List40e();
else if( s == "4.1e" ) titles = List41e();
@ -563,6 +564,7 @@ bool NusDownloader::GetUpdate( const QString & upd, bool decrypt )
else if( s == "2.0j" ) titles = List20j();
else if( s == "3.1j" ) titles = List31j();
else if( s == "3.3j" ) titles = List33j();
else if( s == "3.4j" ) titles = List34j();
else if( s == "4.0j" ) titles = List40j();
else if( s == "4.1j" ) titles = List41j();
@ -592,8 +594,8 @@ QMap< quint64, quint16 > NusDownloader::List20j()
titles.insert( 0x100000023ull, 0xc10 ); // IOS35 - not really part of this update, but needed for sneek
titles.insert( 0x100000100ull, 0x2 );//bcv2
titles.insert( 0x100000101ull, 0x4 );//miosv4
titles.insert( 0x1000848414B45ull, 0 );//EULA - HAKE
titles.insert( 0x1000848414C45ull, 0x2 );//regsel //region select isnt in the paper mario update, but putting it here just to be safe
titles.insert( 0x1000848414B4aull, 0 );//EULA - HAKJ
titles.insert( 0x1000848414C4aull, 0x2 );//regsel //region select isnt in the paper mario update, but putting it here just to be safe
titles.insert( 0x1000248414341ull, 0x2 );//nigaoeNRv2 - MII
titles.insert( 0x1000248414141ull, 0x1 );//photov1
titles.insert( 0x1000248414241ull, 0x4 );//shoppingv4
@ -602,9 +604,8 @@ QMap< quint64, quint16 > NusDownloader::List20j()
QMap< quint64, quint16 > NusDownloader::List31j()
{
QMap< quint64, quint16 > titles = List20u();//TODO - missing a few in here
QMap< quint64, quint16 > titles = List20j();//TODO - missing a few in here
titles.insert( 0x100000002ull, 256 );//sys menu
titles.insert( 0x10000000eull, 262 );//14v262 - should actually be 14v257 but that version isnt available on NUS
titles.insert( 0x100000014ull, 12 );//20v12
titles.insert( 0x100000015ull, 514 );//21v514
titles.insert( 0x100000016ull, 777 );//22v777 - should be v772
@ -614,7 +615,7 @@ QMap< quint64, quint16 > NusDownloader::List31j()
titles.insert( 0x100000021ull, 1040 );//33v1040
titles.insert( 0x100000022ull, 1039 );//34v1039
titles.insert( 0x100000023ull, 1040 );//35v1040
titles.insert( 0x100000024ull, 1042 );//36v1040
titles.insert( 0x100000024ull, 1042 );//36v1042
//titles.insert( 0x100000025ull, 2070 );//37v2070//3.1u has this one but not 3.1j??
titles.insert( 0x1000248415941ull, 0x1 );//photo2v1
titles.insert( 0x1000848414B4aull, 0 );//EULA - HAKJ
@ -628,6 +629,27 @@ QMap< quint64, quint16 > NusDownloader::List31j()
return titles;
}
QMap< quint64, quint16 > NusDownloader::List33j()
{
QMap< quint64, quint16 > titles = List31j();//TODO - missing 3.2j
titles.insert( 0x100000002ull, 352 );//sys menu
titles.insert( 0x10000000bull, 10 );//11v10
titles.insert( 0x10000000cull, 6 );//12v6
titles.insert( 0x10000000dull, 10 );//13v10
titles.insert( 0x10000000full, 257 );//15v257
titles.insert( 0x100000011ull, 512 );//17v512
titles.insert( 0x10000001eull, 2576 );//30v2576
titles.insert( 0x10000001full, 2576 );//31v2576
titles.insert( 0x100000025ull, 2070 );//37v2070
titles.insert( 0x100000100ull, 0x4 );//bcv4
titles.insert( 0x1000248415941ull, 0x1 );//photo2v1
titles.insert( 0x1000848414B4aull, 2 );//EULA - HAKJ
titles.insert( 0x100000101ull, 8 );//miosv8
titles.insert( 0x1000248414341ull, 5 );//nigaoeNRv5 - MII
titles.insert( 0x1000248414241ull, 10 );//shoppingv10
return titles;
}
QMap< quint64, quint16 > NusDownloader::List34j()
{
QMap< quint64, quint16 > titles;
@ -851,20 +873,31 @@ QMap< quint64, quint16 > NusDownloader::List31e()
titles.insert( 0x100000023ull, 1040 );//35v1040
titles.insert( 0x100000024ull, 1042 );//36v1042
titles.insert( 0x100000002ull, 258 );//sys menu
titles.insert( 0x100000100ull, 0x2 );//bcv2
titles.insert( 0x10000001eull, 1039 );//30v1039
titles.insert( 0x10000001full, 1039 );//31v1039
titles.insert( 0x100000101ull, 5 );//miosv5
titles.insert( 0x1000848414B50ull, 2 );//EULA - HAKP
titles.insert( 0x1000248414650ull, 0x7 ); // forcast v7 HAFP
titles.insert( 0x1000248414750ull, 0x7 ); // news v7 HAGP
titles.insert( 0x1000848414C50ull, 0x2 );//regsel
titles.insert( 0x1000248414341ull, 4 );//nigaoeNRv4 - MII
titles.insert( 0x1000248415941ull, 0x2 ); // photo channel 1.1 HAYA
titles.insert( 0x1000248414141ull, 0x1 );//photov1
titles.insert( 0x1000248414241ull, 7 );//shoppingv7
titles.insert( 0x1000248414741ull, 0x3 );//news channel HAGA
titles.insert( 0x1000248414641ull, 0x3 );//Weather Channel HAFA
return titles;
}
QMap< quint64, quint16 > NusDownloader::List33e()
{
QMap< quint64, quint16 > titles = List31e();
titles.insert( 0x100000002ull, 354 );//RVL-WiiSystemmenu-v354.wad
titles.insert( 0x10000000bull, 10 );//11v10
titles.insert( 0x10000000cull, 6 );//12v6
titles.insert( 0x10000000dull, 10 );//13v10
titles.insert( 0x10000000eull, 262 );//14v262 - should actually be 14v257 but that version isnt available on NUS
titles.insert( 0x10000000full, 257 );//15v257
titles.insert( 0x100000011ull, 512 );//17v512
titles.insert( 0x100000014ull, 12 );//20v12
titles.insert( 0x100000015ull, 514 );//21v514
titles.insert( 0x100000016ull, 777 );//22v777 - should be v772
titles.insert( 0x10000001cull, 1292 );//28v1292 - should be 1228
titles.insert( 0x10000001eull, 2576 );//30v2576
titles.insert( 0x10000001full, 2576 );//31v2576
titles.insert( 0x100000025ull, 2070 );//37v2070
titles.insert( 0x100000100ull, 4 );//bcv4
titles.insert( 0x100000101ull, 8 );//miosv8
titles.insert( 0x1000248414341ull, 5 );//nigaoeNRv5 - MII
titles.insert( 0x1000248414241ull, 10 );//shoppingv10
return titles;
}
@ -1401,4 +1434,3 @@ QMap< quint64, quint16 > NusDownloader::List43k()
titles.insert( 0x100000002ull, 0x206 ); // SystemMenu 4.3K
return titles;
}

View File

@ -82,7 +82,7 @@ public:
static QMap< quint64, quint16 > List30e();
static QMap< quint64, quint16 > List31e();
//static QMap< quint64, quint16 > List32e();
//static QMap< quint64, quint16 > List33e();
static QMap< quint64, quint16 > List33e();
static QMap< quint64, quint16 > List34e();
static QMap< quint64, quint16 > List40e();
static QMap< quint64, quint16 > List41e();
@ -98,7 +98,7 @@ public:
//static QMap< quint64, quint16 > List30j();
static QMap< quint64, quint16 > List31j();
//static QMap< quint64, quint16 > List32j();
//static QMap< quint64, quint16 > List33j();
static QMap< quint64, quint16 > List33j();
static QMap< quint64, quint16 > List34j();
static QMap< quint64, quint16 > List40j();
static QMap< quint64, quint16 > List41j();

View File

@ -152,7 +152,7 @@ void NandThread::run()
quint32 NandThread::FileCount( QTreeWidgetItem *item )
{
if( item->text( 6 ) == "00" )//its a folder, recurse through it and count all the files
if( item->text( 7 ).startsWith( "02" ) )//its a folder, recurse through it and count all the files
{
quint32 ret = 0;
quint16 cnt = item->childCount();

View File

@ -365,7 +365,7 @@ void NandWindow::on_treeWidget_currentItemChanged( QTreeWidgetItem* current, QTr
{
Q_UNUSED( previous );
if( !current || current->text( 6 ) == "00" )
if( !current || current->text( 7 ).startsWith( "02" ) )
return;
bool ok = false;
quint16 entry = current->text( 1 ).toInt( &ok );

View File

@ -322,30 +322,16 @@ void MainWindow::on_actionImportWad_triggered()
ShowMessage( tr( "<b>Error setting the basepath of the nand to %1</b>" ).arg( QFileInfo( ui->lineEdit_nandPath->text() ).absoluteFilePath() ) );
return;
}
QString fn = QFileDialog::getSaveFileName(this,
QString fn = QFileDialog::getOpenFileName( this,
tr("Wad files(*.wad)"),
QCoreApplication::applicationDirPath(),
tr("WadFiles (*.wad)"));
if(fn == "") return;
QFile f(fn);
if(!f.open( QIODevice::ReadOnly)) {
ShowMessage( tr( "Error opening %1" ).arg( fn ) );
QByteArray data = ReadFile( fn );
if( data.isEmpty() )
return;
}
qint64 len = f.size();
char* buffer = new char[len];
qint64 read = f.read(buffer, len);
f.close();
hexdump(buffer, 0x40);
if(read != len) {
ShowMessage( tr( "Error reading %1" ).arg( fn ) );
free(buffer);
return;
}
QByteArray data(buffer, len);
free(buffer);
Wad wad(data);
if( !wad.IsOk() ) {
ShowMessage( tr( "Wad data not ok" ) );;

View File

@ -20,7 +20,7 @@
<item>
<widget class="QLineEdit" name="lineEdit_tid">
<property name="text">
<string>0000000100000002</string>
<string/>
</property>
<property name="maxLength">
<number>16</number>
@ -43,7 +43,7 @@
</size>
</property>
<property name="text">
<string>513</string>
<string/>
</property>
<property name="maxLength">
<number>5</number>