From 00f7302b0dcb28d3bd7b6ba45342ee65a8bd31f4 Mon Sep 17 00:00:00 2001 From: "giantpune@gmail.com" Date: Thu, 16 Dec 2010 23:37:30 +0000 Subject: [PATCH] * 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 git-svn-id: http://wiiqt.googlecode.com/svn/trunk@22 389f4c8b-5dfe-645f-db0e-df882bc27289 --- WiiQt/nandbin.cpp | 871 ++++++++++++++++++++++--------------- WiiQt/nandbin.h | 36 +- WiiQt/nandspare.cpp | 2 +- WiiQt/nusdownloader.cpp | 70 ++- WiiQt/nusdownloader.h | 4 +- nandExtract/nandthread.cpp | 2 +- nandExtract/nandwindow.cpp | 2 +- nand_dump/mainwindow.cpp | 22 +- nand_dump/mainwindow.ui | 6 +- 9 files changed, 629 insertions(+), 386 deletions(-) diff --git a/WiiQt/nandbin.cpp b/WiiQt/nandbin.cpp index 98260d5..2e98e18 100755 --- a/WiiQt/nandbin.cpp +++ b/WiiQt/nandbin.cpp @@ -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 u = GetFatsForEntry( 0 ); + quint16 ss = fats.size(); + qDebug() << "total used clusters" << u.size() << "of" << ss << "total"; + quint16 lost = 0; + QList ffs; + QList 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 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 NandBin::GetFatsForEntry( quint16 i ) +{ + //qDebug() << "NandBin::GetFatsForEntry" << i; + fst_t fst = GetFST( i ); + + QList 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 - - {1, {0x4a, 0x7c, 0x6f, 0x30, 0x38, 0xde, 0xea, 0x7a, 0x07, 0xd3, 0x32, 0x32, 0x02, 0x4b, 0xe9, 0x5a, 0xfb, 0x56, 0xbf, 0x65}}, - {1, {0x2c, 0xdd, 0x5a, 0xff, 0xd2, 0xe7, 0x8c, 0x53, 0x76, 0x16, 0xa1, 0x19, 0xa7, 0xa2, 0xe1, 0xc5, 0x68, 0xe9, 0x1f, 0x22}}, - {0, {0xf0, 0x1e, 0x8a, 0xca, 0x02, 0x9e, 0xe0, 0xcb, 0x52, 0x87, 0xf5, 0x05, 0x5d, 0xa1, 0xa0, 0xbe, 0xd2, 0xa5, 0x33, 0xfa}}, - {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 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 + QListfreeClusters; + 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; +} diff --git a/WiiQt/nandbin.h b/WiiQt/nandbin.h index 951a2d3..20a9b35 100755 --- a/WiiQt/nandbin.h +++ b/WiiQt/nandbin.h @@ -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 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 GetFatsForEntry( quint16 i ); + + //use the above function to search and display lost clusters + void ShowLostClusters(); + const Blocks0to7 BootBlocks(){ return bootBlocks; } const QList 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 ); diff --git a/WiiQt/nandspare.cpp b/WiiQt/nandspare.cpp index 6652427..42f917f 100644 --- a/WiiQt/nandspare.cpp +++ b/WiiQt/nandspare.cpp @@ -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(); diff --git a/WiiQt/nusdownloader.cpp b/WiiQt/nusdownloader.cpp index 6a406e2..c50eb78 100644 --- a/WiiQt/nusdownloader.cpp +++ b/WiiQt/nusdownloader.cpp @@ -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; } - diff --git a/WiiQt/nusdownloader.h b/WiiQt/nusdownloader.h index 587abb4..013e62a 100644 --- a/WiiQt/nusdownloader.h +++ b/WiiQt/nusdownloader.h @@ -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(); diff --git a/nandExtract/nandthread.cpp b/nandExtract/nandthread.cpp index 1b1adbf..8af5623 100644 --- a/nandExtract/nandthread.cpp +++ b/nandExtract/nandthread.cpp @@ -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(); diff --git a/nandExtract/nandwindow.cpp b/nandExtract/nandwindow.cpp index 10c2f4d..d381b92 100755 --- a/nandExtract/nandwindow.cpp +++ b/nandExtract/nandwindow.cpp @@ -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 ); diff --git a/nand_dump/mainwindow.cpp b/nand_dump/mainwindow.cpp index bbaed57..53a740c 100644 --- a/nand_dump/mainwindow.cpp +++ b/nand_dump/mainwindow.cpp @@ -322,30 +322,16 @@ void MainWindow::on_actionImportWad_triggered() ShowMessage( tr( "Error setting the basepath of the nand to %1" ).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 ) ); - return; - } + 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" ) );; diff --git a/nand_dump/mainwindow.ui b/nand_dump/mainwindow.ui index edfb471..04cc557 100644 --- a/nand_dump/mainwindow.ui +++ b/nand_dump/mainwindow.ui @@ -20,7 +20,7 @@ - 0000000100000002 + 16 @@ -43,7 +43,7 @@ - 513 + 5 @@ -218,7 +218,7 @@ - +