diff --git a/nandExtract/aes.c b/WiiQt/aes.c similarity index 100% rename from nandExtract/aes.c rename to WiiQt/aes.c diff --git a/nandExtract/aes.h b/WiiQt/aes.h similarity index 100% rename from nandExtract/aes.h rename to WiiQt/aes.h diff --git a/WiiQt/ash.cpp b/WiiQt/ash.cpp new file mode 100644 index 0000000..1d4ec86 --- /dev/null +++ b/WiiQt/ash.cpp @@ -0,0 +1,429 @@ +#include "ash.h" +bool IsAshCompressed( const QByteArray ba ) +{ + return ba.startsWith( "ASH" ); +} + +QByteArray DecryptAsh( const QByteArray ba ) +{ + if( !IsAshCompressed( ba ) ) + { + qWarning() << "DecryptAsh -> wrong magic"; + return QByteArray(); + } + quint32 r[ 32 ]; + quint32 count=0; + quint32 t; + + quint64 memAddr = (quint64)( ba.data() );//in + r[4] = 0x80000000; + qint64 inDiff = memAddr - r[ 4 ];//difference in r[ 4 ] and the real address. hack to support higher memory addresses than crediar's version + + r[5] = 0x415348; + r[6] = 0x415348; + +//Rvl_decode_ash: + + r[5] = qFromBigEndian(*(quint32 *)( r[4] + inDiff + 4 ) ); + r[5] = r[5] & 0x00FFFFFF; + + quint32 size = r[5]; + //qDebug() << "Decompressed size:" << hex << size; + + char crap2[ size ]; + quint64 memAddr2 = (quint64)( crap2 );//outbuf + r[3] = 0x90000000; + qint64 outDiff = memAddr2 - r[ 3 ];//difference in r[ 3 ] and the real address + + quint32 o = r[ 3 ]; + memset( (void*)( r[ 3 ] + outDiff ), 0, size ); + + r[24] = 0x10; + r[28] = qFromBigEndian(*(quint32 *)(r[4]+8 + inDiff)); + r[25] = 0; + r[29] = 0; + r[26] = qFromBigEndian(*(quint32 *)(r[4]+0xC + inDiff)); + r[30] = qFromBigEndian(*(quint32 *)(r[4]+r[28] + inDiff)); + r[28] = r[28] + 4; + //r[8] = 0x8108<<16; + //HACK, pointer to RAM + + char crap3[ 0x100000 ]; + quint64 memAddr3 = (quint64)( crap3 );//outbuf + r[8] = 0x84000000; + qint64 outDiff2 = memAddr3 - r[ 8 ];//difference in r[ 3 ] and the real address + memset( (void*)( r[8] + outDiff2 ), 0, 0x100000 ); + + r[8] = r[8]; + r[9] = r[8] + 0x07FE; + r[10] = r[9] + 0x07FE; + r[11] = r[10] + 0x1FFE; + r[31] = r[11] + 0x1FFE; + r[23] = 0x200; + r[22] = 0x200; + r[27] = 0; + +loc_81332124: + + if( r[25] != 0x1F ) + goto loc_81332140; + + r[0] = r[26] >> 31; + r[26]= qFromBigEndian(*(quint32 *)(r[4] + r[24] + inDiff)); + r[25]= 0; + r[24]= r[24] + 4; + goto loc_8133214C; + +loc_81332140: + + r[0] = r[26] >> 31; + r[25]= r[25] + 1; + r[26]= r[26] << 1; + +loc_8133214C: + + if( r[0] == 0 ) + goto loc_81332174; + + r[0] = r[23] | 0x8000; + *(quint16 *)(r[31] + outDiff2) = (quint16)qFromBigEndian((quint16)r[0]); + r[0] = r[23] | 0x4000; + *(quint16 *)(r[31]+2 + outDiff2) = (quint16)qFromBigEndian((quint16)r[0]); + + r[31] = r[31] + 4; + r[27] = r[27] + 2; + r[23] = r[23] + 1; + r[22] = r[22] + 1; + + goto loc_81332124; + +loc_81332174: + + r[12] = 9; + r[21] = r[25] + r[12]; + t = r[21]; + if( r[21] > 0x20 ) + goto loc_813321AC; + + r[21] = (~(r[12] - 0x20))+1; + r[6] = r[26] >> r[21]; + if( t == 0x20 ) + goto loc_8133219C; + + r[26] = r[26] << r[12]; + r[25] = r[25] + r[12]; + goto loc_813321D0; + +loc_8133219C: + + r[26]= qFromBigEndian(*(quint32 *)(r[4] + r[24] + inDiff)); + r[25]= 0; + r[24]= r[24] + 4; + goto loc_813321D0; + +loc_813321AC: + + r[0] = (~(r[12] - 0x20))+1; + r[6] = r[26] >> r[0]; + r[26]= qFromBigEndian(*(quint32 *)(r[4] + r[24] + inDiff)); + r[0] = (~(r[21] - 0x40))+1; + r[24]= r[24] + 4; + r[0] = r[26] >> r[0]; + r[6] = r[6] | r[0]; + r[25] = r[21] - 0x20; + r[26] = r[26] << r[25]; + +loc_813321D0: + + r[12]= (quint16)qFromBigEndian((quint16)(*(quint16 *)(( r[31] + outDiff2 ) - 2))); + r[31] -= 2; + r[27]= r[27] - 1; + r[0] = r[12] & 0x8000; + r[12]= (r[12] & 0x1FFF) << 1; + if( r[0] == 0 ) + goto loc_813321F8; + + *(quint16 *)(r[9]+r[12] + outDiff2 ) = (quint16)qFromBigEndian((quint16)r[6]);//????? + r[6] = (r[12] & 0x3FFF)>>1;//extrwi %r6, %r12, 14,17 + if( r[27] != 0 ) + goto loc_813321D0; + + goto loc_81332204; + +loc_813321F8: + + *(quint16 *)(r[8]+r[12] + outDiff2) = (quint16)qFromBigEndian((quint16)r[6]); + r[23] = r[22]; + goto loc_81332124; + +loc_81332204: + + r[23] = 0x800; + r[22] = 0x800; + +loc_8133220C: + + if( r[29] != 0x1F ) + goto loc_81332228; + + r[0] = r[30] >> 31; + r[30]= qFromBigEndian(*(quint32 *)(r[4] + r[28] + inDiff)); + r[29]= 0; + r[28]= r[28] + 4; + goto loc_81332234; + +loc_81332228: + + r[0] = r[30] >> 31; + r[29]= r[29] + 1; + r[30]= r[30] << 1; + +loc_81332234: + + if( r[0] == 0 ) + goto loc_8133225C; + + r[0] = r[23] | 0x8000; + *(quint16 *)(r[31] + outDiff2) = (quint16)qFromBigEndian((quint16)r[0]); + r[0] = r[23] | 0x4000; + *(quint16 *)(r[31]+2 + outDiff2) = (quint16)qFromBigEndian((quint16)r[0]); + + r[31] = r[31] + 4; + r[27] = r[27] + 2; + r[23] = r[23] + 1; + r[22] = r[22] + 1; + + goto loc_8133220C; + +loc_8133225C: + + r[12] = 0xB; + r[21] = r[29] + r[12]; + t = r[21]; + if( r[21] > 0x20 ) + goto loc_81332294; + + r[21] = (~(r[12] - 0x20))+1; + r[7] = r[30] >> r[21]; + if( t == 0x20 ) + goto loc_81332284; + + r[30] = r[30] << r[12]; + r[29] = r[29] + r[12]; + goto loc_813322B8; + +loc_81332284: + + r[30]= qFromBigEndian(*(quint32 *)(r[4] + r[28] + inDiff)); + r[29]= 0; + r[28]= r[28] + 4; + goto loc_813322B8; + +loc_81332294: + + r[0] = (~(r[12] - 0x20))+1; + r[7] = r[30] >> r[0]; + r[30]= qFromBigEndian(*(quint32 *)(r[4] + r[28] + inDiff)); + r[0] = (~(r[21] - 0x40))+1; + r[28]= r[28] + 4; + r[0] = r[30] >> r[0]; + r[7] = r[7] | r[0]; + r[29]= r[21] - 0x20; + r[30]= r[30] << r[29]; + +loc_813322B8: + + r[12]= (quint16)qFromBigEndian((quint16)(*(quint16 *)((r[31] + outDiff2 ) - 2))); + r[31] -= 2; + r[27]= r[27] - 1; + r[0] = r[12] & 0x8000; + r[12]= (r[12] & 0x1FFF) << 1; + if( r[0] == 0 ) + goto loc_813322E0; + + *(quint16 *)(r[11]+r[12] + outDiff2 ) = (quint16)qFromBigEndian((quint16)r[7]);//???? + r[7] = (r[12] & 0x3FFF)>>1;// extrwi %r7, %r12, 14,17 + if( r[27] != 0 ) + goto loc_813322B8; + + goto loc_813322EC; + +loc_813322E0: + + *(quint16 *)(r[10]+r[12] + outDiff2 ) = (quint16)qFromBigEndian((quint16)r[7]); + r[23] = r[22]; + goto loc_8133220C; + +loc_813322EC: + + r[0] = r[5]; + +loc_813322F0: + + r[12]= r[6]; + +loc_813322F4: + + if( r[12] < 0x200 ) + goto loc_8133233C; + + if( r[25] != 0x1F ) + goto loc_81332318; + + r[31] = r[26] >> 31; + r[26] = qFromBigEndian(*(quint32 *)(r[4] + r[24] + inDiff)); + r[24] = r[24] + 4; + r[25] = 0; + goto loc_81332324; + +loc_81332318: + + r[31] = r[26] >> 31; + r[25] = r[25] + 1; + r[26] = r[26] << 1; + +loc_81332324: + + r[27] = r[12] << 1; + if( r[31] != 0 ) + goto loc_81332334; + + r[12] = (quint16)qFromBigEndian((quint16)(*(quint16 *)(r[8] + r[27] + outDiff2 ))); + goto loc_813322F4; + +loc_81332334: + + r[12] = (quint16)qFromBigEndian((quint16)(*(quint16 *)(r[9] + r[27] + outDiff2 ))); + goto loc_813322F4; + +loc_8133233C: + + if( r[12] >= 0x100 ) + goto loc_8133235C; + + *(quint8 *)(r[3] + outDiff) = r[12]; + r[3] = r[3] + 1; + r[5] = r[5] - 1; + if( r[5] != 0 ) + goto loc_813322F0; + + goto loc_81332434; + +loc_8133235C: + + r[23] = r[7]; + +loc_81332360: + + if( r[23] < 0x800 ) + goto loc_813323A8; + + if( r[29] != 0x1F ) + goto loc_81332384; + + r[31] = r[30] >> 31; + r[30] = qFromBigEndian(*(quint32 *)(r[4] + r[28] + inDiff)); + r[28] = r[28] + 4; + r[29] = 0; + goto loc_81332390; + +loc_81332384: + + r[31] = r[30] >> 31; + r[29] = r[29] + 1; + r[30] = r[30] << 1; + +loc_81332390: + + r[27] = r[23] << 1; + if( r[31] != 0 ) + goto loc_813323A0; + + r[23] = (quint16)qFromBigEndian((quint16)(*(quint16 *)(r[10] + r[27] + outDiff2 ))); + goto loc_81332360; + +loc_813323A0: + + r[23] = (quint16)qFromBigEndian((quint16)(*(quint16 *)(r[11] + r[27] + outDiff2 ))); + goto loc_81332360; + +loc_813323A8: + + r[12] = r[12] - 0xFD; + r[23] = ~r[23] + r[3] + 1; + r[5] = ~r[12] + r[5] + 1; + r[31] = r[12] >> 3; + + if( r[31] == 0 ) + goto loc_81332414; + + count = r[31]; + +loc_813323C0: + + r[31] = *(quint8 *)(( r[23] + outDiff ) - 1); + *(quint8 *)(r[3] + outDiff) = r[31]; + + r[31] = *(quint8 *)( r[23] + outDiff ); + *(quint8 *)(r[3]+1 + outDiff) = r[31]; + + r[31] = *(quint8 *)(( r[23] + outDiff ) + 1); + *(quint8 *)(r[3]+2 + outDiff) = r[31]; + + r[31] = *(quint8 *)(( r[23] + outDiff ) + 2); + *(quint8 *)(r[3]+3 + outDiff) = r[31]; + + r[31] = *(quint8 *)(( r[23] + outDiff ) + 3); + *(quint8 *)(r[3]+4 + outDiff) = r[31]; + + r[31] = *(quint8 *)(( r[23] + outDiff ) + 4); + *(quint8 *)(r[3]+5 + outDiff) = r[31]; + + r[31] = *(quint8 *)(( r[23] + outDiff ) + 5); + *(quint8 *)(r[3]+6 + outDiff) = r[31]; + + r[31] = *(quint8 *)(( r[23] + outDiff ) + 6); + *(quint8 *)(r[3]+7 + outDiff) = r[31]; + + r[23] = r[23] + 8; + r[3] = r[3] + 8; + + if( --count ) + goto loc_813323C0; + + r[12] = r[12] & 7; + if( r[12] == 0 ) + goto loc_8133242C; + +loc_81332414: + + count = r[12]; + +loc_81332418: + + r[31] = *(quint8 *)(( r[23] + outDiff ) - 1); + r[23] = r[23] + 1; + *(quint8 *)(r[3] + outDiff ) = r[31]; + r[3] = r[3] + 1; + + if( --count ) + goto loc_81332418; + +loc_8133242C: + + if( r[5] != 0 ) + goto loc_813322F0; + +loc_81332434: + + r[3] = r[0]; + + QByteArray ret( r[ 3 ], '\0' ); + QBuffer outBuf( &ret ); + outBuf.open( QIODevice::WriteOnly ); + outBuf.write( (const char*)( o + outDiff ), r[ 3 ] ); + outBuf.close(); + + return ret; +} + diff --git a/WiiQt/ash.h b/WiiQt/ash.h new file mode 100644 index 0000000..ac2959f --- /dev/null +++ b/WiiQt/ash.h @@ -0,0 +1,13 @@ +#ifndef ASH_H +#define ASH_H + +#include "includes.h" + +//returns a byteArray of decompressed ASH data +//returns an empty array if the data doesnt have the ASH magic word +QByteArray DecryptAsh( const QByteArray ba ); + +//check if data starts with "ASH". no other check is done +bool IsAshCompressed( const QByteArray ba ); + +#endif // ASH_H diff --git a/nand_dump/includes.h b/WiiQt/includes.h similarity index 90% rename from nand_dump/includes.h rename to WiiQt/includes.h index 7ddcad8..96f7a8b 100644 --- a/nand_dump/includes.h +++ b/WiiQt/includes.h @@ -15,8 +15,8 @@ #include #include #include -#include -#include +//#include +//#include #include #include #include diff --git a/WiiQt/lz77.cpp b/WiiQt/lz77.cpp new file mode 100644 index 0000000..322ab21 --- /dev/null +++ b/WiiQt/lz77.cpp @@ -0,0 +1,393 @@ +#include "lz77.h" + +//most functions here are taken from wii.cs by leathl +//they have just been tweeked slightly to work with Qt C++ + +LZ77::LZ77() +{ + match_position = 0; + match_length = 0; + textsize = 0; + codesize = 0; +} + +int LZ77::GetLz77Offset( const QByteArray &data ) +{ + QByteArray start = data.left( 5000 ); + + return start.indexOf( "LZ77" ); +} + +void LZ77::InitTree() +{ + int i; + int N = 4096; + for( i = N + 1; i <= N + 256; i++ ) + rson[ i ] = N; + + for( i = 0; i < N; i++ ) + dad[ i ] = N; +} + +void LZ77::DeleteNode( int p ) +{ + int N = 4096; + int q; + + if( dad[ p ] == N ) + return; /* not in tree */ + + if( rson[ p ] == N ) + q = lson[ p ]; + + else if( lson[ p ] == N ) + q = rson[ p ]; + + else + { + q = lson[ p ]; + + if( rson[ q ] != N ) + { + do + { + q = rson[ q ]; + } + while( rson[ q ] != N ); + + rson[ dad[ q ] ] = lson[ q ]; + dad[ lson[ q ] ] = dad[ q ]; + + lson[ q ] = lson[ p ]; + dad[ lson[ p ] ] = q; + } + rson[ q ] = rson[ p ]; + dad[ rson[ p ] ] = q; + } + dad[ q ] = dad[ p ]; + + if( rson[ dad[ p ] ] == p ) + rson[ dad[ p ] ] = q; + else + lson[ dad[ p ] ] = q; + + dad[ p ] = N; +} + +void LZ77::InsertNode( int r ) +{ + int i, p, cmp; + int N = 4096; + int F = 18; + cmp = 1; + p = N + 1 + ( text_buf[ r ] == ( quint16 )0xffff ? ( quint16 )0 : text_buf[ r ] ); + rson[ r ] = lson[ r ] = N; + match_length = 0; + for( ; ; ) + { + if( cmp >= 0 ) + { + if( rson[ p ] != N ) + p = rson[ p ]; + else + { + rson[ p ] = r; + dad[ r ] = p; + return; + } + } + else + { + if ( lson[ p ] != N ) + p = lson[ p ]; + + else + { + lson[ p ] = r; + dad[ r ] = p; + return; + } + } + for( i = 1; i < F; i++ ) + if( ( cmp = text_buf[ r + i ] - text_buf[ p + i ] ) != 0 ) + break; + if( i > match_length ) + { + match_position = p; + if( ( match_length = i ) >= F ) + break; + } + } + + dad[ r ] = dad[ p ]; + lson[ r ] = lson[ p ]; + rson[ r ] = rson[ p ]; + dad[ lson[ p ] ] = r; + dad[ rson[ p ] ] = r; + + if( rson[ dad[ p ] ] == p ) + rson[ dad[ p ] ] = r; + + else + lson[ dad[ p ] ] = r; + + dad[ p ] = N; +} + +quint32 LZ77::GetDecompressedSize( const QByteArray &data ) +{ + int off = GetLz77Offset( data ); + if( off == -1 ) + { + qWarning() << "LZ77::GetDecompressedSize -> no lz77 magic"; + return 0; + } + QByteArray ba = data; + QBuffer buf( &ba ); + if( !buf.open( QBuffer::ReadOnly ) ) + { + qWarning() << "LZ77::GetDecompressedSize -> Can't create buffer"; + return 0; + } + buf.seek( off + 4 ); + + quint32 gbaheader; + buf.seek( off + 4 ); + buf.read( (char*)&gbaheader, 4 ); + + return ( gbaheader >> 8 ); +} + +QByteArray LZ77::Decompress( const QByteArray &compressed, int offset ) +{ + int N = 4096; + int F = 18; + int threshold = 2; + int k, r, z, flags; + quint16 i, j; + char ch; + quint32 decomp_size; + quint32 cur_size = 0; + + QByteArray crap = compressed; + QBuffer infile( &crap ); + if( !infile.open( QBuffer::ReadOnly ) ) + { + qWarning() << "Can't create buffer 1"; + return QByteArray(); + } + QByteArray ret; + QBuffer outfile( &ret ); + if( !outfile.open( QBuffer::ReadWrite ) ) + { + qWarning() << "Can't create buffer 2"; + return QByteArray(); + } + + quint32 gbaheader; + infile.seek( offset + 4 ); + infile.read( (char*)&gbaheader, 4 ); + + decomp_size = gbaheader >> 8; + quint8 text_buf[ N + 17 ]; + + for( i = 0; i < N - F; i++ ) + text_buf[ i ] = 0xdf; + + r = N - F; + flags = 7; + z = 7; + + while( true ) + { + flags <<= 1; + z++; + if( z == 8 ) + { + if( !infile.getChar( &ch ) ) + break; + flags = (quint8) ch; + z = 0; + } + if( ( flags & 0x80 ) == 0 ) + { + if( !infile.getChar( &ch ) ) + break; + + if( cur_size < decomp_size ) + outfile.putChar( ch ); + + text_buf[ r++ ] = ch; + r &= ( N - 1 ); + cur_size++; + } + else + { + if( !infile.getChar( &ch ) ) + break; + i = (quint8)ch; + + if( !infile.getChar( &ch ) ) + break; + j = (quint8)ch; + + j = j | ( (i << 8) & 0xf00 ); + i = ( ( i >> 4 ) & 0x0f ) + threshold; + + for( k = 0; k <= i; k++ ) + { + ch = text_buf[ ( r - j - 1 ) & ( N - 1 ) ]; + if( cur_size < decomp_size ) + outfile.putChar( ch ); + + text_buf[ r++ ] = ch; + r &= ( N - 1 ); + cur_size++; + } + } + } + + return ret; +} + +QByteArray LZ77::Decompress( const QByteArray &compressed ) +{ + int off = GetLz77Offset( compressed ); + if( off < 0 ) + { + qWarning() << "LZ77::Decompress -> data is not compressed"; + return compressed; + } + return Decompress( compressed, off ); +} + +QByteArray LZ77::Compress( const QByteArray &ba ) +{ + //testing 1, 2 + //return ba; + LZ77 lz; + QByteArray ret = lz.Compr( ba ); + return ret; +} + +QByteArray LZ77::Compr( const QByteArray &ba ) +{ + int i, len, r, s, last_match_length, code_buf_ptr; + char ch; + int code_buf[ 17 ]; + int mask; + int N = 4096; + int F = 18; + int threshold = 2; + quint32 filesize = (ba.size() << 8) + 0x10; + + QByteArray crap = ba; + QBuffer infile( &crap ); + if( !infile.open( QBuffer::ReadOnly ) ) + { + qWarning() << "Can't create buffer 1"; + return QByteArray(); + } + QByteArray ret; + QBuffer output( &ret ); + if( !output.open( QBuffer::ReadWrite ) ) + { + 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 ); + + InitTree(); + code_buf[ 0 ] = 0; + code_buf_ptr = 1; + mask = 0x80; + s = 0; + r = N - F; + + for( i = s; i < r; i++ ) + text_buf[ i ] = 0xffff; + + for( len = 0; len < F && infile.getChar( &ch ); len++ ) + text_buf[ r + len ] = (quint8)ch; + + if( ( textsize = len ) == 0) + return ba; + + for( i = 1; i <= F; i++ ) + InsertNode( r - i ); + + InsertNode( r ); + + do + { + if( match_length > len ) + match_length = len; + + if( match_length <= threshold ) + { + match_length = 1; + code_buf[ code_buf_ptr++ ] = text_buf[ r ]; + } + else + { + code_buf[ 0 ] |= mask; + + code_buf[ code_buf_ptr++ ] = (quint8) + ( ( ( r - match_position - 1 ) >> 8) & 0x0f ) | + ( ( match_length - ( threshold + 1 ) ) << 4 ); + + code_buf[ code_buf_ptr++ ] = (quint8)( ( r - match_position - 1 ) & 0xff ); + } + if( ( mask >>= 1 ) == 0 ) + { + for( i = 0; i < code_buf_ptr; i++ ) + output.putChar( (quint8)code_buf[ i ] ); + + codesize += code_buf_ptr; + code_buf[ 0 ] = 0; code_buf_ptr = 1; + mask = 0x80; + } + + last_match_length = match_length; + for( i = 0; i < last_match_length && infile.getChar( &ch ); i++ ) + { + DeleteNode( s ); + text_buf[ s ] = (quint8)ch; + if( s < F - 1 ) + text_buf[ s + N ] = (quint8)ch; + + s = ( s + 1 ) & ( N - 1 ); + r = ( r + 1 ) & ( N - 1 ); + InsertNode( r ); + } + + while( i++ < last_match_length ) + { + DeleteNode( s ); + s = ( s + 1 ) & ( N - 1 ); + r = ( r + 1 ) & ( N - 1 ); + if( --len != 0 ) + InsertNode( r ); + } + } + while( len > 0 ); + + if( code_buf_ptr > 1 ) + { + for( i = 0; i < code_buf_ptr; i++ ) + output.putChar( (quint8)code_buf[ i ] ); + + codesize += code_buf_ptr; + } + int padding = codesize % 4; + if( padding != 0 ) + output.write( QByteArray( 4 - padding, '\0' ) ); + + infile.close(); + return ret; +} diff --git a/WiiQt/lz77.h b/WiiQt/lz77.h new file mode 100644 index 0000000..6dee47e --- /dev/null +++ b/WiiQt/lz77.h @@ -0,0 +1,48 @@ +#ifndef LZ77_H +#define LZ77_H + +#include "includes.h" + +//class for daling with LZ77 compression +//! in most cases, you just want to use the static functions +// QByteArray stuff = LZ77::Decompress( someCompressedData ); +// QByteArray compressedData = LZ77::Compress( someData ); +class LZ77 +{ +public: + LZ77(); + void InsertNode( int r ); + void DeleteNode( int p ); + void InitTree(); + + //gets the offset in a bytearray if 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 ); + + 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 ); + + //compressed a qbytearray with the lz77 argorythm + //returns a qbytearray ncluding the lz77 header + static QByteArray Compress( const QByteArray &ba ); + +private: + int lson[ 4097 ]; + int rson[ 4353 ]; + int dad[ 4097 ]; + quint16 text_buf[ 4113 ]; + int match_position; + int match_length; + int textsize; + int codesize; +}; + +#endif // LZ77_H diff --git a/WiiQt/md5.cpp b/WiiQt/md5.cpp new file mode 100644 index 0000000..92ab1b7 --- /dev/null +++ b/WiiQt/md5.cpp @@ -0,0 +1,377 @@ +/* MD5 + converted to C++ class by Frank Thilo (thilo@unix-ag.org) + for bzflag (http://www.bzflag.org) + + based on: + + md5.h and md5.c + reference implemantion of RFC 1321 + + Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +/* interface header */ +#include "md5.h" + +/* system implementation headers */ +#include +#include + + +// Constants for MD5Transform routine. +#define S11 7 +#define S12 12 +#define S13 17 +#define S14 22 +#define S21 5 +#define S22 9 +#define S23 14 +#define S24 20 +#define S31 4 +#define S32 11 +#define S33 16 +#define S34 23 +#define S41 6 +#define S42 10 +#define S43 15 +#define S44 21 + +//char output[50]; + +/////////////////////////////////////////////// + +// F, G, H and I are basic MD5 functions. +inline MD5::uint4 MD5::F(uint4 x, uint4 y, uint4 z) { + return (x&y) | (~x&z); +} + +inline MD5::uint4 MD5::G(uint4 x, uint4 y, uint4 z) { + return (x&z) | (y&~z); +} + +inline MD5::uint4 MD5::H(uint4 x, uint4 y, uint4 z) { + return x^y^z; +} + +inline MD5::uint4 MD5::I(uint4 x, uint4 y, uint4 z) { + return y ^ (x | ~z); +} + +// rotate_left rotates x left n bits. +inline MD5::uint4 MD5::rotate_left(uint4 x, int n) { + return (x << n) | (x >> (32-n)); +} + +// FF, GG, HH, and II transformations for rounds 1, 2, 3, and 4. +// Rotation is separate from addition to prevent recomputation. +inline void MD5::FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a+ F(b,c,d) + x + ac, s) + b; +} + +inline void MD5::GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a + G(b,c,d) + x + ac, s) + b; +} + +inline void MD5::HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a + H(b,c,d) + x + ac, s) + b; +} + +inline void MD5::II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac) { + a = rotate_left(a + I(b,c,d) + x + ac, s) + b; +} + +////////////////////////////////////////////// + +// default ctor, just initailize +MD5::MD5() +{ + init(); +} + +////////////////////////////////////////////// + +// nifty shortcut ctor, compute MD5 for string and finalize it right away +MD5::MD5(const std::string &text) +{ + init(); + update(text.c_str(), text.length()); + finalize(); +} + +////////////////////////////// + +void MD5::init() +{ + finalized=false; + + count[0] = 0; + count[1] = 0; + + // load magic initialization constants. + state[0] = 0x67452301; + state[1] = 0xefcdab89; + state[2] = 0x98badcfe; + state[3] = 0x10325476; +} + +////////////////////////////// + +// decodes input (unsigned char) into output (uint4). Assumes len is a multiple of 4. +void MD5::decode(uint4 output[], const uint1 input[], size_type len) +{ + for (unsigned int i = 0, j = 0; j < len; i++, j += 4) + output[i] = ((uint4)input[j]) | (((uint4)input[j+1]) << 8) | + (((uint4)input[j+2]) << 16) | (((uint4)input[j+3]) << 24); +} + +////////////////////////////// + +// encodes input (uint4) into output (unsigned char). Assumes len is +// a multiple of 4. +void MD5::encode(uint1 output[], const uint4 input[], size_type len) +{ + for (size_type i = 0, j = 0; j < len; i++, j += 4) { + output[j] = input[i] & 0xff; + output[j+1] = (input[i] >> 8) & 0xff; + output[j+2] = (input[i] >> 16) & 0xff; + output[j+3] = (input[i] >> 24) & 0xff; + } +} + +////////////////////////////// + +// apply MD5 algo on a block +void MD5::transform(const uint1 block[blocksize]) +{ + uint4 a = state[0], b = state[1], c = state[2], d = state[3], x[16]; + decode (x, block, blocksize); + + /* Round 1 */ + FF (a, b, c, d, x[ 0], S11, 0xd76aa478); /* 1 */ + FF (d, a, b, c, x[ 1], S12, 0xe8c7b756); /* 2 */ + FF (c, d, a, b, x[ 2], S13, 0x242070db); /* 3 */ + FF (b, c, d, a, x[ 3], S14, 0xc1bdceee); /* 4 */ + FF (a, b, c, d, x[ 4], S11, 0xf57c0faf); /* 5 */ + FF (d, a, b, c, x[ 5], S12, 0x4787c62a); /* 6 */ + FF (c, d, a, b, x[ 6], S13, 0xa8304613); /* 7 */ + FF (b, c, d, a, x[ 7], S14, 0xfd469501); /* 8 */ + FF (a, b, c, d, x[ 8], S11, 0x698098d8); /* 9 */ + FF (d, a, b, c, x[ 9], S12, 0x8b44f7af); /* 10 */ + FF (c, d, a, b, x[10], S13, 0xffff5bb1); /* 11 */ + FF (b, c, d, a, x[11], S14, 0x895cd7be); /* 12 */ + FF (a, b, c, d, x[12], S11, 0x6b901122); /* 13 */ + FF (d, a, b, c, x[13], S12, 0xfd987193); /* 14 */ + FF (c, d, a, b, x[14], S13, 0xa679438e); /* 15 */ + FF (b, c, d, a, x[15], S14, 0x49b40821); /* 16 */ + + /* Round 2 */ + GG (a, b, c, d, x[ 1], S21, 0xf61e2562); /* 17 */ + GG (d, a, b, c, x[ 6], S22, 0xc040b340); /* 18 */ + GG (c, d, a, b, x[11], S23, 0x265e5a51); /* 19 */ + GG (b, c, d, a, x[ 0], S24, 0xe9b6c7aa); /* 20 */ + GG (a, b, c, d, x[ 5], S21, 0xd62f105d); /* 21 */ + GG (d, a, b, c, x[10], S22, 0x2441453); /* 22 */ + GG (c, d, a, b, x[15], S23, 0xd8a1e681); /* 23 */ + GG (b, c, d, a, x[ 4], S24, 0xe7d3fbc8); /* 24 */ + GG (a, b, c, d, x[ 9], S21, 0x21e1cde6); /* 25 */ + GG (d, a, b, c, x[14], S22, 0xc33707d6); /* 26 */ + GG (c, d, a, b, x[ 3], S23, 0xf4d50d87); /* 27 */ + GG (b, c, d, a, x[ 8], S24, 0x455a14ed); /* 28 */ + GG (a, b, c, d, x[13], S21, 0xa9e3e905); /* 29 */ + GG (d, a, b, c, x[ 2], S22, 0xfcefa3f8); /* 30 */ + GG (c, d, a, b, x[ 7], S23, 0x676f02d9); /* 31 */ + GG (b, c, d, a, x[12], S24, 0x8d2a4c8a); /* 32 */ + + /* Round 3 */ + HH (a, b, c, d, x[ 5], S31, 0xfffa3942); /* 33 */ + HH (d, a, b, c, x[ 8], S32, 0x8771f681); /* 34 */ + HH (c, d, a, b, x[11], S33, 0x6d9d6122); /* 35 */ + HH (b, c, d, a, x[14], S34, 0xfde5380c); /* 36 */ + HH (a, b, c, d, x[ 1], S31, 0xa4beea44); /* 37 */ + HH (d, a, b, c, x[ 4], S32, 0x4bdecfa9); /* 38 */ + HH (c, d, a, b, x[ 7], S33, 0xf6bb4b60); /* 39 */ + HH (b, c, d, a, x[10], S34, 0xbebfbc70); /* 40 */ + HH (a, b, c, d, x[13], S31, 0x289b7ec6); /* 41 */ + HH (d, a, b, c, x[ 0], S32, 0xeaa127fa); /* 42 */ + HH (c, d, a, b, x[ 3], S33, 0xd4ef3085); /* 43 */ + HH (b, c, d, a, x[ 6], S34, 0x4881d05); /* 44 */ + HH (a, b, c, d, x[ 9], S31, 0xd9d4d039); /* 45 */ + HH (d, a, b, c, x[12], S32, 0xe6db99e5); /* 46 */ + HH (c, d, a, b, x[15], S33, 0x1fa27cf8); /* 47 */ + HH (b, c, d, a, x[ 2], S34, 0xc4ac5665); /* 48 */ + + /* Round 4 */ + II (a, b, c, d, x[ 0], S41, 0xf4292244); /* 49 */ + II (d, a, b, c, x[ 7], S42, 0x432aff97); /* 50 */ + II (c, d, a, b, x[14], S43, 0xab9423a7); /* 51 */ + II (b, c, d, a, x[ 5], S44, 0xfc93a039); /* 52 */ + II (a, b, c, d, x[12], S41, 0x655b59c3); /* 53 */ + II (d, a, b, c, x[ 3], S42, 0x8f0ccc92); /* 54 */ + II (c, d, a, b, x[10], S43, 0xffeff47d); /* 55 */ + II (b, c, d, a, x[ 1], S44, 0x85845dd1); /* 56 */ + II (a, b, c, d, x[ 8], S41, 0x6fa87e4f); /* 57 */ + II (d, a, b, c, x[15], S42, 0xfe2ce6e0); /* 58 */ + II (c, d, a, b, x[ 6], S43, 0xa3014314); /* 59 */ + II (b, c, d, a, x[13], S44, 0x4e0811a1); /* 60 */ + II (a, b, c, d, x[ 4], S41, 0xf7537e82); /* 61 */ + II (d, a, b, c, x[11], S42, 0xbd3af235); /* 62 */ + II (c, d, a, b, x[ 2], S43, 0x2ad7d2bb); /* 63 */ + II (b, c, d, a, x[ 9], S44, 0xeb86d391); /* 64 */ + + state[0] += a; + state[1] += b; + state[2] += c; + state[3] += d; + + // Zeroize sensitive information. + memset(x, 0, sizeof x); +} + +////////////////////////////// + +// MD5 block update operation. Continues an MD5 message-digest +// operation, processing another message block +void MD5::update(const unsigned char input[], size_type length) +{ + // compute number of bytes mod 64 + size_type index = count[0] / 8 % blocksize; + + // Update number of bits + if ((count[0] += (length << 3)) < (length << 3)) + count[1]++; + count[1] += (length >> 29); + + // number of bytes we need to fill in buffer + size_type firstpart = 64 - index; + + size_type i; + + // transform as many times as possible. + if (length >= firstpart) + { + // fill buffer first, transform + memcpy(&buffer[index], input, firstpart); + transform(buffer); + + // transform chunks of blocksize (64 bytes) + for (i = firstpart; i + blocksize <= length; i += blocksize) + transform(&input[i]); + + index = 0; + } + else + i = 0; + + // buffer remaining input + memcpy(&buffer[index], &input[i], length-i); +} + +////////////////////////////// + +// for convenience provide a verson with signed char +void MD5::update(const char input[], size_type length) +{ + update((const unsigned char*)input, length); +} + +////////////////////////////// + +// MD5 finalization. Ends an MD5 message-digest operation, writing the +// the message digest and zeroizing the context. +MD5& MD5::finalize() +{ + static unsigned char padding[64] = { + 0x80, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 + }; + + if (!finalized) { + // Save number of bits + unsigned char bits[8]; + encode(bits, count, 8); + + // pad out to 56 mod 64. + size_type index = count[0] / 8 % 64; + size_type padLen = (index < 56) ? (56 - index) : (120 - index); + update(padding, padLen); + + // Append length (before padding) + update(bits, 8); + + // Store state in digest + encode(digest, state, 16); + + // Zeroize sensitive information. + memset(buffer, 0, sizeof buffer); + memset(count, 0, sizeof count); + + finalized=true; + } + //sprintf(output,"%s",this); + return *this; +} + +////////////////////////////// + +// return hex representation of digest as string +std::string MD5::hexdigest() const +{ + if (!finalized) + return ""; + + char buf[33]; + for (int i=0; i<16; i++) + sprintf(buf+i*2, "%02x", digest[i]); + buf[32]=0; + + sprintf( (char*)outputBuf,"%s",buf); + return std::string(buf); +} + +char* MD5::hexdigestChar() const +{ + if (!finalized) + return NULL; + + memset( (char*)&outputBuf, 0, 0x20 ); + memcpy( (char*)&outputBuf, &digest, 16 ); + return (char*)outputBuf; +} + +////////////////////////////// + +std::ostream& operator<<(std::ostream& out, MD5 md5) +{ + return out << md5.hexdigest(); +} + +////////////////////////////// + +std::string md5(const std::string str) +{ + MD5 md5 = MD5(str); + + return md5.hexdigest(); +} + diff --git a/WiiQt/md5.h b/WiiQt/md5.h new file mode 100644 index 0000000..3e3488b --- /dev/null +++ b/WiiQt/md5.h @@ -0,0 +1,99 @@ +/* MD5 + converted to C++ class by Frank Thilo (thilo@unix-ag.org) + for bzflag (http://www.bzflag.org) + + based on: + + md5.h and md5.c + reference implementation of RFC 1321 + + Copyright (C) 1991-2, RSA Data Security, Inc. Created 1991. All +rights reserved. + +License to copy and use this software is granted provided that it +is identified as the "RSA Data Security, Inc. MD5 Message-Digest +Algorithm" in all material mentioning or referencing this software +or this function. + +License is also granted to make and use derivative works provided +that such works are identified as "derived from the RSA Data +Security, Inc. MD5 Message-Digest Algorithm" in all material +mentioning or referencing the derived work. + +RSA Data Security, Inc. makes no representations concerning either +the merchantability of this software or the suitability of this +software for any particular purpose. It is provided "as is" +without express or implied warranty of any kind. + +These notices must be retained in any copies of any part of this +documentation and/or software. + +*/ + +#ifndef BZF_MD5_H +#define BZF_MD5_H + +#include +#include + + + +// a small class for calculating MD5 hashes of strings or byte arrays +// it is not meant to be fast or secure +// +// usage: 1) feed it blocks of uchars with update() +// 2) finalize() +// 3) get hexdigest() string +// or +// MD5(std::string).hexdigest() +// +// assumes that char is 8 bit and int is 32 bit +class MD5 +{ +public: + typedef unsigned int size_type; // must be 32bit + + MD5(); + MD5(const std::string& text); + void update(const unsigned char *buf, size_type length); + void update(const char *buf, size_type length); + MD5& finalize(); + std::string hexdigest() const; + char* hexdigestChar() const; + friend std::ostream& operator<<(std::ostream&, MD5 md5); + +private: + void init(); + typedef unsigned char uint1; // 8bit + typedef unsigned int uint4; // 32bit + enum {blocksize = 64}; // VC6 won't eat a const static int here + + void transform(const uint1 block[blocksize]); + static void decode(uint4 output[], const uint1 input[], size_type len); + static void encode(uint1 output[], const uint4 input[], size_type len); + + bool finalized; + uint1 buffer[blocksize]; // bytes that didn't fit in last 64 byte chunk + uint4 count[2]; // 64bit counter for number of bits (lo, hi) + uint4 state[4]; // digest so far + uint1 digest[16]; // the result + + // low level logic operations + static inline uint4 F(uint4 x, uint4 y, uint4 z); + static inline uint4 G(uint4 x, uint4 y, uint4 z); + static inline uint4 H(uint4 x, uint4 y, uint4 z); + static inline uint4 I(uint4 x, uint4 y, uint4 z); + static inline uint4 rotate_left(uint4 x, int n); + static inline void FF(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + static inline void GG(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + static inline void HH(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + static inline void II(uint4 &a, uint4 b, uint4 c, uint4 d, uint4 x, uint4 s, uint4 ac); + + //little buffer for giving the result + char outputBuf[ 0x20 ]; +}; + +std::string md5(const std::string str); + +#endif + diff --git a/nandExtract/nandbin.cpp b/WiiQt/nandbin.cpp similarity index 86% rename from nandExtract/nandbin.cpp rename to WiiQt/nandbin.cpp index 519c739..914ee19 100755 --- a/nandExtract/nandbin.cpp +++ b/WiiQt/nandbin.cpp @@ -22,8 +22,7 @@ NandBin::~NandBin() bool NandBin::SetPath( const QString &path ) { - nandPath = path;//TODO: dont need both these variables. definitely dont need to set the global one here - nandFile = path; + //nandPath = path; if( f.isOpen() ) f.close(); @@ -246,7 +245,7 @@ bool NandBin::GetKey( int type ) case 0: case 1: { - QString keyPath = nandFile; + QString keyPath = nandPath; int sl = keyPath.lastIndexOf( "/" ); if( sl == -1 ) { @@ -514,6 +513,72 @@ 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( 6 ).contains( "1" ) ) + { + 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::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; diff --git a/nandExtract/nandbin.h b/WiiQt/nandbin.h similarity index 60% rename from nandExtract/nandbin.h rename to WiiQt/nandbin.h index d4ef9ae..4f90a1c 100755 --- a/nandExtract/nandbin.h +++ b/WiiQt/nandbin.h @@ -4,7 +4,7 @@ #include "includes.h" struct fst_t { - quint8 filename[ 0xc ];//showmii wads has size 0xb here but reads 0xc bytes into name?? + quint8 filename[ 0xc ]; quint8 mode; quint8 attr; quint16 sub; @@ -17,25 +17,42 @@ struct fst_t // class to deal with an encrypted wii nand dump // basic usage... create an object, set a path, call InitNand. then you can get the detailed list of entries with GetTree() // extract files with GetFile() + +//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(); + + //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( QIcon dirs = QIcon(), 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(); - //returns the data that makes up the file of a given entry# - QByteArray GetFile( quint16 entry ); //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 @@ -48,13 +65,27 @@ public: //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# + 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 ); + + private: QByteArray key; qint32 loc_super; qint32 loc_fat; qint32 loc_fst; QString extractPath; - QString nandFile; + QString nandPath; QFile f; int type; @@ -69,7 +100,6 @@ private: quint16 GetFAT( quint16 fat_entry ); fst_t GetFST( quint16 entry ); QByteArray GetCluster( quint16 cluster_entry, bool decrypt = true ); - //QByteArray ReadPage( quint32 i, bool withEcc = false ); QByteArray GetFile( fst_t fst ); QString FstName( fst_t fst ); @@ -81,8 +111,11 @@ private: QTreeWidgetItem *root; bool AddChildren( QTreeWidgetItem *parent, quint16 entry ); + QTreeWidgetItem *ItemFromPath( const QString &path ); + QTreeWidgetItem *FindItem( const QString &s, QTreeWidgetItem *parent ); signals: + //connect to these to receive messages from this object void SendError( QString ); void SendText( QString ); }; diff --git a/nand_dump/nanddump.cpp b/WiiQt/nanddump.cpp similarity index 98% rename from nand_dump/nanddump.cpp rename to WiiQt/nanddump.cpp index 4510118..8a9a89e 100644 --- a/nand_dump/nanddump.cpp +++ b/WiiQt/nanddump.cpp @@ -126,6 +126,8 @@ QByteArray NandDump::GetSettingTxt() bool NandDump::SetSettingTxt( const QByteArray ba ) { + if( basePath.isEmpty() ) + return false; QString path = basePath + "/title/00000001/00000002/data"; if( !QFileInfo( path ).exists() && !QDir().mkpath( path ) ) return false; @@ -134,6 +136,8 @@ bool NandDump::SetSettingTxt( const QByteArray ba ) const QByteArray NandDump::GetFile( const QString &path ) { + if( basePath.isEmpty() ) + return QByteArray(); QFile f( basePath + path ); if( !f.open( QIODevice::ReadOnly ) ) { @@ -148,6 +152,8 @@ const QByteArray NandDump::GetFile( const QString &path ) //write some file to the nand bool NandDump::SaveData( const QByteArray ba, const QString& path ) { + if( basePath.isEmpty() ) + return false; qDebug() << "NandDump::SaveData" << path << hex << ba.size(); QFile f( basePath + path ); if( !f.open( QIODevice::WriteOnly ) ) @@ -164,6 +170,8 @@ bool NandDump::SaveData( const QByteArray ba, const QString& path ) void NandDump::DeleteData( const QString & path ) { qDebug() << "NandDump::DeleteData" << path; + if( basePath.isEmpty() ) + return; QFile::remove( basePath + path ); } diff --git a/nand_dump/nanddump.h b/WiiQt/nanddump.h similarity index 70% rename from nand_dump/nanddump.h rename to WiiQt/nanddump.h index 70141a9..f0d2764 100644 --- a/nand_dump/nanddump.h +++ b/WiiQt/nanddump.h @@ -6,6 +6,10 @@ #include "sharedcontentmap.h" #include "uidmap.h" +//class for handeling an extracted wii nand filesystem +//! nothing can be done unless basePath is set. do this either by setting it in the constructor, or by calling SetPath() +//! current reading and writing is limited to installing a title in the form of NusJob, reading/writing/deleting specific paths +//! for performance reasons, the uid and content map are cached and only written to the HDD when the destructor or Flush() is called class NandDump { public: @@ -27,17 +31,27 @@ public: bool DeleteTitle( quint64 tid, bool deleteData = false ); //check what version a given title is on this nand, returns 0 if it isnt installed - quint16 GetTitleVersion( quint64 tid ); + //quint16 GetTitleVersion( quint64 tid ); + + //get a list of all titles for which there is a ticket & tmd + // returns a map of < tid, version > + //QMap< quint64, quint16 > GetInstalledTitles(); //write the current uid & content.map to the PC //failure to make sure this is done can end up with a broken nand bool Flush(); + //overloads GetFile() with "/title/00000001/00000002/data/setting.txt" QByteArray GetSettingTxt(); bool SetSettingTxt( const QByteArray ba ); + //reads a file from the nand and returns it as a qbytearray const QByteArray GetFile( const QString &path ); + + //tries to write the given bytearray to a file of the given path bool SaveData( const QByteArray ba, const QString& path ); + + //expects a file, not directory void DeleteData( const QString & path ); diff --git a/nand_dump/nusdownloader.cpp b/WiiQt/nusdownloader.cpp similarity index 100% rename from nand_dump/nusdownloader.cpp rename to WiiQt/nusdownloader.cpp diff --git a/nand_dump/nusdownloader.h b/WiiQt/nusdownloader.h similarity index 99% rename from nand_dump/nusdownloader.h rename to WiiQt/nusdownloader.h index ceeb713..587abb4 100644 --- a/nand_dump/nusdownloader.h +++ b/WiiQt/nusdownloader.h @@ -1,6 +1,9 @@ #ifndef NUSDOWNLOADER_H #define NUSDOWNLOADER_H +#include +#include + #include "includes.h" #include "tiktmd.h" diff --git a/saveToy/savebanner.cpp b/WiiQt/savebanner.cpp similarity index 96% rename from saveToy/savebanner.cpp rename to WiiQt/savebanner.cpp index 9fe37f2..56ac8c4 100644 --- a/saveToy/savebanner.cpp +++ b/WiiQt/savebanner.cpp @@ -1,7 +1,7 @@ #include "savebanner.h" #include "tools.h" -int TPL_ConvertRGB5A3ToBitMap(quint8* tplbuf, quint8** bitmapdata, quint32 width, quint32 height); +static int ConvertRGB5A3ToBitMap(quint8* tplbuf, quint8** bitmapdata, quint32 width, quint32 height); SaveBanner::SaveBanner() { @@ -201,7 +201,7 @@ QImage SaveBanner::ConvertTextureToImage( const QByteArray &ba, quint32 w, quint { //qDebug() << "SaveBanner::ConvertTextureToImage" << ba.size() << hex << w << h; quint8* bitmapdata = NULL;//this will hold the converted image - int ret = TPL_ConvertRGB5A3ToBitMap( (quint8*)ba.constData(), &bitmapdata, w, h ); + int ret = ConvertRGB5A3ToBitMap( (quint8*)ba.constData(), &bitmapdata, w, h ); if( !ret ) { qWarning() << "SaveBanner::ConvertTextureToImage -> error converting image"; @@ -211,10 +211,9 @@ QImage SaveBanner::ConvertTextureToImage( const QByteArray &ba, quint32 w, quint QImage im2 = im.copy( im.rect() );//make a copy of the image so the "free" wont delete any data we still want free( bitmapdata ); return im2; - } -int TPL_ConvertRGB5A3ToBitMap(quint8* tplbuf, quint8** bitmapdata, quint32 width, quint32 height) +static int ConvertRGB5A3ToBitMap(quint8* tplbuf, quint8** bitmapdata, quint32 width, quint32 height) { quint32 x, y; quint32 x1, y1; diff --git a/saveToy/savebanner.h b/WiiQt/savebanner.h similarity index 100% rename from saveToy/savebanner.h rename to WiiQt/savebanner.h diff --git a/nand_dump/settingtxtdialog.cpp b/WiiQt/settingtxtdialog.cpp similarity index 100% rename from nand_dump/settingtxtdialog.cpp rename to WiiQt/settingtxtdialog.cpp diff --git a/nand_dump/settingtxtdialog.h b/WiiQt/settingtxtdialog.h similarity index 53% rename from nand_dump/settingtxtdialog.h rename to WiiQt/settingtxtdialog.h index 34b14ea..1c4ec46 100644 --- a/nand_dump/settingtxtdialog.h +++ b/WiiQt/settingtxtdialog.h @@ -3,6 +3,8 @@ #include "includes.h" +//this class creates a dialog used to create & edit a setting.txt for a wii nand filesystem +// in most cases, the static function Edit() is what you want to use namespace Ui { class SettingTxtDialog; } @@ -15,6 +17,10 @@ public: explicit SettingTxtDialog( QWidget *parent = 0, const QByteArray &old = QByteArray() ); ~SettingTxtDialog(); + //displays a dialog window with teh given parent. if any data is ginev as old, it will try to populate the dialog with that + // otherwise it will use the defaulte values + // returns empty if the user clicked cancel, or a bytearray containing an encrypted setting.txt if they clicked ok + // the data is ready for writing to a wii nand static QByteArray Edit( QWidget *parent = 0, const QByteArray &old = QByteArray() ); static QByteArray LolCrypt( QByteArray ba ); diff --git a/nand_dump/settingtxtdialog.ui b/WiiQt/settingtxtdialog.ui similarity index 100% rename from nand_dump/settingtxtdialog.ui rename to WiiQt/settingtxtdialog.ui diff --git a/nand_dump/sha1.c b/WiiQt/sha1.c similarity index 100% rename from nand_dump/sha1.c rename to WiiQt/sha1.c diff --git a/nand_dump/sha1.h b/WiiQt/sha1.h similarity index 100% rename from nand_dump/sha1.h rename to WiiQt/sha1.h diff --git a/nand_dump/sharedcontentmap.cpp b/WiiQt/sharedcontentmap.cpp similarity index 99% rename from nand_dump/sharedcontentmap.cpp rename to WiiQt/sharedcontentmap.cpp index 51ca696..b6f3a4e 100644 --- a/nand_dump/sharedcontentmap.cpp +++ b/WiiQt/sharedcontentmap.cpp @@ -118,5 +118,5 @@ void SharedContentMap::AddEntry( const QString &app, const QByteArray &hash ) { data += app.toAscii() + hash; //qDebug() << "SharedContentMap::AddEntry -> added entry, rechecking this beast"; - Check(); + //Check(); } diff --git a/nand_dump/sharedcontentmap.h b/WiiQt/sharedcontentmap.h similarity index 60% rename from nand_dump/sharedcontentmap.h rename to WiiQt/sharedcontentmap.h index bb4647d..4c825fc 100644 --- a/nand_dump/sharedcontentmap.h +++ b/WiiQt/sharedcontentmap.h @@ -3,6 +3,7 @@ #include "includes.h" +//class for handling a content.map from a wii nand class SharedContentMap { public: @@ -13,11 +14,18 @@ public: //if a path is given, it will check that the hashes in the map match up with the contents in the folder bool Check( const QString &path = QString() ); + //gets a string containing the 8 letter app that matches the given hash. + //returns an empty string if the hash is not found in the map QString GetAppFromHash( QByteArray hash ); + + //gets the first available u32 that is not already in the map and returns it as a string QString GetNextEmptyCid(); + //adds an entry to the end of the map + //! this function doesnt check if the entry already exists void AddEntry( const QString &app, const QByteArray &hash ); + //get the entire data ready for writing to a wii nand const QByteArray Data(){ return data; } private: diff --git a/nand_dump/tiktmd.cpp b/WiiQt/tiktmd.cpp similarity index 100% rename from nand_dump/tiktmd.cpp rename to WiiQt/tiktmd.cpp diff --git a/nand_dump/tiktmd.h b/WiiQt/tiktmd.h similarity index 92% rename from nand_dump/tiktmd.h rename to WiiQt/tiktmd.h index c81f751..6a4ae0e 100644 --- a/nand_dump/tiktmd.h +++ b/WiiQt/tiktmd.h @@ -135,9 +135,6 @@ typedef struct _cert_ecdsa { #define COMMON_KEY {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, 0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7} -#define TITLE_IDH(x) ((u32)(((u64)(x))>>32)) -#define TITLE_IDL(x) ((u32)(((u64)(x)))) - //just a quick class to try to keep the rest of the code from getting full of the same shit over and over class Ticket { @@ -199,18 +196,19 @@ public: quint16 Version(); //functions to edit the TMD + //you probably want to call FakeSign() after using these bool SetTid( quint64 tid ); bool SetVersion( quint16 v ); - bool SetType( quint16 cid, quint16 type ); - bool SetSize( quint16 cid, quint32 size ); - bool SetHash( quint16 cid, const QByteArray hash ); + bool SetType( quint16 i, quint16 type ); + bool SetSize( quint16 i, quint32 size ); + bool SetHash( quint16 i, const QByteArray hash ); bool FakeSign(); - //get the tmd data + //returns a qbytearray containing the tmd, from the rsa signature through the last content const QByteArray Data(){ return data; } - //print the tmd info to qDebug() + //print some tmd info to qDebug() void Dbg(); @@ -225,7 +223,7 @@ private: void SetPointer(); //this is just a pointer to the actual good stuff in "data". - //whenever data is changes, this pointer will become invalid and needs to be reset + //whenever data is changed, this pointer will become invalid and needs to be reset tmd *p_tmd; }; diff --git a/nand_dump/tools.cpp b/WiiQt/tools.cpp similarity index 99% rename from nand_dump/tools.cpp rename to WiiQt/tools.cpp index b943e01..ad3b4dd 100644 --- a/nand_dump/tools.cpp +++ b/WiiQt/tools.cpp @@ -3,9 +3,9 @@ #include "aes.h" #include "sha1.h" -QString currentDir; -QString cachePath = "./NUS_cache"; -QString nandPath = "./dump"; +//QString currentDir; +//QString cachePath = "./NUS_cache"; +//QString nandPath = "./dump"; char ascii( char s ) { if ( s < 0x20 ) return '.'; diff --git a/nand_dump/tools.h b/WiiQt/tools.h similarity index 91% rename from nand_dump/tools.h rename to WiiQt/tools.h index 4d1e668..4a41408 100644 --- a/nand_dump/tools.h +++ b/WiiQt/tools.h @@ -1,12 +1,14 @@ #ifndef TOOLS_H #define TOOLS_H #include "includes.h" +//#include " #define RU(x,n) (-(-(x) & -(n))) //round up #define MIN( x, y ) ( ( x ) < ( y ) ? ( x ) : ( y ) ) #define MAX( x, y ) ( ( x ) > ( y ) ? ( x ) : ( y ) ) +char ascii( char s ); void hexdump( const void *d, int len ); void hexdump( const QByteArray &d, int from = 0, int len = -1 ); @@ -30,13 +32,13 @@ QByteArray ReadFile( const QString &path ); bool WriteFile( const QString &path, const QByteArray ba ); //keep track of the last folder browsed to when looking for files -extern QString currentDir; +//extern QString currentDir; //folder used to cache stuff downloaded from NUS so duplicate titles dont need to be downloaded -extern QString cachePath; +//extern QString cachePath; //folder to use as the base path for the nand -extern QString nandPath; +//extern QString nandPath; #define CERTS_DAT_SIZE 2560 extern const quint8 certs_dat[ CERTS_DAT_SIZE ]; diff --git a/WiiQt/u8.cpp b/WiiQt/u8.cpp new file mode 100644 index 0000000..2dd5bdd --- /dev/null +++ b/WiiQt/u8.cpp @@ -0,0 +1,1415 @@ +#include "u8.h" +#include "lz77.h" +#include "tools.h" +#include "md5.h" +#include "ash.h" + +#define U8_HEADER_ALIGNMENT 0x20 //wiibrew says 0x40, but wii.cs is using 0x20 and i havent heard of any bricks because of it +static quint32 swap24( quint32 i ) +{ +#if Q_BYTE_ORDER == Q_BIG_ENDIAN + return i & 0xffffff; +#else + int r = qFromBigEndian( i ); + return r >> 8; +#endif +} + +U8::U8( bool initialize, int type, const QStringList &names ) +{ + ok = false; + isLz77 = false; + paths.clear(); + nestedU8s.clear(); + fst = NULL; + fstSize = 0; + headerType = type; + imetNames = names; + if( !initialize ) + return; + + //headerType = type; + //imetNames = names; + + ok = CreateEmptyData(); +} + +bool U8::CreateEmptyData() +{ + isLz77 = false; + nestedU8s.clear(); + data = QByteArray( 0x20, '\0' ); + + //create a generic u8 with no real info in it. only a fst with a root node + QBuffer buf( &data ); + if( !buf.open( QBuffer::WriteOnly ) ) + { + qWarning() << "Can't create buffer"; + data.clear(); + return false; + } + buf.putChar( 'U' ); + buf.putChar( '\xAA' ); + buf.putChar( '8' ); + buf.putChar( '\x2D' ); + + rootnode_offset = 0x00000020; + quint32 tmp = qFromBigEndian( rootnode_offset ); + buf.write( (const char*)&tmp, 4 ); + + fstSize = 0x0000000D; + tmp = qFromBigEndian( fstSize ); + buf.write( (const char*)&tmp, 4 ); + + data_offset = qFromBigEndian( 0x00000040 ); + tmp = qFromBigEndian( data_offset ); + buf.write( (const char*)&tmp, 4 ); + buf.close(); + + //create the new fst + QByteArray fstArray = QByteArray( 0x20, '\0' ); + fst = (FEntry*)( fstArray.data() ); + fst[ 0 ].Type = 1; + fst[ 0 ].FileLength = qFromBigEndian( 0x00000001 ); + NameOff = 0x0C; + + data.append( fstArray ); + + //point the fst pointer to the new data + fst = (FEntry*)( data.data() + rootnode_offset ); + + + ok = true; + return true; +} + +bool U8::RenameEntry( const QString &path, const QString &newName ) +{ + qDebug() << "U8::RenameEntry(" << path << "," << newName << ")"; + if( !ok ) + { + qWarning() << "U8::RenameEntry -> archive has no data"; + return false; + } + if( newName.contains( "/" ) ) + { + qWarning() << "U8::RenameEntry -> newName cannot contain \'/\'"; + return false; + } + //check if this is a path to a file in a nested archive + QMap::iterator i = nestedU8s.begin(); + while( i != nestedU8s.constEnd() ) + { + if( path.startsWith( i.key() ) && path != i.key() ) + { + QString subPath = path; + subPath.remove( 0, i.key().size() + 1 );//remove the path of the archive itself + the slash + + bool ret = i.value().RenameEntry( subPath, newName ); + if( !ret ) + { + qWarning() << "U8::RenameEntry -> error replacing data in child U8 archive"; + return false; + } + //qDebug() << "child entry updated, now replacing child archive in this one"; + return ReplaceEntry( i.key(),i.value().GetData() ); + //NOTE - after replacing the entry, "i" is no longer valid. keep that in mind when changing this code. its a bitch to track down this bug + } + i++; + } + //make sure the new filename doesnt already exist + QString parentPath = path; + while( parentPath.startsWith( "/") )//remove leading "/"s + parentPath.remove( 0, 1 ); + while( parentPath.endsWith( "/") )//remove trailing "/"s + parentPath.resize( parentPath.size() - 1 ); + + int slash = parentPath.lastIndexOf( "/" ); + if( slash != -1 ) + { + parentPath.chop( ( parentPath.size() - slash ) ); + parentPath += "/" + newName; + } + else + parentPath = newName; + + if( FindEntry( parentPath ) != -1 ) + { + qWarning() << "U8::RenameEntry ->" << parentPath << "already exists in the archive"; + return false; + } + + //find the entry to rename + int entryToRename = FindEntry( path ); + if( entryToRename == -1 ) + { + qWarning() << "U8::RenameEntry" << path << "doesn\'t exists in the archive"; + qWarning() << "choices are" << paths; + return false; + } + + quint32 newNameLen = newName.size(); + quint32 oldNameLen = FstName( entryToRename ).size(); + int difference = newNameLen - oldNameLen; + + + quint32 nFstSize = fstSize + difference; + int dataAdjustment = 0; + if( RU( U8_HEADER_ALIGNMENT, nFstSize ) < RU( U8_HEADER_ALIGNMENT, fstSize ) ) + dataAdjustment = - RU( U8_HEADER_ALIGNMENT, fstSize - nFstSize ); + + else if( RU( U8_HEADER_ALIGNMENT, nFstSize ) > RU( U8_HEADER_ALIGNMENT, fstSize ) ) + dataAdjustment = RU( U8_HEADER_ALIGNMENT, nFstSize - fstSize ); + + qDebug() << "old size:" << hex << oldNameLen\ + << "new size:" << hex << newNameLen\ + << "difference:" << hex << difference + << "dataAdjustment:" << hex << dataAdjustment; + QByteArray nFstData( ( qFromBigEndian( fst[ 0 ].FileLength ) ) * 0xc, '\0' ); + FEntry *nfst = (FEntry*)( nFstData.data() ); + //make the new root entry + nfst[ 0 ] = fst[ 0 ]; + + + + //the new data payload ( just copy the existing one ) + QByteArray nPayload = data.right( data.size() - data_offset ); + QByteArray nNameTable; + nNameTable.append( '\0' );//add a null byte for the root name + + quint32 cnt = qFromBigEndian( fst[ 0 ].FileLength );//the number of entries + + + for( quint32 i = 1; i < cnt; i++ ) + { + FEntry *e = &fst[ i ];//old entry + FEntry *ne = &nfst[ i ];//new entry + + ne->NameOffset = swap24( nNameTable.size() );//offset to the new entry's name in the string table + + //add the name to the new name table + if( i == (quint32)entryToRename ) + { + nNameTable.append( newName.toLatin1() ); + } + else + nNameTable.append( FstName( e ).toLatin1() ); + + nNameTable.append( '\0' ); + if( e->Type )//nothing special to change for directories except the name offset + { + ne->Type = 1; + ne->ParentOffset = e->ParentOffset; + ne->NextOffset = e->NextOffset; + } + else//only need to change the name offset & the data offset for files + { + ne->Type = 0; + ne->FileOffset = qFromBigEndian( qFromBigEndian( e->FileOffset ) + dataAdjustment );// + qFromBigEndian( dataAdjustment ); + //qFromBigEndian( (quint32)( 0x20 + RU( U8_HEADER_ALIGNMENT, nFstSize ) + nPayload.size() ) ); + ne->FileLength = e->FileLength; + qDebug() << "old offset" << hex << qFromBigEndian( e->FileOffset ) << "new offset" << hex << qFromBigEndian( e->FileOffset ) + dataAdjustment; + } + + } + //now put all the parts together to make a new U8 archive + nFstData.append( nNameTable ); + + data = QByteArray( 0x20, '\0' ); + QBuffer buf( &data ); + if( !buf.open( QBuffer::WriteOnly ) ) + { + qWarning() << "U8::AddEntry -> Can't create buffer"; + return -1; + } + buf.putChar( 'U' ); + buf.putChar( '\xAA' ); + buf.putChar( '8' ); + buf.putChar( '\x2D' ); + + rootnode_offset = 0x00000020; + quint32 t = qFromBigEndian( rootnode_offset ); + buf.write( (const char*)&t, 4 ); + + fstSize = nFstData.size(); + t = qFromBigEndian( fstSize ); + buf.write( (const char*)&t, 4 ); + + data_offset = RU( U8_HEADER_ALIGNMENT, 0x20 + fstSize ); + t = qFromBigEndian( data_offset ); + buf.write( (const char*)&t, 4 ); + buf.close(); + + data.append( nFstData );//add the fst + int padding = data_offset - data.size();//pad to 'data_offset' after the fst + if( padding ) + { + data.append( QByteArray( padding, '\0' ) ); + } + data.append( nPayload );//add the actual file data + + padding = RU( 0x20, data.size() ) - data.size();//pad the entire thing to 0x20 bytes TOTO: should probably already be done, and this step is not really necessary + if( padding ) + { + data.append( QByteArray( padding, '\0' ) ); + } + + + + CreateEntryList(); + + //hexdump( data, 0, 0x100 ); + //hexdump( data ); + return true; +} + +bool U8::ReplaceEntry( const QString &path, const QByteArray &nba, bool autoCompress ) +{ + //qDebug() << "U8::ReplaceEntry(" << path << ")"; + if( !ok ) + { + qWarning() << "U8::ReplaceEntry -> archive has no data"; + return false; + } + //check if this is a path to a file in a nested archive + QMap::iterator i = nestedU8s.begin(); + while( i != nestedU8s.constEnd() ) + { + if( path.startsWith( i.key() ) && path != i.key() ) + { + QString subPath = path; + subPath.remove( 0, i.key().size() + 1 );//remove the path of the archive itself + the slash + //qDebug() << "replacing file" << subPath << "in child U8" << i.key(); + + U8 ch = i.value(); + QString chPath = i.key(); + //qDebug() << "replacing" << subPath << "in child archive"; + bool ret = ch.ReplaceEntry( subPath, nba, autoCompress ); + if( !ret ) + { + qWarning() << "U8::ReplaceEntry -> error replacing data in child U8 archive"; + return false; + } + //qDebug() << "child entry updated, now replacing child archive in this one"; + return ReplaceEntry( chPath, ch.GetData() ); + //NOTE - after replacing the entry, "i" is no longer valid. keep that in mind when changing this code. its a bitch to track down this bug + } + i++; + } + //find the entry to replace + int entryToReplace = FindEntry( path ); + if( entryToReplace == -1 ) + { + qWarning() << "U8::ReplaceEntry" << path << "doesn\'t exists in the archive"; + qWarning() << "choices are" << paths; + return false; + } + if( qFromBigEndian( fst[ entryToReplace ].Type ) ) + { + qWarning() << "U8::ReplaceEntry -> replacing a directory is not supported" << path; + return false; + } + + + /*qDebug() << "old size:" << hex << oldSizePadded\ + << "new size:" << hex << newSizePadded\ + << "difference:" << hex << difference;*/ + QByteArray newData = nba; + 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 ) ) + { + newData = LZ77::Compress( newData ); + } + } + + quint32 newSizePadded = RU( 0x20, newData.size() ); + quint32 oldSizePadded = RU( 0x20, qFromBigEndian( fst[ entryToReplace ].FileLength ) ); + int difference = newSizePadded - oldSizePadded; + + data.remove( qFromBigEndian( fst[ entryToReplace ].FileOffset ), oldSizePadded ); + QByteArray newPaddedData = newData; + if( newSizePadded > (quint32)newPaddedData.size() ) + { + newPaddedData.append( QByteArray( newSizePadded - newPaddedData.size(), '\0' ) ); + } + data.insert( qFromBigEndian( fst[ entryToReplace ].FileOffset ), newPaddedData ); + + if( newSizePadded == oldSizePadded )//the sized match, so nothing else to change + return true; + + //point the fst to the new data + fst = (FEntry*)( data.data() + rootnode_offset ); + + //edit the changed size + FEntry *e = &fst[ entryToReplace ]; + e->FileLength = qFromBigEndian( newData.size() ); + //hexdump( (const void*)e, sizeof( FEntry ) ); + + //edit all the file offsets after the changed item + quint32 cnt = qFromBigEndian( fst[ 0 ].FileLength ); + for( quint32 i = entryToReplace + 1; i < cnt; i++ ) + { + FEntry *e = &fst[ i ];//old entry + if( e->Type )//nothing changes for directories + continue; + + //qDebug() << "changed" << FstName( e ) << "offset from" << hex << qFromBigEndian( fst[ i ].FileOffset ) << "to" << qFromBigEndian( fst[ i ].FileOffset ) + difference; + e->FileOffset = qFromBigEndian( qFromBigEndian( fst[ i ].FileOffset ) + difference ); + } + CreateEntryList(); + + //hexdump( data, 0, 0x100 ); + //hexdump( data ); + return true; + +} + +bool U8::RemoveEntry( const QString &path ) +{ + //qDebug() << "U8::RemoveEntry(" << path << ")"; + if( !ok ) + { + qWarning() << "U8::RemoveEntry -> archive has no data"; + return false; + } + //check if this is a path to a file in a nested archive + QMap::iterator i = nestedU8s.begin(); + while( i != nestedU8s.constEnd() ) + { + if( path.startsWith( i.key() ) && path != i.key() ) + { + QString subPath = path; + subPath.remove( 0, i.key().size() + 1 );//remove the path of the archive itself + the slash + + U8 ch = i.value(); + QString chPath = i.key(); + //qDebug() << "deleting" << subPath << "in child archive"; + + bool ret = ch.RemoveEntry( subPath ); + if( !ret ) + { + qWarning() << "U8::RemoveEntry -> error replacing data in child U8 archive"; + return false; + } + return ReplaceEntry( chPath, ch.GetData() );//insert the new nested archive in this one + //NOTE - after replacing the entry, "i" is no longer valid. keep that in mind when changing this code. its a bitch to track down this bug + } + i++; + } + //find the entry to delete + int entryToDelete = FindEntry( path ); + if( entryToDelete == -1 ) + { + qWarning() << "U8::RemoveEntry" << path << "doesn\'t exists in the archive"; + return false; + } + + //create a list of all the directory indexes that my have their size changed + QList parents; + int parent; + quint32 numDeletedEntries; + quint32 fstLenToDelete; + if( fst[ entryToDelete ].Type )//deleting a directory + { + parent = qFromBigEndian( fst[ entryToDelete ].ParentOffset ); + while( parent ) + { + parents << parent; + parent = qFromBigEndian( fst[ parent ].ParentOffset ); + } + numDeletedEntries = qFromBigEndian( fst[ entryToDelete ].NextOffset ) - entryToDelete; + fstLenToDelete = 0; + for( quint32 i = entryToDelete; i < qFromBigEndian( fst[ entryToDelete ].NextOffset ); i ++ ) + { + fstLenToDelete += FstName( i ).size() + 1 + 0xc; + } + + } + else//deleting a file + { + parent = entryToDelete - 1; + while( parent ) + { + if( fst[ parent ].Type && qFromBigEndian( fst[ parent ].NextOffset ) > (quint32)entryToDelete ) + { + parents << parent; + parent = qFromBigEndian( fst[ parent ].ParentOffset ); + } + else + parent--; + } + numDeletedEntries = 1; + fstLenToDelete = FstName( entryToDelete ).size() + 1 + 0xc; + + } + + + //qDebug() << "will delete" << numDeletedEntries << "entries"; + + quint32 nFstSize = fstSize - fstLenToDelete;//old fst size - 0xc bytes for each deleted entry and the length of its name + 1 null byte + //qDebug() << "nFstSize" << nFstSize; + QByteArray nFstData( ( qFromBigEndian( fst[ 0 ].FileLength ) - numDeletedEntries ) * 0xc, '\0' ); + FEntry *nfst = (FEntry*)( nFstData.data() ); + + //the new data payload ( all the files ) + QByteArray nPayload; + QByteArray nNameTable; + nNameTable.append( '\0' );//add a null byte for the root name + + quint32 cnt = qFromBigEndian( fst[ 0 ].FileLength );//the number of entries + 1 for the new one + + //make the new root entry + nfst[ 0 ] = fst[ 0 ]; + nfst[ 0 ].FileLength = qFromBigEndian( cnt - numDeletedEntries ); + + QList movedDirs;//keep a list of the directories that are shifted back so their children can be shifted too + for( quint32 i = 1; i < cnt; i++ ) + { + if( i >= (quint32)entryToDelete && i < (quint32)( entryToDelete + numDeletedEntries ) )//skip deleted entries + continue; + + //copy old entries and adjust as needed + quint8 adj = i < (quint32)entryToDelete ? 0 : numDeletedEntries; + quint32 ni = i - adj; + + //qDebug() << "keeping" << FstName( i ) << "in the new archive ( moved from" << hex << i << "to" << hex << ni << ")"; + //if( parents.contains( i ) ) + //qDebug() << "\tthis is a parent of the deleted item"; + + FEntry *e = &fst[ i ];//old entry + FEntry *ne = &nfst[ ni ];//new entry + + nfst[ ni ].NameOffset = swap24( nNameTable.size() );//offset to the new entry's name in the string table + + //add the name to the new name table + nNameTable.append( FstName( e ).toLatin1() ); + nNameTable.append( '\0' ); + + + if( e->Type )//directory + { + ne->Type = 1; + if( !adj )//directories before the new entry + { + ne->ParentOffset = e->ParentOffset; + ne->NextOffset = qFromBigEndian( qFromBigEndian( e->NextOffset ) \ + - ( parents.contains( i ) ? numDeletedEntries : 0 ) ); + } + else//directories after the new entry + { + ne->ParentOffset = qFromBigEndian( qFromBigEndian( e->ParentOffset ) \ + - ( movedDirs.contains( qFromBigEndian( e->ParentOffset ) ) ? numDeletedEntries : 0 ) ); + ne->NextOffset = qFromBigEndian( qFromBigEndian( e->NextOffset ) - numDeletedEntries ); + + movedDirs << i; + + //qDebug() << "e.parent:" << hex << qFromBigEndian( e->ParentOffset ) << "movedDirs:" << movedDirs; + //hexdump( (const void*)ne, sizeof( FEntry) ); + } + } + else//file + { + ne->Type = 0; + ne->FileOffset = \ + qFromBigEndian( (quint32)( 0x20 + RU( U8_HEADER_ALIGNMENT, nFstSize ) + nPayload.size() ) ); + ne->FileLength = e->FileLength; + nPayload.append( data.mid( qFromBigEndian( e->FileOffset ), qFromBigEndian( e->FileLength ) ) ); + int padding = RU( 0x20, nPayload.size() ) - nPayload.size();//pad to 0x20 bytes between files + if( padding ) + { + nPayload.append( QByteArray( padding, '\0' ) ); + } + //qDebug() << "writing fileOffset of" << hex << ni << hex << (quint32)( 0x20 + RU( U8_HEADER_ALIGNMENT, nFstSize ) + nPayload.size() ); + } + //hexdump( (const void*)ne, sizeof( FEntry) ); + + } + //hexdump( nFstData ); + + //now put all the parts together to make a new U8 archive + nFstData.append( nNameTable ); + + data = QByteArray( 0x20, '\0' ); + QBuffer buf( &data ); + if( !buf.open( QBuffer::WriteOnly ) ) + { + qWarning() << "U8::AddEntry -> Can't create buffer"; + return -1; + } + buf.putChar( 'U' ); + buf.putChar( '\xAA' ); + buf.putChar( '8' ); + buf.putChar( '\x2D' ); + + rootnode_offset = 0x00000020; + quint32 t = qFromBigEndian( rootnode_offset ); + buf.write( (const char*)&t, 4 ); + + fstSize = nFstData.size(); + t = qFromBigEndian( fstSize ); + buf.write( (const char*)&t, 4 ); + + data_offset = RU( U8_HEADER_ALIGNMENT, 0x20 + fstSize ); + t = qFromBigEndian( data_offset ); + buf.write( (const char*)&t, 4 ); + buf.close(); + + data.append( nFstData );//add the fst + int padding = data_offset - data.size();//pad to 'data_offset' after the fst + if( padding ) + { + data.append( QByteArray( padding, '\0' ) ); + } + data.append( nPayload );//add the actual file data + + padding = RU( 0x20, data.size() ) - data.size();//pad the entire thing to 0x20 bytes TOTO: should probably already be done, and this step is not really necessary + if( padding ) + { + data.append( QByteArray( padding, '\0' ) ); + } + + //make sure the pirvate variables are correct + fst = (FEntry*)( data.data() + rootnode_offset ); + NameOff = 0x0C * qFromBigEndian( fst[ 0 ].FileLength ); + + CreateEntryList(); + + //hexdump( data ); + //qDebug() << "dataSize after removal:" << hex << data.size(); + + return true; + +} + +int U8::AddEntry( const QString &path, int type, const QByteArray &newData ) +{ + //qDebug() << "U8::AddEntry(" << path << "," << type << "," << hex << newData.size() << ")"; + //make sure there is actually data to manipulate + if( !ok && !CreateEmptyData() ) + { + return -1; + } + //check if this is a path to a file in a nested archive + QMap::iterator i = nestedU8s.begin(); + while( i != nestedU8s.constEnd() ) + { + if( path.startsWith( i.key() ) && path != i.key() ) + { + QString subPath = path; + //make a copy of the string, as "i" will become invalid on replace entry, and create a crash that is a bitch to track down + QString thisPath = i.key(); + U8 chVal = i.value(); + //qDebug() << "adding entry" << subPath << "in child U8" << thisPath; + subPath.remove( 0, thisPath.size() + 1 );//remove the path of the archive itself + the slash + bool ret = chVal.AddEntry( subPath, type, newData ); + if( !ret ) + { + qWarning() << "U8::AddEntry -> error replacing data in child U8 archive"; + return false; + } + + if( !ReplaceEntry( thisPath, chVal.GetData() ) )//insert the new nested archive in this one + return -1; + + //qDebug() << "done replacing the child entry in this archive, finding the index to return (" << thisPath << ")"; + return FindEntry( thisPath );//just return the index of the nested archive + } + i++; + } + + //make sure this path doesnt already exist + if( FindEntry( path ) != -1 ) + { + qWarning() << "U8::AddEntry" << path << "already exists in the archive"; + return -1; + } + //find the parent for this new entry + QString parentPath = path; + while( parentPath.startsWith( "/") )//remove leading "/"s + parentPath.remove( 0, 1 ); + while( parentPath.endsWith( "/") )//remove trailing "/"s + parentPath.resize( parentPath.size() - 1 ); + + int parent = 0; + int slash = parentPath.lastIndexOf( "/" ); + if( slash != -1 ) + { + //parentPath.resize( ( parentPath.size() - slash ) - 2 ); + parentPath.chop( ( parentPath.size() - slash ) ); + parent = FindEntry( parentPath ); + if( parent == -1 )//need to create subfolders for this fucker + { + parent = AddEntry( parentPath, 1 ); + if( parent == -1 ) + { + qWarning() << "U8::AddEntry -> could not create subfolders for" << path; + return -1; + } + } + if( !fst[ parent ].Type )//parent maps to a file, not folder + { + qWarning() << "U8::AddEntry -> can't add child entry to a file"; + return -1; + } + } + //qDebug() << "adding entry to" << FstName( parent ); + + //find the index to insert the new path + int newEntry = qFromBigEndian( fst[ parent ].NextOffset ); + + //create a list of all the directory indexes that my have their size changed to hold the new entry + QList parents; + while( parentPath.contains( "/" ) ) + { + parents << FindEntry( parentPath ); + int slash = parentPath.lastIndexOf( "/" ); + parentPath.chop( ( parentPath.size() - slash ) ); + } + parents << FindEntry( parentPath ); + //qDebug() << "parents" << parents; + + //create the new data + + //the new fst + QString name = path;//get the actual name of this entry + int sl = name.lastIndexOf( "/" ); + if( sl != -1 ) + { + name.remove( 0, sl + 1 ); + } + + quint32 nFstSize = fstSize + 0x0C + name.size() + 1;//old fst size + 0xc bytes for the new entry + room for the new name in the string table + QByteArray nFstData( ( qFromBigEndian( fst[ 0 ].FileLength ) + 1 ) * 0xc, '\0' ); + FEntry *nfst = (FEntry*)( nFstData.data() ); + + //the new data payload ( all the files ) + QByteArray nPayload; + QByteArray nNameTable; + nNameTable.append( '\0' );//add a null byte for the root name + + quint32 cnt = qFromBigEndian( fst[ 0 ].FileLength ) + 1;//the number of entries + 1 for the new one + + //make the new root entry + nfst[ 0 ] = fst[ 0 ]; + nfst[ 0 ].FileLength = qFromBigEndian( cnt ); + + QList movedDirs;//keep a list of the directories that are shifted back so their children can be shifted too + for( quint32 i = 1; i < cnt; i++ ) + { + nfst[ i ].NameOffset = swap24( nNameTable.size() );//offset to the new entry's name in the string table + if( i == (quint32)newEntry )//add the new entry + { + FEntry *ne = &nfst[ i ]; + if( type ) + { + ne->Type = 1; + ne->ParentOffset = qFromBigEndian( parent ); + ne->NextOffset = qFromBigEndian( i + 1 ); + } + else + { + ne->Type = 0; + ne->FileLength = qFromBigEndian( (quint32)newData.size() ); + ne->FileOffset =\ + qFromBigEndian( (quint32)( 0x20 + RU( U8_HEADER_ALIGNMENT, nFstSize ) + nPayload.size() ) ); + nPayload.append( newData ); + int padding = RU( 0x20, nPayload.size() ) - nPayload.size();//pad to 0x20 bytes between files + if( padding ) + { + nPayload.append( QByteArray( padding, '\0' ) ); + } + } + nNameTable.append( name.toLatin1() );//add this entry's name to the table + nNameTable.append( '\0' ); + continue; + } + + //copy old entries and adjust as needed + quint8 adj = i < (quint32)newEntry ? 0 : 1; + quint32 ni = i - adj; + + FEntry *e = &fst[ ni ];//old entry + FEntry *ne = &nfst[ i ];//new entry + + //add the name to the new name table + nNameTable.append( FstName( e ).toLatin1() ); + nNameTable.append( '\0' ); + if( e->Type ) + { + ne->Type = 1; + if( !adj )//directories before the new entry + { + ne->ParentOffset = e->ParentOffset; + ne->NextOffset = qFromBigEndian( qFromBigEndian( e->NextOffset ) \ + + ( parents.contains( i ) ? 1 : 0 ) ); + } + else//directories after the new entry + { + movedDirs << ni; + ne->ParentOffset = qFromBigEndian( qFromBigEndian( e->ParentOffset ) \ + + ( movedDirs.contains( qFromBigEndian( e->ParentOffset ) ) ? 1 : 0 ) ); + ne->NextOffset = qFromBigEndian( qFromBigEndian( e->NextOffset ) + 1 ); + } + } + else//all file entries + { + ne->Type = 0; + ne->FileOffset = \ + qFromBigEndian( (quint32)( 0x20 + RU( U8_HEADER_ALIGNMENT, nFstSize ) + nPayload.size() ) ); + ne->FileLength = e->FileLength; + nPayload.append( data.mid( qFromBigEndian( e->FileOffset ), qFromBigEndian( e->FileLength ) ) ); + int padding = RU( 0x20, nPayload.size() ) - nPayload.size();//pad to 0x20 bytes between files + if( padding ) + { + nPayload.append( QByteArray( padding, '\0' ) ); + } + } + + } + //now put all the parts together to make a new U8 archive + nFstData.append( nNameTable ); + + data = QByteArray( 0x20, '\0' ); + QBuffer buf( &data ); + if( !buf.open( QBuffer::WriteOnly ) ) + { + qWarning() << "U8::AddEntry -> Can't create buffer"; + return -1; + } + buf.putChar( 'U' ); + buf.putChar( '\xAA' ); + buf.putChar( '8' ); + buf.putChar( '\x2D' ); + + rootnode_offset = 0x00000020; + quint32 t = qFromBigEndian( rootnode_offset ); + buf.write( (const char*)&t, 4 ); + + fstSize = nFstData.size(); + t = qFromBigEndian( fstSize ); + buf.write( (const char*)&t, 4 ); + + data_offset = RU( U8_HEADER_ALIGNMENT, 0x20 + fstSize ); + t = qFromBigEndian( data_offset ); + buf.write( (const char*)&t, 4 ); + buf.close(); + + data.append( nFstData );//add the fst + int padding = data_offset - data.size();//pad to 'data_offset' after the fst + if( padding ) + { + data.append( QByteArray( padding, '\0' ) ); + } + data.append( nPayload );//add the actual file data + + padding = RU( 0x20, data.size() ) - data.size();//pad the entire thing to 0x20 bytes TOTO: should probably already be done, and this step is not really necessary + if( padding ) + { + data.append( QByteArray( padding, '\0' ) ); + } + + //make sure the pirvate variables are correct + fst = (FEntry*)( data.data() + rootnode_offset ); + NameOff = 0x0C * qFromBigEndian( fst[ 0 ].FileLength ); + //qDebug() << "done adding the entry"; + + CreateEntryList(); + + //hexdump( data ); + return newEntry; +} + +void U8::CreateEntryList() +{ + //qDebug() << "U8::CreateEntryList"; + paths.clear(); + nestedU8s.clear(); + fst = (FEntry*)( data.data() + rootnode_offset ); + quint32 cnt = qFromBigEndian( fst[ 0 ].FileLength ); + if( ( cnt * 0x0d ) + rootnode_offset > (quint32)data.size() ) + { + qWarning() << "U8::CreateEntryList -> invalid archive"; + ok = false; + data.clear(); + paths.clear(); + fst = NULL; + return; + } + NameOff = cnt * 0x0C; + bool fixWarn = false;//ony print the warning 1 time + + //qDebug() << "cnt" << hex << cnt; + for( quint32 i = 1; i < cnt; ++i )//this is not the most effecient way to do things, but it seems to work ok and these archives are small enough that it happens fast anyways + { + //start at the beginning of the fst and enter every directory whos "nextoffset" is greater than this index, + //adding the names along the way and hopefully finding "i" + QString path; + quint32 current = 1; + quint32 folder = 0; + while( 1 ) + { + if( !current ) + { + qWarning() << "U8::CreateEntryList -> error parsing the archive"; + break; + } + if( current == i ) + { + path += FstName( i ); + if( fst[ current ].Type ) + { + //sanity check: make sure the parent offset is correct + if( folder != qFromBigEndian( fst[ current ].ParentOffset ) ) + { + qWarning() << "U8::CreateEntryList -> error parsing the archive - recursion mismatch in" + << path << "expected:" << hex << folder << "got:" << hex << qFromBigEndian( fst[ current ].ParentOffset )\ + << "(" << FstName( qFromBigEndian( fst[ current ].ParentOffset ) ) << ")"; + + //some tools use "recursion" instead of "parent offset". + //it just coincidentally works for archives such as the opening.bnr and .app since they dont have many folders to mess up + if( qFromBigEndian( fst[ current ].ParentOffset ) == (quint32)path.count( "/" ) ) + { + if( !fixWarn ) + { + qWarning() << "This archive was made by a broken tool such as U8Mii. I'm trying to fix it, but I can't make any promises"; + fixWarn = true; + } + fst[ current ].ParentOffset = qFromBigEndian( folder ); + } + } + path += "/"; + paths << path; + } + else + { + //add this file to the list of entries + paths << path; + + //check if this file is really a U8 archive, and add it to the list of children + QByteArray chData = data.mid( qFromBigEndian( fst[ current ].FileOffset ), qFromBigEndian( fst[ current ].FileLength ) ); + if( IsU8( chData ) ) + { + U8 child( chData ); + if( child.IsOK() ) + { + nestedU8s.insert( path, child ); + foreach( QString chPath, child.Entries() ) + { + QString newPath = path + "/" + chPath; + paths << newPath; + } + } + } + } + break; + } + if( fst[ current ].Type && i < qFromBigEndian( fst[ current ].NextOffset ) ) + { + path += FstName( current ) + "/"; + folder = current; + current++; + continue; + + } + current = NextEntryInFolder( current, folder ); + } + //paths << path; + } + //qDebug() << "done"; + //qDebug() << "paths" << paths; +} + +U8::U8( const QByteArray &ba ) +{ + //qDebug() << "U8::U8 dataSize:" << hex << ba.size(); + Load( ba ); +} + +void U8::Load( const QByteArray &ba ) +{ + ok = false; + isLz77 = false; + headerType = U8_Hdr_none; + paths.clear(); + imetNames.clear(); + /*if( ba.size() < 0x80 ) + { + //qWarning() << "U8::Load:" << hex << ba.size(); + //qWarning() << "U8::Load -> where is the rest of the data?"; + return; + }*/ + data = ba; + if( IsAshCompressed( data ) )//decrypt ASH0 files + data = DecryptAsh( data ); + + quint32 tmp; + int off = LZ77::GetLz77Offset( data ); + int off2 = GetU8Offset( data ); + if( off != -1 && ( off2 == -1 || ( off2 != -1 && off < off2 ) ) ) + { + isLz77 = true; + data = LZ77::Decompress( data ); + off2 = GetU8Offset( data ); + } + + if( off2 == -1 ) + { + qDebug() << "This isnt a U8"; + return; + } + + data = data.mid( off2 ); + + QBuffer buf( &data ); + if( !buf.open( QBuffer::ReadOnly ) ) + { + qWarning() << "Can't create buffer"; + return; + } + + buf.seek( 4 ); + buf.read( ( char*)&tmp, 4 ); + rootnode_offset = qFromBigEndian( tmp ); + if( rootnode_offset != 0x20 ) + { + qWarning() << "rootnodeOffset" << hex << rootnode_offset; + qWarning() << hex << data.size(); + hexdump( data ); + } + + buf.read( ( char*)&tmp, 4 ); + fstSize = qFromBigEndian( tmp ); + + buf.read( ( char*)&tmp, 4 ); + data_offset = qFromBigEndian( tmp ); + + buf.close(); + + CreateEntryList(); + + + ReadHeader( ba ); + ok = true; + //qDebug() << "loaded" << paths; +} + +bool U8::IsOK() +{ + return ok; +} + +QString U8::FstName( const FEntry *entry ) +{ + //qDebug() << "U8::FstName"; + if( entry == &fst[ 0 ] ) + return QString(); + + int nameStart = swap24( entry->NameOffset ) + NameOff + rootnode_offset; + return data.mid( nameStart, nameStart + 0x100 ); +} + +QString U8::FstName( quint32 i ) +{ + if( i > fst[ 0 ].FileLength ) + { + qWarning() << "U8::FstName -> index is out of range"; + return QString(); + } + return FstName( &fst[ i ] ); +} + +quint32 U8::NextEntryInFolder( quint32 current, quint32 directory ) +{ + //qDebug() << "U8::NextEntryInFolder(" << hex << current << "," << hex << directory << ")"; + quint32 next = ( fst[ current ].Type ? qFromBigEndian( fst[ current ].FileLength ) : current + 1 ); + //qDebug() << "next" << next << "len" << hex << qFromBigEndian( fst[ directory ].FileLength ); + if( next < qFromBigEndian( fst[ directory ].FileLength ) ) + return next; + + return 0; +} + +int U8::FindEntry( const QString &str, int d ) +{ + //qDebug() << "U8::FindEntry(" << str << "," << d << ")"; + if( str.isEmpty() ) + return 0; + + if( str.startsWith( "/" ) ) + { + QString r = str; + r.remove( 0, 1 ); + return FindEntry( r, d ); + } + + if( !fst[ d ].Type ) + { + qDebug() << "ERROR!!" << FstName( &fst[ d ] ) << "is not a directory"; + return -1; + } + + int next = d + 1; + FEntry *entry = &fst[ next ]; + + while( next ) + { + QString item; + int sl = str.indexOf( "/" ); + if( sl > -1 ) + item = str.left( sl ); + else + item = str; + + if( FstName( entry ) == item ) + { + if( item == str || item + "/" == str )//this is the item we are looking for + { + return next; + } + //this item is a parent folder of one we are looking for + return FindEntry( str.right( ( str.size() - sl ) - 1 ), next ); + } + //find the next entry in this folder + next = NextEntryInFolder( next, d ); + entry = &fst[ next ]; + } + + //no entry with the given path was found + return -1; +} + +const QByteArray U8::GetData( const QString &str, bool onlyPayload ) +{ + //qDebug() << "U8::GetData(" << str << ")"; + if( str.isEmpty() )//give the data for this whole archive + { + if( onlyPayload ) + return data; + + //qDebug() << "U8::GetData -> returning all the data for this archive. headerType:" << headerType; + + QByteArray ret = data; + + switch( headerType ) + { + case U8_Hdr_IMET_app: + case U8_Hdr_IMET_bnr: + ret = AddIMET( headerType );//data is lz77 compressed in this function if it needs to be + break; + case U8_Hdr_IMD5: + { + if( isLz77 ) + ret = LZ77::Compress( ret ); + + ret = AddIMD5( ret ); + + //hexdump( ret, 0, 0x40 ); + } + break; + default: + break; + } + return ret; + } + //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().GetData( subPath, onlyPayload ); + } + i++; + } + int index = FindEntry( str ); + if( index < 0 ) + { + qWarning() << "U8::GetData" << str << "was not found in the archive"; + return QByteArray(); + } + if( fst[ index ].Type ) + { + qWarning() << "U8::GetData" << str << "is a directory"; + return QByteArray(); + } + QByteArray ret = data.mid( qFromBigEndian( fst[ index ].FileOffset ), qFromBigEndian( fst[ index ].FileLength ) ); + //hexdump( ret, 0, 0x40 ); + if( onlyPayload ) + { + if( LZ77::GetLz77Offset( ret ) != -1 ) + ret = LZ77::Decompress( ret ); + + else if( IsAshCompressed( ret ) ) + ret = DecryptAsh( ret ); + } + return ret; +} + +quint32 U8::GetSize( const QString &str ) +{ + if( str.isEmpty() ) + { + return data.size(); + } + int index = FindEntry( str ); + if( index < 0 ) + { + qWarning() << "U8::GetSize" << str << "was not found in the archive"; + return 0; + } + if( fst[ index ].Type ) + { + qWarning() << "U8::GetSize" << str << "is a directory"; + return 0; + } + return qFromBigEndian( fst[ index ].FileLength ); +} + +const QStringList U8::Entries() +{ + return paths; +} + +int U8::GetU8Offset( const QByteArray &ba ) +{ + QByteArray start = ba.left( 5000 ); + return start.indexOf( "U\xAA\x38\x2d" ); +} + +bool U8::IsU8( const QByteArray &ba ) +{ + QByteArray data = ba; + if( IsAshCompressed( data ) )//decrypt ASH0 files + data = DecryptAsh( data ); + + int off = LZ77::GetLz77Offset( data );//decrypt LZ77 + if( off != -1 ) + data = LZ77::Decompress( data ); + + QByteArray start = data.left( 5000 ); + return start.indexOf( "U\xAA\x38\x2d" ) != -1; +} + +#define IMET_MAX_NAME_LEN 0x2a +typedef struct +{ + quint32 sig; // "IMET" + quint32 unk1; + quint32 unk2; + quint32 filesizes[ 3 ]; + quint32 unk3; + quint16 names[ 10 ][ IMET_MAX_NAME_LEN ]; + quint8 zeroes2[ 0x24c ]; + quint8 md5[ 0x10 ]; +} IMET; + +void U8::ReadHeader( const QByteArray &ba ) +{ + //qDebug() << "U8::ReadHeader(" << hex << ba.size() << ")"; + //hexdump( ba ); + headerType = U8_Hdr_none; + imetNames.clear(); + if( ba.startsWith( "IMD5" ) )//dont bother to read any more data + { + //qDebug() << "IMD5 header"; + headerType = U8_Hdr_IMD5; + return; + } + + QByteArray start = ba.left( sizeof( IMET ) + 0x80 ); + QBuffer buf( &start ); + if( !buf.open( QBuffer::ReadOnly ) ) + { + qWarning() << "U8::ReadHeader Can't create buffer"; + return; + } + int off = start.indexOf( "IMET" ); + //qDebug() << "imet offset" << hex << off << "u8 offset" << hex << GetU8Offset( ba ); + if( off == 0x40 || off == 0x80 )//read imet header + { + if( off > GetU8Offset( ba ) )//in case somebody wants to put a IMET archive inside another U8 for whatever reason + return; + + if( off == 0x40 ) + headerType = U8_Hdr_IMET_bnr; + else + headerType = U8_Hdr_IMET_app; + + buf.seek( off ); + IMET imet; + buf.read( (char*)&imet, sizeof( IMET ) ); + buf.close(); + + for( int h = 0; h < 10; h ++ ) + { + QString name; + for( int i = 0; i < IMET_MAX_NAME_LEN && imet.names[ h ][ i ] != 0; i++ )//no need to switch endian, 0x0000 is a palendrome + name += QChar( qFromBigEndian( imet.names[ h ][ i ] ) ); + + imetNames << name; + } + //done + return; + } +} +QByteArray U8::AddIMD5( QByteArray ba ) +{ + quint32 size = ba.size(); + + MD5 hash; + hash.update( ba.data(), size ); + hash.finalize(); + + size = qFromBigEndian( size ); + + QByteArray imd5; + QBuffer buf( &imd5 ); + if( !buf.open( QBuffer::WriteOnly ) ) + { + qWarning() << "U8::AddIMD5 Can't create buffer"; + return QByteArray(); + } + buf.putChar( 'I' ); + buf.putChar( 'M' ); + buf.putChar( 'D' ); + buf.putChar( '5' ); + + buf.write( (const char*)&size, 4 ); + buf.seek( 0x10 ); + + buf.write( (const char*)hash.hexdigestChar(), 16 ); + buf.close(); + //qDebug() << hash.hexdigest().c_str(); + //hexdump( (void*)imd5.data(), 0x20 ); + + ba.prepend( imd5 ); + + return ba; +} + +const QByteArray U8::AddIMD5() +{ + QByteArray ba = data; + ba = AddIMD5( ba ); + return ba; +} + +QByteArray U8::GetIMET( const QStringList &names, int paddingType, quint32 iconSize, quint32 bannerSize, quint32 soundSize ) +{ + QByteArray ret = QByteArray( sizeof( IMET ) + 0x40, '\0' ); + QBuffer buf( &ret ); + if( !buf.open( QBuffer::WriteOnly ) ) + { + qWarning() << "U8::GetIMET Can't create buffer"; + return QByteArray(); + } + buf.seek( 0 + 0x40 ); + buf.putChar( 'I' ); + buf.putChar( 'M' ); + buf.putChar( 'E' ); + buf.putChar( 'T' ); + buf.seek( 6 + 0x40 ); + buf.putChar( '\x6' ); + buf.seek( 11 + 0x40 ); + buf.putChar( '\x3' ); + + quint32 tmp = qFromBigEndian( iconSize ); + buf.write( (char*)&tmp, 4 ); + tmp = qFromBigEndian( bannerSize ); + buf.write( (char*)&tmp, 4 ); + tmp = qFromBigEndian( soundSize ); + buf.write( (char*)&tmp, 4 ); + + int nameOffset = 0x1c + 0x40; + int numNames = names.size(); + for( int i = 0; i < numNames; i++ ) + { + QString name = names.at( i ); + int nameLen = name.size(); + buf.seek( nameOffset + ( IMET_MAX_NAME_LEN * i * 2 ) ); + for( int j = 0; j < nameLen; j++ ) + { + quint16 letter = qFromBigEndian( name.at( j ).unicode() ); + buf.write( ( const char*)&letter, 2 ); + } + } + + MD5 hash; + hash.update( ret.data(), sizeof( IMET ) + 0x40 ); + hash.finalize(); + + buf.seek( 0x5f0 ); + buf.write( (const char*)hash.hexdigestChar(), 16 ); + buf.close(); + + switch( paddingType ) + { + case U8_Hdr_IMET_app: + ret.prepend( QByteArray( 0x40, '\0' ) ); + break; + case U8_Hdr_IMET_bnr: + break; + default: + ret.remove( 0, 0x40 ); + break; + } + return ret; +} + +const QStringList U8::IMETNames() +{ + return imetNames; +} + +void U8::SetImetNames( const QStringList &names ) +{ + imetNames.clear(); + imetNames = names; +} + +const QByteArray U8::AddIMET( int paddingType ) +{ + QByteArray ret = GetData( "meta/icon.bin" ); + + quint32 iconSize; + quint32 bannerSize; + quint32 soundSize; + + //if these are lz77 compressed, write the uncompressed size to the header ?bannerbomb? + //otherwise write the size - 0x20 ( imd5 header size ) + + if( LZ77::GetLz77Offset( ret ) != -1 ) + iconSize = LZ77::GetDecompressedSize( ret ); + else + iconSize = ret.size() - 0x20; + + ret = GetData( "meta/banner.bin" ); + if( LZ77::GetLz77Offset( ret ) != -1 ) + bannerSize = LZ77::GetDecompressedSize( ret ); + else + bannerSize = ret.size() - 0x20; + + ret = GetData( "meta/sound.bin" ); + if( LZ77::GetLz77Offset( ret ) != -1 ) + soundSize = LZ77::GetDecompressedSize( ret ); + else + 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; + + return ret; +} + +int U8::GetHeaderType() +{ + return headerType; +} diff --git a/WiiQt/u8.h b/WiiQt/u8.h new file mode 100644 index 0000000..7097800 --- /dev/null +++ b/WiiQt/u8.h @@ -0,0 +1,184 @@ +#ifndef U8_H +#define U8_H + +#include "includes.h" + +/*order of the names in the imet header +japanese +english +german +french +spanish +italian +dutch +simp_chinese +trad_chinese +korean +*/ + +struct FEntry +{ + union + { + struct + { + unsigned int Type :8; + unsigned int NameOffset :24; + }; + unsigned int TypeName; + }; + union + { + struct // File Entry + { + unsigned int FileOffset; + unsigned int FileLength; + }; + struct // Dir Entry + { + unsigned int ParentOffset; + unsigned int NextOffset; + }; + unsigned int entry[ 2 ]; + }; +}; + +enum +{ + U8_Hdr_none = 0, + U8_Hdr_IMET_app,//basically the same except the app has 0x40 bytes of padding more than the bnr + U8_Hdr_IMET_bnr, + U8_Hdr_IMD5 +}; + +//class to handle U8 archives found in wii games & system files +// handles different header types, lz77 & ash +//! basic usage is to read an existing U8 archive into a qbytearray and pass it to this class through the second constructor or Load() +//! you can also create a brand-spankin-new U8 archive by using the first constructor +//! check IsOk() to see if that it was loaded ok +//! then call Entries() to get a list of everything that is in the archive +//! access the data in that archive with AddEntry(), RemoveEntry(), ReplaceEntry(), RenameEntry(), and GetData() +class U8 +{ +public: + //creates a U8 object + //if initialize is true, it will call CreateEmptyData() in this U8 + //you can also set the header type & imet names, though they dont affect the actual data in the archive + U8( bool initialize = false, int type = U8_Hdr_none, const QStringList &names = QStringList() ); + U8( const QByteArray &ba ); + + //check if the u8 constructor finished successfully + bool IsOK(); + + void Load( const QByteArray &ba ); + + //set the variables and create an fst with only a root node + bool CreateEmptyData(); + + //adds an entry to this U8 with the given path, and type, and if it is a file entry, the given data + //returns the index of the created entry or -1 on error + int AddEntry( const QString &path, int type, const QByteArray &ba = QByteArray() ); + + //removes an entry, and all its children, from the archive + bool RemoveEntry( const QString &path ); + + //replace a file in the archive with different data ( only supports files, no directories ) + //if autoCompress is true, it will check if the data to be replaced is ASH/LZ77 compressed and make sure the new data is LZ77'd + bool ReplaceEntry( const QString &path, const QByteArray &ba = QByteArray(), bool autoCompress = true ); + + //remane an entry. this expects a full path of the entry to be named, and newName is only the replacement name ( not the full path ) + bool RenameEntry( const QString &path, const QString &newName ); + + + //returns the index of an item in the archive FST, or -1 the item is not present + //it accepts names like "/arc/anim/". parent is the directory to start looking in + int FindEntry( const QString &str, int parent = 0 ); + quint32 NextEntryInFolder( quint32 current, quint32 directory ); + QString FstName( const FEntry *entry ); + QString FstName( quint32 i ); + + //gets the data for a path inside the archive + //if no path is given, returns the data for the whole archive, starting at the U8 tag + //if onlyPayload is true, it will return the data unLZ77'd ( and without its IMET/IMD5 header for nested U8 archives ) + const QByteArray GetData( const QString &str = QString(), bool onlyPayload = false ); + + //get the size of an entry in the archive + //if no path is given, returns the size for the whole archive, starting at the U8 tag + quint32 GetSize( const QString &str = QString() ); + + //returns a list of all the entries in the u8 archive + //directories end with "/" + const QStringList Entries(); + + //check the first 5000 bytes of a file for the u8 tag. + //if necessary, un-LZ77 the data before checking + //this doesnt check anything else in the archive + static bool IsU8( const QByteArray &ba ); + + //returns the location of the U8 tag in the first 5000 bytes of the given data + static int GetU8Offset( const QByteArray &ba ); + + //adds the IMD5 header to the given bytearray and returns it ( original data is modified ) + static QByteArray AddIMD5( QByteArray ba ); + + //returns the U8 archive data with an IMD5 header added + //the actual data is not changed + const QByteArray AddIMD5(); + + //adds the IMET header to the given bytearray and returns it ( original data is modified ) + static QByteArray AddIMET( QByteArray ba, const QStringList &names ); + + //get an IMET header using the given names and sizes + //padding type will add 0, 0x40, or 0x80 bytes of 0s to the beginning + static QByteArray GetIMET( const QStringList &names, int paddingType = U8_Hdr_IMET_app, quint32 iconSize = 0, quint32 bannerSize = 0, quint32 soundSize = 0 ); + + //returns the U8 archive data with an IMET header added + //the actual archive data is not changed + const QByteArray AddIMET( int paddingType = U8_Hdr_IMET_app ); + + //ruterns a list of the names as read from the imet header + const QStringList IMETNames(); + + //set the imet names + //the data in the archive is not changed, but calling AddIMET() on this banner will use the new names + void SetImetNames( const QStringList &names ); + + //void SetHeaderType( int type ); + int GetHeaderType(); + + + + //reads from the fst and makes the list of paths + void CreateEntryList(); + + +private: + QByteArray data;//data starting at the U8 tag + QStringList paths; + bool ok; + + //if this archive as a whole is lz77 compressed + bool isLz77; + + QStringList imetNames; + int headerType; + void ReadHeader( const QByteArray &ba ); + + + + //just a pointer to the fst data. + //this pointer needs to be updated any time data is changed + FEntry* fst; + + //stored in host endian ! + quint32 fstSize; + quint32 rootnode_offset; + quint32 data_offset; + quint32 NameOff; + + //a list of the nested u8 archives + QMapnestedU8s; +}; + + +#endif // U8_H diff --git a/nand_dump/uidmap.cpp b/WiiQt/uidmap.cpp similarity index 100% rename from nand_dump/uidmap.cpp rename to WiiQt/uidmap.cpp diff --git a/nand_dump/uidmap.h b/WiiQt/uidmap.h similarity index 77% rename from nand_dump/uidmap.h rename to WiiQt/uidmap.h index b0fe8c9..a688279 100644 --- a/nand_dump/uidmap.h +++ b/WiiQt/uidmap.h @@ -3,6 +3,8 @@ #include "includes.h" +//class for handling the uid.sys in a virtual nand +// class UIDmap { public: @@ -18,9 +20,11 @@ public: quint32 GetUid( quint64 tid, bool autoCreate = true ); //creates a new uid.sys with the system menu entry. - //if addFactorySetupDiscs is true, it will add some entries for the setup discs used in the wii factory ( serve no purpose other than to just exist ) + //if addFactorySetupDiscs is true, it will add some entries for the setup discs used in the wii factory + // ( serve no purpose other than to just exist ) void CreateNew( bool addFactorySetupDiscs = false ); + //get th entire uid.sys data back in a state ready for writing to a nand const QByteArray Data(){ return data; } private: diff --git a/nand_dump/wad.cpp b/WiiQt/wad.cpp similarity index 96% rename from nand_dump/wad.cpp rename to WiiQt/wad.cpp index 61ef38f..fa53750 100644 --- a/nand_dump/wad.cpp +++ b/WiiQt/wad.cpp @@ -472,14 +472,39 @@ QByteArray Wad::FromDirectory( QDir dir ) QList datas = QList()<< tmdP << tikP; quint16 cnt = t.Count(); + + bool tmdChanged = false; for( quint16 i = 0; i < cnt; i++ ) { QByteArray appD = ReadFile( dir.absoluteFilePath( t.Cid( i ) + ".app" ) ); if( appD.isEmpty() ) return QByteArray(); + if( (quint32)appD.size() != t.Size( i ) ) + { + t.SetSize( i, appD.size() ); + tmdChanged = true; + } + QByteArray realHash = GetSha1( appD ); + if( t.Hash( i ) != realHash ) + { + t.SetHash( i, realHash ); + tmdChanged = true; + } datas << appD; } + //if something in the tmd changed, fakesign it and replace the data in our list with the new data + if( tmdChanged ) + { + if( !t.FakeSign() ) + { + qWarning() << "Error signing the wad"; + } + else + { + datas.replace( 0, t.Data() ); + } + } Wad wad( datas, false ); if( !wad.IsOk() ) return QByteArray(); diff --git a/nand_dump/wad.h b/WiiQt/wad.h similarity index 100% rename from nand_dump/wad.h rename to WiiQt/wad.h diff --git a/nandExtract/nandExtract.pro b/nandExtract/nandExtract.pro index c99d901..070b78c 100755 --- a/nandExtract/nandExtract.pro +++ b/nandExtract/nandExtract.pro @@ -5,12 +5,15 @@ TARGET = nandExtract TEMPLATE = app SOURCES += main.cpp \ nandwindow.cpp \ - nandbin.cpp \ - tools.cpp \ - aes.c + ../WiiQt/nandbin.cpp \ + ../WiiQt/tools.cpp \ + ../WiiQt/savebanner.cpp \ + ../WiiQt/aes.c \ + ../WiiQt/sha1.c + HEADERS += nandwindow.h \ - nandbin.h \ - includes.h \ - tools.h \ - aes.h + ../WiiQt/nandbin.h \ + ../WiiQt/tools.h \ + ../WiiQt/tools.h + FORMS += nandwindow.ui diff --git a/nandExtract/nandwindow.cpp b/nandExtract/nandwindow.cpp index 5a0dc2c..290e2ee 100755 --- a/nandExtract/nandwindow.cpp +++ b/nandExtract/nandwindow.cpp @@ -1,6 +1,5 @@ #include "nandwindow.h" #include "ui_nandwindow.h" -#include "tools.h" NandWindow::NandWindow(QWidget *parent) : QMainWindow(parent), @@ -13,9 +12,9 @@ NandWindow::NandWindow(QWidget *parent) : ui->treeWidget->header()->resizeSection( 0, fm.width( QString( 22, 'W' ) ) );//name ui->treeWidget->header()->resizeSection( 1, fm.width( "WWWWW" ) );//entry # ui->treeWidget->header()->resizeSection( 2, fm.width( "WWWWW" ) );//size - ui->treeWidget->header()->resizeSection( 3, fm.width( "WWWWWWWWWW" ) );//uid - ui->treeWidget->header()->resizeSection( 4, fm.width( "WWWWWWWWWW" ) );//gid - ui->treeWidget->header()->resizeSection( 5, fm.width( "WWWWWWWWWW" ) );//x3 + ui->treeWidget->header()->resizeSection( 3, fm.width( "WWWWWWWWW" ) );//uid + ui->treeWidget->header()->resizeSection( 4, fm.width( "WWWWWWWWW" ) );//gid + ui->treeWidget->header()->resizeSection( 5, fm.width( "WWWWWWWWW" ) );//x3 ui->treeWidget->header()->resizeSection( 6, fm.width( "WWWWW" ) );//mode ui->treeWidget->header()->resizeSection( 7, fm.width( "WWWWW" ) );//attr @@ -131,4 +130,6 @@ void NandWindow::on_actionOpen_Nand_triggered() //delete the made up root item delete tree; ui->statusBar->showMessage( "Loaded " + path, 5000 ); + + //nandBin.GetData( "/title/00000001/00000002/data/setting.txt" );//testing 1,2,1,2 } diff --git a/nandExtract/nandwindow.h b/nandExtract/nandwindow.h index 4cb1722..6a2509c 100755 --- a/nandExtract/nandwindow.h +++ b/nandExtract/nandwindow.h @@ -2,7 +2,7 @@ #define NANDWINDOW_H #include "includes.h" -#include "nandbin.h" +#include "../WiiQt/nandbin.h" namespace Ui { class NandWindow; diff --git a/nand_dump/aes.c b/nand_dump/aes.c deleted file mode 100644 index f986cb1..0000000 --- a/nand_dump/aes.c +++ /dev/null @@ -1,400 +0,0 @@ -/* Rijndael Block Cipher - aes.c - - Written by Mike Scott 21st April 1999 - mike@compapp.dcu.ie - - Permission for free direct or derivative use is granted subject - to compliance with any conditions that the originators of the - algorithm place on its exploitation. - -*/ -/* -#include -#include -#include */ - -#define u8 unsigned char /* 8 bits */ -#define u32 unsigned long /* 32 bits */ -#define u64 unsigned long long - -/* rotates x one bit to the left */ - -#define ROTL(x) (((x)>>7)|((x)<<1)) - -/* Rotates 32-bit word left by 1, 2 or 3 byte */ - -#define ROTL8(x) (((x)<<8)|((x)>>24)) -#define ROTL16(x) (((x)<<16)|((x)>>16)) -#define ROTL24(x) (((x)<<24)|((x)>>8)) - -/* Fixed Data */ - -static u8 InCo[4]={0xB,0xD,0x9,0xE}; /* Inverse Coefficients */ - -static u8 fbsub[256]; -static u8 rbsub[256]; -static u8 ptab[256],ltab[256]; -static u32 ftable[256]; -static u32 rtable[256]; -static u32 rco[30]; - -/* Parameter-dependent data */ - -int Nk,Nb,Nr; -u8 fi[24],ri[24]; -u32 fkey[120]; -u32 rkey[120]; - -static u32 pack(u8 *b) -{ /* pack bytes into a 32-bit Word */ - return ((u32)b[3]<<24)|((u32)b[2]<<16)|((u32)b[1]<<8)|(u32)b[0]; -} - -static void unpack(u32 a,u8 *b) -{ /* unpack bytes from a word */ - b[0]=(u8)a; - b[1]=(u8)(a>>8); - b[2]=(u8)(a>>16); - b[3]=(u8)(a>>24); -} - -static u8 xtime(u8 a) -{ - u8 b; - if (a&0x80) b=0x1B; - else b=0; - a<<=1; - a^=b; - return a; -} - -static u8 bmul(u8 x,u8 y) -{ /* x.y= AntiLog(Log(x) + Log(y)) */ - if (x && y) return ptab[(ltab[x]+ltab[y])%255]; - else return 0; -} - -static u32 SubByte(u32 a) -{ - u8 b[4]; - unpack(a,b); - b[0]=fbsub[b[0]]; - b[1]=fbsub[b[1]]; - b[2]=fbsub[b[2]]; - b[3]=fbsub[b[3]]; - return pack(b); -} - -static u8 product(u32 x,u32 y) -{ /* dot product of two 4-byte arrays */ - u8 xb[4],yb[4]; - unpack(x,xb); - unpack(y,yb); - return bmul(xb[0],yb[0])^bmul(xb[1],yb[1])^bmul(xb[2],yb[2])^bmul(xb[3],yb[3]); -} - -static u32 InvMixCol(u32 x) -{ /* matrix Multiplication */ - u32 y,m; - u8 b[4]; - - m=pack(InCo); - b[3]=product(m,x); - m=ROTL24(m); - b[2]=product(m,x); - m=ROTL24(m); - b[1]=product(m,x); - m=ROTL24(m); - b[0]=product(m,x); - y=pack(b); - return y; -} - -u8 ByteSub(u8 x) -{ - u8 y=ptab[255-ltab[x]]; /* multiplicative inverse */ - x=y; x=ROTL(x); - y^=x; x=ROTL(x); - y^=x; x=ROTL(x); - y^=x; x=ROTL(x); - y^=x; y^=0x63; - return y; -} - -void gentables(void) -{ /* generate tables */ - int i; - u8 y,b[4]; - - /* use 3 as primitive root to generate power and log tables */ - - ltab[0]=0; - ptab[0]=1; ltab[1]=0; - ptab[1]=3; ltab[3]=1; - for (i=2;i<256;i++) - { - ptab[i]=ptab[i-1]^xtime(ptab[i-1]); - ltab[ptab[i]]=i; - } - - /* affine transformation:- each bit is xored with itself shifted one bit */ - - fbsub[0]=0x63; - rbsub[0x63]=0; - for (i=1;i<256;i++) - { - y=ByteSub((u8)i); - fbsub[i]=y; rbsub[y]=i; - } - - for (i=0,y=1;i<30;i++) - { - rco[i]=y; - y=xtime(y); - } - - /* calculate forward and reverse tables */ - for (i=0;i<256;i++) - { - y=fbsub[i]; - b[3]=y^xtime(y); b[2]=y; - b[1]=y; b[0]=xtime(y); - ftable[i]=pack(b); - - y=rbsub[i]; - b[3]=bmul(InCo[0],y); b[2]=bmul(InCo[1],y); - b[1]=bmul(InCo[2],y); b[0]=bmul(InCo[3],y); - rtable[i]=pack(b); - } -} - -void gkey(int nb,int nk,u8 *key) -{ /* blocksize=32*nb bits. Key=32*nk bits */ - /* currently nb,bk = 4, 6 or 8 */ - /* key comes as 4*Nk bytes */ - /* Key Scheduler. Create expanded encryption key */ - int i,j,k,m,N; - int C1,C2,C3; - u32 CipherKey[8]; - - Nb=nb; Nk=nk; - - /* Nr is number of rounds */ - if (Nb>=Nk) Nr=6+Nb; - else Nr=6+Nk; - - C1=1; - if (Nb<8) { C2=2; C3=3; } - else { C2=3; C3=4; } - - /* pre-calculate forward and reverse increments */ - for (m=j=0;j>8)])^ - ROTL16(ftable[(u8)(x[fi[m+1]]>>16)])^ - ROTL24(ftable[(u8)(x[fi[m+2]]>>24)]); - } - t=x; x=y; y=t; /* swap pointers */ - } - -/* Last Round - unroll if possible */ - for (m=j=0;j>8)])^ - ROTL16((u32)fbsub[(u8)(x[fi[m+1]]>>16)])^ - ROTL24((u32)fbsub[(u8)(x[fi[m+2]]>>24)]); - } - for (i=j=0;i>8)])^ - ROTL16(rtable[(u8)(x[ri[m+1]]>>16)])^ - ROTL24(rtable[(u8)(x[ri[m+2]]>>24)]); - } - t=x; x=y; y=t; /* swap pointers */ - } - -/* Last Round - unroll if possible */ - for (m=j=0;j>8)])^ - ROTL16((u32)rbsub[(u8)(x[ri[m+1]]>>16)])^ - ROTL24((u32)rbsub[(u8)(x[ri[m+2]]>>24)]); - } - for (i=j=0;ilineEdit_cachePath->setText( cachePath ); ui->lineEdit_nandPath->setText( nandPath ); ui->lineEdit_extractPath->setText( "./downloaded" ); diff --git a/nand_dump/mainwindow.h b/nand_dump/mainwindow.h index c8ac63d..48e5b00 100644 --- a/nand_dump/mainwindow.h +++ b/nand_dump/mainwindow.h @@ -1,9 +1,9 @@ #ifndef MAINWINDOW_H #define MAINWINDOW_H -#include "includes.h" -#include "nusdownloader.h" -#include "nanddump.h" +#include "../WiiQt/includes.h" +#include "../WiiQt/nusdownloader.h" +#include "../WiiQt/nanddump.h" namespace Ui { class MainWindow; diff --git a/nand_dump/nand.pro b/nand_dump/nand.pro index 5f1183e..b47abf0 100644 --- a/nand_dump/nand.pro +++ b/nand_dump/nand.pro @@ -13,29 +13,27 @@ TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ - tools.cpp \ - uidmap.cpp \ - sharedcontentmap.cpp \ - sha1.c \ - tiktmd.cpp \ - aes.c \ - nusdownloader.cpp \ - nanddump.cpp \ - settingtxtdialog.cpp \ - wad.cpp + ../WiiQt/tools.cpp \ + ../WiiQt/sharedcontentmap.cpp \ + ../WiiQt/tiktmd.cpp \ + ../WiiQt/nusdownloader.cpp \ + ../WiiQt/uidmap.cpp \ + ../WiiQt/nanddump.cpp \ + ../WiiQt/settingtxtdialog.cpp \ + ../WiiQt/wad.cpp \ + ../WiiQt/aes.c \ + ../WiiQt/sha1.c HEADERS += mainwindow.h \ - tools.h \ - includes.h \ - uidmap.h \ - sharedcontentmap.h \ - sha1.h \ - tiktmd.h \ - aes.h \ - nusdownloader.h \ - nanddump.h \ - settingtxtdialog.h \ - wad.h + ../WiiQt/tools.h \ + ../WiiQt/uidmap.h \ + ../WiiQt/sharedcontentmap.h \ + ../WiiQt/tiktmd.h \ + ../WiiQt/nusdownloader.h \ + ../WiiQt/uidmap.h \ + ../WiiQt/nanddump.h \ + ../WiiQt/settingtxtdialog.h \ + ../WiiQt/wad.h FORMS += mainwindow.ui \ - settingtxtdialog.ui + ../WiiQt/settingtxtdialog.ui diff --git a/saveToy/mainwindow.cpp b/saveToy/mainwindow.cpp index 6cd56f3..04b77f2 100644 --- a/saveToy/mainwindow.cpp +++ b/saveToy/mainwindow.cpp @@ -1,6 +1,11 @@ #include "mainwindow.h" #include "ui_mainwindow.h" #include "tools.h" +#include "savebanner.h" + +//TODO... get these from settings and dont use global variables +static QString pcPath = "./saveBackups"; +static QString sneekPath = "/media/SDHC_4GB"; MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), bannerthread( this ) { @@ -108,6 +113,7 @@ void MainWindow::on_actionSet_Sneek_Path_triggered() if( p.isEmpty() ) return; + ui->listWidget_sneekSaves->clear(); GetSavesFromSneek( p ); } diff --git a/saveToy/saveToy.pro b/saveToy/saveToy.pro index 1e2764d..ebb8645 100644 --- a/saveToy/saveToy.pro +++ b/saveToy/saveToy.pro @@ -12,19 +12,21 @@ TEMPLATE = app SOURCES += main.cpp\ mainwindow.cpp \ - savebanner.cpp \ savelistitem.cpp \ saveloadthread.cpp \ - tools.cpp + ../WiiQt/tools.cpp \ + ../WiiQt/savebanner.cpp \ + ../WiiQt/aes.c \ + ../WiiQt/sha1.c HEADERS += mainwindow.h \ - includes.h \ - savebanner.h \ savelistitem.h \ - saveloadthread.h \ - tools.h + saveloadthread.h\ + ../WiiQt/tools.h FORMS += mainwindow.ui RESOURCES += \ rc.qrc + +INCPATH += "../WiiQt" diff --git a/saveToy/saveloadthread.cpp b/saveToy/saveloadthread.cpp index 4881278..1b9db02 100644 --- a/saveToy/saveloadthread.cpp +++ b/saveToy/saveloadthread.cpp @@ -70,6 +70,14 @@ void SaveLoadThread::run() QFileInfoList fiL2 = subDir.entryInfoList(); cnt += fiL2.size(); + subDir.setPath( basePath + "/title/00010002" ); + QFileInfoList fiL3 = subDir.entryInfoList(); + cnt += fiL3.size(); + + subDir.setPath( basePath + "/title/00010004" ); + QFileInfoList fiL4 = subDir.entryInfoList(); + cnt += fiL4.size(); + foreach( QFileInfo f, fiL ) { i++; @@ -97,9 +105,39 @@ void SaveLoadThread::run() QByteArray stuff = ff.readAll(); ff.close(); + quint32 size = GetFolderSize( f.absoluteFilePath() + "/data" ); + emit SendItem( stuff, QString( "00010001" + f.fileName() ), type, size ); + } + foreach( QFileInfo f, fiL3 ) + { + i++; + emit SendProgress( (int)( ( (float)( i ) / (float)cnt ) * (float)100 ) ); + + QFile ff( f.absoluteFilePath() + "/data/banner.bin" ); + if( !ff.exists() || !ff.open( QIODevice::ReadOnly ) ) + continue; + + QByteArray stuff = ff.readAll(); + ff.close(); + quint32 size = GetFolderSize( f.absoluteFilePath() + "/data" ); emit SendItem( stuff, QString( "00010002" + f.fileName() ), type, size ); } + foreach( QFileInfo f, fiL4 ) + { + i++; + emit SendProgress( (int)( ( (float)( i ) / (float)cnt ) * (float)100 ) ); + + QFile ff( f.absoluteFilePath() + "/data/banner.bin" ); + if( !ff.exists() || !ff.open( QIODevice::ReadOnly ) ) + continue; + + QByteArray stuff = ff.readAll(); + ff.close(); + + quint32 size = GetFolderSize( f.absoluteFilePath() + "/data" ); + emit SendItem( stuff, QString( "00010004" + f.fileName() ), type, size ); + } emit SendProgress( 100 ); emit SendDone( type ); diff --git a/saveToy/tools.cpp b/saveToy/tools.cpp deleted file mode 100644 index f9ee29c..0000000 --- a/saveToy/tools.cpp +++ /dev/null @@ -1,46 +0,0 @@ -#include "tools.h" -#include "includes.h" - -QString currentDir; -QString pcPath = "./saveBackups"; -QString sneekPath = "/media/SDHC_4GB"; - -char ascii( char s ) { - if ( s < 0x20 ) return '.'; - if ( s > 0x7E ) return '.'; - return s; -} -void hexdump( const void *d, int len ) { - unsigned char *data; - int i, off; - data = (unsigned char*)d; - fprintf( stderr, "\n"); - for ( off = 0; off < len; off += 16 ) { - fprintf( stderr, "%08x ", off ); - for ( i=0; i<16; i++ ) - { - if( ( i + 1 ) % 4 ) - { - if ( ( i + off ) >= len ) fprintf( stderr," "); - else fprintf( stderr,"%02x",data[ off + i ]); - } - else - { - if ( ( i + off ) >= len ) fprintf( stderr," "); - else fprintf( stderr,"%02x ",data[ off + i ]); - } - } - - fprintf( stderr, " " ); - for ( i = 0; i < 16; i++ ) - if ( ( i + off) >= len ) fprintf( stderr," "); - else fprintf( stderr,"%c", ascii( data[ off + i ])); - fprintf( stderr,"\n"); - } - fflush( stderr ); -} - -void hexdump( const QByteArray &d, int from, int len ) -{ - hexdump( d.data() + from, len == -1 ? d.size() : len ); -} diff --git a/saveToy/tools.h b/saveToy/tools.h deleted file mode 100644 index 30b5d4f..0000000 --- a/saveToy/tools.h +++ /dev/null @@ -1,20 +0,0 @@ -#ifndef TOOLS_H -#define TOOLS_H -#include "includes.h" - -#define RU(x,n) (-(-(x) & -(n))) //round up - -#define MIN( x, y ) ( ( x ) < ( y ) ? ( x ) : ( y ) ) -#define MAX( x, y ) ( ( x ) > ( y ) ? ( x ) : ( y ) ) - -char ascii( char s ); -void hexdump( const void *d, int len ); -void hexdump( const QByteArray &d, int from = 0, int len = -1 ); - -//keep track of the last folder browsed to when looking for files -extern QString currentDir; - -extern QString pcPath; -extern QString sneekPath; - -#endif // TOOLS_H