diff --git a/WiiQt/lz77.cpp b/WiiQt/lz77.cpp index d22d466..7cca25a 100644 --- a/WiiQt/lz77.cpp +++ b/WiiQt/lz77.cpp @@ -18,6 +18,59 @@ int LZ77::GetLz77Offset( const QByteArray &data ) return start.indexOf( "LZ77" ); } +LZ77::CompressionType LZ77::GetCompressedType( const QByteArray &data, int *outOffset ) +{ + if( data.startsWith( 0x10 ) ) + { + if( outOffset ) + { + *outOffset = 0; + } + return v10; + } + if( data.startsWith( 0x11 ) ) + { + if( outOffset ) + { + *outOffset = 0; + } + return v11; + } + int t = GetLz77Offset( data ); + if( t >= 0 ) + { + if( outOffset ) + { + *outOffset = t; + } + return v10_w_magic; + } + return None; +} + +QByteArray LZ77::Decompress( const QByteArray &stuff, LZ77::CompressionType *outType ) +{ + int off; + CompressionType t = GetCompressedType( stuff, &off ); + if( outType ) + { + *outType = t; + } + if( t == v10 ) + { + return Decompress_v10( stuff, 0 ); + } + if( t == v10_w_magic ) + { + return Decompress_v10( stuff, off + 4 ); + } + if( t == v11 ) + { + return LZ77_11::Decompress( stuff ); + } + return stuff; +} + void LZ77::InitTree() { int i; @@ -136,12 +189,16 @@ void LZ77::InsertNode( int r ) quint32 LZ77::GetDecompressedSize( const QByteArray &data ) { - int off = GetLz77Offset( data ); - if( off == -1 ) - { - qWarning() << "LZ77::GetDecompressedSize -> no lz77 magic"; - return 0; - } + int off; + LZ77::CompressionType ct = LZ77::GetCompressedType( data, &off ); + if( ct == None ) + { + return data.size(); + } + if( ct == v10_w_magic ) + { + off += 4; + } QByteArray ba = data; QBuffer buf( &ba ); if( !buf.open( QBuffer::ReadOnly ) ) @@ -149,16 +206,21 @@ quint32 LZ77::GetDecompressedSize( const QByteArray &data ) qWarning() << "LZ77::GetDecompressedSize -> Can't create buffer"; return 0; } - buf.seek( off + 4 ); + buf.seek( off ); quint32 gbaheader; - buf.seek( off + 4 ); + buf.seek( off ); buf.read( (char*)&gbaheader, 4 ); + quint32 ret = ( gbaheader >> 8 ); + if( !ret ) + { + buf.read( (char*)&ret, 4 ); + } - return ( gbaheader >> 8 ); + return ret; } -QByteArray LZ77::Decompress( const QByteArray &compressed, int offset ) +QByteArray LZ77::Decompress_v10( const QByteArray &compressed, int offset ) { int N = 4096; int F = 18; @@ -185,7 +247,7 @@ QByteArray LZ77::Decompress( const QByteArray &compressed, int offset ) } quint32 gbaheader; - infile.seek( offset + 4 ); + infile.seek( offset ); infile.read( (char*)&gbaheader, 4 ); decomp_size = gbaheader >> 8; @@ -255,7 +317,7 @@ QByteArray LZ77::Decompress( const QByteArray &compressed, int offset ) return ret; } -QByteArray LZ77::Decompress( const QByteArray &compressed ) +/*QByteArray LZ77::Decompress( const QByteArray &compressed ) { int off = GetLz77Offset( compressed ); if( off < 0 ) @@ -264,18 +326,33 @@ QByteArray LZ77::Decompress( const QByteArray &compressed ) return compressed; } return Decompress( compressed, off ); +}*/ + +QByteArray LZ77::Compress( const QByteArray &ba, LZ77::CompressionType type ) +{ + if( type == v10 ) + { + return Compress_v10( ba, false ); + } + if( type == v10_w_magic ) + { + return Compress_v10( ba, true ); + } + if( type == v11 ) + { + return LZ77_11::Compress( ba ); + } + return ba; } -QByteArray LZ77::Compress( const QByteArray &ba ) +QByteArray LZ77::Compress_v10( const QByteArray &ba, bool addMagic ) { - //testing 1, 2 - //return ba; LZ77 lz; - QByteArray ret = lz.Compr( ba ); + QByteArray ret = lz.Compr_v10( ba, addMagic ); return ret; } -QByteArray LZ77::Compr( const QByteArray &ba ) +QByteArray LZ77::Compr_v10( const QByteArray &ba, bool addMagic ) { int i, len, r, s, last_match_length, code_buf_ptr; char ch; @@ -284,7 +361,7 @@ QByteArray LZ77::Compr( const QByteArray &ba ) int N = 4096; int F = 18; int threshold = 2; - quint32 filesize = (ba.size() << 8) + 0x10; + quint32 filesize; QByteArray crap = ba; QBuffer infile( &crap ); @@ -300,12 +377,31 @@ QByteArray LZ77::Compr( const QByteArray &ba ) qWarning() << "Can't create buffer 2"; return QByteArray(); } - output.putChar( 'L' ); - output.putChar( 'Z' ); - output.putChar( '7' ); - output.putChar( '7' ); - output.write( (const char*)&filesize, 4 ); + if( addMagic ) + { + output.putChar( 'L' ); + output.putChar( 'Z' ); + output.putChar( '7' ); + output.putChar( '7' ); + } + + if( ba.size() <= 0xffffff ) + { + filesize = ( ba.size() << 8 ) | 0x10; + output.write( (const char*)&filesize, 4 ); + //output.putChar( '\0' ); + //output.putChar( '\0' ); + //output.putChar( '\0' ); + //output.putChar( '\0' ); + } + else + { + filesize = 0x10; + output.write( (const char*)&filesize, 4 ); + filesize = ba.size(); + output.write( (const char*)&filesize, 4 ); + } InitTree(); code_buf[ 0 ] = 0; @@ -396,3 +492,328 @@ QByteArray LZ77::Compr( const QByteArray &ba ) infile.close(); return ret; } + +LZ77_11::LZ77_11() +{ +} + +QByteArray LZ77_11::Compress( const QByteArray &stuff ) +{ + // Test if the file is too large to be compressed + if( (quint64)stuff.size() > 0xFFFFFFFF ) + { + qDebug() << "LZ77_11::Compress -> Input file is too large to compress."; + return QByteArray(); + } + + quint32 decompressedSize = stuff.size(); + + quint32 SourcePointer = 0x0; + quint32 DestPointer = 0x4; + + quint32 tmp; + QByteArray ret( decompressedSize, '\0' );//will reduce the size later + QBuffer buf( &ret ); + buf.open( QIODevice::WriteOnly ); + + + // Set up the Lz Compression Dictionary + LzWindowDictionary LzDictionary; + LzDictionary.SetWindowSize( 0x1000 ); + LzDictionary.SetMaxMatchAmount( 0xFFFF + 273 ); + + // Figure out where we are going to write the decompressed file size + if( stuff.size() <= 0xFFFFFF ) + { + tmp = ( decompressedSize << 8 ) | 0x11; //dont switch endian? + buf.write( (const char*)&tmp, 4 ); + } + else + { + tmp = 0x11; + buf.write( (const char*)&tmp, 4 ); //dont switch endian? + tmp = decompressedSize; + buf.write( (const char*)&tmp, 4 ); //dont switch endian? + DestPointer += 0x4; + } + + // Start compression + while( SourcePointer < decompressedSize ) + { + quint8 Flag = 0x0; + quint32 FlagPosition = DestPointer; + // It will be filled in later + buf.putChar( Flag ); + DestPointer++; + + for( int i = 7; i >= 0; i-- ) + { + QListLzSearchMatch = LzDictionary.Search( stuff, SourcePointer, decompressedSize ); + if( LzSearchMatch[ 1 ] > 0 ) // There is a compression match + { + Flag |= (quint8)( 1 << i ); + + // Write the distance/length pair + if( LzSearchMatch[ 1 ] <= 0xF + 1 ) // 2 bytes + { + buf.putChar( (quint8)( ( ( ( LzSearchMatch[ 1 ] - 1) & 0xF ) << 4 ) | ( ( ( LzSearchMatch[ 0 ] - 1 ) & 0xFFF ) >> 8 ) ) ); + buf.putChar( (quint8)( ( LzSearchMatch[ 0 ] - 1 ) & 0xFF ) ); + DestPointer += 2; + } + else if (LzSearchMatch[1] <= 0xFF + 17) // 3 bytes + { + buf.putChar( (quint8)(((LzSearchMatch[1] - 17) & 0xFF) >> 4) ); + buf.putChar( (quint8)((((LzSearchMatch[1] - 17) & 0xF) << 4) | (((LzSearchMatch[0] - 1) & 0xFFF) >> 8)) ); + buf.putChar( (quint8)((LzSearchMatch[0] - 1) & 0xFF) ); + DestPointer += 3; + } + else // 4 bytes + { + buf.putChar( (quint8)((1 << 4) | (((LzSearchMatch[1] - 273) & 0xFFFF) >> 12)) ); + buf.putChar( (quint8)(((LzSearchMatch[1] - 273) & 0xFFF) >> 4) ); + buf.putChar( (quint8)((((LzSearchMatch[1] - 273) & 0xF) << 4) | (((LzSearchMatch[0] - 1) & 0xFFF) >> 8)) ); + buf.putChar( (quint8)((LzSearchMatch[0] - 1) & 0xFF) ); + DestPointer += 4; + } + + LzDictionary.AddEntryRange( stuff, (int)SourcePointer, LzSearchMatch[ 1 ] ); + LzDictionary.SlideWindow( LzSearchMatch[ 1 ] ); + + SourcePointer += (quint32)LzSearchMatch[ 1 ]; + } + else // There wasn't a match + { + Flag |= (quint8)(0 << i); + + buf.putChar( stuff.at( SourcePointer ) ); + + LzDictionary.AddEntry( stuff, (int)SourcePointer ); + LzDictionary.SlideWindow( 1 ); + + SourcePointer++; + DestPointer++; + } + + // Check for out of bounds + if( SourcePointer >= decompressedSize ) + break; + } + + // Write the flag. + // Note that the original position gets reset after writing. + buf.seek( FlagPosition ); + buf.putChar( Flag ); + buf.seek( DestPointer ); + } + + buf.close(); + ret.resize( DestPointer ); + return ret; +} + +QByteArray LZ77_11::Decompress( QByteArray stuff ) +{ + if( !stuff.startsWith( 0x11 ) ) + { + qWarning() << "LZ77_11::Decompress -> data doesnt start with 0x11"; + return QByteArray(); + } + + // Compressed & Decompressed Data Information + QBuffer b( &stuff ); + b.open( QIODevice::ReadOnly ); + + quint32 compressedSize = (quint32)stuff.size(); + quint32 decompressedSize; + b.read( (char*)&decompressedSize, 4 ); //is this really little endian? + decompressedSize >>= 8; + + quint32 sourcePointer = 0x4; + quint32 destPointer = 0x0; + unsigned char tempbuffer[ 10 ]; + quint32 backwards_offset; + + if( !decompressedSize ) // Next 4 bytes are the decompressed size + { + b.read( (char*)&decompressedSize, 4 ); + sourcePointer += 0x4; + } + + b.close(); + + QByteArray decompressedData( decompressedSize, '\0' ); + if( (quint32)decompressedData.size() != decompressedSize ) + { + qWarning() << "LZ77_11::Decompress -> failed to allocate" << hex << decompressedSize << "bytes"; + return QByteArray(); + } + + // Start Decompression + quint32 num_bytes_to_copy; + quint32 copy_start_index; + while( sourcePointer < compressedSize && destPointer < decompressedSize ) + { + quint8 flag = stuff[ sourcePointer++ ]; + + + for( quint32 i = 0; i < 8; i++ ) + { + if( flag & ( 0x80 >> i ) ) + { + // Take encoded data + tempbuffer[ 0 ] = stuff[ sourcePointer++ ]; + tempbuffer[ 1 ] = stuff[ sourcePointer++ ]; + + switch( tempbuffer[ 0 ] & 0xF0 ) + { + case 0: + tempbuffer[ 2 ] = stuff[ sourcePointer++ ]; + num_bytes_to_copy = ( ( tempbuffer[ 0 ] << 4 ) + ( tempbuffer[ 1 ] >> 4 ) + 0x11 ); + backwards_offset = ( ( ( tempbuffer[ 1 ] & 0x0F ) << 8 ) + tempbuffer[ 2 ] + 1 ); + break; + case 0x10: + tempbuffer[ 2 ] = stuff[ sourcePointer++ ]; + tempbuffer[ 3 ] = stuff[ sourcePointer++ ]; + num_bytes_to_copy = ( ( tempbuffer[ 0 ] & 0x0F ) << 12 ) + ( tempbuffer[ 1 ] << 4 ) + ( tempbuffer[ 2 ] >> 4 ) + 0x111; + backwards_offset = ( ( tempbuffer[ 2 ] & 0x0F ) << 8 ) + tempbuffer[ 3 ] + 1; + break; + default: + num_bytes_to_copy = ( tempbuffer[ 0 ] >> 4 ) + 0x01; + backwards_offset = ( ( tempbuffer[ 0 ] & 0x0F ) << 8 ) + tempbuffer[ 1 ] + 1; + break; + } + copy_start_index = destPointer - backwards_offset; + for( quint32 copy_counter = 0; copy_counter < num_bytes_to_copy; copy_counter++ ) + { + if( ( copy_start_index + copy_counter ) >= destPointer ) + { + qWarning() << "LZ77_11::Decompress -> Error occured while decompressing: The input seems to be telling us to copy uninitialized data."; + return QByteArray(); + } + else + { + decompressedData[ destPointer++ ] = decompressedData[ copy_start_index + copy_counter ]; + } + } + } + else + { + decompressedData[ destPointer++ ] = stuff[ sourcePointer++ ]; + } + } + } + return decompressedData; +} + +LzWindowDictionary::LzWindowDictionary() +{ + WindowSize = 0x1000; + WindowStart = 0; + WindowLength = 0; + MinMatchAmount = 3; + MaxMatchAmount = 18; + BlockSize = 0; +} + +QList LzWindowDictionary::Search( const QByteArray &DecompressedData, quint32 offset, quint32 length ) +{ + RemoveOldEntries( DecompressedData[ offset ] ); // Remove old entries for this index + + if( offset < (quint32)MinMatchAmount || length - offset < (quint32)MinMatchAmount ) // Can't find matches if there isn't enough data + return QList() << 0 << 0; + + QListMatch = QList() << 0 << 0; + int MatchStart; + int MatchSize; + + for( int i = OffsetList[ (quint8)( DecompressedData[ offset ] ) ].size() - 1; i >= 0; i-- ) + { + MatchStart = OffsetList[ (quint8)( DecompressedData[ offset ] ) ][ i ]; + MatchSize = 1; + + while( MatchSize < MaxMatchAmount + && MatchSize < WindowLength + && (quint32)(MatchStart + MatchSize) < offset + && offset + MatchSize < length + && DecompressedData[ offset + MatchSize ] == DecompressedData[ MatchStart + MatchSize ] ) + MatchSize++; + + if( MatchSize >= MinMatchAmount && MatchSize > Match[ 1 ] ) // This is a good match + { + Match = QList() << (int)(offset - MatchStart) << MatchSize; + + if( MatchSize == MaxMatchAmount ) // Don't look for more matches + break; + } + } + + // Return the match. + // If no match was made, the distance & length pair will be zero + return Match; +} + +// Slide the window +void LzWindowDictionary::SlideWindow( int Amount ) +{ + if( WindowLength == WindowSize ) + WindowStart += Amount; + else + { + if( WindowLength + Amount <= WindowSize ) + WindowLength += Amount; + else + { + Amount -= ( WindowSize - WindowLength ); + WindowLength = WindowSize; + WindowStart += Amount; + } + } +} + +// Slide the window to the next block +void LzWindowDictionary::SlideBlock() +{ + WindowStart += BlockSize; +} + +// Remove old entries +void LzWindowDictionary::RemoveOldEntries( quint8 index ) +{ + for( int i = 0; i < OffsetList[ index ].size(); ) // Don't increment i + { + if( OffsetList[ index ][ i ] >= WindowStart ) + break; + else + OffsetList[ index ].removeAt( 0 ); + } +} + +// Set variables +void LzWindowDictionary::SetWindowSize( int size ) +{ + WindowSize = size; +} +void LzWindowDictionary::SetMinMatchAmount( int amount ) +{ + MinMatchAmount = amount; +} +void LzWindowDictionary::SetMaxMatchAmount( int amount ) +{ + MaxMatchAmount = amount; +} +void LzWindowDictionary::SetBlockSize( int size ) +{ + BlockSize = size; + WindowLength = size; // The window will work in blocks now +} + +// Add entries +void LzWindowDictionary::AddEntry( const QByteArray &DecompressedData, int offset ) +{ + OffsetList[ (quint8)( DecompressedData[ offset ] ) ] << offset; +} +void LzWindowDictionary::AddEntryRange( const QByteArray &DecompressedData, int offset, int length ) +{ + for( int i = 0; i < length; i++ ) + AddEntry( DecompressedData, offset + i ); +} diff --git a/WiiQt/lz77.h b/WiiQt/lz77.h index 6dee47e..27f3fb8 100644 --- a/WiiQt/lz77.h +++ b/WiiQt/lz77.h @@ -3,36 +3,47 @@ #include "includes.h" -//class for daling with LZ77 compression +//class for dealing with LZ77 compression (version 0x10) //! in most cases, you just want to use the static functions // QByteArray stuff = LZ77::Decompress( someCompressedData ); // QByteArray compressedData = LZ77::Compress( someData ); class LZ77 { public: + enum CompressionType + { + None, // not compressed + v10, // version 0x10 + v11, // version 0x11 + v10_w_magic // version 0x10 with "LZ77" magic bytes + }; + LZ77(); void InsertNode( int r ); void DeleteNode( int p ); void InitTree(); - //gets the offset in a bytearray if the lz77 magic word + //gets the offset in a bytearray of the lz77 magic word static int GetLz77Offset( const QByteArray &data ); //gets the decompressed size of a lz77 compressed buffer static quint32 GetDecompressedSize( const QByteArray &data ); - //used internally by the static compression function - QByteArray Compr( const QByteArray &ba ); + //decompress a buffer that is compressed with the 0x10 variant + static QByteArray Decompress_v10( const QByteArray &compressed, int offset ); - static QByteArray Decompress( const QByteArray &compressed, int offset ); - //finds the lz77 magic word and decompressed the data after it - // returns only the decompressed data. anything before the lz77 magic word is not included - static QByteArray Decompress( const QByteArray &compressed ); + static QByteArray Compress( const QByteArray &ba, CompressionType type ); //compressed a qbytearray with the lz77 argorythm //returns a qbytearray ncluding the lz77 header - static QByteArray Compress( const QByteArray &ba ); + static QByteArray Compress_v10( const QByteArray &ba, bool addMagic = true ); + + //check the type of archive, and get theoffset of the "LZ77" magic word in the case of v10_w_magic + static CompressionType GetCompressedType( const QByteArray &data, int *outOffset = NULL ); + + // decompress data and get whatever type of compression was used on the data + static QByteArray Decompress( const QByteArray &stuff, CompressionType *outType = NULL ); private: int lson[ 4097 ]; @@ -43,6 +54,44 @@ private: int match_length; int textsize; int codesize; + + + //used internally by the static compression function + QByteArray Compr_v10( const QByteArray &ba, bool addMagic = true ); +}; + +class LZ77_11 +{ +public: + LZ77_11(); + static QByteArray Compress( const QByteArray &stuff ); + static QByteArray Decompress( const QByteArray stuff ); +}; + +class LzWindowDictionary +{ +public: + LzWindowDictionary(); + + QList Search( const QByteArray &DecompressedData, quint32 offset, quint32 length ); + void SlideWindow( int Amount ); + void SlideBlock(); + void RemoveOldEntries( quint8 index ); + void SetWindowSize( int size ); + void SetMinMatchAmount( int amount ); + void SetMaxMatchAmount( int amount ); + void SetBlockSize( int size ); + void AddEntry( const QByteArray &DecompressedData, int offset ); + void AddEntryRange( const QByteArray &DecompressedData, int offset, int length ); + +private: + int WindowSize; + int WindowStart; + int WindowLength; + int MinMatchAmount; + int MaxMatchAmount; + int BlockSize; + QList OffsetList[ 0x100 ]; }; #endif // LZ77_H diff --git a/WiiQt/nandbin.cpp b/WiiQt/nandbin.cpp index da7b3bb..8283c5a 100755 --- a/WiiQt/nandbin.cpp +++ b/WiiQt/nandbin.cpp @@ -1,1866 +1,1866 @@ -#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" << 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" << 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 ) << 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 - 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() << 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 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" << hex << loc << magic << currentSuperCluster; - rewind++; - //hexdump( sss ); - continue; - } - - f.read( (char*)¤t, 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 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 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() << 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; -} - -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:" << 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 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 ); - 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; - -} - - +#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" << 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" << 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 ) << 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 + 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() << 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 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" << hex << loc << magic << currentSuperCluster; + rewind++; + //hexdump( sss ); + continue; + } + + f.read( (char*)¤t, 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 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 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() << 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; +} + +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:" << 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 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 ); + 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; + +} + + diff --git a/WiiQt/nandbin.h b/WiiQt/nandbin.h index af76e43..c6c6dd3 100755 --- a/WiiQt/nandbin.h +++ b/WiiQt/nandbin.h @@ -1,221 +1,221 @@ -#ifndef NANDBIN_H -#define NANDBIN_H - -#include "includes.h" -#include "blocks0to7.h" -#include "nandspare.h" - -struct fst_t -{ - quint8 filename[ 0xc ]; - quint8 attr; - quint8 wtf; - quint16 sub; - quint16 sib; - quint32 size; - quint32 uid; - quint16 gid; - quint32 x3; - quint16 fst_pos;//not really part of the nand structure, but needed when calculating hmac data -}; -// 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() -//! you should verify anything written with this code before attempting to install it on you wii - -//once InitNand() is called, you can get the contents of the nand in a nice QTreeWidgetItem* with GetTree() -class NandBin : public QObject -{ - Q_OBJECT - -public: - //creates a NandBin object. if a path is given, it will call SetPath() on that path. though you cant check the return value - NandBin( QObject * parent = 0, const QString &path = QString() ); - - //destroys this object, and all its used resources ( closes the nand.bin file and deletes the filetree ) - ~NandBin(); - - //create a "blank" nand at the given path, with spare data and keeys.bin appended to the end - //keys should be a 0x400 byte array containing a keys.bin from bootmii - //first8 should be a bytearray containing 0x108000 bytes - the first 8 blocks of the nand with spare data - //badBlocks is a list of blocks to be marked bad, in the range 8 - 4079 - bool CreateNew( const QString &path, const QByteArray &keys, const QByteArray &first8, const QList &badBlocks = QList() ); - - //sets the path of this object to path. returns false if it cannot open an already existing file - //keys.bin should be in this same path if they are to be used - bool SetPath( const QString &path ); - - //try to read the filesystem and create a tree from its contents - //this takes care of the stuff like reading the keys, finding teh superblock, and creating the QTreeWidgetItem* tree - //icons given here will be the ones used when asking for that tree - bool InitNand( const QIcon &dirs = QIcon(), const QIcon &files = QIcon() ); - - //get a root item containing children that are actually entries in the nand dump - //the root itself is just a container to hold them all and can be deleted once its children are taken - //! all returned items are cloned and it is up to you to delete them ! - //text is assigned to the items as follows... - // 0 name - // 1 entry # - // 2 size - // 3 uid - // 4 gid - // 5 x3 - // 6 mode - // 7 attr - QTreeWidgetItem *GetTree(); - - //extracts an item( and all its children ) to a directory - //this function is BLOCKING and will block the current thread, so if done in the gui thread, it will freeze your GUI till it returns - bool ExtractToDir( QTreeWidgetItem *item, const QString &path ); - - //print a little info about the free space - void ShowInfo(); - - //set this to change ":" in names to "-" on etracting. - //theres more illegal characters in FAT, but thes seems to be the only one that happens on the nand FS - void SetFixNamesForFAT( bool fix = true ); - - //returns the data that makes up the file of a given entry# - const QByteArray GetFile( quint16 entry ); - - - //get data for a given path - //! this function is slower than the above one, as it first iterates through the QTreeWidgetItems till it finds the right ono - //! and then end up calling the above one anyways. - //the path should be a file, not folder - //returns an empty array on failure - //path should start with "/" and items should be delimited by "/" - //ie... /title/00000001/00000002/data/setting.txt - const QByteArray GetData( const QString &path ); - - //returns the fats for this nand. - const QList GetFats() { return fats; } - - //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(); - - const 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 ); - - //write the current changes to the metadata( if you dont do this, then none of the other stuff youve done wont be saved ) - // but at the same time, you probably dont need to overuse this. ( no need to write metadata every time you make a single change ) - bool WriteMetaData(); - - //functions to verify spare data - bool CheckEcc( quint32 pageNo ); - bool CheckHmacData( quint16 entry ); - - //verify hmac stuff for a given supercluster - //expects 0x7f00 - 0x7ff0 - bool CheckHmacMeta( quint16 clNo ); - - //wipe out all data within the nand FS, leaving only the root entry - //preserve all bad/reserved clusters - //if secure is true, overwrite old file data with 0xff - bool Format( bool secure = true ); - - //get the path of this nand - const QString FilePath(); - - //get the keys.bin for this object - const QByteArray Keys(); - - -private: - QByteArray key; - qint32 loc_super; - qint32 loc_fat; - qint32 loc_fst; - quint16 currentSuperCluster; - quint32 superClusterVersion; - QString extractPath; - QString nandPath; - QFile f; - int type; - - bool fatNames; - QIcon groupIcon; - QIcon keyIcon; - - NandSpare spare;//used to handle the hmac mumbojumbo - - //read all the fst and remember them rather than seeking back and forth in the file all the time - // uses ~120KiB RAM - bool fstInited; - fst_t fsts[ 0x17ff ]; - - //cache the fat to keep from having to look them up repeatedly - // uses ~64KiB - QListfats; - - int GetDumpType( quint64 fileSize ); - bool GetKey( int type ); - const QByteArray ReadKeyfile( const QString &path, quint8 type );//type 0 for nand key, type 1 for hmac - qint32 FindSuperblock(); - quint16 GetFAT( quint16 fat_entry ); - fst_t GetFST( quint16 entry ); - const QByteArray GetCluster( quint16 cluster_entry, bool decrypt = true ); - const QByteArray GetFile( fst_t fst ); - - const QString FstName( fst_t fst ); - bool ExtractFST( quint16 entry, const QString &path, bool singleFile = false ); - bool ExtractDir( fst_t fst, const QString &parent ); - bool ExtractFile( fst_t fst, const 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; - bool AddChildren( QTreeWidgetItem *parent, quint16 entry ); - QTreeWidgetItem *ItemFromPath( const QString &path ); - QTreeWidgetItem *FindItem( const QString &s, QTreeWidgetItem *parent ); - - //holds info about boot1 & 2 - Blocks0to7 bootBlocks; - - bool WriteCluster( quint32 pageNo, const QByteArray &data, const QByteArray &hmac ); - 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 - //so far, many errors are only outputting to qDebug() and qWarning(). - void SendError( QString ); - void SendText( QString ); -}; - -#endif // NANDBIN_H +#ifndef NANDBIN_H +#define NANDBIN_H + +#include "includes.h" +#include "blocks0to7.h" +#include "nandspare.h" + +struct fst_t +{ + quint8 filename[ 0xc ]; + quint8 attr; + quint8 wtf; + quint16 sub; + quint16 sib; + quint32 size; + quint32 uid; + quint16 gid; + quint32 x3; + quint16 fst_pos;//not really part of the nand structure, but needed when calculating hmac data +}; +// 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() +//! you should verify anything written with this code before attempting to install it on you wii + +//once InitNand() is called, you can get the contents of the nand in a nice QTreeWidgetItem* with GetTree() +class NandBin : public QObject +{ + Q_OBJECT + +public: + //creates a NandBin object. if a path is given, it will call SetPath() on that path. though you cant check the return value + NandBin( QObject * parent = 0, const QString &path = QString() ); + + //destroys this object, and all its used resources ( closes the nand.bin file and deletes the filetree ) + ~NandBin(); + + //create a "blank" nand at the given path, with spare data and keeys.bin appended to the end + //keys should be a 0x400 byte array containing a keys.bin from bootmii + //first8 should be a bytearray containing 0x108000 bytes - the first 8 blocks of the nand with spare data + //badBlocks is a list of blocks to be marked bad, in the range 8 - 4079 + bool CreateNew( const QString &path, const QByteArray &keys, const QByteArray &first8, const QList &badBlocks = QList() ); + + //sets the path of this object to path. returns false if it cannot open an already existing file + //keys.bin should be in this same path if they are to be used + bool SetPath( const QString &path ); + + //try to read the filesystem and create a tree from its contents + //this takes care of the stuff like reading the keys, finding teh superblock, and creating the QTreeWidgetItem* tree + //icons given here will be the ones used when asking for that tree + bool InitNand( const QIcon &dirs = QIcon(), const QIcon &files = QIcon() ); + + //get a root item containing children that are actually entries in the nand dump + //the root itself is just a container to hold them all and can be deleted once its children are taken + //! all returned items are cloned and it is up to you to delete them ! + //text is assigned to the items as follows... + // 0 name + // 1 entry # + // 2 size + // 3 uid + // 4 gid + // 5 x3 + // 6 mode + // 7 attr + QTreeWidgetItem *GetTree(); + + //extracts an item( and all its children ) to a directory + //this function is BLOCKING and will block the current thread, so if done in the gui thread, it will freeze your GUI till it returns + bool ExtractToDir( QTreeWidgetItem *item, const QString &path ); + + //print a little info about the free space + void ShowInfo(); + + //set this to change ":" in names to "-" on etracting. + //theres more illegal characters in FAT, but thes seems to be the only one that happens on the nand FS + void SetFixNamesForFAT( bool fix = true ); + + //returns the data that makes up the file of a given entry# + const QByteArray GetFile( quint16 entry ); + + + //get data for a given path + //! this function is slower than the above one, as it first iterates through the QTreeWidgetItems till it finds the right ono + //! and then end up calling the above one anyways. + //the path should be a file, not folder + //returns an empty array on failure + //path should start with "/" and items should be delimited by "/" + //ie... /title/00000001/00000002/data/setting.txt + const QByteArray GetData( const QString &path ); + + //returns the fats for this nand. + const QList GetFats() { return fats; } + + //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(); + + const 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 ); + + //write the current changes to the metadata( if you dont do this, then none of the other stuff youve done wont be saved ) + // but at the same time, you probably dont need to overuse this. ( no need to write metadata every time you make a single change ) + bool WriteMetaData(); + + //functions to verify spare data + bool CheckEcc( quint32 pageNo ); + bool CheckHmacData( quint16 entry ); + + //verify hmac stuff for a given supercluster + //expects 0x7f00 - 0x7ff0 + bool CheckHmacMeta( quint16 clNo ); + + //wipe out all data within the nand FS, leaving only the root entry + //preserve all bad/reserved clusters + //if secure is true, overwrite old file data with 0xff + bool Format( bool secure = true ); + + //get the path of this nand + const QString FilePath(); + + //get the keys.bin for this object + const QByteArray Keys(); + + +private: + QByteArray key; + qint32 loc_super; + qint32 loc_fat; + qint32 loc_fst; + quint16 currentSuperCluster; + quint32 superClusterVersion; + QString extractPath; + QString nandPath; + QFile f; + int type; + + bool fatNames; + QIcon groupIcon; + QIcon keyIcon; + + NandSpare spare;//used to handle the hmac mumbojumbo + + //read all the fst and remember them rather than seeking back and forth in the file all the time + // uses ~120KiB RAM + bool fstInited; + fst_t fsts[ 0x17ff ]; + + //cache the fat to keep from having to look them up repeatedly + // uses ~64KiB + QListfats; + + int GetDumpType( quint64 fileSize ); + bool GetKey( int type ); + const QByteArray ReadKeyfile( const QString &path, quint8 type );//type 0 for nand key, type 1 for hmac + qint32 FindSuperblock(); + quint16 GetFAT( quint16 fat_entry ); + fst_t GetFST( quint16 entry ); + const QByteArray GetCluster( quint16 cluster_entry, bool decrypt = true ); + const QByteArray GetFile( fst_t fst ); + + const QString FstName( fst_t fst ); + bool ExtractFST( quint16 entry, const QString &path, bool singleFile = false ); + bool ExtractDir( fst_t fst, const QString &parent ); + bool ExtractFile( fst_t fst, const 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; + bool AddChildren( QTreeWidgetItem *parent, quint16 entry ); + QTreeWidgetItem *ItemFromPath( const QString &path ); + QTreeWidgetItem *FindItem( const QString &s, QTreeWidgetItem *parent ); + + //holds info about boot1 & 2 + Blocks0to7 bootBlocks; + + bool WriteCluster( quint32 pageNo, const QByteArray &data, const QByteArray &hmac ); + 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 + //so far, many errors are only outputting to qDebug() and qWarning(). + void SendError( QString ); + void SendText( QString ); +}; + +#endif // NANDBIN_H diff --git a/WiiQt/nanddump.cpp b/WiiQt/nanddump.cpp index 232ef80..6b8aa62 100644 --- a/WiiQt/nanddump.cpp +++ b/WiiQt/nanddump.cpp @@ -440,7 +440,7 @@ bool NandDump::InstallNusItem( const NusJob &job ) bool NandDump::InstallWad( Wad wad ) { - if( !wad.Tid() || wad.content_count() < 3 ) + if( !wad.Tid() || wad.content_count() < 1 ) { qWarning() << "NandDump::InstallNusItem -> invalid item"; return false; diff --git a/WiiQt/nusdownloader.h b/WiiQt/nusdownloader.h index 8d6bdc7..ec96bb8 100644 --- a/WiiQt/nusdownloader.h +++ b/WiiQt/nusdownloader.h @@ -12,7 +12,8 @@ #define UPDATING_USER_AGENT "wii libnup/1.0" #define VIRTUAL_CONSOLE_USER_AGENT "libec-3.0.7.06111123" #define WIICONNECT24_USER_AGENT "WiiConnect24/1.0FC4plus1 (build 061114161108)" -#define NUS_BASE_URL "http://ccs.shop.wii.com/ccs/download/" +//#define NUS_BASE_URL "http://ccs.shop.wii.com/ccs/download/" +#define NUS_BASE_URL "http://nus.cdn.shop.wii.com/ccs/download/" diff --git a/WiiQt/savebanner.cpp b/WiiQt/savebanner.cpp index 895915d..edeafcd 100644 --- a/WiiQt/savebanner.cpp +++ b/WiiQt/savebanner.cpp @@ -29,13 +29,13 @@ SaveBanner::SaveBanner( QByteArray stuff ) qWarning() << "SaveBanner::SaveBanner -> bad file magic" << hex << qFromBigEndian( magic ); return; } - //no clue what this stuff is, dont really need it though - //i suspect instructions for animation ? ( VC icons display forwards and backwards in the system menu ) - //also speed is not always the same - quint32 tmp; - f.read( (char*)&tmp, 4 ); - quint32 tmp2; - f.read( (char*)&tmp2, 4 ); + + f.read( (char*)&attributes, 4 ); + f.read( (char*)&speeds, 2 ); + + attributes = qFromBigEndian( attributes ); + speeds = qFromBigEndian( speeds ); + f.seek( 0x20 ); quint16 name[ 0x20 ]; @@ -50,7 +50,48 @@ SaveBanner::SaveBanner( QByteArray stuff ) saveTitle = saveTitle.trimmed(); - //qDebug() << hex << qFromBigEndian( tmp ) << qFromBigEndian( tmp2 ) << saveTitle; + + QString speedStr; + // 0 = lastimage, speed range from 1 - 3 + for( int i = 0; i < 8; i++ ) + { + speedStr += QString::number( ( speeds >> ( 2 * ( i ) ) ) & 3 ); + if( i < 7 ) + { + speedStr += " "; + } + } + + QString flags; + // nocopy bit + if( attributes & 1 ) + { + flags += "nocopy"; + } + + // animation type bit + if( attributes & 0x10 ) + { + if( !flags.isEmpty() ) + { + flags += ", "; + } + flags += "forward and reverse"; + } + else + { + if( !flags.isEmpty() ) + { + flags += ", "; + } + flags += "loop"; + } + flags = flags.leftJustified( 27, QChar( ' ' ) ); + qDebug() << hex << //QString( "%1" ).arg( tmp, 8, 16, QChar( '0' ) ) << + //QString( "%1" ).arg( speeds, 4, 16, QChar( '0' ) ) << + speedStr << + flags << + saveTitle; //QString title2; for( int i = 0; i < 0x20 && name2[ i ] != 0; i++ ) @@ -72,33 +113,25 @@ SaveBanner::SaveBanner( QByteArray stuff ) } //get the images that make up the icon - while( f.pos() != size ) - { - QByteArray icn = f.read( 0x1200 ); - //check that there is actually data. some banners use all 0x00 for some images - bool null = true; - for( int i = 0; i < 0x1200; i++ ) - { - if( icn.at( i ) )//this buffer contains at least 1 byte of data, try to turn it into an image - { - null = false; - break; - } - } - if( null ) - { - //qDebug() << "skipping empty image"; - break; - } + for( quint8 i = 0; i < 8 && f.pos() < size; i++ ) + { + QByteArray icn = f.read( 0x1200 ); - //make this texture int an image - QImage iconImg = ConvertTextureToImage( icn, 0x30, 0x30 ); - if( iconImg.isNull() ) - break; + //make this texture int an image + QImage iconImg = ConvertTextureToImage( icn, 0x30, 0x30 ); + if( iconImg.isNull() ) + break; - //add the image to the list - iconImgs << iconImg; - } + //add the image to the list + iconImgs << iconImg; + + if( !( ( speeds >> ( 2 * i ) ) & 3 ) )// this is the last image + { + break; + } + } + + qDebug() << "imgCnt:" << iconImgs.size(); f.close(); ok = true; diff --git a/WiiQt/savebanner.h b/WiiQt/savebanner.h index aca4d36..e13abaf 100644 --- a/WiiQt/savebanner.h +++ b/WiiQt/savebanner.h @@ -6,20 +6,30 @@ class SaveBanner { public: + SaveBanner(); SaveBanner( const QString &bannerpath ); SaveBanner( QByteArray stuff ); - QImage BannerImg(){ return bannerImg; } - QList< QImage > IconImgs() { return iconImgs; } + const QImage &BannerImg() const { return bannerImg; } + const QList< QImage > &IconImgs() const { return iconImgs; } - QString Title(){ return saveTitle; } - QString SubTitle(){ return saveTitle2; } + const QString &Title() const { return saveTitle; } + const QString &SubTitle() const { return saveTitle2; } + + quint32 Attributes() { return attributes; } + quint16 Speeds() { return speeds; } private: bool ok; QImage bannerImg; QList< QImage > iconImgs; + + quint32 attributes; // bit 5 = animation type. if its set, the animation plays forward and backward + // if its not set, the animation just plays forward and loops + + quint16 speeds; // 2 bits per frame. 0 signifies the last frame? + QString saveTitle; QString saveTitle2; diff --git a/WiiQt/tools.h b/WiiQt/tools.h index 135bd6a..ba6de81 100644 --- a/WiiQt/tools.h +++ b/WiiQt/tools.h @@ -29,6 +29,10 @@ #define SD_IV { 0x21, 0x67, 0x12, 0xe6, 0xaa, 0x1f, 0x68, 0x9f, 0x95, 0xc5, 0xa2, 0x23, 0x24, 0xdc, 0x6a, 0x98 }; #define MD5_BLANKER { 0x0e, 0x65, 0x37, 0x81, 0x99, 0xbe, 0x45, 0x17, 0xab, 0x06, 0xec, 0x22, 0x45, 0x1a, 0x57, 0x93 }; +// debug helpers +#define DBG qDebug() << __PRETTY_FUNCTION__ +#define WRN qWarning() << __PRETTY_FUNCTION__ + #define TITLE_LATEST_VERSION 0xffff //struct used to keep all the data about a NUS request together diff --git a/WiiQt/u8.cpp b/WiiQt/u8.cpp index 66273e6..96680f6 100644 --- a/WiiQt/u8.cpp +++ b/WiiQt/u8.cpp @@ -1,5 +1,4 @@ #include "u8.h" -#include "lz77.h" #include "tools.h" //#include "md5.h" #include "ash.h" @@ -18,7 +17,8 @@ static quint32 swap24( quint32 i ) U8::U8( bool initialize, int type, const QStringList &names ) { ok = false; - isLz77 = false; + //isLz77 = false; + lz77Type = LZ77::None; wii_cs_error = false; paths.clear(); nestedU8s.clear(); @@ -37,7 +37,8 @@ U8::U8( bool initialize, int type, const QStringList &names ) bool U8::CreateEmptyData() { - isLz77 = false; + //isLz77 = false; + lz77Type = LZ77::None; nestedU8s.clear(); data = QByteArray( 0x20, '\0' ); @@ -319,10 +320,15 @@ bool U8::ReplaceEntry( const QString &path, const QByteArray &nba, bool autoComp if( autoCompress ) { QByteArray oldData = data.mid( qFromBigEndian( fst[ entryToReplace ].FileOffset ), qFromBigEndian( fst[ entryToReplace ].FileLength ) ); - bool oldCompressed = ( LZ77::GetLz77Offset( oldData ) > -1 || IsAshCompressed( oldData ) ); - if( oldCompressed && LZ77::GetLz77Offset( newData ) == -1 && !IsAshCompressed( newData ) ) + LZ77::CompressionType ct = LZ77::GetCompressedType( oldData ); + bool oldCompressed = ( ct != LZ77::None || IsAshCompressed( oldData ) ); + if( oldCompressed && LZ77::GetCompressedType( newData ) == LZ77::None && !IsAshCompressed( newData ) ) { - newData = LZ77::Compress( newData ); + if( ct == LZ77::None ) + { + ct = LZ77::v10; + } + newData = LZ77::Compress( newData, ct ); } } @@ -940,7 +946,8 @@ void U8::Load( const QByteArray &ba ) { ok = false; wii_cs_error = false; - isLz77 = false; + //isLz77 = false; + lz77Type = LZ77::None; headerType = U8_Hdr_none; paths.clear(); imetNames.clear(); @@ -955,14 +962,39 @@ void U8::Load( const QByteArray &ba ) data = DecryptAsh( data ); quint32 tmp; - int off = LZ77::GetLz77Offset( data ); - int off2 = GetU8Offset( data ); + int off; + int off2; + LZ77::CompressionType type = LZ77::GetCompressedType( data, &off ); + if( type == LZ77::v10 ) + { + lz77Type = type; + data = LZ77::Decompress_v10( data, 0 ); + } + else if( type == LZ77::v11 ) + { + lz77Type = type; + data = LZ77_11::Decompress( data ); + } + else if( type == LZ77::v10_w_magic ) + { + off2 = GetU8Offset( data ); + if( off2 >= 0 && off < off2 ) + { + lz77Type = LZ77::v10_w_magic; + data = LZ77::Decompress_v10( data, off + 4 ); + } + } + off2 = GetU8Offset( data ); + //int off = LZ77::GetLz77Offset( data ); + /*int off2 = GetU8Offset( data ); if( off != -1 && ( off2 == -1 || ( off2 != -1 && off < off2 ) ) ) { - isLz77 = true; - data = LZ77::Decompress( data ); + //isLz77 = true; + lz77Type = LZ77::v10_w_magic; + //data = LZ77::Decompress( data ); + data = LZ77::Decompress_v10( data, off + 4 ); off2 = GetU8Offset( data ); - } + }*/ if( off2 == -1 ) { @@ -1111,8 +1143,9 @@ const QByteArray U8::GetData( const QString &str, bool onlyPayload ) break; case U8_Hdr_IMD5: { - if( isLz77 ) - ret = LZ77::Compress( ret ); + //if( isLz77 ) + if( lz77Type == LZ77::v10_w_magic ) + ret = LZ77::Compress_v10( ret ); ret = AddIMD5( ret ); @@ -1151,11 +1184,17 @@ const QByteArray U8::GetData( const QString &str, bool onlyPayload ) //hexdump( ret, 0, 0x40 ); if( onlyPayload ) { - if( LZ77::GetLz77Offset( ret ) != -1 ) - ret = LZ77::Decompress( ret ); + LZ77::CompressionType ct; + ret = LZ77::Decompress( ret, &ct ); + if( ct == LZ77::None && IsAshCompressed( ret ) ) + { + ret = DecryptAsh( ret ); + } + //if( LZ77::GetLz77Offset( ret ) != -1 ) + // ret = LZ77::Decompress( ret ); - else if( IsAshCompressed( ret ) ) - ret = DecryptAsh( ret ); + //else if( IsAshCompressed( ret ) ) + // ret = DecryptAsh( ret ); } return ret; } @@ -1166,6 +1205,18 @@ quint32 U8::GetSize( const QString &str ) { return data.size(); } + //check if this is a path to a file in a nested archive + QMap::iterator i = nestedU8s.begin(); + while( i != nestedU8s.constEnd() ) + { + if( str.startsWith( i.key() ) && str != i.key() ) + { + QString subPath = str; + subPath.remove( 0, i.key().size() + 1 );//remove the path of the archive itself + the slash + return i.value().GetSize( subPath ); + } + ++i; + } int index = FindEntry( str ); if( index < 0 ) { @@ -1197,9 +1248,7 @@ bool U8::IsU8( const QByteArray &ba ) if( IsAshCompressed( data ) )//decrypt ASH0 files data = DecryptAsh( data ); - int off = LZ77::GetLz77Offset( data );//decrypt LZ77 - if( off != -1 ) - data = LZ77::Decompress( data ); + data = LZ77::Decompress( data ); QByteArray start = data.left( 5000 ); return start.indexOf( "U\xAA\x38\x2d" ) != -1; @@ -1413,10 +1462,11 @@ const QByteArray U8::AddIMET( int paddingType ) soundSize = ret.size() - 0x20; ret = GetIMET( imetNames, paddingType, iconSize, bannerSize, soundSize ); - if( isLz77 )//really? can the entire banner be lz77 compressed? - ret += LZ77::Compress( data ); - else - ret += data; + //if( isLz77 )//really? can the entire banner be lz77 compressed? + // ret += LZ77::Compress( data ); + //else + // ret += data; + ret += LZ77::Compress( data, lz77Type ); return ret; } diff --git a/WiiQt/u8.h b/WiiQt/u8.h index 06d3892..7c4ad15 100644 --- a/WiiQt/u8.h +++ b/WiiQt/u8.h @@ -1,6 +1,7 @@ #ifndef U8_H #define U8_H +#include "lz77.h" #include "includes.h" /*order of the names in the imet header @@ -163,7 +164,8 @@ private: bool wii_cs_error; //if this archive as a whole is lz77 compressed - bool isLz77; + //bool isLz77; + LZ77::CompressionType lz77Type; QStringList imetNames; int headerType;