#include "nandbin.h" #include "tools.h" NandBin::NandBin( QObject * parent, const QString &path ) : QObject( parent ) { type = -1; fatNames = false; fstInited = false; root = NULL; if( !path.isEmpty() ) SetPath( path ); } NandBin::~NandBin() { if( f.isOpen() ) { #ifdef NAND_BIN_CAN_WRITE f.flush(); #endif f.close(); } if( root ) delete root; } bool NandBin::SetPath( const QString &path ) { fstInited = false; nandPath = path; bootBlocks = Blocks0to7(); if( f.isOpen() ) f.close(); f.setFileName( path ); bool ret = ( f.exists() && #ifdef NAND_BIN_CAN_WRITE f.open( QIODevice::ReadWrite ) ); #else f.open( QIODevice::ReadOnly ) ); #endif if( !ret ) { emit SendError( tr( "Cant open %1" ).arg( path ) ); } return ret; } const QString NandBin::FilePath() { if( !f.isOpen() ) return QString(); return QFileInfo( f ).absoluteFilePath(); } bool NandBin::CreateNew( const QString &path, const QByteArray &keys, const QByteArray &first8, const QList &badBlocks ) { #ifndef NAND_BIN_CAN_WRITE Q_UNUSED( path ); Q_UNUSED( keys ); Q_UNUSED( first8 ); Q_UNUSED( badBlocks ); qWarning() << __FILE__ << "was built without write support"; return false; #else if( keys.size() != 0x400 || first8.size() != 0x108000 ) { qWarning() << "NandBin::CreateNew -> bad sizes" << Qt::hex << keys.size() << first8.size(); return false; } if( f.isOpen() ) f.close(); //create the new file, write the first 8 blocks, fill it with 0xff, and write the keys.bin at the end f.setFileName( path ); if( !f.open( QIODevice::ReadWrite | QIODevice::Truncate ) ) { qWarning() << "NandBin::CreateNew -> can't create file" << path; return false; } f.write( first8 ); QByteArray block( 0x4200, 0xff );//generic empty cluster for( quint16 i = 0; i < 0x7fc0; i++ ) f.write( block ); f.write( keys ); if( f.pos() != 0x21000400 )//no room left on the drive? { qWarning() << "NandBin::CreateNew -> dump size is wrong" << (quint32)f.pos(); return false; } //setup variables nandPath = path; currentSuperCluster = 0x7f00; superClusterVersion = 1; type = 2; fats.clear(); memset( &fsts, 0, sizeof( fst_t ) * 0x17ff ); for( quint16 i = 0; i < 0x17ff; i++ ) fsts[ i ].fst_pos = i; //reserve blocks 0 - 7 for( quint16 i = 0; i < 0x40; i++ ) { fats << 0xfffc; } //mark all the "normal" blocks - free, or bad for( quint16 i = 0x40; i < 0x7f00; i++ ) { if( badBlocks.contains( i / 8 ) ) fats << 0xfffd; else fats << 0xfffe; } //reserve the superclusters for( quint16 i = 0x7f00; i < 0x8000; i++ ) { fats << 0xfffc; } //make the root item fsts[ 0 ].filename[ 0 ] = '/'; fsts[ 0 ].attr = 0x16; fsts[ 0 ].sib = 0xffff; fsts[ 0 ].sub = 0xffff; fstInited = true; //set keys QByteArray hmacKey = keys.mid( 0x144, 0x14 ); spare.SetHMacKey( hmacKey );//set the hmac key for calculating spare data key = keys.mid( 0x158, 0x10 ); //write the metada to each of the superblocks for( quint8 i = 0; i < 0x10; i++ ) { if( !WriteMetaData() ) { qWarning() << "NandBin::CreateNew -> error writing superblock" << i; return false; } } //build the tree if( root ) delete root; root = new QTreeWidgetItem( QStringList() << nandPath ); AddChildren( root, 0 ); return true; #endif } bool NandBin::Format( bool secure ) { #ifndef NAND_BIN_CAN_WRITE Q_UNUSED( secure ); qWarning() << __FILE__ << "was built without write support"; return false; #else if( !f.isOpen() || fats.size() != 0x8000 ) { qWarning() << "NandBin::Format -> error" << Qt::hex << fats.size() << f.isOpen(); return false; } //mark any currently used clusters free QByteArray cluster( 0x4200, 0xff );//generic empty cluster for( quint16 i = 0x40; i < 0x7f00; i++ ) { if( fats.at( i ) >= 0xf000 && fats.at( i ) != 0xfffe ) //preserve special marked ones continue; fats[ i ] = 0xfffe; //free the cluster if( !secure ) continue; f.seek( 0x4200 * i ); //overwrite anything there with the unused cluster f.write( cluster ); } //reset fsts memset( &fsts, 0, sizeof( fst_t ) * 0x17ff ); for( quint16 i = 0; i < 0x17ff; i++ ) fsts[ i ].fst_pos = i; //make the root item fsts[ 0 ].filename[ 0 ] = '/'; fsts[ 0 ].attr = 0x16; fsts[ 0 ].sib = 0xffff; fsts[ 0 ].sub = 0xffff; //write the metada to each of the superblocks for( quint8 i = 0; i < 0x10; i++ ) { if( !WriteMetaData() ) { qWarning() << "NandBin::Format -> error writing superblock" << i; return false; } } //build the tree if( root ) delete root; root = new QTreeWidgetItem( QStringList() << nandPath ); AddChildren( root, 0 ); return true; #endif } QTreeWidgetItem *NandBin::GetTree() { //qDebug() << "NandBin::GetTree()"; return root->clone(); } bool NandBin::ExtractToDir( QTreeWidgetItem *item, const QString &path ) { if( !item ) return false; bool ok = false; quint16 entry = item->text( 1 ).toInt( &ok ); if( !ok ) { emit SendError( tr( "Error converting entry(%1) to a number" ).arg( item->text( 1 ) ) ); return false; } return ExtractFST( entry, path, true );//dont bother extracting this item's siblings } 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 ) { //qDebug() << "NandBin::AddChildren" << parent->text( 0 ) << Qt::hex << entry; if( entry >= 0x17ff ) { qDebug() << "NandBin::AddChildren: entry >= 0x17ff"; emit SendError( tr( "entry is above 0x17ff mmmmkay [ 0x%1 ]" ).arg( entry, 0, 16 ) ); return false; } fst_t fst = GetFST( entry ); if( !fst.filename[ 0 ] )//something is amiss, better quit now { qDebug() << "NandBin::AddChildren: !fst.filename[ 0 ]"; return false; } if( fst.sib != 0xffff ) { if( !AddChildren( parent, fst.sib ) ) return false; } QTreeWidgetItem *child = CreateItem( parent, FstName( fst ), fst.size, entry, fst.uid, fst.gid, fst.x3, fst.attr, fst.wtf ); //set some icons 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 ) ) return false; } return true; } const QString NandBin::FstName( fst_t fst ) { QByteArray ba( (char*)fst.filename, 0xc ); QString ret = QString( ba ); if( fatNames ) { ret.replace( ":", "-" ); ret.replace( "?", "-" ); ret.replace( "<", "-" ); ret.replace( ">", "-" ); ret.replace( "\\", "-" ); ret.replace( ":", "-" ); ret.replace( "*", "-" ); ret.replace( "|", "-" ); ret.replace( "\"", "-" ); ret.replace( "^", "-" ); } return ret; } bool NandBin::ExtractFST( quint16 entry, const QString &path, bool singleFile ) { //qDebug() << "NandBin::ExtractFST(" << Qt::hex << entry << "," << path << ")"; fst_t fst = GetFST( entry ); if( !fst.filename[ 0 ] )//something is amiss, better quit now return false; if( !singleFile && fst.sib != 0xffff && !ExtractFST( fst.sib, path ) ) return false; switch( fst.attr & 3 ) { case 2: if( !ExtractDir( fst, path ) ) return false; break; case 1: if( !ExtractFile( fst, path ) ) return false; break; default://wtf emit SendError( tr( "Unknown fst mode. Bailing out" ) ); return false; break; } return true; } bool NandBin::ExtractDir( fst_t fst, const QString &parent ) { //qDebug() << "NandBin::ExtractDir(" << parent << ")"; //QByteArray ba( (char*)fst.filename, 0xc ); QString filename = FstName( fst ); QFileInfo fi( parent ); if( filename != "/" ) { fi.setFile( parent + "/" + filename ); if( !fi.exists() && !QDir().mkpath( fi.absoluteFilePath() ) ) { emit SendError( tr( "Can\'t create directory \"%1\"" ).arg( fi.absoluteFilePath() ) ); return false; } } if( fst.sub != 0xffff && !ExtractFST( fst.sub, fi.absoluteFilePath() ) ) return false; return true; } bool NandBin::ExtractFile( fst_t fst, const QString &parent ) { //QByteArray ba( (char*)fst.filename, 0xc ); QString filename = FstName( fst ); QFileInfo fi( parent + "/" + filename ); qDebug() << "extract" << fi.absoluteFilePath(); emit SendText( tr( "Extracting \"%1\"" ).arg( fi.absoluteFilePath() ) ); QByteArray data = GetFile( fst ); if( fst.size && !data.size() )//dont worry if files dont have anything in them anyways //return true; return false; if( !WriteFile( fi.absoluteFilePath(), data ) ) { emit SendError( tr( "Error writing \"%1\"" ).arg( fi.absoluteFilePath() ) ); return false; } return true; } bool NandBin::InitNand( const QIcon &dirs, const QIcon &files ) { fstInited = false; memset( (void*)&fsts, 0, sizeof( fst_t ) * 0x17ff ); fats.clear(); type = GetDumpType( f.size() ); if( type < 0 || type > 3 ) return false; //qDebug() << "dump type:" << type; if( !GetKey( type ) ) return false; loc_super = FindSuperblock(); if( loc_super < 0 ) return false; quint32 n_fatlen[] = { 0x010000, 0x010800, 0x010800 }; loc_fat = loc_super; loc_fst = loc_fat + 0x0C + n_fatlen[ type ]; //cache all the entries for( quint16 i = 0; i < 0x17ff; i++ ) fsts[ i ] = GetFST( i ); //cache all the fats for( quint16 i = 0; i < 0x8000; i++ ) fats << GetFAT( i ); fstInited = true; if( root ) delete root; groupIcon = dirs; keyIcon = files; root = new QTreeWidgetItem( QStringList() << nandPath ); AddChildren( root, 0 ); //checkout the blocks for boot1&2 QListblocks; for( quint16 i = 0; i < 8; i++ ) { QByteArray block; for( quint16 j = 0; j < 8; j++ ) { block += GetCluster( ( i * 8 ) + j, false ); } if( block.size() != 0x4000 * 8 ) { qDebug() << "wrong block size" << i; return false; } blocks << block; } if( !bootBlocks.SetBlocks( blocks ) ) return false; //ShowInfo(); 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() << Qt::hex << i << fats.at( i ); break; } } qDebug() << "found" << lost << "lost clusters\nUNK ( 0xffff )" << Qt::hex << ffs.size() << ffs << "\nfree " << frs.size(); } int NandBin::GetDumpType( quint64 fileSize ) { quint64 sizes[] = { 536870912, // type 0 | 536870912 == no ecc 553648128, // type 1 | 553648128 == ecc 553649152 }; // type 2 | 553649152 == old bootmii for( int i = 0; i < 3; i++ ) { if( sizes[ i ] == fileSize ) return i; } emit SendError( tr( "Can't tell what type of nand dump this is" ) ); return -1; } const QList NandBin::Boot2Infos() { if( !bootBlocks.IsOk() ) return QList(); return bootBlocks.Boot2Infos(); } quint8 NandBin::Boot1Version() { if( !bootBlocks.IsOk() ) return 0; return bootBlocks.Boot1Version(); } bool NandBin::GetKey( int type ) { QByteArray hmacKey; switch( type ) { case 0: case 1: { QString keyPath = nandPath; int sl = keyPath.lastIndexOf( "/" ); if( sl == -1 ) { emit SendError( tr( "Error getting path of keys.bin" ) ); return false; } keyPath.resize( sl + 1 ); keyPath += "keys.bin"; key = ReadKeyfile( keyPath, 0 ); if( key.isEmpty() ) return false; hmacKey = ReadKeyfile( keyPath, 1 ); if( hmacKey.isEmpty() ) return false; } break; case 2: { if( !f.isOpen() ) { emit SendError( tr( "Tried to read keys from unopened file" ) ); return false; } f.seek( 0x21000144 ); hmacKey = f.read( 20 ); f.seek( 0x21000158 ); key = f.read( 16 ); } break; default: emit SendError( tr( "Tried to read keys for unknown dump type" ) ); return false; break; } spare.SetHMacKey( hmacKey );//set the hmac key for calculating spare data //hexdump( hmacKey ); return true; } const QByteArray NandBin::Keys() { QByteArray ret; switch( type ) { case 0: case 1: { QString keyPath = nandPath; int sl = keyPath.lastIndexOf( "/" ); if( sl == -1 ) { emit SendError( tr( "Error getting path of keys.bin" ) ); return ret; } keyPath.resize( sl + 1 ); keyPath += "keys.bin"; ret = ReadFile( keyPath ); } break; case 2: { if( !f.isOpen() ) { emit SendError( tr( "Tried to read keys from unopened file" ) ); return ret; } f.seek( 0x21000000 ); ret = f.read( 0x400 ); } break; default: emit SendError( tr( "Tried to read keys for unknown dump type" ) ); return ret; break; } if( ret.size() != 0x400 ) return QByteArray(); return ret; } const QByteArray NandBin::ReadKeyfile( const QString &path, quint8 type ) { QByteArray retval; QFile f( path ); if( !f.exists() || !f.open( QIODevice::ReadOnly ) ) { emit SendError( tr( "Can't open %1!" ).arg( path ) ); return retval; } if( f.size() < 0x400 ) { f.close(); emit SendError( tr( "keys.bin is too small!" ) ); return retval; } if( type == 0 ) { f.seek( 0x158 ); retval = f.read( 16 ); } else if( type == 1 ) { f.seek( 0x144 ); retval = f.read( 20 ); } f.close(); return retval; } qint32 NandBin::FindSuperblock() { if( type < 0 || type > 3 ) { emit SendError( tr( "Tried to get superblock of unknown dump type" ) ); return -1; } if( !f.isOpen() ) { emit SendError( tr( "Tried to get superblock of unopened dump" ) ); return -1; } quint32 loc = 0, current = 0, magic = 0; superClusterVersion = 0; quint32 n_start[] = { 0x1FC00000, 0x20BE0000, 0x20BE0000 }, n_end[] = { 0x20000000, 0x21000000, 0x21000000 }, n_len[] = { 0x40000, 0x42000, 0x42000 }; quint8 rewind = 1; for( loc = n_start[ type ], currentSuperCluster = 0x7f00; loc < n_end[ type ]; loc += n_len[ type ], currentSuperCluster += 0x10 ) { f.seek( loc ); //QByteArray sss = f.peek( 0x50 ); f.read( (char*)&magic, 4 );//no need to switch endian here if( magic != 0x53464653 ) { qWarning() << "oops, this isnt a supercluster" << Qt::hex << loc << magic << currentSuperCluster; rewind++; //hexdump( sss ); continue; } f.read( (char*)¤t, 4 ); current = qFromBigEndian( current ); //qDebug() << "superblock" << Qt::hex << current << currentSuperCluster << loc; if( current > superClusterVersion ) superClusterVersion = current; else { //qDebug() << "using superblock" << Qt::hex << superClusterVersion << currentSuperCluster - 0x10 << f.pos() - n_len[ type ]; //currentSuperCluster -= ( 0x10 * rewind ); //loc -= ( n_len[ type ] * rewind ); rewind = 1; break; } if( loc == n_end[ type ] ) { rewind = 1; } } if( !superClusterVersion ) return -1; currentSuperCluster -= ( 0x10 * rewind ); loc -= ( n_len[ type ] * rewind ); //qDebug() << "using superblock" << Qt::hex << superClusterVersion << currentSuperCluster << "page:" << ( loc / 0x840 ); return loc; } fst_t NandBin::GetFST( quint16 entry ) { //qDebug() << "NandBin::GetFST(" << Qt::hex << entry << ")"; fst_t fst; if( entry >= 0x17FF ) { emit SendError( tr( "Tried to get entry above 0x17fe [ 0x%1 ]" ).arg( entry, 0, 16 ) ); fst.filename[ 0 ] = '\0'; return fst; } if( fstInited )//we've already read this once, just give back the one we already know { //qDebug() << "reading from cache" << Qt::hex << entry; return fsts[ entry ]; } // compensate for 64 bytes of ecc data every 64 fst entries quint32 n_fst[] = { 0, 2, 2 }; int loc_entry = ( ( ( entry / 0x40 ) * n_fst[ type ] ) + entry ) * 0x20; if( (quint32)f.size() < loc_fst + loc_entry + sizeof( fst_t ) ) { qDebug() << Qt::hex << (quint32)f.size() << loc_fst << loc_entry << type << n_fst[ type ]; emit SendError( tr( "Tried to read fst_t beyond size of nand.bin" ) ); fst.filename[ 0 ] = '\0'; return fst; } f.seek( loc_fst + loc_entry ); f.read( (char*)&fst.filename, 0xc ); 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 { f.read( (char*)&fst.size, 2 ); f.seek( f.pos() + 0x40 ); f.read( (char*)(&fst.size) + 2, 2 ); } else f.read( (char*)&fst.size, 4 ); f.read( (char*)&fst.uid, 4 ); f.read( (char*)&fst.gid, 2 ); f.read( (char*)&fst.x3, 4 ); fst.sub = qFromBigEndian( fst.sub ); fst.sib = qFromBigEndian( fst.sib ); fst.size = qFromBigEndian( fst.size ); fst.uid = qFromBigEndian( fst.uid ); fst.gid = qFromBigEndian( fst.gid ); fst.x3 = qFromBigEndian( fst.x3 ); fst.fst_pos = entry; //fst.mode &= 1; return fst; } quint16 NandBin::GetFAT( quint16 fat_entry ) { if( fstInited ) return fats.at( fat_entry ); fat_entry += 6; // location in fat of cluster chain quint32 n_fat[] = { 0, 0x20, 0x20 }; int loc = loc_fat + ((((fat_entry / 0x400) * n_fat[type]) + fat_entry) * 2); if( (quint32)f.size() < loc + sizeof( quint16 ) ) { emit SendError( tr( "Tried to read FAT entry beyond size of nand.bin" ) ); return 0; } f.seek( loc ); quint16 ret; f.read( (char*)&ret, 2 ); ret = qFromBigEndian( ret ); return ret; } const QByteArray NandBin::GetPage( quint32 pageNo, bool withEcc ) { //qDebug() << "NandBin::GetPage( " << Qt::hex << pageNo << ", " << withEcc << " )"; quint32 n_pagelen[] = { 0x800, 0x840, 0x840 }; if( f.size() < ( pageNo + 1 ) * n_pagelen[ type ] ) { emit SendError( tr( "Tried to read page past size of nand.bin" ) ); return QByteArray(); } f.seek( pageNo * n_pagelen[ type ] ); //seek to the beginning of the page to read //qDebug() << "reading page from" << Qt::hex << (quint32)f.pos(); QByteArray page = f.read( ( type && withEcc ) ? n_pagelen[ type ] : 0x800 ); return page; } const QByteArray NandBin::GetCluster( quint16 cluster_entry, bool decrypt ) { //qDebug() << "NandBin::GetCluster" << Qt::hex << cluster_entry; quint32 n_clusterlen[] = { 0x4000, 0x4200, 0x4200 }; quint32 n_pagelen[] = { 0x800, 0x840, 0x840 }; if( f.size() < ( cluster_entry * n_clusterlen[ type ] ) + ( 8 * n_pagelen[ type ] ) ) { emit SendError( tr( "Tried to read cluster past size of nand.bin" ) ); return QByteArray(); } QByteArray cluster; for( int i = 0; i < 8; i++ ) { f.seek( ( cluster_entry * n_clusterlen[ type ] ) + ( i * n_pagelen[ type ] ) ); //seek to the beginning of the page to read //qDebug() << "reading page from" << Qt::hex << (quint32)f.pos(); //QByteArray page = f.read( n_pagelen[ type ] ); //read the page, with ecc QByteArray page = f.read( 0x800 ); //read the page, skip the ecc //hexdump( page.mid( 0x800, 0x40 ) );//just here for debugging purposes //cluster += page.left( 0x800 ); cluster += page; } if( cluster.size() != 0x4000 ) { qDebug() << "actual cluster size" << Qt::hex << cluster.size(); emit SendError( tr( "Error reading cluster" ) ); return QByteArray(); } if( !decrypt ) return cluster; //really redundant to do this for ever AES decryption, but the AES code only lets //1 key set at a time and it may be changed if some other object is decrypting something else AesSetKey( key ); QByteArray ret = AesDecrypt( 0, cluster ); return ret; } const QByteArray NandBin::GetFile( quint16 entry ) { fst_t fst = GetFST( entry ); if( !fst.filename[ 0 ] )//something is amiss, better quit now return QByteArray(); return GetFile( fst ); } const QByteArray NandBin::GetFile( fst_t fst_ ) { //qDebug() << "NandBin::GetFile" << QByteArray( (const char*)fst_.filename, 12 ); if( !fst_.size ) return QByteArray(); quint16 fat = fst_.sub; //int cluster_span = (int)( fst.size / 0x4000) + 1; QByteArray data; //QList readFats; //int idx = 0;a for (int i = 0; fat < 0xFFF0; i++) { QByteArray cluster = GetCluster( fat ); if( cluster.size() != 0x4000 ) return QByteArray(); //debug shit... am i creating correct hmac data? //WriteDecryptedCluster( 0, cluster, fst_, idx++ ); data += cluster; //readFats << fat; fat = GetFAT( fat ); } //qDebug() << "actually read data from fats\n" << Qt::hex << readFats; //qDebug() << "stopped reading because of" << Qt::hex << fat; //this check doesnt really seem to matter, it always appears to be 1 extra cluster added to the end //of the file and that extra bit is dropped in this function before the data is returned. /*if( data.size() != cluster_span * 0x4000 ) { qDebug() << "data.size() != cluster_span * 0x4000 :: " << Qt::hex << data.size() << cluster_span << ( cluster_span * 0x4000 ) << "expected size:" << Qt::hex << fst.size; emit SendError( tr( "Error reading file [ block size is not a as expected ] %1" ).arg( FstName( fst ) ) ); }*/ if( (quint32)data.size() < fst_.size ) { qWarning() << "NandBin::GetFile() -> (quint32)data.size() < fst.size : " << Qt::hex << data.size() << "expected size:" << Qt::hex << fst_.size; emit SendError( tr( "Error reading file [ returned data size is less that the size in the fst ]" ) ); return QByteArray(); } if( (quint32)data.size() > fst_.size ) data.resize( fst_.size );//dont need to give back all the data, only up to the expected size return data; } const QList NandBin::GetFatsForFile( quint16 i ) { //qDebug() << "NandBin::GetFatsForFile" << i; QList ret; fst_t fst = GetFST( i ); if( fst.filename[ 0 ] == '\0' ) return ret; quint16 fat = fst.sub; //qDebug() << Qt::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() << Qt::hex << fat; } //qDebug() << Qt::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; } void NandBin::SetFixNamesForFAT( bool fix ) { fatNames = fix; } const QByteArray NandBin::GetData( const QString &path ) { QTreeWidgetItem *item = ItemFromPath( path ); if( !item ) return QByteArray(); if( !item->text( 7 ).startsWith( "01" ) ) { qDebug() << "NandBin::GetData -> can't get data for a folder" << item->text( 0 ); return QByteArray(); } bool ok = false; quint16 entry = item->text( 1 ).toInt( &ok, 10 ); if( !ok ) return QByteArray(); return GetFile( entry ); } QTreeWidgetItem *NandBin::ItemFromPath( const QString &path ) { if( !root || !root->childCount() ) return NULL; QTreeWidgetItem *item = root->child( 0 ); if( item->text( 0 ) != "/" ) { qWarning() << "NandBin::ItemFromPath -> root is not \"/\"" << item->text( 0 ); return NULL; } if( !path.startsWith( "/" ) || path.contains( "//" )) { qWarning() << "NandBin::ItemFromPath -> invalid path"; return NULL; } int slash = 1; while( slash ) { int nextSlash = path.indexOf( "/", slash + 1 ); QString lookingFor = path.mid( slash, nextSlash - slash ); item = FindItem( lookingFor, item ); if( !item ) { qWarning() << "NandBin::ItemFromPath ->item not found" << path; return NULL; } slash = nextSlash + 1; } 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(); for( int i = 0; i child( i ); if( r->text( 0 ) == s ) { return r; } } return NULL; } void NandBin::ShowInfo() { quint16 badBlocks = 0; quint16 reserved = 0; quint16 freeBlocks = 0; quint16 used = 0; QListbadOnes; for( quint16 i = 0; i < 0x8000; i++ ) { quint16 fat = GetFAT( i ); if( 0xfffc == fat ) reserved++; else if( 0xfffd == fat ) { badBlocks++; if( i % 8 == 0 ) { badOnes << ( i / 8 ); } } else if( 0xfffe == fat ) freeBlocks++; else used ++; } if( badBlocks ) badBlocks /= 8; if( reserved ) reserved /= 8; if( freeBlocks ) freeBlocks /= 8; qDebug() << "free blocks:" << freeBlocks << "\nbadBlocks:" << badBlocks << badOnes << "\nreserved :" << reserved << "\nused :" << used; } 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 ) { qWarning() << "NandBin::WriteCluster -> size:" << Qt::hex << data.size(); return false; } for( int i = 0; i < 8; i++ ) { QByteArray spareData( 0x40, '\0' ); quint8* sp = (quint8*)spareData.data(); QByteArray ecc = spare.CalcEcc( data.mid( i * 0x800, 0x800 ) ); memcpy( sp + 0x30, ecc.data(), 0x10 ); sp[ 0 ] = 0xff; // good block if( !hmac.isEmpty() ) { if( i == 6 ) { memcpy( (char*)sp + 1, hmac.data(), 20 ); memcpy( (char*)sp + 21, hmac.data(), 12 ); } else if( i == 7 ) { memcpy( (char*)sp + 1, (char*)(hmac.data()) + 12, 8 ); } } if( !WritePage( pageNo + i, data.mid( i * 0x800, 0x800 ) + spareData ) ) return false; } return true; } bool NandBin::WriteDecryptedCluster( quint32 pageNo, const QByteArray &data, fst_t fst, quint16 idx ) { //qDebug() << "NandBin::WriteDecryptedCluster"; QByteArray hmac = spare.Get_hmac_data( data, fst.uid, (const unsigned char *)&fst.filename, fst.fst_pos, fst.x3, idx ); //hexdump( hmac ); //return true; AesSetKey( key ); QByteArray encData = AesEncrypt( 0, data ); return WriteCluster( pageNo, encData, hmac ); } bool NandBin::WritePage( quint32 pageNo, const QByteArray &data ) { #ifndef NAND_BIN_CAN_WRITE Q_UNUSED( pageNo ); Q_UNUSED( data ); qWarning() << __FILE__ << "was built without write support"; return false; #else //qDebug() << "NandBin::WritePage(" << Qt::hex << pageNo << ")"; quint32 n_pagelen[] = { 0x800, 0x840, 0x840 }; if( (quint32)data.size() != n_pagelen[ type ] ) { qWarning() << "data is wrong size" << Qt::hex << data.size(); return false; } if( f.size() < ( pageNo + 1 ) * n_pagelen[ type ] ) { emit SendError( tr( "Tried to write page past size of nand.bin" ) ); return false; } f.seek( (quint64)pageNo * (quint64)n_pagelen[ type ] ); //seek to the beginning of the page to write //qDebug() << "writing page at:" << f.pos() << Qt::hex << (quint32)f.pos(); //hexdump( data, 0, 0x20 ); return ( f.write( data ) == data.size() ); #endif } 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; //qDebug() << "looking for first empty node"; for( i = 1; i < 0x17ff; i++ )//cant be entry 0 because that is the root { //qDebug() << Qt::hex << i << FstName( fsts[ i ] ); if( !fsts[ i ].filename[ 0 ] )//this one doesnt have a filename, it cant be used already break; } if( i == 0x17ff ) { return 0; } QByteArray n = name.toLatin1(); n.resize( 12, 0x00 ); //qDebug() << "will add entry for" << n << "at" << Qt::hex << i; memcpy( &fsts[ i ].filename, n.data(), 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; //hexdump( (const void*)&fsts[ i ], sizeof( fst_t ) ); 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; //method 1: this works, and the nand is bootable. but doesnt mimic the IOS FS driver. ( my entries appear in reversed order when walking the FS ) //fsts[ entryNo ].sib = ret; //method 2: trying to mimic the IOS FS driver ( insert new entries at the start of the chain, instead of the end ) fsts[ ret ].sib = fsts[ parIdx ].sub; fsts[ parIdx ].sub = ret; } QTreeWidgetItem *child = CreateItem( par, name, 0, ret, uid, gid, 0, fsts[ ret ].attr, 0 ); if( attr == NAND_FILE ) { 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 ) { if( !item ) { qWarning() << "cant delete a null item"; return false; } //qDebug() << "NandBin::DeleteItem" << item->text( 0 ); 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; quint16 cnt = par->childCount(); for( quint16 i = 0; i < cnt; i++ ) { if( par->child( i )->text( 0 ) == item->text( 0 ) ) { pId = i; //qDebug() << "found the item"; break; } if( i == cnt - 1 )//not found { qWarning() << "wtf 15" << pId << i << cnt; return false; } } } 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; QList toFree = GetFatsForFile( idx ); foreach( quint16 cl, toFree ) { fats.replace( cl, 0xfffe ); } // qDebug() << "delete loop done. freed" << toFree.size() << "clusters"; } break; case 2: { qDebug() << "deleting children of" << item->text( 0 ); quint32 cnt = item->childCount();//delete all the children of this item // qDebug() << cnt << "childern"; for( quint32 i = cnt; i > 0; i-- ) { if( !DeleteItem( item->child( i - 1 ) ) ) { qWarning() << "wtf6"; return false; } } } break; } memset( &fsts[ idx ], 0, sizeof( fst_t ) ); //clear this entry fsts[ idx ].fst_pos = idx; //reset this QTreeWidgetItem *d = par->takeChild( pId ); // qDebug() << "deleting tree item" << d->text( 0 ); delete d; return true; } bool NandBin::SetData( const QString &path, const QByteArray &data ) { QTreeWidgetItem *i = ItemFromPath( path ); if( !i ) { qDebug() << "!item" << path; return false; } bool ok = false; quint16 idx = i->text( 1 ).toInt( &ok );//find the entry if( !ok || idx > 0x17fe ) { qDebug() << "out of range" << path; return false; } return SetData( idx, data ); } bool NandBin::SetData( quint16 idx, const QByteArray &data ) { fst_t fst = fsts[ idx ]; //qDebug() << "NandBin::SetData" << FstName( fst ); if( ( fst.attr & 3 ) != 1 ) { qDebug() << idx << "is a folder"; 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 ); QRandomGenerator rng(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 = rng.generate() % 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++ )// <- this one scatters files all over the place quint16 max = freeClusters.at( freeClusters.size() - 1 ); // <- this one keeps files together; appears to closer mimic IOS's behavior for( quint16 i = block * 8; i < max && 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, 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" << Qt::hex << ( cl * 8 ) << "is bad??"; continue; } }*/ } } //sort clusters so file is written in order ( not like it matters on flash memory, though ) std::sort( fts.begin(), fts.end() ); //qDebug() << "about to writing shit" << clCnt << fts.size(); //qDebug() << "file will be on clusters\n" << Qt::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++ ) { fats.replace( fts.at( 0 ), fts.at( 1 ) ); fts.takeFirst(); } //qDebug() << "1 followed the chain to" << num << "items. expected" << clCnt; fats.replace( fts.at( 0 ), 0xfffb );//last cluster in chain fts.takeFirst(); //qDebug() << "fixed the last one" << Qt::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(); } fsts[ idx ].size = data.size(); QTreeWidgetItem *i = ItemFromEntry( idx, root ); if( !i ) { qDebug() << "!ItemFromEntry"; return false; } i->setText( 2, QString( "%1" ).arg( data.size(), 0, 16 ) ); return true; } bool NandBin::WriteMetaData() { //make sure the currect cluster is sane if( currentSuperCluster < 0x7f00 || currentSuperCluster > 0x7ff0 || currentSuperCluster % 16 || fats.size() != 0x8000 ) return false; quint16 nextSuperCluster = currentSuperCluster + 0x10; if( nextSuperCluster > 0x7ff0 ) nextSuperCluster = 0x7f00; quint32 nextClusterVersion = superClusterVersion + 1; QByteArray scl( 0x4000 * 16, '\0' ); //this will hold all the data //qDebug() << "created the meta block buffer" << Qt::hex << scl.size(); QBuffer b( &scl ); b.open( QIODevice::WriteOnly ); quint32 tmp; quint16 t; b.write( "SFFS" ); //magic word tmp = qFromBigEndian( nextClusterVersion ); b.write( (const char*)&tmp, 4 ); //version tmp = qFromBigEndian( (quint32)0 ); //tmp = qFromBigEndian( (quint32)0x10 ); //wiibrew says its always 0x10. but mine is 0 b.write( (const char*)&tmp, 4 ); //qDebug() << "writing the fats at" << Qt::hex << (quint32)b.pos(); //write all the fats for( quint16 i = 0; i < 0x8000; i++ ) { t = qFromBigEndian( fats.at( i ) ); b.write( (const char*)&t, 2 ); } //qDebug() << "writing the fsts at" << Qt::hex << (quint32)b.pos(); //write all the fst entries for( quint16 i = 0; i < 0x17ff; i++ ) { fst_t fst = fsts[ i ]; b.write( (const char*)&fst.filename, 0xc ); b.write( (const char*)&fst.attr, 1 ); b.write( (const char*)&fst.wtf, 1 ); t = qFromBigEndian( fst.sub ); b.write( (const char*)&t, 2 ); t = qFromBigEndian( fst.sib ); b.write( (const char*)&t, 2 ); tmp = qFromBigEndian( fst.size ); b.write( (const char*)&tmp, 4 ); tmp = qFromBigEndian( fst.uid ); b.write( (const char*)&tmp, 4 ); t = qFromBigEndian( fst.gid ); b.write( (const char*)&t, 2 ); tmp = qFromBigEndian( fst.x3 ); b.write( (const char*)&tmp, 4 ); } //qDebug() << "done adding shit" << Qt::hex << (quint32)b.pos(); b.close(); QByteArray hmR = spare.Get_hmac_meta( scl, nextSuperCluster ); //qDebug() << "about to write the meta block" << Qt::hex << nextSuperCluster << nextClusterVersion << "to page" << (quint32)( nextSuperCluster * 8 ); for( quint8 i = 0; i < 0x10; i++ ) { bool ret = WriteCluster( (quint32)( ( nextSuperCluster + i ) * 8 ), scl.mid( 0x4000 * i, 0x4000 ), ( i == 15 ? hmR : QByteArray() ) ); if( !ret ) { qCritical() << "failed to write the metadata. this nand may be broken now :(" << i; return false; } } currentSuperCluster = nextSuperCluster; superClusterVersion = nextClusterVersion; //make sure all the data is really written f.flush(); // in case the version wraps around back to 0 if( !superClusterVersion ) { qDebug() << "NandBin::WriteMetaData -> SFFS generation rolled back to 0"; for( quint16 i = 0; i < 15; i++ ) { if( !WriteMetaData() ) return false; } } return true; } bool NandBin::CheckEcc( quint32 pageNo ) { if( !type ) return false; QByteArray whole = GetPage( pageNo, true ); if( whole.size() != 0x840 ) return false; QByteArray data = whole.left( 0x800 ); bool used = false; //dont calculate ecc for unused pages for( quint16 i = 0; i < 0x800; i++ ) { if( data.at( i ) != '\xff' ) { used = true; break; } } if( !used ) return true; QByteArray ecc = whole.right( 0x10 ); QByteArray calc = spare.CalcEcc( data ); return ( ecc == calc ); } bool NandBin::CheckHmacData( quint16 entry ) { if( entry > 0x17fe ) { qDebug() << "bad entry #" << Qt::hex << entry; return false; } fst_t fst = fsts[ entry ]; if( ( fst.attr & 3 ) != 1 ) { qDebug() << "bad attributes" << ( fst.attr & 3 ); return false; } if( !fst.size ) return true; quint16 clCnt = ( RU( fst.size, 0x4000 ) / 0x4000 ); //qDebug() << FstName( fst ) << "is" << Qt::hex << fst.size << "bytes (" << clCnt << ") clusters"; quint16 fat = fst.sub; QByteArray sp1; QByteArray sp2; QByteArray hmac; //qDebug() << "fat" << Qt::hex << fat; for( quint32 i = 0; i < clCnt; i++ ) { if( fat > 0x7fff ) { qDebug() << "fat is out of range" << Qt::hex << fat; return false; } QByteArray cluster = GetCluster( fat ); //hmac is calculated with the decrypted cluster data if( cluster.size() != 0x4000 ) { qDebug() << "error reading cluster"; return false; } sp1 = GetPage( ( fat * 8 ) + 6, true ); //the spare data of these 2 pages hold the hmac data for the cluster sp2 = GetPage( ( fat * 8 ) + 7, true ); if( sp1.isEmpty() || sp2.isEmpty() ) { qDebug() << "error getting spare data"; return false; } sp1 = sp1.right( 0x40 ); //only keep the spare data and drop the data sp2 = sp2.right( 0x40 ); hmac = spare.Get_hmac_data( cluster, fst.uid, (const unsigned char*)&fst.filename, entry, fst.x3, i ); //this part is kinda ugly, but this is how it is layed out by big N //really it allows 1 copy of hmac to be bad, but im being strict about it if( sp1.mid( 1, 0x14 ) != hmac ) { qWarning() << "hmac bad (1)"; goto error; } if( sp1.mid( 0x15, 0xc ) != hmac.left( 0xc ) ) { qWarning() << "hmac bad (2)"; goto error; } if( sp2.mid( 1, 8 ) != hmac.right( 8 ) ) { qWarning() << "hmac bad (3)"; goto error; } //qDebug() << "hmac ok for cluster" << i; //data += cluster; fat = GetFAT( fat ); } return true; error: qWarning() << FstName( fst ) << "is" << Qt::hex << fst.size << "bytes (" << clCnt << ") clusters"; hexdump( sp1 ); hexdump( sp2 ); hexdump( hmac ); return false; } bool NandBin::CheckHmacMeta( quint16 clNo ) { if( clNo < 0x7f00 || clNo > 0x7ff0 || clNo % 0x10 ) return false; QByteArray data; for( quint8 i = 0; i < 0x10; i++ ) { data += GetCluster( ( clNo + i ), false ); } QByteArray hmac = spare.Get_hmac_meta( data, clNo ); quint32 baseP = ( clNo + 15 ) * 8; //qDebug() << "baseP" << Qt::hex << baseP << ( baseP + 6 ) << ( baseP + 7 ); QByteArray sp1 = GetPage( baseP + 6, true ); //the spare data of these 2 pages hold the hmac data for the supercluster QByteArray sp2 = GetPage( baseP + 7, true ); if( sp1.isEmpty() || sp2.isEmpty() ) { qDebug() << "error getting spare data"; return false; } sp1 = sp1.right( 0x40 ); //only keep the spare data and drop the data sp2 = sp2.right( 0x40 ); //this part is kinda ugly, but this is how it is layed out by big N //really it allows 1 copy of hmac to be bad, but im being strict about it if( sp1.mid( 1, 0x14 ) != hmac ) { qWarning() << "hmac bad (1)"; goto error; } if( sp1.mid( 0x15, 0xc ) != hmac.left( 0xc ) ) { qWarning() << "hmac bad (2)"; goto error; } if( sp2.mid( 1, 8 ) != hmac.right( 8 ) ) { qWarning() << "hmac bad (3)"; goto error; } return true; error: qWarning() << "supercluster" << Qt::hex << clNo; hexdump( sp1 ); hexdump( sp2 ); hexdump( hmac ); return false; }