wiiqt/WiiQt/nandbin.cpp
giantpune 2de556bc77 * adding new program... punetwiin. its purpose is kinda like betwiin. but it works slightly different. for one, it takes a full nand.bin for both done files. it pulls the files and filesystem from the first nand, and it pull boot1&2, the keys and the bad block map from the second nand. then it bakes on low heat for 45 minutes and when its done, you will have the contents from the filesystem in teh first nand mixed with everything else from the second nand.
i only performed limited testing, but it seems to work fine.  in linux, the full process took < 20 seconds.  in windows, it was about 2 minutes.  bad blocks are taken into account, so i personally feel that it is safer that betwiin ( but im not knocking that program at all :) )

anybody using punetwiin is encouraged to verify the content of the output file before even thinking about installing it.
2011-05-08 06:09:02 +00:00

1870 lines
55 KiB
C++
Executable File

#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();
}
//#if 0 // apparently you dont need any extra reserved blocks for the thing to boot?
bool NandBin::CreateNew( const QString &path, const QByteArray &keys, const QByteArray &first8, const QList<quint16> &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" << 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
}
//#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" << 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
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 )
{
//qDebug() << "NandBin::AddChildren" << parent->text( 0 ) << 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(" << 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
QList<QByteArray>blocks;
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<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\nUNK ( 0xffff )" << 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<Boot2Info> NandBin::Boot2Infos()
{
if( !bootBlocks.IsOk() )
return QList<Boot2Info>();
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 false;
}
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 false;
}
f.seek( 0x21000000 );
ret = f.read( 0x400 );
}
break;
default:
emit SendError( tr( "Tried to read keys for unknown dump type" ) );
return QByteArray();
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" << hex << loc << magic << currentSuperCluster;
rewind++;
//hexdump( sss );
continue;
}
f.read( (char*)&current, 4 );
current = qFromBigEndian( current );
//qDebug() << "superblock" << hex << current << currentSuperCluster << loc;
if( current > superClusterVersion )
superClusterVersion = current;
else
{
//qDebug() << "using superblock" << 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" << hex << superClusterVersion << currentSuperCluster << "page:" << ( loc / 0x840 );
return loc;
}
fst_t NandBin::GetFST( quint16 entry )
{
//qDebug() << "NandBin::GetFST(" << 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" << 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() << 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( " << 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" << 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" << 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" << 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" << 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<quint16> 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" << hex << readFats;
//qDebug() << "stopped reading because of" << 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 :: "
<< hex << data.size()
<< cluster_span
<< ( cluster_span * 0x4000 )
<< "expected size:" << 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 : "
<< hex << data.size()
<< "expected size:" << 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<quint16> NandBin::GetFatsForFile( quint16 i )
{
//qDebug() << "NandBin::GetFatsForFile" << i;
QList<quint16> ret;
fst_t fst = GetFST( i );
if( fst.filename[ 0 ] == '\0' )
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;
}
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 <cnt; i++ )
{
QTreeWidgetItem *r = parent->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;
QList<quint16>badOnes;
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:" << 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(" << hex << pageNo << ")";
quint32 n_pagelen[] = { 0x800, 0x840, 0x840 };
if( (quint32)data.size() != n_pagelen[ type ] )
{
qWarning() << "data is wrong size" << 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() << 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() << 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 );
//qDebug() << "will add entry for" << n << "at" << 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<quint16> 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<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++ )// <- 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" << hex << ( cl * 8 ) << "is bad??";
continue;
}
}*/
}
}
//sort clusters so file is written in order ( not like it matters on flash memory, though )
qSort( fts.begin(), fts.end() );
//qDebug() << "about to writing shit" << clCnt << fts.size();
//qDebug() << "file will be on clusters\n" << 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" << 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" << 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" << 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" << 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" << hex << (quint32)b.pos();
b.close();
QByteArray hmR = spare.Get_hmac_meta( scl, nextSuperCluster );
//qDebug() << "about to write the meta block" << 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 #" << 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" << hex << fst.size << "bytes (" << clCnt << ") clusters";
quint16 fat = fst.sub;
QByteArray sp1;
QByteArray sp2;
QByteArray hmac;
//qDebug() << "fat" << hex << fat;
for( quint32 i = 0; i < clCnt; i++ )
{
if( fat > 0x7fff )
{
qDebug() << "fat is out of range" << 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" << 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" << 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" << hex << clNo;
hexdump( sp1 );
hexdump( sp2 );
hexdump( hmac );
return false;
}