diff --git a/.gitattributes b/.gitattributes index 8e948c5..a60083f 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1 +1,58 @@ * text=auto !eol +nandExtract/aes.c -text +nandExtract/aes.h -text +nandExtract/includes.h -text +nandExtract/main.cpp -text +nandExtract/nandExtract.pro -text +nandExtract/nandExtract.pro.user -text +nandExtract/nandbin.cpp -text +nandExtract/nandbin.h -text +nandExtract/nandwindow.cpp -text +nandExtract/nandwindow.h -text +nandExtract/nandwindow.ui -text +nandExtract/tools.cpp -text +nandExtract/tools.h -text +nand_dump/aes.c -text +nand_dump/aes.h -text +nand_dump/includes.h -text +nand_dump/main.cpp -text +nand_dump/mainwindow.cpp -text +nand_dump/mainwindow.h -text +nand_dump/mainwindow.ui -text +nand_dump/nand.pro -text +nand_dump/nanddump.cpp -text +nand_dump/nanddump.h -text +nand_dump/nusdownloader.cpp -text +nand_dump/nusdownloader.h -text +nand_dump/settingtxtdialog.cpp -text +nand_dump/settingtxtdialog.h -text +nand_dump/settingtxtdialog.ui -text +nand_dump/sha1.c -text +nand_dump/sha1.h -text +nand_dump/sharedcontentmap.cpp -text +nand_dump/sharedcontentmap.h -text +nand_dump/tiktmd.cpp -text +nand_dump/tiktmd.h -text +nand_dump/tools.cpp -text +nand_dump/tools.h -text +nand_dump/uidmap.cpp -text +nand_dump/uidmap.h -text +nand_dump/wad.cpp -text +nand_dump/wad.h -text +saveToy/includes.h -text +saveToy/main.cpp -text +saveToy/mainwindow.cpp -text +saveToy/mainwindow.h -text +saveToy/mainwindow.ui -text +saveToy/noBanner.png -text +saveToy/noIcon.png -text +saveToy/rc.qrc -text +saveToy/saveToy.pro -text +saveToy/savebanner.cpp -text +saveToy/savebanner.h -text +saveToy/savelistitem.cpp -text +saveToy/savelistitem.h -text +saveToy/saveloadthread.cpp -text +saveToy/saveloadthread.h -text +saveToy/tools.cpp -text +saveToy/tools.h -text diff --git a/nandExtract/aes.c b/nandExtract/aes.c new file mode 100644 index 0000000..801d9d0 --- /dev/null +++ b/nandExtract/aes.c @@ -0,0 +1,400 @@ +/* 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;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include + + +#endif // INCLUDES_H diff --git a/nandExtract/main.cpp b/nandExtract/main.cpp new file mode 100755 index 0000000..dd679f9 --- /dev/null +++ b/nandExtract/main.cpp @@ -0,0 +1,10 @@ +#include +#include "nandwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + NandWindow w; + w.show(); + return a.exec(); +} diff --git a/nandExtract/nandExtract.pro b/nandExtract/nandExtract.pro new file mode 100755 index 0000000..c99d901 --- /dev/null +++ b/nandExtract/nandExtract.pro @@ -0,0 +1,16 @@ +# ------------------------------------------------- +# Project created by QtCreator 2010-12-06T03:40:50 +# ------------------------------------------------- +TARGET = nandExtract +TEMPLATE = app +SOURCES += main.cpp \ + nandwindow.cpp \ + nandbin.cpp \ + tools.cpp \ + aes.c +HEADERS += nandwindow.h \ + nandbin.h \ + includes.h \ + tools.h \ + aes.h +FORMS += nandwindow.ui diff --git a/nandExtract/nandExtract.pro.user b/nandExtract/nandExtract.pro.user new file mode 100644 index 0000000..103f335 --- /dev/null +++ b/nandExtract/nandExtract.pro.user @@ -0,0 +1,113 @@ + + + + ProjectExplorer.Project.ActiveTarget + 0 + + + ProjectExplorer.Project.EditorSettings + + System + + + + ProjectExplorer.Project.Target.0 + + Desktop + Qt4ProjectManager.Target.DesktopTarget + 1 + 0 + + + qmake + QtProjectManager.QMakeBuildStep + + + + Make + Qt4ProjectManager.MakeStep + false + + + + 2 + + Make + Qt4ProjectManager.MakeStep + true + + clean + + + + 1 + false + + Debug + Qt4ProjectManager.Qt4BuildConfiguration + 2 + /home/j/c/QtWii/nandExtract-build-desktop + 8 + 0 + true + + + + qmake + QtProjectManager.QMakeBuildStep + + + + Make + Qt4ProjectManager.MakeStep + false + + + + 2 + + Make + Qt4ProjectManager.MakeStep + true + + clean + + + + 1 + false + + Release + Qt4ProjectManager.Qt4BuildConfiguration + 0 + /home/j/c/QtWii/nandExtract + 8 + 0 + true + + 2 + + nandExtract + Qt4ProjectManager.Qt4RunConfiguration + 2 + + nandExtract.pro + false + false + + false + false + + + 1 + + + + ProjectExplorer.Project.TargetCount + 1 + + + ProjectExplorer.Project.Updater.FileVersion + 4 + + diff --git a/nandExtract/nandbin.cpp b/nandExtract/nandbin.cpp new file mode 100755 index 0000000..054dd9e --- /dev/null +++ b/nandExtract/nandbin.cpp @@ -0,0 +1,536 @@ +#include "nandbin.h" +#include "tools.h" + +NandBin::NandBin( QObject * parent, const QString &path ) : QObject( parent ) +{ + type = -1; + fatNames = false; + root = NULL; + if( !path.isEmpty() ) + SetPath( path ); +} + +NandBin::~NandBin() +{ + if( f.isOpen() ) + f.close(); + + if( root ) + delete root; +} + +bool NandBin::SetPath( const QString &path ) +{ + nandPath = path; + if( f.isOpen() ) + f.close(); + + f.setFileName( path ); + bool ret = !f.exists() || !f.open( QIODevice::ReadOnly ); + if( ret ) + { + emit SendError( tr( "Cant open %1" ).arg( path ) ); + } + + return !ret; +} + +QTreeWidgetItem *NandBin::GetTree() +{ + return root->clone(); +} + +bool NandBin::ExtractToDir( QTreeWidgetItem *item, const QString &path ) +{ + if( !item ) + return false; + bool ok = false; + quint16 entry = item->text( 1 ).toInt( &ok ); + if( !ok ) + { + emit SendError( tr( "Error converting entry(%1) to a number" ).arg( item->text( 1 ) ) ); + return false; + } + return ExtractFST( entry, path ); + + return true; +} + +bool NandBin::AddChildren( QTreeWidgetItem *parent, quint16 entry ) +{ + if( entry >= 0x17ff ) + { + emit SendError( tr( "entry is above 0x17ff mmmmkay [ 0x%1 ]" ).arg( entry, 0, 16 ) ); + return false; + } + + fst_t fst = GetFST( entry ); + + if( !fst.filename[ 0 ] )//something is amiss, better quit now + return false; + + if( fst.sib != 0xffff ) + { + if( !AddChildren( parent, fst.sib ) ) + return false; + } + QStringList text; + QString name = FstName( fst ); + + QString en = QString( "%1" ).arg( entry ); + QString size = QString( "%1" ).arg( fst.size, 0, 16 ); + QString uid = QString( "%1" ).arg( fst.uid, 8, 16, QChar( '0' ) ); + QString gid = QString( "%1 (\"%2%3\")" ).arg( fst.gid, 4, 16, QChar( '0' ) ) + .arg( QChar( ascii( (char)( (fst.gid >> 8) & 0xff ) ) ) ) + .arg( QChar( ascii( (char)( (fst.gid) & 0xff ) ) ) ); + QString x3 = QString( "%1" ).arg( fst.x3, 8, 16, QChar( '0' ) ); + QString mode = QString( "%1" ).arg( fst.mode, 2, 16, QChar( '0' ) ); + QString attr = QString( "%1" ).arg( fst.attr, 2, 16, QChar( '0' ) ); + + text << name << en << size << uid << gid << x3 << mode << attr; + QTreeWidgetItem *child = new QTreeWidgetItem( parent, text ); + + //try to add subfolder contents to the tree + if( !fst.mode && fst.sub != 0xffff && !AddChildren( child, fst.sub ) ) + return false; + + return true; +} + +QString NandBin::FstName( fst_t fst ) +{ + QByteArray ba( (char*)fst.filename, 0xc ); + QString ret = QString( ba ); + if( fatNames ) + ret.replace( ":", "-" ); + return ret; +} + +bool NandBin::ExtractFST( quint16 entry, const QString &path ) +{ + //qDebug() << "NandBin::ExtractFST(" << hex << entry << "," << path << ")"; + fst_t fst = GetFST( entry ); + + if( !fst.filename[ 0 ] )//something is amiss, better quit now + return false; + + if( fst.sib != 0xffff && !ExtractFST( fst.sib, path ) ) + return false; + + switch( fst.mode ) + { + case 0: + if( !ExtractDir( fst, path ) ) + return false; + break; + case 1: + if( !ExtractFile( fst, path ) ) + return false; + break; + default://wtf + emit SendError( tr( "Unknown fst mode. Bailing out" ) ); + return false; + break; + } + return true; +} + +bool NandBin::ExtractDir( fst_t fst, QString parent ) +{ + //qDebug() << "NandBin::ExtractDir(" << parent << ")"; + QByteArray ba( (char*)fst.filename, 0xc ); + QString filename( ba ); + + QFileInfo fi( parent + "/" + filename ); + if( filename != "/" && !fi.exists() && !QDir().mkpath( fi.absoluteFilePath() ) ) + return false; + + if( fst.sub != 0xffff && !ExtractFST( fst.sub, fi.absoluteFilePath() ) ) + return false; + return true; +} + +bool NandBin::ExtractFile( fst_t fst, QString parent ) +{ + QByteArray ba( (char*)fst.filename, 0xc ); + QString filename( ba ); + QFileInfo fi( parent + "/" + filename ); + qDebug() << "extract" << fi.absoluteFilePath(); + emit SendText( tr( "Extracting \"%1\"" ).arg( fi.absoluteFilePath() ) ); + + QByteArray data = GetFile( fst ); + if( fst.size && !data.size() )//dont worry if files dont have anything in them anyways + return false; + + QFile out( fi.absoluteFilePath() ); + if( out.exists() )// !! delete existing files + out.remove(); + + if( !out.open( QIODevice::WriteOnly ) ) + { + emit SendError( tr( "Can't open \"%1\" for writing" ).arg( fi.absoluteFilePath() ) ); + return false; + } + out.write( data ); + out.close(); + return true; +} + +bool NandBin::InitNand() +{ + type = GetDumpType( f.size() ); + if( type < 0 || type > 3 ) + return false; + + //qDebug() << "dump type:" << type; + if( !GetKey( type ) ) + return false; + + loc_super = FindSuperblock(); + if( loc_super < 0 ) + return false; + + quint32 n_fatlen[] = { 0x010000, 0x010800, 0x010800 }; + loc_fat = loc_super; + loc_fst = loc_fat + 0x0C + n_fatlen[ type ]; + + if( root ) + delete root; + + root = new QTreeWidgetItem( QStringList() << nandPath ); + AddChildren( root, 0 ); + + ShowInfo(); + return true; +} + +int NandBin::GetDumpType( quint64 fileSize ) +{ + quint64 sizes[] = { 536870912, // type 0 | 536870912 == no ecc + 553648128, // type 1 | 553648128 == ecc + 553649152 }; // type 2 | 553649152 == old bootmii + for( int i = 0; i < 3; i++ ) + { + if( sizes[ i ] == fileSize ) + return i; + } + emit SendError( tr( "Can't tell what type of nand dump this is" ) ); + return -1; +} + +bool NandBin::GetKey( int type ) +{ + switch( type ) + { + case 0: + case 1: + { + QString keyPath = nandFile; + int sl = keyPath.lastIndexOf( "/" ); + if( sl == -1 ) + { + emit SendError( tr( "Error getting path of keys.bin" ) ); + return false; + } + keyPath.resize( sl ); + keyPath += "keys.bin"; + + key = ReadKeyfile( keyPath ); + if( key.isEmpty() ) + return false; + } + break; + case 2: + { + if( !f.isOpen() ) + { + emit SendError( tr( "Tried to read keys from unopened file" ) ); + return false; + } + f.seek( 0x21000158 ); + key = f.read( 16 ); + } + break; + default: + emit SendError( tr( "Tried to read keys for unknown dump type" ) ); + return false; + break; + } + return true; +} + +QByteArray NandBin::ReadKeyfile( QString path ) +{ + QByteArray retval; + QFile f( path ); + if( !f.exists() || !f.open( QIODevice::ReadOnly ) ) + { + emit SendError( tr( "Can't open %1!" ).arg( path ) ); + return retval; + } + if( f.size() < 0x16e ) + { + f.close(); + emit SendError( tr( "keys.bin is too small!" ) ); + return retval; + } + f.seek( 0x158 ); + retval = f.read( 16 ); + f.close(); + + return retval; +} + +qint32 NandBin::FindSuperblock() +{ + if( type < 0 || type > 3 ) + { + emit SendError( tr( "Tried to get superblock of unknown dump type" ) ); + return -1; + } + if( !f.isOpen() ) + { + emit SendError( tr( "Tried to get superblock of unopened dump" ) ); + return -1; + } + quint32 loc = 0, current = 0, last = 0; + quint32 n_start[] = { 0x1FC00000, 0x20BE0000, 0x20BE0000 }, + n_end[] = { 0x20000000, 0x21000000, 0x21000000 }, + n_len[] = { 0x40000, 0x42000, 0x42000 }; + + + for( loc = n_start[ type ]; loc < n_end[ type ]; loc += n_len[ type ] ) + { + f.read( (char*)¤t, 4 ); + current = qFromBigEndian( current ); + + //qDebug() << "superblock" << hex << current; + + if( current > last ) + last = current; + else + { + //qDebug() << "superblock loc" << hex << loc - n_len[ type ]; + return loc - n_len[ type ]; + } + + f.seek( n_len[ type ] - 4 ); + } + return -1; +} + +fst_t NandBin::GetFST( quint16 entry ) +{ + //qDebug() << "NandBin::GetFST(" << hex << entry << ")"; + fst_t fst; + if( entry >= 0x17FF ) + { + emit SendError( tr( "Tried to get entry above 0x17ff [ 0x%1 ]" ).arg( entry, 0, 16 ) ); + fst.filename[ 0 ] = '\0'; + return fst; + } + // compensate for 64 bytes of ecc data every 64 fst entries + quint32 n_fst[] = { 0, 2, 2 }; + int loc_entry = ( ( ( entry / 0x40 ) * n_fst[ type ] ) + entry ) * 0x20; + if( (quint32)f.size() < loc_fst + loc_entry + sizeof( fst_t ) ) + { + emit SendError( tr( "Tried to read fst_t beyond size of nand.bin" ) ); + fst.filename[ 0 ] = '\0'; + return fst; + } + f.seek( loc_fst + loc_entry ); + + f.read( (char*)&fst.filename, 0xc ); + f.read( (char*)&fst.mode, 1 ); + f.read( (char*)&fst.attr, 1 ); + f.read( (char*)&fst.sub, 2 ); + f.read( (char*)&fst.sib, 2 ); + if( type && ( entry + 1 ) % 64 == 0 )//bug in other nand.bin extracterizers. the entry for every 64th fst item is inturrupeted by some ecc shit + { + f.read( (char*)&fst.size, 2 ); + f.seek( f.pos() + 0x40 ); + f.read( (char*)(&fst.size) + 2, 2 ); + } + else + f.read( (char*)&fst.size, 4 ); + f.read( (char*)&fst.uid, 4 ); + f.read( (char*)&fst.gid, 2 ); + f.read( (char*)&fst.x3, 4 ); + + fst.sub = qFromBigEndian( fst.sub ); + fst.sib = qFromBigEndian( fst.sib ); + fst.size = qFromBigEndian( fst.size ); + fst.uid = qFromBigEndian( fst.uid ); + fst.gid = qFromBigEndian( fst.gid ); + fst.x3 = qFromBigEndian( fst.x3 ); + + fst.mode &= 1; + return fst; +} + +quint16 NandBin::GetFAT( quint16 fat_entry ) +{ + /* + * compensate for "off-16" storage at beginning of superblock + * 53 46 46 53 XX XX XX XX 00 00 00 00 + * S F F S "version" padding? + * 1 2 3 4 5 6*/ + fat_entry += 6; + + // location in fat of cluster chain + quint32 n_fat[] = { 0, 0x20, 0x20 }; + int loc = loc_fat + ((((fat_entry / 0x400) * n_fat[type]) + fat_entry) * 2); + + if( (quint32)f.size() < loc + sizeof( quint16 ) ) + { + emit SendError( tr( "Tried to read FAT entry beyond size of nand.bin" ) ); + return 0; + } + f.seek( loc ); + + quint16 ret; + f.read( (char*)&ret, 2 ); + ret = qFromBigEndian( ret ); + return ret; +} + +QByteArray NandBin::GetCluster( quint16 cluster_entry, bool decrypt ) +{ + //qDebug() << "NandBin::GetCluster" << hex << cluster_entry; + quint32 n_clusterlen[] = { 0x4000, 0x4200, 0x4200 }; + quint32 n_pagelen[] = { 0x800, 0x840, 0x840 }; + + if( f.size() < ( cluster_entry * n_clusterlen[ type ] ) + ( 8 * n_pagelen[ type ] ) ) + { + emit SendError( tr( "Tried to read cluster past size of nand.bin" ) ); + return QByteArray(); + } + + QByteArray cluster; + + for( int i = 0; i < 8; i++ ) + { + f.seek( ( cluster_entry * n_clusterlen[ type ] ) + ( i * n_pagelen[ type ] ) ); //seek to the beginning of the page to read + //QByteArray page = f.read( n_pagelen[ type ] ); //read the page, with ecc + QByteArray page = f.read( 0x800 ); //read the page, skip the ecc + + cluster += page; + } + if( cluster.size() != 0x4000 ) + { + qDebug() << "actual cluster size" << hex << cluster.size(); + emit SendError( tr( "Error reading cluster" ) ); + return QByteArray(); + } + if( !decrypt ) + return cluster; + + //really redundant to do this for ever AES decryption, but the AES code only lets + //1 key set at a time and it may be changed if some other object is decrypting something else + AesSetKey( key ); + + QByteArray ret = AesDecrypt( 0, cluster );//TODO... is IV really always 0? + return ret; +} + +QByteArray NandBin::GetFile( quint16 entry ) +{ + fst_t fst = GetFST( entry ); + if( !fst.filename[ 0 ] )//something is amiss, better quit now + return QByteArray(); + return GetFile( fst ); +} + +QByteArray NandBin::GetFile( fst_t fst ) +{ + if( !fst.size ) + return QByteArray(); + + quint16 fat = fst.sub; + //int cluster_span = (int)( fst.size / 0x4000) + 1; + + QByteArray data; + + for (int i = 0; fat < 0xFFF0; i++) + { + QByteArray cluster = GetCluster( fat ); + if( cluster.size() != 0x4000 ) + return QByteArray(); + + data += cluster; + fat = GetFAT( fat ); + } + //this check doesnt really seem to matter, it always appears to be 1 extra cluster added to the end + //of the file and that extra bit is dropped in this function before the data is returned. + /*if( data.size() != cluster_span * 0x4000 ) + { + qDebug() << "data.size() != cluster_span * 0x4000 :: " + << hex << data.size() + << cluster_span + << ( cluster_span * 0x4000 ) + << "expected size:" << hex << fst.size; + + emit SendError( tr( "Error reading file [ block size is not a as expected ] %1" ).arg( FstName( fst ) ) ); + }*/ + if( (quint32)data.size() < fst.size ) + { + qDebug() << "(quint32)data.size() < fst.size :: " + << hex << data.size() + << "expected size:" << hex << fst.size; + + emit SendError( tr( "Error reading file [ returned data size is less that the size in the fst ]" ) ); + return QByteArray(); + } + + if( (quint32)data.size() > fst.size ) + data.resize( fst.size );//dont need to give back all the data, only up to the expected size + + return data; +} + +void NandBin::SetFixNamesForFAT( bool fix ) +{ + fatNames = fix; +} + +/* + * 0xFFFB - last cluster within a chain + * 0xFFFC - reserved cluster + * 0xFFFD - bad block (marked at factory) -- you should always see these in groups of 8 (8 clusters per NAND block) + * 0xFFFE - empty (unused / available) space + */ +void NandBin::ShowInfo() +{ + quint16 badBlocks = 0; + quint16 reserved = 0; + quint16 freeBlocks = 0; + QListbadOnes; + for( quint16 i = 0; i < 0x8000; i++ ) + { + quint16 fat = GetFAT( i ); + if( 0xfffc == fat ) + reserved++; + else if( 0xfffd == fat ) + { + badBlocks++; + if( i % 8 == 0 ) + { + badOnes << ( i / 8 ); + } + } + else if( 0xfffe == fat ) + freeBlocks++; + } + if( badBlocks ) + badBlocks /= 8; + + if( reserved ) + reserved /= 8; + + if( freeBlocks ) + freeBlocks /= 8; + + qDebug() << "free blocks:" << hex << freeBlocks + << "\nbadBlocks:" << hex << badBlocks << badOnes + << "\nreserved:" << hex << reserved; +} diff --git a/nandExtract/nandbin.h b/nandExtract/nandbin.h new file mode 100755 index 0000000..c7b186d --- /dev/null +++ b/nandExtract/nandbin.h @@ -0,0 +1,85 @@ +#ifndef NANDBIN_H +#define NANDBIN_H + +#include "includes.h" +struct fst_t +{ + quint8 filename[ 0xc ];//showmii wads has size 0xb here but reads 0xc bytes into name?? + quint8 mode; + quint8 attr; + quint16 sub; + quint16 sib; + quint32 size; + quint32 uid; + quint16 gid; + quint32 x3; +}; +// 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() +class NandBin : public QObject +{ + Q_OBJECT + +public: + NandBin( QObject * parent = 0, const QString &path = QString() ); + ~NandBin(); + bool SetPath( const QString &path ); + bool InitNand(); + + //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 + 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 + bool ExtractToDir( QTreeWidgetItem *item, const QString &path ); + + //print a little info about the free space + void ShowInfo(); + + //set this to change ":" in names to "-" on etracting. + //theres more illegal characters in FAT, but thes seems to be the only one that happens on the nand FS + void SetFixNamesForFAT( bool fix = true ); + +private: + QByteArray key; + qint32 loc_super; + qint32 loc_fat; + qint32 loc_fst; + QString extractPath; + QString nandFile; + QFile f; + int type; + + bool fatNames; + + int GetDumpType( quint64 fileSize ); + bool GetKey( int type ); + QByteArray ReadKeyfile( QString path ); + qint32 FindSuperblock(); + 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 ); + bool ExtractFST( quint16 entry, const QString &path ); + bool ExtractDir( fst_t fst, QString parent ); + bool ExtractFile( fst_t fst, QString parent ); + + + + QTreeWidgetItem *root; + bool AddChildren( QTreeWidgetItem *parent, quint16 entry ); + +signals: + void SendError( QString ); + void SendText( QString ); +}; + +#endif // NANDBIN_H diff --git a/nandExtract/nandwindow.cpp b/nandExtract/nandwindow.cpp new file mode 100755 index 0000000..f298a6f --- /dev/null +++ b/nandExtract/nandwindow.cpp @@ -0,0 +1,119 @@ +#include "nandwindow.h" +#include "ui_nandwindow.h" +#include "tools.h" + +NandWindow::NandWindow(QWidget *parent) : + QMainWindow(parent), + ui(new Ui::NandWindow), + nandBin( this ) +{ + ui->setupUi(this); + ui->treeWidget->header()->resizeSection( 0, 300 );//name + + + connect( &nandBin, SIGNAL( SendError( QString ) ), this, SLOT( GetError( QString ) ) ); + connect( &nandBin, SIGNAL( SendText( QString ) ), this, SLOT( GetStatusUpdate( QString ) ) ); +} + +NandWindow::~NandWindow() +{ + delete ui; +} + +void NandWindow::changeEvent(QEvent *e) +{ + QMainWindow::changeEvent(e); + switch (e->type()) { + case QEvent::LanguageChange: + ui->retranslateUi(this); + break; + default: + break; + } +} + +void NandWindow::GetStatusUpdate( QString s ) +{ + ui->statusBar->showMessage( s ); +} + +void NandWindow::GetError( QString str ) +{ + qWarning() << str; +} + +void NandWindow::ExtractShit() +{ + ui->statusBar->showMessage( "Trying to extract..." ); + nandBin.ExtractToDir( exItem, exPath );//who cares if it returns false? not me. thats what the qDebug() info is for + ui->statusBar->showMessage( "Done", 5000 ); + +} + +//nand window right-clicked +void NandWindow::on_treeWidget_customContextMenuRequested( QPoint pos ) +{ + QPoint globalPos = ui->treeWidget->viewport()->mapToGlobal( pos ); + QTreeWidgetItem* item = ui->treeWidget->itemAt( pos ); + if( !item )//right-clicked in the partition window, but not on an item + { + qDebug() << "no item selected"; + return; + } + + QMenu myMenu( this ); + QAction extractA( tr( "Extract" ), &myMenu ); + myMenu.addAction( &extractA ); + + QAction* s = myMenu.exec( globalPos ); + //respond to what was selected + if( s ) + { + // something was chosen, do stuff + if( s == &extractA )//extract a file + { + QString path = QFileDialog::getExistingDirectory( this, tr("Select a destination") ); + if( path.isEmpty() ) + return; + + exPath = path; + exItem = item; + + //ghetto, but gives the dialog box time to dissappear before the gui freezes as it extracts the nand + QTimer::singleShot( 250, this, SLOT( ExtractShit() ) ); + } + } +} + +void NandWindow::on_actionOpen_Nand_triggered() +{ + QString path = QFileDialog::getOpenFileName( this, tr( "Select a Nand to open" ) ); + if( path.isEmpty() ) + return; + + if( !nandBin.SetPath( path ) ) + { + qDebug() << " error in nandBin.SetPath"; + ui->statusBar->showMessage( "Error setting path to " + path ); + return; + } + ui->statusBar->showMessage( "Loading " + path ); + if( !nandBin.InitNand() ) + { + qDebug() << " error in nandBin.InitNand()"; + ui->statusBar->showMessage( "Error reading " + path ); + return; + } + + ui->treeWidget->clear(); + + //get an item holding a tree with all the items of the nand + QTreeWidgetItem* tree = nandBin.GetTree(); + + //take the actual contents of the nand from the made up root and add them to the gui widget + ui->treeWidget->addTopLevelItems( tree->takeChildren() ); + + //delete the made up root item + delete tree; + ui->statusBar->showMessage( "Loaded " + path, 5000 ); +} diff --git a/nandExtract/nandwindow.h b/nandExtract/nandwindow.h new file mode 100755 index 0000000..4cb1722 --- /dev/null +++ b/nandExtract/nandwindow.h @@ -0,0 +1,40 @@ +#ifndef NANDWINDOW_H +#define NANDWINDOW_H + +#include "includes.h" +#include "nandbin.h" + +namespace Ui { + class NandWindow; +} + +class NandWindow : public QMainWindow { + Q_OBJECT +public: + NandWindow(QWidget *parent = 0); + ~NandWindow(); + +protected: + void changeEvent(QEvent *e); + +private: + Ui::NandWindow *ui; + + NandBin nandBin; + + //just put these here and create a slot to make the GUI not + //look so ugly when it hangs while stuff is extracting + QString exPath; + QTreeWidgetItem* exItem; + +public slots: + void GetError( QString ); + void GetStatusUpdate( QString ); + +private slots: + void on_actionOpen_Nand_triggered(); + void on_treeWidget_customContextMenuRequested(QPoint pos); + void ExtractShit(); +}; + +#endif // NANDWINDOW_H diff --git a/nandExtract/nandwindow.ui b/nandExtract/nandwindow.ui new file mode 100755 index 0000000..062daa1 --- /dev/null +++ b/nandExtract/nandwindow.ui @@ -0,0 +1,105 @@ + + + NandWindow + + + + 0 + 0 + 657 + 499 + + + + NandWindow + + + + + + + Qt::CustomContextMenu + + + + Name + + + + + Entry + + + + + Size + + + + + uid + + + + + gid + + + + + x3 + + + + + Mode + + + + + Attr + + + + + + + + + + 0 + 0 + 657 + 27 + + + + + File + + + + + + + + TopToolBarArea + + + false + + + + + + Open Nand... + + + Ctrl+O + + + + + + + diff --git a/nandExtract/tools.cpp b/nandExtract/tools.cpp new file mode 100644 index 0000000..7200bca --- /dev/null +++ b/nandExtract/tools.cpp @@ -0,0 +1,131 @@ +#include "tools.h" +#include "includes.h" +#include "aes.h" +//#include "sha1.h" + +QString currentDir; +QString cachePath = "./NUS_cache"; +QString nandPath = "./dump"; + +char ascii( char s ) { + if ( s < 0x20 ) return '.'; + if ( s > 0x7E ) return '.'; + return s; +} + +//using stderr just because qtcreator stows it correctly when mixed with qDebug(), qWarning(), etc +//otherwise it may not be shown in the correct spot in the output due to stdout/stderr caches +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 ); +} + +void hexdump12( const void *d, int len ) { + unsigned char *data; + int i, off; + data = (unsigned char*)d; + fprintf( stderr, "\n"); + for ( off = 0; off < len; off += 12 ) { + fprintf( stderr, "%08x ", off ); + for ( i=0; i<12; 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 < 12; i++ ) + if ( ( i + off) >= len ) fprintf( stderr," "); + else fprintf( stderr,"%c", ascii( data[ off + i ])); + fprintf( stderr,"\n"); + } + fflush( stderr ); +} + +void hexdump12( const QByteArray &d, int from, int len ) +{ + hexdump12( d.data() + from, len == -1 ? d.size() : len ); +} + +QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo ) +{ + QByteArray padding( RU( padTo, orig.size() ) - orig.size(), '\0' ); + //qDebug() << "padding with" << hex << RU( padTo, orig.size() ) << "bytes" << + return orig + padding; +} + +QByteArray AesDecrypt( quint16 index, const QByteArray source ) +{ + static quint8 iv[ 16 ]; + + quint16 beidx = qFromBigEndian( index ); + memset( iv, 0, 16 ); + memcpy( iv, &beidx, 2 ); + QByteArray ret( source.size(), '\0' ); + aes_decrypt( iv, (const quint8*)source.data(), (quint8*)ret.data(), source.size() ); + return ret; +} + +void AesSetKey( const QByteArray key ) +{ + aes_set_key( (const quint8*)key.data() ); +} +/* +QByteArray GetSha1( QByteArray stuff ) +{ + SHA1Context sha; + SHA1Reset( &sha ); + SHA1Input( &sha, (const unsigned char*)stuff.data(), stuff.size() ); + if( !SHA1Result( &sha ) ) + { + qWarning() << "GetSha1 -> sha error"; + return QByteArray(); + } + QByteArray ret( 20, '\0' ); + quint8 *p = (quint8 *)ret.data(); + for( int i = 0; i < 5 ; i++ ) + { + quint32 part = qFromBigEndian( sha.Message_Digest[ i ] ); + memcpy( p + ( i * 4 ), &part, 4 ); + } + //hexdump( ret ); + return ret; +}*/ diff --git a/nandExtract/tools.h b/nandExtract/tools.h new file mode 100644 index 0000000..e11bac7 --- /dev/null +++ b/nandExtract/tools.h @@ -0,0 +1,36 @@ +#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 ); + +void hexdump12( const void *d, int len ); +void hexdump12( const QByteArray &d, int from = 0, int len = -1 ); + +//simplified functions for crypto shit +void AesSetKey( const QByteArray key ); +QByteArray AesDecrypt( quint16 index, const QByteArray source ); + +//QByteArray GetSha1( QByteArray stuff ); + +//get a padded version of the given buffer +QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo ); + +//keep track of the last folder browsed to when looking for files +extern QString currentDir; + +//folder used to cache stuff downloaded from NUS so duplicate titles dont need to be downloaded +extern QString cachePath; + +//folder to use as the base path for the nand +extern QString nandPath; + +#endif // TOOLS_H diff --git a/nand_dump/aes.c b/nand_dump/aes.c new file mode 100644 index 0000000..f986cb1 --- /dev/null +++ b/nand_dump/aes.c @@ -0,0 +1,400 @@ +/* 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;i +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + + +#endif // INCLUDES_H diff --git a/nand_dump/main.cpp b/nand_dump/main.cpp new file mode 100644 index 0000000..9ae175b --- /dev/null +++ b/nand_dump/main.cpp @@ -0,0 +1,11 @@ +#include +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/nand_dump/mainwindow.cpp b/nand_dump/mainwindow.cpp new file mode 100644 index 0000000..2454fa7 --- /dev/null +++ b/nand_dump/mainwindow.cpp @@ -0,0 +1,242 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "settingtxtdialog.h" +#include "uidmap.h" +#include "sha1.h" +#include "tiktmd.h" +#include "tools.h" +#include "aes.h" + +MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::MainWindow ), nus ( this ) +{ + ui->setupUi(this); + + //connect to the nus object so we can respond to what it is saying with pretty stuff in the gui + connect( &nus, SIGNAL( SendDownloadProgress( int ) ), ui->progressBar_dl, SLOT( setValue( int ) ) ); + connect( &nus, SIGNAL( SendTitleProgress( int ) ), ui->progressBar_title, SLOT( setValue( int ) ) ); + connect( &nus, SIGNAL( SendTotalProgress( int ) ), ui->progressBar_whole, SLOT( setValue( int ) ) ); + connect( &nus, SIGNAL( SendText( QString ) ), ui->statusBar, SLOT( showMessage( QString ) ) ); + connect( &nus, SIGNAL( SendError( const QString &, NusJob ) ), this, SLOT( GetError( const QString &, NusJob ) ) ); + connect( &nus, SIGNAL( SendDone() ), this, SLOT( NusIsDone() ) ); + connect( &nus, SIGNAL( SendData( NusJob ) ), this, SLOT( ReceiveTitleFromNus( NusJob) ) ); + + //TODO, really get these paths from settings + ui->lineEdit_cachePath->setText( cachePath ); + ui->lineEdit_nandPath->setText( nandPath ); + nand.SetPath( nandPath ); + nus.SetCachePath( cachePath ); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +//some slots to respond to the NUS downloader +void MainWindow::GetError( const QString &message, NusJob job ) +{ + QString dataStuff = QString( "%1 items:" ).arg( job.data.size() ); + for( int i = 0; i < job.data.size(); i++ ) + dataStuff += QString( " %1" ).arg( job.data.at( i ).size(), 0, 16, QChar( ' ' ) ); + + QString str = tr( "Error getting title from NUS: %1" ).arg( message ); + QString j = QString( "NusJob( %1, %2, %3, %4 )
" ) + .arg( job.tid, 16, 16, QChar( '0' ) ) + .arg( job.version ).arg( job.decrypt ? "decrypted" : "encrypted" ) + .arg( dataStuff ); + + + ui->plainTextEdit_log->appendHtml( str ); + ui->plainTextEdit_log->appendHtml( j ); +} + +void MainWindow::ShowMessage( const QString& mes ) +{ + QString str = mes + "
"; + ui->plainTextEdit_log->appendHtml( str ); +} + +void MainWindow::NusIsDone() +{ + QString str = tr( "NUS ojbect is done working
" ); + ui->plainTextEdit_log->appendHtml( str ); + ui->statusBar->showMessage( tr( "Done" ), 5000 ); + if( ui->radioButton_folder->isChecked() ) + { + ui->lineEdit_extractPath->setEnabled( true ); + ui->pushButton_decFolder->setEnabled( true ); + } + else if( ui->radioButton_nand->isChecked() ) + { + ui->lineEdit_nandPath->setEnabled( true ); + ui->pushButton_nandPath->setEnabled( true ); + } + else if( ui->radioButton_wad->isChecked() ) + { + ui->lineEdit_wad->setEnabled( true ); + ui->pushButton_wad->setEnabled( true ); + } + + ui->radioButton_folder->setEnabled( true ); + ui->radioButton_nand->setEnabled( true ); + ui->radioButton_wad->setEnabled( true ); +} + +void MainWindow::ReceiveTitleFromNus( NusJob job ) +{ + //QString dataStuff = QString( "%1 items:" ).arg( job.data.size() ); + //for( int i = 0; i < job.data.size(); i++ ) + //dataStuff += QString( " %1" ).arg( job.data.at( i ).size(), 0, 16, QChar( ' ' ) ); + + QString str = tr( "Received a completed download from NUS" ); + QString title = QString( "%1v%2" ).arg( job.tid, 16, 16, QChar( '0' ) ).arg( job.version ); + /*QString j = QString( "NusJob( %1, %2, %3, %4 )
" ) + .arg( job.tid, 16, 16, QChar( '0' ) ) + .arg( job.version ).arg( job.decrypt ? "decrypted" : "encrypted" ) + .arg( dataStuff ); + + ui->plainTextEdit_log->appendHtml( j );*/ + ui->plainTextEdit_log->appendHtml( str ); +return; + //do something with the data we got + if( ui->radioButton_folder->isChecked() ) + { + + } + else if( ui->radioButton_nand->isChecked() ) + { + bool ok = nand.InstallNusItem( job ); + if( ok ) + ShowMessage( tr( "Installed %1 title to nand" ).arg( title ) ); + else + ShowMessage( tr( "Error %1 title to nand" ).arg( title ) ); + } + else if( ui->radioButton_wad->isChecked() ) + { + } + + //bool r = nand.InstallNusItem( job ); + //qDebug() << "install:" << r; +} + +//clicked the button to get a title +void MainWindow::on_pushButton_GetTitle_clicked() +{ + bool ok = false; + quint64 tid = ui->lineEdit_tid->text().toLongLong( &ok, 16 ); + if( !ok ) + { + ShowMessage( "Error converting \"" + ui->lineEdit_tid->text() + "\" to a hex number." ); + return; + } + quint32 ver = 0; + if( !ui->lineEdit_version->text().isEmpty() ) + { + ver = ui->lineEdit_version->text().toInt( &ok, 10 ); + if( !ok ) + { + ShowMessage( "Error converting \"" + ui->lineEdit_version->text() + "\" to a decimal number." ); + return; + } + if( ver > 0xffff ) + { + ShowMessage( tr( "Version %1 is too high. Max is 65535" ).arg( ver ) ); + return; + } + } + //decide how we want nus to give us the title + bool decrypt = true; + if( ui->radioButton_folder->isChecked() ) + { + if( ui->lineEdit_extractPath->text().isEmpty() ) + { + ShowMessage( tr( "No path given to save downloads in." ) ); + return; + } + ui->lineEdit_extractPath->setEnabled( false ); + ui->pushButton_decFolder->setEnabled( false ); + } + else if( ui->radioButton_nand->isChecked() ) + { + if( ui->lineEdit_nandPath->text().isEmpty() ) + { + ShowMessage( tr( "No path given for nand dump base." ) ); + return; + } + ui->lineEdit_nandPath->setEnabled( false ); + ui->pushButton_nandPath->setEnabled( false ); + } + else if( ui->radioButton_wad->isChecked() ) + { + if( ui->lineEdit_wad->text().isEmpty() ) + { + ShowMessage( tr( "No path given to save wads in." ) ); + return; + } + decrypt = false; + ui->lineEdit_wad->setEnabled( false ); + ui->pushButton_wad->setEnabled( false ); + + } + + ui->radioButton_folder->setEnabled( false ); + ui->radioButton_nand->setEnabled( false ); + ui->radioButton_wad->setEnabled( false ); + //dont set these to 0 in case the button is pressed while something else is already being downloaded + //ui->progressBar_dl->setValue( 0 ); + //ui->progressBar_title->setValue( 0 ); + //ui->progressBar_whole->setValue( 0 ); + nus.SetCachePath( ui->lineEdit_cachePath->text() ); + nus.Get( tid, decrypt, ver ); +} + +//ratio buttons toggled +void MainWindow::on_radioButton_nand_toggled( bool checked ) +{ + ui->lineEdit_nandPath->setEnabled( checked ); + ui->pushButton_nandPath->setEnabled( checked ); +} + +void MainWindow::on_radioButton_folder_toggled( bool checked ) +{ + ui->lineEdit_extractPath->setEnabled( checked ); + ui->pushButton_decFolder->setEnabled( checked ); +} + +void MainWindow::on_radioButton_wad_toggled( bool checked ) +{ + ui->lineEdit_wad->setEnabled( checked ); + ui->pushButton_wad->setEnabled( checked ); +} + +//search for a path to use as the nand basepath +void MainWindow::on_pushButton_nandPath_clicked() +{ + QString path = ui->lineEdit_nandPath->text().isEmpty() ? "/media" : ui->lineEdit_nandPath->text(); + QString f = QFileDialog::getExistingDirectory( this, tr( "Select Nand Base Folder" ), path ); + if( f.isEmpty() ) + return; + + ui->lineEdit_nandPath->setText( f ); + nus.SetCachePath( ui->lineEdit_cachePath->text() ); +} + +void MainWindow::on_pushButton_decFolder_clicked() +{ + QString path = ui->lineEdit_extractPath->text().isEmpty() ? "/media" : ui->lineEdit_extractPath->text(); + QString f = QFileDialog::getExistingDirectory( this, tr( "Select folder to decrypt this title to" ), path ); + if( f.isEmpty() ) + return; + + ui->lineEdit_extractPath->setText( f ); +} + +void MainWindow::on_pushButton_wad_clicked() +{ + QString path = ui->lineEdit_wad->text().isEmpty() ? "/media" : ui->lineEdit_wad->text(); + QString f = QFileDialog::getExistingDirectory( this, tr( "Select folder to save wads to" ), path ); + if( f.isEmpty() ) + return; + + ui->lineEdit_wad->setText( f ); +} diff --git a/nand_dump/mainwindow.h b/nand_dump/mainwindow.h new file mode 100644 index 0000000..6a557ee --- /dev/null +++ b/nand_dump/mainwindow.h @@ -0,0 +1,45 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "includes.h" +#include "nusdownloader.h" +#include "nanddump.h" + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow( QWidget *parent = 0 ); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + + NusDownloader nus; + NandDump nand; + + void ShowMessage( const QString& mes ); + +public slots: + //slots for getting info from the NUS downloader + void GetError( const QString &message, NusJob job ); + void NusIsDone(); + void ReceiveTitleFromNus( NusJob job ); + + +private slots: + void on_pushButton_wad_clicked(); + void on_pushButton_decFolder_clicked(); + void on_pushButton_nandPath_clicked(); + void on_radioButton_wad_toggled(bool checked); + void on_radioButton_folder_toggled(bool checked); + void on_radioButton_nand_toggled(bool checked); + void on_pushButton_GetTitle_clicked(); +}; + +#endif // MAINWINDOW_H diff --git a/nand_dump/mainwindow.ui b/nand_dump/mainwindow.ui new file mode 100644 index 0000000..e29a1d6 --- /dev/null +++ b/nand_dump/mainwindow.ui @@ -0,0 +1,229 @@ + + + MainWindow + + + + 0 + 0 + 585 + 457 + + + + QtShitGetter + + + + + + + + + 0000000100000002 + + + 16 + + + + + + + v + + + + + + + + 107 + 16777215 + + + + 513 + + + 5 + + + + + + + Get It! + + + + + + + + + + + + + + Local Cache + + + + + + + + + + + + + Download + + + + + + + 0 + + + + + + + Title + + + + + + + 0 + + + + + + + Total + + + + + + + 0 + + + + + + + + + + + Nand + + + true + + + true + + + + + + + + + + Search... + + + + + + + Folder + + + + + + + false + + + + + + + false + + + Search... + + + + + + + Wad + + + + + + + false + + + + + + + false + + + Search... + + + + + + + + + + + + + + + + 0 + 0 + 585 + 27 + + + + + + TopToolBarArea + + + false + + + + + + + + diff --git a/nand_dump/nand.pro b/nand_dump/nand.pro new file mode 100644 index 0000000..5f1183e --- /dev/null +++ b/nand_dump/nand.pro @@ -0,0 +1,41 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2010-12-02T23:30:12 +# +#------------------------------------------------- + +QT += core gui\ + network + +TARGET = nand +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 + +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 + +FORMS += mainwindow.ui \ + settingtxtdialog.ui diff --git a/nand_dump/nanddump.cpp b/nand_dump/nanddump.cpp new file mode 100644 index 0000000..fb913ae --- /dev/null +++ b/nand_dump/nanddump.cpp @@ -0,0 +1,393 @@ +#include "nanddump.h" +#include "tiktmd.h" +#include "tools.h" + +NandDump::NandDump( const QString &path ) +{ + uidDirty = true; + cmDirty = true; + if( !path.isEmpty() ) + SetPath( path ); +} +NandDump::~NandDump() +{ + if( !basePath.isEmpty() ) + Flush();//no need to check the return, the class is destructing and we wouldnt do anything to fix it +} + +bool NandDump::Flush() +{ + bool ret = FlushUID(); + return FlushContentMap() && ret; +} + +bool NandDump::SetPath( const QString &path ) +{ + //check what is already in this path and create stuff that is missing + QFileInfo fi( path ); + basePath = fi.absoluteFilePath(); + if( fi.exists() && fi.isFile() ) + { + qWarning() << "NandDump::SetPath ->" << path << "is a file"; + return false; + } + if( !fi.exists() && !QDir().mkpath( path ) ) + { + qWarning() << "NandDump::SetPath -> cant create" << path; + return false; + } + + //make sure some subfolders are there + QDir d( path ); + if( ( !d.exists( "title" ) && !d.mkdir( "title" ) )//these should be good enough + || ( !d.exists( "ticket" ) && !d.mkdir( "ticket" ) ) + || ( !d.exists( "shared1" ) && !d.mkdir( "shared1" ) ) + || ( !d.exists( "shared2" ) && !d.mkdir( "shared2" ) ) + || ( !d.exists( "sys" ) && !d.mkdir( "sys" ) ) ) + { + qWarning() << "NandDump::SetPath -> error creating subfolders in" << path; + return false; + } + + //make sure there is a valid uid.sys + QString uidPath = fi.absoluteFilePath() + "/sys/uid.sys"; + fi.setFile( uidPath ); + if( !fi.exists() && !FlushUID() ) + { + qWarning() << "NandDump::SetPath -> can\'t create new uid.sys"; + return false; + } + else + { + QFile f( uidPath ); + if( !f.open( QIODevice::ReadOnly ) ) + { + qWarning() << "NandDump::SetPath -> error opening existing uid.sys" << uidPath; + return false; + } + QByteArray u = f.readAll(); + f.close(); + uidMap = UIDmap( u ); + uidMap.Check();//not really taking any action, but it will spit out errors in the debug output + uidDirty = false; + } + + //make sure there is a valid content.map + QString cmPath = basePath + "/shared1/content.map"; + fi.setFile( cmPath ); + if( !fi.exists() && !FlushContentMap() ) + { + qWarning() << "NandDump::SetPath -> can\'t create new content map"; + return false; + } + else + { + QFile f( cmPath ); + if( !f.open( QIODevice::ReadOnly ) ) + { + qWarning() << "NandDump::SetPath -> error opening existing content.map" << cmPath; + return false; + } + QByteArray u = f.readAll(); + f.close(); + cMap = SharedContentMap( u );//checked automatically by the constructor + cMap.Check( basePath + "/shared1" );//just checking to make sure everything is ok. + cmDirty = false; + } + + //TODO - need a setting.txt up in here + + + + return true; +} + +//write the uid to the HDD +bool NandDump::FlushUID() +{ + if( uidDirty ) + uidDirty = !SaveData( uidMap.Data(), "/sys/uid.sys" ); + return !uidDirty; +} + +//write the shared content map to the HDD +bool NandDump::FlushContentMap() +{ + if( cmDirty ) + cmDirty = !SaveData( cMap.Data(), "/shared1/content.map" ); + return !cmDirty; +} + +QByteArray NandDump::GetSettingTxt() +{ + return GetFile( "/title/00000001/00000002/data/setting.txt" ); +} + +bool NandDump::SetSettingTxt( const QByteArray ba ) +{ + QString path = basePath + "/title/00000001/00000002/data"; + if( !QFileInfo( path ).exists() && !QDir().mkpath( path ) ) + return false; + return SaveData( ba, "/title/00000001/00000002/data/setting.txt" ); +} + +const QByteArray NandDump::GetFile( const QString &path ) +{ + QFile f( basePath + path ); + if( !f.open( QIODevice::ReadOnly ) ) + { + qWarning() << "NandDump::GetFile -> cant open file for reading" << path; + return QByteArray(); + } + QByteArray ret = f.readAll(); + f.close(); + return ret; +} + +//write some file to the nand +bool NandDump::SaveData( const QByteArray ba, const QString& path ) +{ + qDebug() << "NandDump::SaveData" << path << hex << ba.size(); + QFile f( basePath + path ); + if( !f.open( QIODevice::WriteOnly ) ) + { + qWarning() << "NandDump::SaveData -> cant open file for writing" << path; + return false; + } + f.write( ba ); + f.close(); + return true; +} + +//delete a file from the nand +void NandDump::DeleteData( const QString & path ) +{ + qDebug() << "NandDump::DeleteData" << path; + QFile::remove( basePath + path ); +} + +bool NandDump::InstallTicket( const QByteArray ba, quint64 tid ) +{ + Ticket t( ba ); + if( t.Tid() != tid ) + { + qWarning() << "NandDump::InstallTicket -> bad tid" << hex << tid << t.Tid(); + return false; + } + //only write the first chunk of the ticket to the nand + QByteArray start = ba.left( t.SignedSize() ); + if( start.size() != 0x2a4 ) + { + qWarning() << "NandDump::InstallTicket -> ticket size" << hex << start.size(); + } + QString p = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ); + p.insert( 8 ,"/" ); + p.prepend( "/ticket/" ); + QString folder = p; + folder.resize( 17 ); + folder.prepend( basePath ); + if( !QFileInfo( folder ).exists() && !QDir().mkpath( folder ) ) + return false; + + p.append( ".tik" ); + return SaveData( start, p ); +} + +bool NandDump::InstallTmd( const QByteArray ba, quint64 tid ) +{ + Tmd t( ba ); + if( t.Tid() != tid ) + { + qWarning() << "NandDump::InstallTmd -> bad tid" << hex << tid << t.Tid(); + return false; + } + //only write the first chunk of the ticket to the nand + QByteArray start = ba.left( t.SignedSize() ); + QString p = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ); + p.insert( 8 ,"/" ); + p.prepend( "/title/" ); + p.append( "/content/title.tmd" ); + return SaveData( start, p ); +} + +bool NandDump::InstallSharedContent( const QByteArray ba, const QByteArray hash ) +{ + QByteArray h = hash; + if( h.isEmpty() ) + h = GetSha1( ba ); + + if( !cMap.GetAppFromHash( h ).isEmpty() )//already have this one + return true; + + //qDebug() << "adding shared content"; + QString appName = cMap.GetNextEmptyCid(); + QString p = "/shared1/" + appName + ".app"; + if( !SaveData( ba, p ) ) + return false; + + cMap.AddEntry( appName, hash ); + cmDirty = true; + return true; +} + +bool NandDump::InstallPrivateContent( const QByteArray ba, quint64 tid, const QString &cid ) +{ + QString p = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ); + p.insert( 8 ,"/" ); + p.prepend( "/title/" ); + p.append( "/content/" + cid + ".app" ); + return SaveData( ba, p ); +} + +//delete all .app and .tmd files in the content folder for this tid +void NandDump::AbortInstalling( quint64 tid ) +{ + QString p = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ); + p.insert( 8 ,"/" ); + p.prepend( "/title" ); + p.append( "/content" ); + QDir d( p ); + if( !d.exists() ) + return; + + QFileInfoList fiL = d.entryInfoList( QStringList() << "*.app" << ".tmd", QDir::Files ); + foreach( QFileInfo fi, fiL ) + QFile::remove( fi.absoluteFilePath() ); +} + +bool NandDump::DeleteTitle( quint64 tid, bool deleteData ) +{ + QString tidStr = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ); + tidStr.insert( 8 ,"/" ); + QString tikPath = tidStr; + tikPath.prepend( "/ticket/" ); + tikPath.append( ".tik" ); + DeleteData( tikPath ); + + if( !deleteData ) + { + AbortInstalling( tid ); + return true; + } + + QString tPath = basePath + "/title/" + tidStr; + return RecurseDeleteFolder( tPath ); +} + +//go through and delete all the stuff in a given folder and then delete the folder itself +//this function expects an absolute path, not a relitive one inside the nand dump +bool NandDump::RecurseDeleteFolder( const QString &path ) +{ + QDir d( path ); + QFileInfoList fiL = d.entryInfoList( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot ); + foreach( QFileInfo fi, fiL ) + { + if( fi.isFile() && !QFile::remove( fi.absoluteFilePath() ) ) + { + qWarning() << "NandDump::RecurseDeleteFolder -> error deleting" << fi.absoluteFilePath(); + return false; + } + if( fi.isDir() && !RecurseDeleteFolder( fi.absoluteFilePath() ) ) + { + qWarning() << "NandDump::RecurseDeleteFolder -> error deleting" << fi.absoluteFilePath(); + return false; + } + } + return QDir().rmdir( path ); +} + +bool NandDump::InstallNusItem( NusJob job ) +{ + if( !job.tid || !job.version || job.data.size() < 3 ) + { + qWarning() << "NandDump::InstallNusItem -> invalid item"; + return false; + } + if( !uidDirty ) + { + uidDirty = uidMap.GetUid( job.tid, false ) != 0;//only frag the uid as dirty if it has to be, this way it is only flushed if needed + } + uidMap.GetUid( job.tid ); + QString p = QString( "%1" ).arg( job.tid, 16, 16, QChar( '0' ) ); + p.insert( 8 ,"/" ); + p.prepend( "/title/" ); + QString path = basePath + p + "/content"; + if( !QFileInfo( path ).exists() && !QDir().mkpath( path ) ) + return false; + + path = basePath + p + "/data"; + if( !QFileInfo( path ).exists() && !QDir().mkpath( path ) ) + return false; + + QByteArray ba = job.data.takeFirst(); + if( !InstallTmd( ba, job.tid ) ) + return false; + + Tmd t( ba ); + + ba = job.data.takeFirst(); + Ticket ti( ba ); + if( !InstallTicket( ba, job.tid ) ) + { + AbortInstalling( job.tid ); + return false; + } + + quint32 cnt = qFromBigEndian( t.payload()->num_contents ); + if( cnt != (quint32)job.data.size() ) + { + AbortInstalling( job.tid ); + return false; + } + + for( quint32 i = 0; i < cnt; i++ ) + { + //make sure the content is not encrypted + QByteArray decData; + if( job.decrypt ) + { + decData = job.data.takeFirst(); + } + else + { + //seems like a waste to keep setting the key, but for now im doing it this way + //so multiple objects can be decrypting titles at the same time + AesSetKey( ti.DecryptedKey() ); + QByteArray paddedEncrypted = PaddedByteArray( job.data.takeFirst(), 0x40 ); + decData = AesDecrypt( i, paddedEncrypted ); + decData.resize( t.Size( i ) ); + QByteArray realHash = GetSha1( decData ); + if( realHash != t.Hash( i ) ) + { + qWarning() << "NandDump::InstallNusItem -> hash doesnt match for content" << hex << i; + hexdump( realHash ); + hexdump( t.Hash( i ) ); + AbortInstalling( job.tid ); + return false; + } + } + if( t.Type( i ) == 0x8001 ) + { + if( !InstallSharedContent( decData, t.Hash( i ) ) ) + { + AbortInstalling( job.tid ); + return false; + } + } + else if( t.Type( i ) == 1 ) + { + if( !InstallPrivateContent( decData, job.tid, t.Cid( i ) ) ) + { + AbortInstalling( job.tid ); + return false; + } + } + else//unknown content type + { + qWarning() << "NandDump::InstallNusItem -> unknown content type"; + AbortInstalling( job.tid ); + return false; + } + } + return true; +} diff --git a/nand_dump/nanddump.h b/nand_dump/nanddump.h new file mode 100644 index 0000000..47d3ee4 --- /dev/null +++ b/nand_dump/nanddump.h @@ -0,0 +1,67 @@ +#ifndef NANDDUMP_H +#define NANDDUMP_H + +#include "nusdownloader.h" +#include "includes.h" +#include "sharedcontentmap.h" +#include "uidmap.h" + +class NandDump +{ +public: + NandDump( const QString &path = QString() ); + ~NandDump(); + + //sets the basepath for this nand + //if it doesnt exist, the function will try to create it + //also creates the normal folders in the nand + bool SetPath( const QString &path ); + + //installs a title to the nand dump from an already existing NusJob + //returns false if something went wrong + bool InstallNusItem( NusJob job ); + + //tries to delete a title from the nand dump + //deleteData gives the option to just delete the title and leave behind its data + 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 ); + + //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(); + + QByteArray GetSettingTxt(); + bool SetSettingTxt( const QByteArray ba ); + + const QByteArray GetFile( const QString &path ); + bool SaveData( const QByteArray ba, const QString& path ); + void DeleteData( const QString & path ); + + +private: + QString basePath; + SharedContentMap cMap; + UIDmap uidMap; + + //write the current uid.sys to disc + bool uidDirty; + bool FlushUID(); + + //write the content.map to disc + bool cmDirty; + bool FlushContentMap(); + + bool InstallTicket( const QByteArray ba, quint64 tid ); + bool InstallTmd( const QByteArray ba, quint64 tid ); + bool InstallSharedContent( const QByteArray ba, const QByteArray hash = QByteArray() ); + bool InstallPrivateContent( const QByteArray ba, quint64 tid, const QString &cid ); + void AbortInstalling( quint64 tid ); + + //go through and delete all the stuff in a given folder and then delete the folder itself + //this function expects an absolute path, not a relitive one inside the nand dump + bool RecurseDeleteFolder( const QString &path ); +}; + +#endif // NANDDUMP_H diff --git a/nand_dump/nusdownloader.cpp b/nand_dump/nusdownloader.cpp new file mode 100644 index 0000000..fe52be8 --- /dev/null +++ b/nand_dump/nusdownloader.cpp @@ -0,0 +1,1476 @@ +#include "nusdownloader.h" +#include "tools.h" + + +NusDownloader::NusDownloader( QObject *parent, const QString &cPath ) : QObject( parent ), cachePath( cPath ), curTmd( QByteArray() )//, manager( this ) +{ + currentJob.tid = 0; + currentJob.version = 0; + totalJobs = 0; + running = false; +} + +//change the cache path +void NusDownloader::SetCachePath( const QString &cPath ) +{ + cachePath = cPath; +} + +//add a single job to the list +void NusDownloader::GetTitle( NusJob job ) +{ + //qDebug() << "NusDownloader::GetTitle"; + jobList.append( job ); + totalJobs++; + + if( !running ) + { + //qDebug() << "no job is running, starting this one"; + QTimer::singleShot( 0, this, SLOT( StartNextJob() ) ); + } + running = true; +} + +//add a list of jobs to the list +void NusDownloader::GetTitles( QList jobs ) +{ + //qDebug() << "NusDownloader::GetTitles"; + jobList.append( jobs ); + totalJobs += jobs.size(); + + if( !running ) + QTimer::singleShot( 0, this, SLOT( StartNextJob() ) ); + + running = true; +} + +//add a new job to the list +void NusDownloader::Get( quint64 tid, bool decrypt, quint16 version ) +{ + NusJob j; + j.tid = tid; + j.decrypt = decrypt; + j.version = version; + + GetTitle( j ); +} + +//get how much of this title is already downloaded +quint32 NusDownloader::TitleSizeDownloaded() +{ + quint32 ret = 0; + for( int i = 0; i < currentJob.data.size(); i++ ) + ret += currentJob.data.at( i ).size(); + return ret; +} + +//start downloading the next title in the list +void NusDownloader::StartNextJob() +{ + //qDebug() << "NusDownloader::StartNextJob"; + if( jobList.isEmpty() )//nothing else to do + { + currentJob.tid = 0; + totalJobs = 0; + emit SendTitleProgress( 100 ); + emit SendTotalProgress( 100 ); + running = false; + //qDebug() << "done"; + emit SendDone(); + return; + } + //pull the first title from the list + currentJob = jobList.takeFirst(); + SendTitleProgress( 0 ); + downloadJob tmdJob; + tmdJob.tid = QString( "%1" ).arg( currentJob.tid, 16, 16, QChar( '0' ) ); + tmdJob.index = IDX_TMD; + if( currentJob.version ) + { + tmdJob.name = QString( "tmd.%1" ).arg( currentJob.version ); + QByteArray stuff = GetDataFromCache( tmdJob ); + //DbgJoB( currentJob ); + if( !stuff.isEmpty() ) + { + //qDebug() << "tmdJob.data size:" << hex << stuff.size(); + //DbgJoB( currentJob ); + ReadTmdAndGetTicket( stuff ); + } + else + { + dlJob = tmdJob; + QTimer::singleShot( 500, this, SLOT( StartDownload() ) ); + } + } + else//download the latest tmd to get the version + { + tmdJob.name = "tmd"; + dlJob = tmdJob; + QTimer::singleShot( 500, this, SLOT( StartDownload() ) ); + } + +} + +//tries to read data for the job from the PC +QByteArray NusDownloader::GetDataFromCache( downloadJob job ) +{ + //qDebug() << "NusDownloader::GetDataFromCache"; + if( cachePath.isEmpty() || !currentJob.version ) + return QByteArray(); + + QFileInfo fi( cachePath ); + if( !fi.exists() || !fi.isDir() ) + { + //qWarning() << "NusDownloader::GetDataFromCache -> cachePath is not a directory"; + return QByteArray(); + } + + QFile f( GetCachePath( job.index ) ); + if( !f.exists() || !f.open( QIODevice::ReadOnly ) ) + { + //qWarning() << "NusDownloader::GetDataFromCache -> file cant be opened for reading" << QFileInfo( f ).absoluteFilePath(); + return QByteArray(); + } + + //qDebug() << "reading data from PC"; + QByteArray ret = f.readAll(); + f.close(); + //qDebug() << "read" << hex << ret.size() << "bytes of data from" << QFileInfo( f ).absoluteFilePath(); + return ret; +} + +//load the tmd and try to get the ticket +void NusDownloader::ReadTmdAndGetTicket( QByteArray ba ) +{ + //qDebug() << "NusDownloader::ReadTmdAndGetTicket" << hex << ba.size(); + curTmd = Tmd( ba ); + if( curTmd.Tid() != currentJob.tid ) + { + qDebug() << curTmd.Tid() << currentJob.tid; + CurrentJobErrored( tr( "TID in TMD doesn't match expected." ) ); + return; + } + if( !currentJob.version ) + { + currentJob.version = qFromBigEndian( curTmd.payload()->title_version ); + } + else if( currentJob.version != qFromBigEndian( curTmd.payload()->title_version ) ) + { + CurrentJobErrored( tr( "Version in TMD doesn't match expected." ) ); + return; + } + //add the tmd data to the current job return + currentJob.data << ba; + + //calculate the total size for this title + totalTitleSize = 0; + for( quint32 i = 0; i < qFromBigEndian( curTmd.payload()->num_contents ); i++ ) + { + totalTitleSize += curTmd.Size( i ); + } + + totalTitleSize += ba.size() + 0x9a4;//ticket size for ios 9. should be good enough for everything else + + + //now get the ticket + downloadJob tikJob = CreateJob( "cetk", IDX_CETK ); + QByteArray stuff = GetDataFromCache( tikJob ); + if( stuff.isEmpty() ) + { + dlJob = tikJob; + QTimer::singleShot( 0, this, SLOT( StartDownload() ) ); + } + else + { + Ticket t( stuff ); + //set this key to decrypt contents + decKey = t.DecryptedKey(); + //AesSetKey( t.DecryptedKey() ); + //add the ticket data to the return + currentJob.data << stuff; + QTimer::singleShot( 0, this, SLOT( GetNextItemForCurrentTitle() ) ); + } +} + +//save data downloaded from the internet to local HDD for future downloads +bool NusDownloader::SaveDataToCache( const QString &path, const QByteArray &stuff ) +{ + //make sure there is all the parent folders needed to hold this folder + if( path.count( "/" ) < 4 || !path.startsWith( cachePath + "/" )) + { + qWarning() << "NusDownloader::SaveDataToCache -> bad path" << path << cachePath; + return false; + } + QString parent = path;//really ugly, but somehow still prettier than a recursing mkdir function + parent.resize( parent.lastIndexOf( "/" ) ); + parent.remove( 0, cachePath.size() + 1 ); + QDir d( cachePath ); + if( !d.exists() || !d.mkpath( parent ) ) + { + qWarning() << "NusDownloader::SaveDataToCache -> cant create directory" << d.absolutePath(); + return false; + } + QFile f( path ); + if( f.exists() ) + { + qWarning() << "NusDownloader::SaveDataToCache -> file already exists" << path; + return false; + } + if( !f.open( QIODevice::WriteOnly ) ) + { + qWarning() << "NusDownloader::SaveDataToCache -> can't create file" << path; + return false; + } + f.write( stuff );//probably should check the return values on these. but if they dont go right, then the person has bigger things to worry about + f.close(); + qDebug() << "saved" << hex << stuff.size() << "bytes to" << path; + return true; +} + +downloadJob NusDownloader::CreateJob( QString name, quint16 index ) +{ + downloadJob r; + r.tid = QString( "%1" ).arg( currentJob.tid, 16, 16, QChar( '0' ) ); + r.name = name; + r.index = index; + r.data = QByteArray(); + return r; +} + +//send an error about the current job and move to the next +void NusDownloader::CurrentJobErrored( const QString &str ) +{ + qWarning() << "NusDownloader::CurrentJobErrored ->" << str; + emit SendError( str, currentJob ); + QTimer::singleShot( 0, this, SLOT( StartNextJob() ) ); +} + +//get the next content for the current title +void NusDownloader::GetNextItemForCurrentTitle() +{ + //qDebug() << "NusDownloader::GetNextItemForCurrentTitle" << currentJob.data.size() - 2; + //DbgJoB( currentJob ); + if( currentJob.data.size() < 2 ) + { + qDebug() << "currentJob.data.size() < 2 )" << currentJob.data.size(); + CurrentJobErrored( tr( "Tried to download contents without having the TMD & Ticket") ); + return; + } + + quint32 alreadyHave = currentJob.data.size() - 2;//number of contest from this title already gotten + + if( alreadyHave >= qFromBigEndian( curTmd.payload()->num_contents ) )//WTF + { + qDebug() << "alreadyHave >= qFromBigEndian( curTmd.payload()->num_contents )" << alreadyHave << qFromBigEndian( curTmd.payload()->num_contents ); + CurrentJobErrored( tr( "Tried to download more contents then this title has." ) ); + return; + } + //send progress about how much of this title we already have + int prog = (int)( (float)( (float)TitleSizeDownloaded() / (float)totalTitleSize ) * 100.0f ); + //qDebug() << "titleProg:" << hex << TitleSizeDownloaded() << totalTitleSize << prog; + emit SendTitleProgress( prog ); + + downloadJob appJob = CreateJob( curTmd.Cid( alreadyHave ), alreadyHave ); + QByteArray stuff = GetDataFromCache( appJob ); + if( stuff.isEmpty() ) + { + dlJob = appJob; + QTimer::singleShot( 0, this, SLOT( StartDownload() ) ); + //StartDownload( appJob ); + } + else + { + //hexdump( stuff ); + if( !DecryptCheckHashAndAppendData( stuff, appJob.index ) ) + { + CurrentJobErrored( tr( "Cached data has a different hash than expected." ) ); + return; + } + //qDebug() << "hash matched for index" << alreadyHave; + if( alreadyHave + 1 < qFromBigEndian( curTmd.payload()->num_contents ) ) + QTimer::singleShot( 0, this, SLOT( GetNextItemForCurrentTitle() ) );//next content + + else + { + int progress = (int)( ( (float)( totalJobs - jobList.size() ) / (float)totalJobs ) * 100.0f ); + emit SendTotalProgress( progress ); + emit SendTitleProgress( 100 ); + emit SendData( currentJob ); + QTimer::singleShot( 0, this, SLOT( StartNextJob() ) );//start next job + } + } +} + +//get a path for an item in the cache +QString NusDownloader::GetCachePath( quint32 idx ) +{ + //qDebug() << "NusDownloader::GetCachePath" << currentJob.version << currentJob.tid; + if( !currentJob.version || !currentJob.tid )//c'mon guy + return QString(); + + QString path = cachePath; + if( path.endsWith( "/" ) ) + path.resize( path.size() - 1 ); + QString idPath = QString( "/%1" ).arg( currentJob.tid, 16, 16, QChar( '0' ) ); + idPath.insert( 9, "/" ); + QString verPath = QString( "/v%1/" ).arg( currentJob.version ); + path += idPath + verPath; + switch( idx ) + { + case IDX_CETK: + path += "cetk"; + break; + case IDX_TMD: + path += QString( "tmd.%1" ).arg( currentJob.version ); + break; + default: + path += curTmd.Cid( idx ); + break; + } + return path; +} + +//print info about a job +void NusDownloader::DbgJoB( NusJob job ) +{ + QString dataStuff = QString( "%1 items:" ).arg( job.data.size() ); + for( int i = 0; i < job.data.size(); i++ ) + dataStuff += QString( " %1" ).arg( job.data.at( i ).size(), 0, 16, QChar( ' ' ) ); + + qDebug() << QString( "NusJob( %1, %2, %3, %4 )" ) + .arg( job.tid, 16, 16, QChar( '0' ) ) + .arg( job.version ).arg( job.decrypt ? "decrypted" : "encrypted" ) + .arg( dataStuff ); +} + +//check a hash and add the data to the return item +bool NusDownloader::DecryptCheckHashAndAppendData( const QByteArray &encData, quint16 idx ) +{ + //seems like a waste to keep setting the key, but for now im doing it this way + //so multiple objects can be decrypting titles at the same time by different objects + AesSetKey( decKey ); + + //qDebug() << "NusDownloader::DecryptCheckHashAndAppendData" << hex << encData.size() << idx; + QByteArray paddedEncrypted = PaddedByteArray( encData, 0x40 ); + QByteArray decData = AesDecrypt( idx, paddedEncrypted ); + decData.resize( curTmd.Size( idx ) ); + QByteArray realHash = GetSha1( decData ); + if( realHash != curTmd.Hash( idx ) ) + { + qWarning() << "NusDownloader::DecryptCheckHashAndAppendData -> hash doesnt match for content" << hex << idx; + //CurrentJobErrored( tr( "Downloaded data has a different hash than expected." ) ); + hexdump( realHash ); + hexdump( curTmd.Hash( idx ) ); + return false; + } + //add whatever data is requested to the return + if( currentJob.decrypt ) + currentJob.data << decData; + else + currentJob.data << encData; + + return true; +} + +//something is done downloading +void NusDownloader::FileIsFinishedDownloading( downloadJob job ) +{ + //qDebug() << "NusDownloader::FileIsFinishedDownloading" << job.index; + if( job.data.isEmpty() ) + { + qWarning() << "NusDownloader::FileIsFinishedDownloading -> got empty data in return"; + CurrentJobErrored( tr( "Error downloading, returned empty data" ) ); + return; + } + + //this is kinda ugly, but we need to get the path to save the data in the cache + //and since we are using all these asyncronous signals and slots, we have to get cPath at different times for different situations + QString cPath; + switch( job.index ) + { + case IDX_TMD: + { + ReadTmdAndGetTicket( job.data ); + cPath = GetCachePath( job.index ); + } + break; + case IDX_CETK: + { + Ticket t( job.data ); + decKey = t.DecryptedKey(); + //set this key to decrypt contents + //AesSetKey( t.DecryptedKey() ); + //add the ticket data to the return + currentJob.data << job.data; + //start downloading the contents + GetNextItemForCurrentTitle(); + + cPath = GetCachePath( job.index ); + } + break; + default: + { + if( job.index > qFromBigEndian( curTmd.payload()->num_contents ) ) + { + qWarning() << "NusDownloader::FileIsFinishedDownloading -> received data that doesnt fit anywhere"; + CurrentJobErrored( tr( "I have confused myself and cannot find where some downloaded data goes." ) ); + return; + } + if( job.index != currentJob.data.size() - 2 ) + { + qWarning() << "NusDownloader::FileIsFinishedDownloading -> index doesnt match what it should"; + CurrentJobErrored( tr( "I have confused myself and cannot find where some downloaded data goes." ) ); + return; + } + if( !DecryptCheckHashAndAppendData( job.data, job.index ) ) + { + CurrentJobErrored( tr( "Downloaded data has a different hash than expected." ) ); + return; + } + + + if( job.index == qFromBigEndian( curTmd.payload()->num_contents ) - 1 )//this is the last content for this title + { + int progress = (int)( ( (float)( totalJobs - jobList.size() ) / (float)totalJobs ) * 100.0f ); + emit SendTotalProgress( progress ); + emit SendTitleProgress( 100 ); + emit SendData( currentJob ); + QTimer::singleShot( 0, this, SLOT( StartNextJob() ) );//move on to next job + } + + else + QTimer::singleShot( 0, this, SLOT( GetNextItemForCurrentTitle() ) );//next content + + cPath = GetCachePath( job.index ); + } + break; + } + + //try to save this data to the cache + if( cPath.isEmpty() ) + return; + + SaveDataToCache( cPath, job.data ); +} + +//get something from somewhere +void NusDownloader::StartDownload() +{ + //qDebug() << "NusDownloader::StartDownload" << dlJob.index; + emit SendDownloadProgress( 0 ); + QString dlUrl = NUS_BASE_URL + dlJob.tid + "/" + dlJob.name; + qDebug() << "url" << dlUrl; + currentJobText = dlUrl; + + QUrl url( dlUrl ); + + QNetworkRequest request( url ); + request.setRawHeader("User-Agent", UPDATING_USER_AGENT ); + + currentDownload = manager.get( request ); + connect( currentDownload, SIGNAL( downloadProgress( qint64, qint64 ) ), this, SLOT( downloadProgress( qint64, qint64 ) ) ); + connect( currentDownload, SIGNAL( finished() ), this, SLOT( downloadFinished() ) ); + connect( currentDownload, SIGNAL( readyRead() ), this, SLOT( downloadReadyRead() ) ); + + downloadTime.start(); +} + +//get a progress update from a download and turn it into some signals with text and numbers +void NusDownloader::downloadProgress( qint64 bytesReceived, qint64 bytesTotal ) +{ + //qDebug() << "NusDownloader::downloadProgress" << bytesTotal; + Q_UNUSED( bytesTotal ); + // calculate the download speed + double speed = bytesReceived * 1000.0 / downloadTime.elapsed(); + QString unit; + if( speed < 1024 ) + { + unit = "bytes/sec"; + } + else if( speed < 1024 * 1024 ) + { + speed /= 1024; + unit = "kB/s"; + } + else + { + speed /= 1024*1024; + unit = "MB/s"; + } + emit SendText( currentJobText + " " + QString::fromLatin1( "%1 %2" ).arg( speed, 3, 'f', 1 ).arg( unit ) ); + int progress = (int)( ( (float)bytesReceived / (float)bytesTotal ) * 100.0f ); + emit SendDownloadProgress( progress ); +} + +//file is done downloading +void NusDownloader::downloadFinished() +{ + //qDebug() << "NusDownloader::downloadFinished"; + if( currentDownload->error() ) + { + qDebug() << "currentDownload->error()"; + CurrentJobErrored( tr( "Error downloading part of the title." ) ); + } + else + { + emit SendDownloadProgress( 100 ); + FileIsFinishedDownloading( dlJob ); + + } + currentDownload->deleteLater(); +} + +//read from the file that is downloading +void NusDownloader::downloadReadyRead() +{ + //qDebug() << "NusDownloader::downloadReadyRead"; + dlJob.data += currentDownload->readAll(); +} + +bool NusDownloader::GetUpdate( const QString & upd, bool decrypt ) +{ + QString s = upd.toLower(); + QMap< quint64, quint16 > titles; + + titles.insert( 0x1000248414741ull, 0x3 );//news channel HAGA + titles.insert( 0x1000248414641ull, 0x3 );//Weather Channel HAFA + + if( s.endsWith( "u" ) )//add titles as they are on the wiimposter logs + { + /* +00010002-HAAA 00010002-48414141 Photo Channel +00010002-HABA 00010002-48414241 Shopping Channel +00010002-HACA 00010002-48414341 Mii Channel +00010002-HAFx, 00010002-HAFA 00010002-484146xx, 00010002-48414641 Weather Channel +(both HAFA and HAFx for your region are factory installed) +00010002-HAGx, 00010002-HAGA 00010002-484147xx, 00010002-48414741 News Channel +(both HAGA and HAGx for your region are factory installed) +00010002-HAYA 00010002-48415941 Photo Channel 1.1 +00010001-HAEA 00010001-48414541 Wii Message Board +(Not a channel by itself, but mailbox archives are stored under this name) + +00010008-HAKx 00010008-48414bxx EULA +00010008-HALx 00010008-48414cxx rgnsel (region select) +*/ + //2.2 ( v193 ) is on NUS but the impersonator doesnt have a log for it. need to get it from a game + + + //3.2u update + /* + //3.3u changes + + + //3.4u update + titles.insert( 0x100000032ull, 0x1319 ); + titles.insert( 0x1000000feull, 0x2 ); + titles.insert( 0x100000002ull, 0x181 ); + titles.insert( 0x10000001eull, 0xb00 ); + titles.insert( 0x10000001full, 0xc10 ); + titles.insert( 0x100000025ull, 0xe19 ); + titles.insert( 0x100000100ull, 0x5 ); + titles.insert( 0x100000101ull, 0x9 ); + titles.insert( 0x1000248414341ull, 0x6 ); + titles.insert( 0x100000010ull, 0x200 ); + titles.insert( 0x100000026ull, 0xe1a ); + titles.insert( 0x100000035ull, 0x141d ); + titles.insert( 0x100000037ull, 0x141d ); + titles.insert( 0x10000003cull, 0x181e ); + titles.insert( 0x10000003dull, 0x131a ); + titles.insert( 0x100000009ull, 0x209 ); + titles.insert( 0x10000000cull, 0xc ); + titles.insert( 0x10000000dull, 0x10 ); + titles.insert( 0x10000000eull, 0x107 ); + titles.insert( 0x10000000full, 0x10a ); + titles.insert( 0x100000011ull, 0x206 ); + titles.insert( 0x100000015ull, 0x20d ); + titles.insert( 0x100000016ull, 0x309 ); + titles.insert( 0x10000001cull, 0x50d ); + titles.insert( 0x10000001full, 0xc14 ); + titles.insert( 0x100000021ull, 0xb12 ); + titles.insert( 0x100000022ull, 0xc0f ); + titles.insert( 0x100000032ull, 0x1319 ); + titles.insert( 0x100000024ull, 0xc16 ); + titles.insert( 0x100000025ull, 0xe1c ); + titles.insert( 0x1000000feull, 0x3 ); + + //4.0u update + titles.insert( 0x100000002ull, 0x1a1 ); + titles.insert( 0x100000032ull, 0x1400 ); + titles.insert( 0x100000033ull, 0x1300 ); + titles.insert( 0x1000248414241ull, 0x10 ); + titles.insert( 0x1000248415941ull, 0x3 ); + + //4.1u update + titles.insert( 0x100000002ull, 0x1c1 ); + + //4.2u update + //titles.insert( 0x100000001ull, 0x4 );//make people really ask for the boot2 update if they want it + titles.insert( 0x100000038ull, 0x151d ); + titles.insert( 0x100000039ull, 0x161d ); + titles.insert( 0x100000046ull, 0x1a1f ); + titles.insert( 0x1000000deull, 0xff00 ); + titles.insert( 0x1000000dfull, 0xff00 ); + titles.insert( 0x1000000f9ull, 0xff00 ); + titles.insert( 0x1000000faull, 0xff00 ); + titles.insert( 0x100000002ull, 0x1e1 ); + titles.insert( 0x100000009ull, 0x30a ); + titles.insert( 0x10000000cull, 0x10d ); + titles.insert( 0x10000000dull, 0x111 ); + titles.insert( 0x10000000eull, 0x208 ); + titles.insert( 0x10000000full, 0x20b ); + titles.insert( 0x100000011ull, 0x307 ); + titles.insert( 0x100000015ull, 0x30e ); + titles.insert( 0x100000016ull, 0x40d ); + titles.insert( 0x10000001cull, 0x60e ); + titles.insert( 0x10000001full, 0xd15 ); + titles.insert( 0x100000021ull, 0xc13 ); + titles.insert( 0x100000022ull, 0xd14 ); + titles.insert( 0x100000023ull, 0xd15 ); + titles.insert( 0x100000024ull, 0xd17 ); + titles.insert( 0x100000025ull, 0xf1d ); + titles.insert( 0x100000026ull, 0xf1b ); + titles.insert( 0x100000035ull, 0x151e ); + titles.insert( 0x100000037ull, 0x151e ); + titles.insert( 0x10000003cull, 0x1900 ); + titles.insert( 0x10000003dull, 0x151d ); + titles.insert( 0x1000000feull, 0x104 ); + titles.insert( 0x100000100ull, 0x6 ); + titles.insert( 0x100000101ull, 0xa ); + titles.insert( 0x1000248414241ull, 0x12 ); + + //4.3u update + //4.3u update + titles.insert( 0x100000009ull, 0x40a ); // IOS9 + titles.insert( 0x10000000cull, 0x20e ); // IOS12 + titles.insert( 0x10000000dull, 0x408 ); // IOS13 + titles.insert( 0x10000000eull, 0x408 ); // IOS14 + titles.insert( 0x10000000full, 0x408 ); // IOS15 + titles.insert( 0x100000011ull, 0x408 ); // IOS17 + titles.insert( 0x100000015ull, 0x40f ); // IOS21 + titles.insert( 0x100000016ull, 0x50e ); // IOS22 + titles.insert( 0x10000001cull, 0x70f ); // IOS28 + titles.insert( 0x10000001full, 0xe18 ); // IOS31 + titles.insert( 0x100000021ull, 0xe18 ); // IOS33 + titles.insert( 0x100000021ull, 0xe18 ); // IOS34 + titles.insert( 0x100000023ull, 0xe18 ); // IOS35 + titles.insert( 0x100000024ull, 0xe18 ); // IOS36 + titles.insert( 0x100000025ull, 0x161f ); // IOS37 + titles.insert( 0x100000026ull, 0x101c ); // IOS38 + titles.insert( 0x100000028ull, 0xc00 ); // IOS40 + titles.insert( 0x100000029ull, 0xe17 ); // IOS41 + titles.insert( 0x10000002bull, 0xe17 ); // IOS43 + titles.insert( 0x10000002dull, 0xe17 ); // IOS45 + titles.insert( 0x10000002eull, 0xe17 ); // IOS46 + titles.insert( 0x100000030ull, 0x101c ); // IOS48 + titles.insert( 0x100000034ull, 0x1700 ); // IOS52 + titles.insert( 0x100000035ull, 0x161f ); // IOS53 + titles.insert( 0x100000037ull, 0x161f ); // IOS55 + titles.insert( 0x100000038ull, 0x161e ); // IOS56 + titles.insert( 0x100000039ull, 0x171f ); // IOS57 + titles.insert( 0x10000003aull, 0x1820 ); // IOS58 + titles.insert( 0x10000003dull, 0x161e ); // IOS61 + titles.insert( 0x100000046ull, 0x1b00 ); // IOS70 + titles.insert( 0x100000050ull, 0x1b20 ); // IOS80 + titles.insert( 0x1000000feull, 0xff00 ); // IOS254 + titles.insert( 0x100000002ull, 0x201 ); // SystemMenu 4.3U + titles.insert( 0x1000248414241ull, 0x14 ); // ShopChannel + titles.insert( 0x1000848414b45ull, 0x3 ); // EULA*/ + + + + QMap< quint64, quint16 >::ConstIterator i = titles.begin(); + while( i != titles.end() ) + { + Get( i.key(), decrypt, i.value() ); + i++; + } + + + /* + + + + */ + + /* + 3.0e ( from GH3 ) + boot2v2 + 11v10 + 12v6 + 13v10 + 15v257 + 17v512 + 20v12 + 21v514 + 30v1039 + 31v1039 + bcv2 + eula_EUv2 + forecast_EUv7 + miosv5 + newsv3 + news_EUv7 + nigaoeNRv4 + photov1 + rgnsel_EUv2 + shoppingv7 + weatherv3 + sysmenuv226 + + 3.1e ( from rayman raving rabbids tv party ) + boot2v2 + 11v10 + 12v6 + 13v10 + 14v257 + 15v257 + 17v512 + 20v12 + 21v514 + 22v772 + 28v1288 + 30v1040 + 31v1040 + 33v1040 + 34v1039 + 35v1040 + 36v1042 + bcv2 + eula_EUv2 + forecast_EUv7 + miosv5 + newsv3 + news_EUv7 + nigaoeNRv4 + photo2v1 + rgnsel_EUv2 + shoppingv7 + weatherv3 + sysmenuv258 + +*/ + +//3.4e update +titles.insert( 0x100000004ull, 0xff00 ); // IOS4 +titles.insert( 0x100000009ull, 0x209 ); // IOS9 +titles.insert( 0x10000000aull, 0x300 ); // IOS10 +titles.insert( 0x10000000bull, 0x100 ); // IOS11 +titles.insert( 0x10000000cull, 0xb ); // IOS12 +titles.insert( 0x10000000dull, 0xf ); // IOS13 +titles.insert( 0x10000000eull, 0x106 ); // IOS14 +titles.insert( 0x10000000full, 0x109 ); // IOS15 +titles.insert( 0x100000011ull, 0x205 ); // IOS17 +titles.insert( 0x100000014ull, 0x100 ); // IOS20 +titles.insert( 0x100000015ull, 0x20a ); // IOS21 +titles.insert( 0x100000016ull, 0x309 ); // IOS22 +titles.insert( 0x10000001cull, 0x50c ); // IOS28 +titles.insert( 0x10000001eull, 0xb00 ); // IOS30 +titles.insert( 0x10000001full, 0xc10 ); // IOS31 +titles.insert( 0x100000021ull, 0xb10 ); // IOS33 +titles.insert( 0x100000022ull, 0xc0f ); // IOS34 +titles.insert( 0x100000023ull, 0xc10 ); // IOS35 +titles.insert( 0x100000024ull, 0xc12 ); // IOS36 +titles.insert( 0x100000025ull, 0xe19 ); // IOS37 +titles.insert( 0x100000032ull, 0x1319 ); // IOS50 +titles.insert( 0x100000033ull, 0x1219 ); // IOS51 +titles.insert( 0x1000000feull, 0x2 ); // IOS254 +titles.insert( 0x100000002ull, 0x182 ); // SystemMenu 3.4E +titles.insert( 0x100000100ull, 0x5 ); // BC +titles.insert( 0x100000101ull, 0x9 ); // MIOS +titles.insert( 0x1000248414141, 0x2 ); // Channel HAAA +titles.insert( 0x1000248414241, 0xd ); // Channel HABA +titles.insert( 0x1000248414341, 0x6 ); // Channel HACA +titles.insert( 0x1000248414650, 0x7 ); // Channel HAFP +titles.insert( 0x1000248414750, 0x7 ); // Channel HAGP +titles.insert( 0x1000248415941, 0x2 ); // Channel HAYA +titles.insert( 0x1000248414b50, 0x2 ); // Channel HAKP +titles.insert( 0x1000248414c50, 0x2 ); // Channel HALP + +//4.0e +titles.insert( 0x100000009ull, 0x209 ); // IOS9 +titles.insert( 0x10000000cull, 0xc ); // IOS12 +titles.insert( 0x10000000dull, 0x10 ); // IOS13 +titles.insert( 0x10000000eull, 0x107 ); // IOS14 +titles.insert( 0x10000000full, 0x10a ); // IOS15 +titles.insert( 0x100000010ull, 0x200 ); // IOS16 +titles.insert( 0x100000011ull, 0x206 ); // IOS17 +titles.insert( 0x100000015ull, 0x20d ); // IOS21 +titles.insert( 0x100000016ull, 0x30c ); // IOS22 +titles.insert( 0x10000001cull, 0x50d ); // IOS28 +titles.insert( 0x10000001full, 0xc14 ); // IOS31 +titles.insert( 0x100000021ull, 0xb12 ); // IOS33 +titles.insert( 0x100000022ull, 0xc13 ); // IOS34 +titles.insert( 0x100000023ull, 0xc14 ); // IOS35 +titles.insert( 0x100000024ull, 0xc16 ); // IOS36 +titles.insert( 0x100000025ull, 0xe1c ); // IOS37 +titles.insert( 0x100000026ull, 0xe1a ); // IOS38 +titles.insert( 0x100000032ull, 0x1400 ); // IOS50 +titles.insert( 0x100000033ull, 0x1300 ); // IOS51 +titles.insert( 0x100000035ull, 0x141d ); // IOS53 +titles.insert( 0x100000037ull, 0x141d ); // IOS55 +titles.insert( 0x10000003cull, 0x181e ); // IOS60 +titles.insert( 0x10000003dull, 0x131a ); // IOS61 +titles.insert( 0x1000000feull, 0x3 ); // IOS254 +titles.insert( 0x1000248414241, 0x10 ); // Channel HABA +titles.insert( 0x1000248415941, 0x3 ); // Channel HAYA +titles.insert( 0x100000002ull, 0x1a2 ); // SystemMenu 4.0E + +//4.1E +titles.insert( 0x100000002ull, 0x1c2 ); // SystemMenu 4.1E + +//4.2E +//titles.insert( 0x100000001ull, 0x4 );//make people really ask for the boot2 update if they want it +titles.insert( 0x100000009ull, 0x30a ); // IOS9 +titles.insert( 0x10000000cull, 0x10d ); // IOS12 +titles.insert( 0x10000000dull, 0x111 ); // IOS13 +titles.insert( 0x10000000eull, 0x208 ); // IOS14 +titles.insert( 0x10000000full, 0x20b ); // IOS15 +titles.insert( 0x100000011ull, 0x307 ); // IOS17 +titles.insert( 0x100000015ull, 0x30e ); // IOS21 +titles.insert( 0x100000016ull, 0x40d ); // IOS22 +titles.insert( 0x10000001cull, 0x60e ); // IOS28 +titles.insert( 0x10000001full, 0xd15 ); // IOS31 +titles.insert( 0x100000021ull, 0xc13 ); // IOS33 +titles.insert( 0x100000022ull, 0xd14 ); // IOS34 +titles.insert( 0x100000023ull, 0xd15 ); // IOS35 +titles.insert( 0x100000024ull, 0xd17 ); // IOS36 +titles.insert( 0x100000025ull, 0xf1d ); // IOS37 +titles.insert( 0x100000026ull, 0xf1b ); // IOS38 +titles.insert( 0x100000035ull, 0x151e ); // IOS53 +titles.insert( 0x100000037ull, 0x151e ); // IOS55 +titles.insert( 0x100000038ull, 0x151d ); // IOS56 +titles.insert( 0x100000039ull, 0x161d ); // IOS57 +titles.insert( 0x10000003cull, 0x1900 ); // IOS60 +titles.insert( 0x10000003dull, 0x151d ); // IOS61 +titles.insert( 0x100000046ull, 0x1a1f ); // IOS70 +titles.insert( 0x1000000deull, 0xff00 ); // IOS222 +titles.insert( 0x1000000dfull, 0xff00 ); // IOS223 +titles.insert( 0x1000000f9ull, 0xff00 ); // IOS249 +titles.insert( 0x1000000faull, 0xff00 ); // IOS250 +titles.insert( 0x1000000feull, 0x104 ); // IOS254 +titles.insert( 0x100000100ull, 0x6 ); // BC +titles.insert( 0x100000101ull, 0xa ); // MIOS +titles.insert( 0x1000248414241, 0x11 ); // Channel HABA +titles.insert( 0x1000248414241ull, 0x12 ); // ShopChannel +titles.insert( 0x100000002ull, 0x1e2 ); // SystemMenu 4.2E + + + + + + + + + + + } + return true; + } + +QMap< quint64, quint16 > NusDownloader::List30u() +{ + QMap< quint64, quint16 > titles; + //( from GH3 ) + //titles.insert( 0x100000001ull, 2 );//boot2 + titles.insert( 0x100000002ull, 225 );//sys menu + titles.insert( 0x10000000bull, 10 );//11v10 + titles.insert( 0x10000000cull, 6 );//12v6 + titles.insert( 0x10000000dull, 10 );//13v10 + titles.insert( 0x10000000full, 257 );//15v257 + titles.insert( 0x100000011ull, 512 );//17v512 + titles.insert( 0x100000014ull, 12 );//20v12 + titles.insert( 0x100000015ull, 514 );//21v514 + titles.insert( 0x10000001eull, 1039 );//30v1039 + titles.insert( 0x10000001full, 1039 );//31v1039 + titles.insert( 0x100000100ull, 0x2 );//bcv2 + titles.insert( 0x100000101ull, 0x5 );//miosv5 + titles.insert( 0x1000848414B45ull, 0x2 );//EULA - HAKE + titles.insert( 0x1000848414C45ull, 0x2 );//regsel + titles.insert( 0x1000248414645ull, 0x7 );//forecast + titles.insert( 0x1000248414745ull, 0x7 );//news_USv7 + titles.insert( 0x1000248414341ull, 0x4 );//nigaoeNRv4 - MII + titles.insert( 0x1000248414141ull, 0x1 );//photov1 + titles.insert( 0x1000248414241ull, 0x7 );//shoppingv7 + titles.insert( 0x1000248414741ull, 0x3 );//news channel HAGA + titles.insert( 0x1000248414641ull, 0x3 );//Weather Channel HAFA + return titles; + } + +QMap< quint64, quint16 > NusDownloader::List31u() +{ + QMap< quint64, quint16 > titles = List30u(); + //( from rockband2 ) + //titles.insert( 0x100000001ull, 2 );//boot2 + titles.insert( 0x100000002ull, 257 );//sys menu + titles.insert( 0x10000000eull, 262 );//14v262 - should actually be 14v257 but that version isnt available on NUS + titles.insert( 0x10000001eull, 1040 );//30v1040 + titles.insert( 0x10000001full, 1040 );//31v1040 + titles.insert( 0x100000021ull, 1040 );//33v1040 + titles.insert( 0x100000022ull, 1039 );//34v1040 + titles.insert( 0x100000023ull, 1040 );//35v1040 + titles.insert( 0x100000024ull, 1042 );//36v1040 + titles.insert( 0x100000025ull, 2070 );//37v2070 + titles.insert( 0x1000248415941ull, 0x1 );//photo2v1 ?? FIXME: this disc didnt have the photochannelv1 on it, but wiimpersonator logs say it is present is 3.2u + return titles; +} + +QMap< quint64, quint16 > NusDownloader::List32u() +{ + QMap< quint64, quint16 > titles = List31u(); + titles.insert( 0x100000002ull, 0x121 ); + titles.insert( 0x10000000bull, 0xa ); + titles.insert( 0x10000000cull, 0x6 ); + titles.insert( 0x10000000dull, 0xa ); + titles.insert( 0x10000000full, 0x104 ); + titles.insert( 0x100000011ull, 0x200 ); + titles.insert( 0x100000014ull, 0xc ); + titles.insert( 0x100000015ull, 0x205 ); + titles.insert( 0x10000001eull, 0xa10 ); + titles.insert( 0x10000001full, 0x410 ); + titles.insert( 0x100000021ull, 0x410 ); + titles.insert( 0x100000022ull, 0x40f ); + titles.insert( 0x100000023ull, 0x410 ); + titles.insert( 0x100000025ull, 0x816 ); + titles.insert( 0x100000100ull, 0x2 ); + titles.insert( 0x100000101ull, 0x5 ); + titles.insert( 0x1000248414141ull, 0x1 ); + titles.insert( 0x1000248414241ull, 0x8 ); + titles.insert( 0x1000248414341ull, 0x4 ); + titles.insert( 0x1000248414645ull, 0x7 ); + titles.insert( 0x1000248414745ull, 0x7 ); + titles.insert( 0x1000248415941ull, 0x1 ); + titles.insert( 0x1000848414B45ull, 0x2 ); + titles.insert( 0x1000848414C45ull, 0x2 ); + return titles; +} + +QMap< quint64, quint16 > NusDownloader::List33u() +{ + QMap< quint64, quint16 > titles = List32u(); + titles.insert( 0x100000002ull, 0x161 ); + titles.insert( 0x100000004ull, 0xff00 ); + titles.insert( 0x100000009ull, 0x208 ); + titles.insert( 0x10000000aull, 0x300 ); + titles.insert( 0x10000000bull, 0x100 ); + titles.insert( 0x10000000cull, 0xb ); + titles.insert( 0x10000000dull, 0xf ); + titles.insert( 0x10000000eull, 0x106 ); + titles.insert( 0x10000000full, 0x109 ); + titles.insert( 0x100000011ull, 0x205 ); + titles.insert( 0x100000014ull, 0x100 ); + titles.insert( 0x100000015ull, 0x20a ); + titles.insert( 0x100000016ull, 0x309 ); + titles.insert( 0x10000001cull, 0x50c ); + titles.insert( 0x10000001full, 0xa10 ); + titles.insert( 0x100000021ull, 0x3b1 ); + titles.insert( 0x100000022ull, 0xc0f ); + titles.insert( 0x100000023ull, 0xc10 ); + titles.insert( 0x100000024ull, 0xc12 ); + titles.insert( 0x100000033ull, 0x1219 ); + titles.insert( 0x100000100ull, 0x4 ); + titles.insert( 0x100000101ull, 0x8 ); + titles.insert( 0x1000248414341ull, 0x5 ); + titles.insert( 0x1000248414141ull, 0x2 ); + titles.insert( 0x1000248414241ull, 0xd ); + titles.insert( 0x1000248415941ull, 0x2 ); + return titles; +} + +QMap< quint64, quint16 > NusDownloader::List34u() +{ + QMap< quint64, quint16 > titles = List33u(); + titles.insert( 0x100000032ull, 0x1319 ); + titles.insert( 0x1000000feull, 0x2 ); + titles.insert( 0x100000002ull, 0x181 ); + titles.insert( 0x10000001eull, 0xb00 ); + titles.insert( 0x10000001full, 0xc10 ); + titles.insert( 0x100000025ull, 0xe19 ); + titles.insert( 0x100000100ull, 0x5 ); + titles.insert( 0x100000101ull, 0x9 ); + titles.insert( 0x1000248414341ull, 0x6 ); + titles.insert( 0x100000010ull, 0x200 ); + titles.insert( 0x100000026ull, 0xe1a ); + titles.insert( 0x100000035ull, 0x141d ); + titles.insert( 0x100000037ull, 0x141d ); + titles.insert( 0x10000003cull, 0x181e ); + titles.insert( 0x10000003dull, 0x131a ); + titles.insert( 0x100000009ull, 0x209 ); + titles.insert( 0x10000000cull, 0xc ); + titles.insert( 0x10000000dull, 0x10 ); + titles.insert( 0x10000000eull, 0x107 ); + titles.insert( 0x10000000full, 0x10a ); + titles.insert( 0x100000011ull, 0x206 ); + titles.insert( 0x100000015ull, 0x20d ); + titles.insert( 0x100000016ull, 0x309 ); + titles.insert( 0x10000001cull, 0x50d ); + titles.insert( 0x10000001full, 0xc14 ); + titles.insert( 0x100000021ull, 0xb12 ); + titles.insert( 0x100000022ull, 0xc0f ); + titles.insert( 0x100000032ull, 0x1319 ); + titles.insert( 0x100000024ull, 0xc16 ); + titles.insert( 0x100000025ull, 0xe1c ); + titles.insert( 0x1000000feull, 0x3 ); + return titles; +} + +QMap< quint64, quint16 > NusDownloader::List40u() +{ + QMap< quint64, quint16 > titles = List34u(); +} + +/* + + QMap< quint64, quint16 > NusDownloader::List40u(); + QMap< quint64, quint16 > NusDownloader::List41u(); + QMap< quint64, quint16 > NusDownloader::List42u(); + QMap< quint64, quint16 > NusDownloader::List43u(); + + +*//* +//4.3U +titles.insert( 0x100000009ull, 0x40a ); // IOS9 +titles.insert( 0x10000000cull, 0x20e ); // IOS12 +titles.insert( 0x10000000dull, 0x408 ); // IOS13 +titles.insert( 0x10000000eull, 0x408 ); // IOS14 +titles.insert( 0x10000000full, 0x408 ); // IOS15 +titles.insert( 0x100000011ull, 0x408 ); // IOS17 +titles.insert( 0x100000015ull, 0x40f ); // IOS21 +titles.insert( 0x100000016ull, 0x50e ); // IOS22 +titles.insert( 0x10000001cull, 0x70f ); // IOS28 +titles.insert( 0x10000001full, 0xe18 ); // IOS31 +titles.insert( 0x100000021ull, 0xe18 ); // IOS33 +titles.insert( 0x100000021ull, 0xe18 ); // IOS34 +titles.insert( 0x100000023ull, 0xe18 ); // IOS35 +titles.insert( 0x100000024ull, 0xe18 ); // IOS36 +titles.insert( 0x100000025ull, 0x161f ); // IOS37 +titles.insert( 0x100000026ull, 0x101c ); // IOS38 +titles.insert( 0x100000028ull, 0xc00 ); // IOS40 +titles.insert( 0x100000029ull, 0xe17 ); // IOS41 +titles.insert( 0x10000002bull, 0xe17 ); // IOS43 +titles.insert( 0x10000002dull, 0xe17 ); // IOS45 +titles.insert( 0x10000002eull, 0xe17 ); // IOS46 +titles.insert( 0x100000030ull, 0x101c ); // IOS48 +titles.insert( 0x100000034ull, 0x1700 ); // IOS52 +titles.insert( 0x100000035ull, 0x161f ); // IOS53 +titles.insert( 0x100000037ull, 0x161f ); // IOS55 +titles.insert( 0x100000038ull, 0x161e ); // IOS56 +titles.insert( 0x100000039ull, 0x171f ); // IOS57 +titles.insert( 0x10000003aull, 0x1820 ); // IOS58 +titles.insert( 0x10000003dull, 0x161e ); // IOS61 +titles.insert( 0x100000046ull, 0x1b00 ); // IOS70 +titles.insert( 0x100000050ull, 0x1b20 ); // IOS80 +titles.insert( 0x1000000feull, 0xff00 ); // IOS254 +titles.insert( 0x100000002ull, 0x201 ); // SystemMenu 4.3U +titles.insert( 0x1000248414241ull, 0x14 ); // ShopChannel +titles.insert( 0x1000848414b45ull, 0x3 ); // EULA + +//2.1e ( paper mario ) +//IOS11 v10, IOS12 v6, IOS13 v10, IOS15 v257, IOS17 v512, IOS21 v514, bc v2, EULA EU v0, +//MIOS v4, News v3, NigaoeNR-v2, PhotoChannel v1, Shopping Channel v4, Weather Channel v3, SystemMenu v162 +2.1e( metriod 3 ) - different than the paper mario one + ftp://10.0.8.53/dvd/0%20Update/files/_sys/BOOT2-v2-64.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/IOS11-64-v10.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/IOS12-64-v6.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/IOS13-64-v10.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/IOS15-64-v257.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/IOS17-64-v512.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/IOS20-64-v12.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/IOS21-64-v514.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/IOS22-64-v772.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/IOS28-64-v1288.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/RVL-Eulav_EU-v0.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/RVL-News-v3.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/RVL-NigaoeNR-v2.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/RVL-Shopping-v4.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/RVL-Weather-v3.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/RVL-WiiSystemmenu-v162.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/RVL-bc-v2.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/RVL-mios-v4.wad.out.wad + ftp://10.0.8.53/dvd/0%20Update/files/_sys/RVL-photo-v1.wad.out.wad + +//3.4E +titles.insert( 0x100000004ull, 0xff00 ); // IOS4 +titles.insert( 0x100000009ull, 0x208 ); // IOS9 +titles.insert( 0x10000000aull, 0x300 ); // IOS10 +titles.insert( 0x10000000bull, 0x100 ); // IOS11 +titles.insert( 0x10000000cull, 0xb ); // IOS12 +titles.insert( 0x10000000dull, 0xf ); // IOS13 +titles.insert( 0x10000000eull, 0x106 ); // IOS14 +titles.insert( 0x10000000full, 0x109 ); // IOS15 +titles.insert( 0x100000011ull, 0x205 ); // IOS17 +titles.insert( 0x100000014ull, 0x100 ); // IOS20 +titles.insert( 0x100000015ull, 0x20a ); // IOS21 +titles.insert( 0x100000016ull, 0x309 ); // IOS22 +titles.insert( 0x10000001cull, 0x50c ); // IOS28 +titles.insert( 0x10000001eull, 0xb00 ); // IOS30 +titles.insert( 0x10000001full, 0xc10 ); // IOS31 +titles.insert( 0x100000021ull, 0xb10 ); // IOS33 +titles.insert( 0x100000022ull, 0xc0f ); // IOS34 +titles.insert( 0x100000023ull, 0xc10 ); // IOS35 +titles.insert( 0x100000024ull, 0xc12 ); // IOS36 +titles.insert( 0x100000025ull, 0xe19 ); // IOS37 +titles.insert( 0x100000032ull, 0x1319 ); // IOS50 +titles.insert( 0x100000033ull, 0x1219 ); // IOS51 +titles.insert( 0x1000000feull, 0x2 ); // IOS254 +titles.insert( 0x100000002ull, 0x182 ); // SystemMenu 3.4E +titles.insert( 0x100000100ull, 0x5 ); // BC +titles.insert( 0x100000101ull, 0x9 ); // MIOS +titles.insert( 0x1000248414141ull, 0x2 ); // Channel HAAA +titles.insert( 0x1000248414241ull, 0xd ); // Channel HABA +titles.insert( 0x1000248414341ull, 0x6 ); // Channel HACA +titles.insert( 0x1000248414650ull, 0x7 ); // Channel HAFP +titles.insert( 0x1000248414750ull, 0x7 ); // Channel HAGP +titles.insert( 0x1000248415941ull, 0x2 ); // Channel HAYA +titles.insert( 0x1000248414b50ull, 0x2 ); // Channel HAKP +titles.insert( 0x1000248414c50ull, 0x2 ); // Channel HALP + +//4.0E +titles.insert( 0x100000009ull, 0x209 ); // IOS9 +titles.insert( 0x10000000cull, 0xc ); // IOS12 +titles.insert( 0x10000000dull, 0x10 ); // IOS13 +titles.insert( 0x10000000eull, 0x107 ); // IOS14 +titles.insert( 0x10000000full, 0x10a ); // IOS15 +titles.insert( 0x100000010ull, 0x200 ); // IOS16 +titles.insert( 0x100000011ull, 0x206 ); // IOS17 +titles.insert( 0x100000015ull, 0x20d ); // IOS21 +titles.insert( 0x100000016ull, 0x30c ); // IOS22 +titles.insert( 0x10000001cull, 0x50d ); // IOS28 +titles.insert( 0x10000001full, 0xc14 ); // IOS31 +titles.insert( 0x100000021ull, 0xb12 ); // IOS33 +titles.insert( 0x100000022ull, 0xc13 ); // IOS34 +titles.insert( 0x100000023ull, 0xc14 ); // IOS35 +titles.insert( 0x100000024ull, 0xc16 ); // IOS36 +titles.insert( 0x100000025ull, 0xe1c ); // IOS37 +titles.insert( 0x100000026ull, 0xe1a ); // IOS38 +titles.insert( 0x100000032ull, 0x1400 ); // IOS50 +titles.insert( 0x100000033ull, 0x1300 ); // IOS51 +titles.insert( 0x100000035ull, 0x141d ); // IOS53 +titles.insert( 0x100000037ull, 0x141d ); // IOS55 +titles.insert( 0x10000003cull, 0x181e ); // IOS60 +titles.insert( 0x10000003dull, 0x131a ); // IOS61 +titles.insert( 0x1000000feull, 0x3 ); // IOS254 +!!titles.insert( 0x1000248414241ull, 0x10 ); // Channel HABA +!!titles.insert( 0x1000248415941ull, 0x3 ); // Channel HAYA +titles.insert( 0x100000002ull, 0x1a2 ); // SystemMenu 4.0E + +//4.1E +titles.insert( 0x100000002ull, 0x1c2 ); // SystemMenu 4.1E + +//4.2E +//titles.insert( 0x100000001ull, 0x4 );//make people really ask for the boot2 update if they want it +titles.insert( 0x100000009ull, 0x30a ); // IOS9 +titles.insert( 0x10000000cull, 0x10d ); // IOS12 +titles.insert( 0x10000000dull, 0x111 ); // IOS13 +titles.insert( 0x10000000eull, 0x208 ); // IOS14 +titles.insert( 0x10000000full, 0x20b ); // IOS15 +titles.insert( 0x100000011ull, 0x307 ); // IOS17 +titles.insert( 0x100000015ull, 0x30e ); // IOS21 +titles.insert( 0x100000016ull, 0x40d ); // IOS22 +titles.insert( 0x10000001cull, 0x60e ); // IOS28 +titles.insert( 0x10000001full, 0xd15 ); // IOS31 +titles.insert( 0x100000021ull, 0xc13 ); // IOS33 +titles.insert( 0x100000022ull, 0xd14 ); // IOS34 +titles.insert( 0x100000023ull, 0xd15 ); // IOS35 +titles.insert( 0x100000024ull, 0xd17 ); // IOS36 +titles.insert( 0x100000025ull, 0xf1d ); // IOS37 +titles.insert( 0x100000026ull, 0xf1b ); // IOS38 +titles.insert( 0x100000035ull, 0x151e ); // IOS53 +titles.insert( 0x100000037ull, 0x151e ); // IOS55 +titles.insert( 0x100000038ull, 0x151d ); // IOS56 +titles.insert( 0x100000039ull, 0x161d ); // IOS57 +titles.insert( 0x10000003cull, 0x1900 ); // IOS60 +titles.insert( 0x10000003dull, 0x151d ); // IOS61 +titles.insert( 0x100000046ull, 0x1a1f ); // IOS70 +titles.insert( 0x1000000deull, 0xff00 ); // IOS222 +titles.insert( 0x1000000dfull, 0xff00 ); // IOS223 +titles.insert( 0x1000000f9ull, 0xff00 ); // IOS249 +titles.insert( 0x1000000faull, 0xff00 ); // IOS250 +titles.insert( 0x1000000feull, 0x104 ); // IOS254 +titles.insert( 0x100000100ull, 0x6 ); // BC +titles.insert( 0x100000101ull, 0xa ); // MIOS +titles.insert( 0x1000248414241ull, 0x11 ); // Channel HABA +titles.insert( 0x1000248414241ull, 0x12 ); // ShopChannel +titles.insert( 0x100000002ull, 0x1e2 ); // SystemMenu 4.2E + +//4.3E +titles.insert( 0x100000009ull, 0x40a ); // IOS9 +titles.insert( 0x10000000cull, 0x20e ); // IOS12 +titles.insert( 0x10000000dull, 0x408 ); // IOS13 +titles.insert( 0x10000000eull, 0x408 ); // IOS14 +titles.insert( 0x10000000full, 0x408 ); // IOS15 +titles.insert( 0x100000011ull, 0x408 ); // IOS17 +titles.insert( 0x100000015ull, 0x40f ); // IOS21 +titles.insert( 0x100000016ull, 0x50e ); // IOS22 +titles.insert( 0x10000001cull, 0x70f ); // IOS28 +titles.insert( 0x10000001full, 0xe18 ); // IOS31 +titles.insert( 0x100000021ull, 0xe18 ); // IOS33 +titles.insert( 0x100000022ull, 0xe18 ); // IOS34 +titles.insert( 0x100000023ull, 0xe18 ); // IOS35 +titles.insert( 0x100000024ull, 0xe18 ); // IOS36 +titles.insert( 0x100000025ull, 0x161f ); // IOS37 +titles.insert( 0x100000026ull, 0x101c ); // IOS38 +titles.insert( 0x100000028ull, 0xc00 ); // IOS40 +titles.insert( 0x100000029ull, 0xe17 ); // IOS41 +titles.insert( 0x10000002bull, 0xe17 ); // IOS43 +titles.insert( 0x10000002dull, 0xe17 ); // IOS45 +titles.insert( 0x10000002eull, 0xe17 ); // IOS46 +titles.insert( 0x100000030ull, 0x101c ); // IOS48 +titles.insert( 0x100000034ull, 0x1700 ); // IOS52 +titles.insert( 0x100000035ull, 0x161f ); // IOS53 +titles.insert( 0x100000037ull, 0x161f ); // IOS55 +titles.insert( 0x100000038ull, 0x161e ); // IOS56 +titles.insert( 0x100000039ull, 0x171f ); // IOS57 +titles.insert( 0x10000003aull, 0x1820 ); // IOS58 +titles.insert( 0x10000003dull, 0x161e ); // IOS61 +titles.insert( 0x100000046ull, 0x1b00 ); // IOS70 +titles.insert( 0x100000050ull, 0x1b20 ); // IOS80 +titles.insert( 0x1000000feull, 0xff00 ); // IOS254 +titles.insert( 0x1000848414b50ull, 0x3 ); // EULA +titles.insert( 0x1000248414241ull, 0x14 ); // ShopChannel +titles.insert( 0x100000002ull, 0x202 ); // SystemMenu 4.3E + +//3.5K +titles.insert( 0x100000004ull, 0xff00 ); // IOS4 +titles.insert( 0x100000009ull, 0x209 ); // IOS9 +titles.insert( 0x100000015ull, 0x20d ); // IOS21 +titles.insert( 0x100000025ull, 0xe1c ); // IOS37 +titles.insert( 0x100000028ull, 0xc00 ); // IOS40 +titles.insert( 0x100000029ull, 0xb13 ); // IOS41 +titles.insert( 0x10000002bull, 0xb13 ); // IOS43 +titles.insert( 0x10000002dull, 0xb13 ); // IOS45 +titles.insert( 0x10000002eull, 0xb15 ); // IOS46 +titles.insert( 0x100000033ull, 0x1300 ); // IOS51 +titles.insert( 0x100000034ull, 0x161d ); // IOS52 +titles.insert( 0x100000035ull, 0x141d ); // IOS53 +titles.insert( 0x100000037ull, 0x141d ); // IOS55 +titles.insert( 0x10000003dull, 0x131a ); // IOS61 +titles.insert( 0x1000000feull, 0x3 ); // IOS254 +titles.insert( 0x100000100ull, 0x5 ); // BC +titles.insert( 0x100000101ull, 0x9 ); // MIOS +titles.insert( 0x100024841424bull, 0x10 ); // Channel HABK +titles.insert( 0x1000248414c4bull, 0x2 ); // Channel HALK +titles.insert( 0x100000002ull, 0x186 ); // SystemMenu 3.5K + +//4.1K +titles.insert( 0x100000010ull, 0x200 ); // IOS16 +titles.insert( 0x100000029ull, 0xc13 ); // IOS41 +titles.insert( 0x10000002bull, 0xc13 ); // IOS43 +titles.insert( 0x10000002dull, 0xc13 ); // IOS45 +titles.insert( 0x10000002eull, 0xc15 ); // IOS46 +titles.insert( 0x100000034ull, 0x1700 ); // IOS52 +titles.insert( 0x10000003cull, 0x181e ); // IOS60 +titles.insert( 0x100024841594bull, 0x3 ); // Channel HAYK +titles.insert( 0x100000002ull, 0x1c6 ); // SystemMenu 4.1K + +//4.2K +//titles.insert( 0x100000001ull, 0x4 );//make people really ask for the boot2 update if they want it +titles.insert( 0x100000009ull, 0x30a ); // IOS9 +titles.insert( 0x100000015ull, 0x30e ); // IOS21 +titles.insert( 0x100000024ull, 0xd17 ); // IOS36 +titles.insert( 0x100000025ull, 0xf1d ); // IOS37 +titles.insert( 0x100000029ull, 0xd14 ); // IOS41 +titles.insert( 0x10000002bull, 0xd14 ); // IOS43 +titles.insert( 0x10000002dull, 0xd14 ); // IOS45 +titles.insert( 0x10000002eull, 0xd16 ); // IOS46 +titles.insert( 0x100000035ull, 0x151e ); // IOS53 +titles.insert( 0x100000037ull, 0x151e ); // IOS55 +titles.insert( 0x100000038ull, 0x151d ); // IOS56 +titles.insert( 0x100000039ull, 0x161d ); // IOS57 +titles.insert( 0x10000003cull, 0x1900 ); // IOS60 +titles.insert( 0x10000003dull, 0x151d ); // IOS61 +titles.insert( 0x100000046ull, 0x1a1f ); // IOS70 +titles.insert( 0x1000000deull, 0xff00 ); // IOS222 +titles.insert( 0x1000000dfull, 0xff00 ); // IOS223 +titles.insert( 0x1000000f9ull, 0xff00 ); // IOS249 +titles.insert( 0x1000000faull, 0xff00 ); // IOS250 +titles.insert( 0x1000000feull, 0x104 ); // IOS254 +titles.insert( 0x100000100ull, 0x6 ); // BC +titles.insert( 0x100000101ull, 0xa ); // MIOS +titles.insert( 0x100024841424bull, 0x11 ); // Channel HABK +titles.insert( 0x1000248414241ull, 0x12 ); // ShopChannel +titles.insert( 0x100000002ull, 0x1e6 ); // SystemMenu 4.2K + +//4.3K +titles.insert( 0x100000009ull, 0x40a ); // IOS9 +titles.insert( 0x10000000aull, 0x300 ); // IOS10 +titles.insert( 0x10000000bull, 0x100 ); // IOS11 +titles.insert( 0x10000000cull, 0x20e ); // IOS12 +titles.insert( 0x10000000dull, 0x408 ); // IOS13 +titles.insert( 0x10000000eull, 0x408 ); // IOS14 +titles.insert( 0x10000000full, 0x408 ); // IOS15 +titles.insert( 0x100000011ull, 0x408 ); // IOS17 +titles.insert( 0x100000014ull, 0x100 ); // IOS20 +titles.insert( 0x100000015ull, 0x40f ); // IOS21 +titles.insert( 0x100000016ull, 0x50e ); // IOS22 +titles.insert( 0x10000001cull, 0x70f ); // IOS28 +titles.insert( 0x10000001eull, 0xb00 ); // IOS30 +titles.insert( 0x10000001full, 0xe18 ); // IOS31 +titles.insert( 0x100000021ull, 0xe18 ); // IOS33 +titles.insert( 0x100000022ull, 0xe18 ); // IOS34 +titles.insert( 0x100000023ull, 0xe18 ); // IOS35 +titles.insert( 0x100000024ull, 0xe18 ); // IOS36 +titles.insert( 0x100000025ull, 0x161f ); // IOS37 +titles.insert( 0x100000026ull, 0x101c ); // IOS38 +titles.insert( 0x100000029ull, 0xe17 ); // IOS41 +titles.insert( 0x10000002bull, 0xe17 ); // IOS43 +titles.insert( 0x10000002dull, 0xe17 ); // IOS45 +titles.insert( 0x10000002eull, 0xe17 ); // IOS46 +titles.insert( 0x100000030ull, 0x101c ); // IOS48 +titles.insert( 0x100000032ull, 0x1400 ); // IOS50 +titles.insert( 0x100000035ull, 0x161f ); // IOS53 +titles.insert( 0x100000037ull, 0x161f ); // IOS55 +titles.insert( 0x100000038ull, 0x161e ); // IOS56 +titles.insert( 0x100000039ull, 0x171f ); // IOS57 +titles.insert( 0x10000003aull, 0x1820 ); // IOS58 +titles.insert( 0x10000003dull, 0x161e ); // IOS61 +titles.insert( 0x100000046ull, 0x1b00 ); // IOS70 +titles.insert( 0x100000050ull, 0x1b20 ); // IOS80 +titles.insert( 0x1000000feull, 0xff00 ); // IOS254 +titles.insert( 0x1000848414b4bull, 0x3 ); // EULA +titles.insert( 0x1000248414241ull, 0x14 ); // ShopChannel +titles.insert( 0x100000002ull, 0x206 ); // SystemMenu 4.3K + +//3.4J +titles.insert( 0x100000004ull, 0xff00 ); // IOS4 +titles.insert( 0x100000009ull, 0x208 ); // IOS9 +titles.insert( 0x10000000aull, 0x300 ); // IOS10 +titles.insert( 0x10000000bull, 0x100 ); // IOS11 +titles.insert( 0x10000000cull, 0xb ); // IOS12 +titles.insert( 0x10000000dull, 0xf ); // IOS13 +titles.insert( 0x10000000eull, 0x106 ); // IOS14 +titles.insert( 0x10000000full, 0x109 ); // IOS15 +titles.insert( 0x100000011ull, 0x205 ); // IOS17 +titles.insert( 0x100000014ull, 0x100 ); // IOS20 +titles.insert( 0x100000015ull, 0x20a ); // IOS21 +titles.insert( 0x100000016ull, 0x309 ); // IOS22 +titles.insert( 0x10000001cull, 0x50c ); // IOS28 +titles.insert( 0x10000001eull, 0xb00 ); // IOS30 +titles.insert( 0x10000001full, 0xc10 ); // IOS31 +titles.insert( 0x100000021ull, 0xb10 ); // IOS33 +titles.insert( 0x100000022ull, 0xc0f ); // IOS34 +titles.insert( 0x100000023ull, 0xc10 ); // IOS35 +titles.insert( 0x100000024ull, 0xc12 ); // IOS36 +titles.insert( 0x100000025ull, 0xe19 ); // IOS37 +titles.insert( 0x100000032ull, 0x1319 ); // IOS50 +titles.insert( 0x100000033ull, 0x1219 ); // IOS51 +titles.insert( 0x1000000feull, 0x2 ); // IOS254 +titles.insert( 0x100000002ull, 0x180 ); // SystemMenu 3.4J +titles.insert( 0x100000100ull, 0x5 ); // BC +titles.insert( 0x100000101ull, 0x9 ); // MIOS +titles.insert( 0x1000248414141ull, 0x2 ); // Channel HAAA +titles.insert( 0x1000248414241ull, 0xd ); // Channel HABA +titles.insert( 0x1000248414341ull, 0x6 ); // Channel HACA +titles.insert( 0x100024841464aull, 0x7 ); // Channel HAFJ +titles.insert( 0x100024841474aull, 0x7 ); // Channel HAGJ +titles.insert( 0x1000248415941ull, 0x2 ); // Channel HAYA +titles.insert( 0x1000248414b4aull, 0x2 ); // Channel HAKJ +titles.insert( 0x1000248414c4aull, 0x2 ); // Channel HALJ +titles.insert( 0x100084843434aull, 0x0); // Channel HCCJ + +//4.0J +titles.insert( 0x100000009ull, 0x209 ); // IOS9 +titles.insert( 0x10000000cull, 0xc ); // IOS12 +titles.insert( 0x10000000dull, 0x10 ); // IOS13 +titles.insert( 0x10000000eull, 0x107 ); // IOS14 +titles.insert( 0x10000000full, 0x10a ); // IOS15 +titles.insert( 0x100000010ull, 0x200 ); // IOS16 +titles.insert( 0x100000011ull, 0x206 ); // IOS17 +titles.insert( 0x100000015ull, 0x20d ); // IOS21 +titles.insert( 0x100000016ull, 0x30c ); // IOS22 +titles.insert( 0x10000001cull, 0x50d ); // IOS28 +titles.insert( 0x10000001full, 0xc14 ); // IOS31 +titles.insert( 0x100000021ull, 0xb12 ); // IOS33 +titles.insert( 0x100000022ull, 0xc13 ); // IOS34 +titles.insert( 0x100000023ull, 0xc14 ); // IOS35 +titles.insert( 0x100000024ull, 0xc16 ); // IOS36 +titles.insert( 0x100000025ull, 0xe1c ); // IOS37 +titles.insert( 0x100000026ull, 0xe1a ); // IOS38 +titles.insert( 0x100000032ull, 0x1400 ); // IOS50 +titles.insert( 0x100000033ull, 0x1300 ); // IOS51 +titles.insert( 0x100000035ull, 0x141d ); // IOS53 +titles.insert( 0x100000037ull, 0x141d ); // IOS55 +titles.insert( 0x10000003cull, 0x181e ); // IOS60 +titles.insert( 0x10000003dull, 0x131a ); // IOS61 +titles.insert( 0x1000000feull, 0x3 ); // IOS254 +titles.insert( 0x1000248414241ull, 0x10 ); // Channel HABA +titles.insert( 0x1000248415941ull, 0x3 ); // Channel HAYA +titles.insert( 0x100024843434aull, 0x1 ); // Channel HCCJ +titles.insert( 0x100000002ull, 0x1a0 ); // SystemMenu 4.0J + +//4.1J +titles.insert( 0x100000002ull, 0x1c0 ); // SystemMenu 4.1E +titles.insert( 0x100024843434aull, 0x2 ); // Channel HCCJ + +//4.2J +//titles.insert( 0x100000001ull, 0x4 );//make people really ask for the boot2 update if they want it +titles.insert( 0x100000009ull, 0x30a ); // IOS9 +titles.insert( 0x10000000cull, 0x10d ); // IOS12 +titles.insert( 0x10000000dull, 0x111 ); // IOS13 +titles.insert( 0x10000000eull, 0x208 ); // IOS14 +titles.insert( 0x10000000full, 0x20b ); // IOS15 +titles.insert( 0x100000011ull, 0x307 ); // IOS17 +titles.insert( 0x100000015ull, 0x30e ); // IOS21 +titles.insert( 0x100000016ull, 0x40d ); // IOS22 +titles.insert( 0x10000001cull, 0x60e ); // IOS28 +titles.insert( 0x10000001full, 0xd15 ); // IOS31 +titles.insert( 0x100000021ull, 0xc13 ); // IOS33 +titles.insert( 0x100000022ull, 0xd14 ); // IOS34 +titles.insert( 0x100000023ull, 0xd15 ); // IOS35 +titles.insert( 0x100000024ull, 0xd17 ); // IOS36 +titles.insert( 0x100000025ull, 0xf1d ); // IOS37 +titles.insert( 0x100000026ull, 0xf1b ); // IOS38 +titles.insert( 0x100000035ull, 0x151e ); // IOS53 +titles.insert( 0x100000037ull, 0x151e ); // IOS55 +titles.insert( 0x100000038ull, 0x151d ); // IOS56 +titles.insert( 0x100000039ull, 0x161d ); // IOS57 +titles.insert( 0x10000003cull, 0x1900 ); // IOS60 +titles.insert( 0x10000003dull, 0x151d ); // IOS61 +titles.insert( 0x100000046ull, 0x1a1f ); // IOS70 +titles.insert( 0x1000000deull, 0xff00 ); // IOS222 +titles.insert( 0x1000000dfull, 0xff00 ); // IOS223 +titles.insert( 0x1000000f9ull, 0xff00 ); // IOS249 +titles.insert( 0x1000000faull, 0xff00 ); // IOS250 +titles.insert( 0x1000000feull, 0x104 ); // IOS254 +titles.insert( 0x100000100ull, 0x6 ); // BC +titles.insert( 0x100000101ull, 0xa ); // MIOS +titles.insert( 0x1000248414241ull, 0x11 ); // Channel HABA +titles.insert( 0x1000248414241ull, 0x12 ); // ShopChannel +titles.insert( 0x100000002ull, 0x1e0 ); // SystemMenu 4.2J + +//4.3J +titles.insert( 0x100000009ull, 0x40a ); // IOS9 +titles.insert( 0x10000000cull, 0x20e ); // IOS12 +titles.insert( 0x10000000dull, 0x408 ); // IOS13 +titles.insert( 0x10000000eull, 0x408 ); // IOS14 +titles.insert( 0x10000000full, 0x408 ); // IOS15 +titles.insert( 0x100000011ull, 0x408 ); // IOS17 +titles.insert( 0x100000015ull, 0x40f ); // IOS21 +titles.insert( 0x100000016ull, 0x50e ); // IOS22 +titles.insert( 0x10000001cull, 0x70f ); // IOS28 +titles.insert( 0x10000001full, 0xe18 ); // IOS31 +titles.insert( 0x100000021ull, 0xe18 ); // IOS33 +titles.insert( 0x100000022ull, 0xe18 ); // IOS34 +titles.insert( 0x100000023ull, 0xe18 ); // IOS35 +titles.insert( 0x100000024ull, 0xe18 ); // IOS36 +titles.insert( 0x100000025ull, 0x161f ); // IOS37 +titles.insert( 0x100000026ull, 0x101c ); // IOS38 +titles.insert( 0x100000028ull, 0xc00 ); // IOS40 +titles.insert( 0x100000029ull, 0xe17 ); // IOS41 +titles.insert( 0x10000002bull, 0xe17 ); // IOS43 +titles.insert( 0x10000002dull, 0xe17 ); // IOS45 +titles.insert( 0x10000002eull, 0xe17 ); // IOS46 +titles.insert( 0x100000030ull, 0x101c ); // IOS48 +titles.insert( 0x100000034ull, 0x1700 ); // IOS52 +titles.insert( 0x100000035ull, 0x161f ); // IOS53 +titles.insert( 0x100000037ull, 0x161f ); // IOS55 +titles.insert( 0x100000038ull, 0x161e ); // IOS56 +titles.insert( 0x100000039ull, 0x171f ); // IOS57 +titles.insert( 0x10000003aull, 0x1820 ); // IOS58 +titles.insert( 0x10000003dull, 0x161e ); // IOS61 +titles.insert( 0x100000046ull, 0x1b00 ); // IOS70 +titles.insert( 0x100000050ull, 0x1b20 ); // IOS80 +titles.insert( 0x1000000feull, 0xff00 ); // IOS254 +titles.insert( 0x1000848414b4aull, 0x3 ); // EULA +titles.insert( 0x1000248414241ull, 0x14 ); // ShopChannel +titles.insert( 0x100000002ull, 0x200 ); // SystemMenu 4.3J +*/ diff --git a/nand_dump/nusdownloader.h b/nand_dump/nusdownloader.h new file mode 100644 index 0000000..46513bc --- /dev/null +++ b/nand_dump/nusdownloader.h @@ -0,0 +1,194 @@ +#ifndef NUSDOWNLOADER_H +#define NUSDOWNLOADER_H + +#include "includes.h" +#include "tiktmd.h" + +#define SHOPPING_USER_AGENT "Opera/9.00 (Nintendo Wii; U; ; 1038-58; Wii Shop Channel/1.0; en)" +#define UPDATING_USER_AGENT "wii libnup/1.0" +#define VIRTUAL_CONSOLE_USER_AGENT "libec-3.0.7.06111123" +#define WIICONNECT24_USER_AGENT "WiiConnect24/1.0FC4plus1 (build 061114161108)" +#define NUS_BASE_URL "http://ccs.shop.wii.com/ccs/download/" + +enum +{ + IDX_CETK = 0x9000, + IDX_TMD +}; + +//struct used to keep all the data about a NUS request together +//when a finished job is returned, the data list will be the TMD, then the ticket, then all the contents +struct NusJob +{ + quint64 tid; + quint16 version; + bool decrypt; + QList data; +}; + +struct downloadJob +{ + QString tid; + QString name; + quint16 index; + QByteArray data; +}; + +//class to download titles from nintendo update servers +class NusDownloader : public QObject +{ + Q_OBJECT +public: + explicit NusDownloader( QObject *parent = 0, const QString &cPath = QString() ); + + //set the path used to cache files + void SetCachePath( const QString &cPath = QString() ); + + //get a title from NUS. if version is 0, get the latest one + void GetTitle( NusJob job ); + void GetTitles( QList jobs ); + void Get( quint64 tid, bool decrypt, quint16 version = 0 ); + + //TODO! this function is far from complete + //starts getting all the titles involved for a given update + //expects strings like "4.2u" and "3.3e" + //returns false if it doesnt know the IOS for the given string + // The boot2 update is not uncluded in this. ask for it separately if you want it + bool GetUpdate( const QString & upd, bool decrypt = true ); + + //get a list of titles for a given update + //if a title is not available on NUS, a substitute is given instead ( a later version of the same title ) + //to keep people from bulk DLing and installing and messing something up, any boot2 upudate will not be included + //in the list, ask for it specifically + //lists are created from wiimpersonator logs when available. otherwise they come from examining game update partitions + static QMap< quint64, quint16 > List30u(); + static QMap< quint64, quint16 > List31u(); + static QMap< quint64, quint16 > List32u(); + static QMap< quint64, quint16 > List33u(); + static QMap< quint64, quint16 > List34u(); + static QMap< quint64, quint16 > List40u(); + static QMap< quint64, quint16 > List41u(); + static QMap< quint64, quint16 > List42u(); + static QMap< quint64, quint16 > List43u(); + + /* static QMap< quint64, quint16 > List30e(); + static QMap< quint64, quint16 > List31e(); + static QMap< quint64, quint16 > List32e(); + static QMap< quint64, quint16 > List33e(); + static QMap< quint64, quint16 > List34e(); + static QMap< quint64, quint16 > List40e(); + static QMap< quint64, quint16 > List41e(); + static QMap< quint64, quint16 > List42e(); + static QMap< quint64, quint16 > List43e(); + + static QMap< quint64, quint16 > List30j(); + static QMap< quint64, quint16 > List31j(); + static QMap< quint64, quint16 > List32j(); + static QMap< quint64, quint16 > List33j(); + static QMap< quint64, quint16 > List34j(); + static QMap< quint64, quint16 > List40j(); + static QMap< quint64, quint16 > List41j(); + static QMap< quint64, quint16 > List42j(); + static QMap< quint64, quint16 > List43j();*/ + +private: + + //helper function to create a job + downloadJob CreateJob( QString name, quint16 index ); + + //path on the PC to try and get files from and save to to avoid downloading deplicates from NUS + QString cachePath; + + //get data from the cache and put it in the job's data + //uses the version from currentJob + QByteArray GetDataFromCache( downloadJob job ); + + //saves downloaded data to the HDD for later use + bool SaveDataToCache( const QString &path, const QByteArray &stuff ); + + //check the tmd and try to ticket + void ReadTmdAndGetTicket( QByteArray ba ); + + bool DecryptCheckHashAndAppendData( const QByteArray &encData, quint16 idx ); + + //triggered when a file is done downloading + void FileIsFinishedDownloading( downloadJob job ); + + //send a fail message about the current job and skip to the next + void CurrentJobErrored( const QString &str ); + + //print info about a job to qDebug() + void DbgJoB( NusJob job ); + + //get the path for a file in the local cache + QString GetCachePath( quint32 idx ); + + //list of stuff to do + QListjobList; + + //variables to keep track of the current downloading title + NusJob currentJob; + Tmd curTmd; + + //variables for actually downloading something from the interwebz + QNetworkAccessManager manager; + QQueue< downloadJob > downloadQueue; + QNetworkReply *currentDownload; + QTime downloadTime; + QString currentJobText; + downloadJob dlJob; + + //should be true if this object is busy getting a title and false if all work is done, or a fatal error + bool running; + + //used to help in sending the overall progress done, should be reset on fatal error or when all jobs are done + quint32 totalJobs; + + //used to help in sending the progress for the current title + quint32 totalTitleSize; + quint32 TitleSizeDownloaded(); + + QByteArray decKey; + + + +signals: + void SendError( const QString &message, NusJob job );//send an errer and the title the error is about + //send an errer and the title the error is about, no more jobs will be done, and the SendDone signal will not be emited + void SendFatalErrorError( const QString &message, NusJob job ); + void SendDone();//message that all jobs are done + + //send progress about the currently downloading job + void SendDownloadProgress( int ); + + //send progress about the overall progress + void SendTotalProgress( int ); + + //send progress info about the current title + void SendTitleProgress( int ); + + //sends a completed job to whoever is listening + void SendData( NusJob ); + + //a file is done downloading + void finished(); + + //send pretty text about what is happening + void SendText( QString ); + + //maybe this one is redundant + //void SendJobFinished( downloadJob ); + +public slots: + void StartNextJob(); + void GetNextItemForCurrentTitle(); + + //slots for actually downloading something from online + void StartDownload(); + void downloadProgress( qint64 bytesReceived, qint64 bytesTotal ); + void downloadFinished(); + void downloadReadyRead(); +}; + + +#endif // NUSDOWNLOADER_H diff --git a/nand_dump/settingtxtdialog.cpp b/nand_dump/settingtxtdialog.cpp new file mode 100644 index 0000000..6111abd --- /dev/null +++ b/nand_dump/settingtxtdialog.cpp @@ -0,0 +1,116 @@ +#include "settingtxtdialog.h" +#include "ui_settingtxtdialog.h" +#include "tools.h" + +SettingTxtDialog::SettingTxtDialog( QWidget *parent, const QByteArray &old ) : QDialog(parent), ui(new Ui::SettingTxtDialog) +{ + ui->setupUi( this ); + if( !old.isEmpty() ) + { + QByteArray copy = old; + copy = LolCrypt( copy ); + + QString str( copy ); + str.replace( "\r\n", "\n" );//maybe not needed to do this in 2 steps, but there may be some reason the file only uses "\n", so do it this way to be safe + QStringList parts = str.split( "\n", QString::SkipEmptyParts ); + foreach( QString part, parts ) + { + QString p = part; + if( part.startsWith( "AREA=" ) ) + { + p.remove( 0, 5 ); + ui->lineEdit_area->setText( p ); + } + else if( part.startsWith( "MODEL=" ) ) + { + p.remove( 0, 6 ); + ui->lineEdit_model->setText( p ); + } + else if( part.startsWith( "DVD=" ) ) + { + p.remove( 0, 4 ); + ui->lineEdit_dvd->setText( p ); + } + else if( part.startsWith( "MPCH=" ) ) + { + p.remove( 0, 5 ); + ui->lineEdit_mpch->setText( p ); + } + else if( part.startsWith( "CODE=" ) ) + { + p.remove( 0, 5 ); + ui->lineEdit_code->setText( p ); + } + else if( part.startsWith( "SERNO=" ) ) + { + p.remove( 0, 6 ); + ui->lineEdit_serno->setText( p ); + } + else if( part.startsWith( "VIDEO=" ) ) + { + p.remove( 0, 6 ); + ui->lineEdit_video->setText( p ); + } + else if( part.startsWith( "GAME=" ) ) + { + p.remove( 0, 5 ); + ui->lineEdit_game->setText( p ); + } + else + { + qDebug() << "SettingTxtDialog::SettingTxtDialog -> unhandled shit" << p; + } + } + } +} + +SettingTxtDialog::~SettingTxtDialog() +{ + delete ui; +} + +//ok button clicked +void SettingTxtDialog::on_buttonBox_accepted() +{ + QString s = "AREA=" + ui->lineEdit_area->text() + "\r\n" + + "MODEL=" + ui->lineEdit_model->text() + "\r\n" + + "DVD=" + ui->lineEdit_dvd->text() + "\r\n" + + "MPCH=" + ui->lineEdit_mpch->text() + "\r\n" + + "CODE=" + ui->lineEdit_code->text() + "\r\n" + + "SERNO=" + ui->lineEdit_serno->text() + "\r\n" + + "VIDEO=" + ui->lineEdit_video->text() + "\r\n" + + "GAME=" + ui->lineEdit_game->text(); + + ret = s.toAscii(); + ret = PaddedByteArray( ret, 0x100 ); + //hexdump( ret ); + ret = LolCrypt( ret ); + //hexdump( ret ); +} + +QByteArray SettingTxtDialog::LolCrypt( QByteArray ba ) +{ + int s; + for( s = ba.size() - 1; s > 0; s-- ) + if( ba.at( s ) != '\0' ) + break; + + QByteArray ret = ba; + quint32 key = 0x73b5dbfa; + quint8 * stuff = (quint8 *)ret.data(); + for( int i = 0; i < s + 1; i++ ) + { + *stuff ^= ( key & 0xff ); + stuff++; + key = ( ( key << 1 ) | ( key >> 31 ) ); + } + return ret; +} + +QByteArray SettingTxtDialog::Edit( QWidget *parent, const QByteArray &old ) +{ + SettingTxtDialog d( parent, old ); + if( d.exec() ) + return d.ret; + return QByteArray(); +} diff --git a/nand_dump/settingtxtdialog.h b/nand_dump/settingtxtdialog.h new file mode 100644 index 0000000..34b14ea --- /dev/null +++ b/nand_dump/settingtxtdialog.h @@ -0,0 +1,31 @@ +#ifndef SETTINGTXTDIALOG_H +#define SETTINGTXTDIALOG_H + +#include "includes.h" + +namespace Ui { + class SettingTxtDialog; +} + +class SettingTxtDialog : public QDialog +{ + Q_OBJECT + +public: + explicit SettingTxtDialog( QWidget *parent = 0, const QByteArray &old = QByteArray() ); + ~SettingTxtDialog(); + + static QByteArray Edit( QWidget *parent = 0, const QByteArray &old = QByteArray() ); + static QByteArray LolCrypt( QByteArray ba ); + +private: + Ui::SettingTxtDialog *ui; + QByteArray ret; + + + +private slots: + void on_buttonBox_accepted(); +}; + +#endif // SETTINGTXTDIALOG_H diff --git a/nand_dump/settingtxtdialog.ui b/nand_dump/settingtxtdialog.ui new file mode 100644 index 0000000..123b57a --- /dev/null +++ b/nand_dump/settingtxtdialog.ui @@ -0,0 +1,183 @@ + + + SettingTxtDialog + + + + 0 + 0 + 333 + 295 + + + + Dialog + + + + + + + + Area + + + + + + + USA + + + + + + + Model + + + + + + + RVL-001(USA) + + + + + + + DVD + + + + + + + 0 + + + + + + + MPCH + + + + + + + 0x7FFE + + + + + + + Code + + + + + + + LU + + + + + + + SerNo + + + + + + + 123456789 + + + 9 + + + + + + + Video + + + + + + + NTSC + + + + + + + Game + + + + + + + US + + + + + + + + + Qt::Horizontal + + + QDialogButtonBox::Cancel|QDialogButtonBox::Ok + + + + + + + + + buttonBox + accepted() + SettingTxtDialog + accept() + + + 248 + 254 + + + 157 + 274 + + + + + buttonBox + rejected() + SettingTxtDialog + reject() + + + 316 + 260 + + + 286 + 274 + + + + + diff --git a/nand_dump/sha1.c b/nand_dump/sha1.c new file mode 100755 index 0000000..3dda3ad --- /dev/null +++ b/nand_dump/sha1.c @@ -0,0 +1,371 @@ +/* + * sha1.c + * + * Copyright (C) 1998, 2009 + * Paul E. Jones + * All Rights Reserved + * + ***************************************************************************** + * $Id: sha1.c 12 2009-06-22 19:34:25Z paulej $ + ***************************************************************************** + * + * Description: + * This file implements the Secure Hashing Standard as defined + * in FIPS PUB 180-1 published April 17, 1995. + * + * The Secure Hashing Standard, which uses the Secure Hashing + * Algorithm (SHA), produces a 160-bit message digest for a + * given data stream. In theory, it is highly improbable that + * two messages will produce the same message digest. Therefore, + * this algorithm can serve as a means of providing a "fingerprint" + * for a message. + * + * Portability Issues: + * SHA-1 is defined in terms of 32-bit "words". This code was + * written with the expectation that the processor has at least + * a 32-bit machine word size. If the machine word size is larger, + * the code should still function properly. One caveat to that + * is that the input functions taking characters and character + * arrays assume that only 8 bits of information are stored in each + * character. + * + * Caveats: + * SHA-1 is designed to work with messages less than 2^64 bits + * long. Although SHA-1 allows a message digest to be generated for + * messages of any number of bits less than 2^64, this + * implementation only works with messages with a length that is a + * multiple of the size of an 8-bit character. + * + */ + +#include "sha1.h" + +/* + * Define the circular shift macro + */ +#define SHA1CircularShift(bits,word) \ + ((((word) << (bits)) & 0xFFFFFFFF) | \ + ((word) >> (32-(bits)))) + +/* Function prototypes */ +void SHA1ProcessMessageBlock(SHA1Context *); +void SHA1PadMessage(SHA1Context *); + +/* + * SHA1Reset + * + * Description: + * This function will initialize the SHA1Context in preparation + * for computing a new message digest. + * + * Parameters: + * context: [in/out] + * The context to reset. + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1Reset(SHA1Context *context) +{ + context->Length_Low = 0; + context->Length_High = 0; + context->Message_Block_Index = 0; + + context->Message_Digest[0] = 0x67452301; + context->Message_Digest[1] = 0xEFCDAB89; + context->Message_Digest[2] = 0x98BADCFE; + context->Message_Digest[3] = 0x10325476; + context->Message_Digest[4] = 0xC3D2E1F0; + + context->Computed = 0; + context->Corrupted = 0; +} + +/* + * SHA1Result + * + * Description: + * This function will return the 160-bit message digest into the + * Message_Digest array within the SHA1Context provided + * + * Parameters: + * context: [in/out] + * The context to use to calculate the SHA-1 hash. + * + * Returns: + * 1 if successful, 0 if it failed. + * + * Comments: + * + */ +int SHA1Result(SHA1Context *context) +{ + + if (context->Corrupted) + { + return 0; + } + + if (!context->Computed) + { + SHA1PadMessage(context); + context->Computed = 1; + } + + return 1; +} + +/* + * SHA1Input + * + * Description: + * This function accepts an array of octets as the next portion of + * the message. + * + * Parameters: + * context: [in/out] + * The SHA-1 context to update + * message_array: [in] + * An array of characters representing the next portion of the + * message. + * length: [in] + * The length of the message in message_array + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1Input( SHA1Context *context, + const unsigned char *message_array, + unsigned length) +{ + if (!length) + { + return; + } + + if (context->Computed || context->Corrupted) + { + context->Corrupted = 1; + return; + } + + while(length-- && !context->Corrupted) + { + context->Message_Block[context->Message_Block_Index++] = + (*message_array & 0xFF); + + context->Length_Low += 8; + /* Force it to 32 bits */ + context->Length_Low &= 0xFFFFFFFF; + if (context->Length_Low == 0) + { + context->Length_High++; + /* Force it to 32 bits */ + context->Length_High &= 0xFFFFFFFF; + if (context->Length_High == 0) + { + /* Message is too long */ + context->Corrupted = 1; + } + } + + if (context->Message_Block_Index == 64) + { + SHA1ProcessMessageBlock(context); + } + + message_array++; + } +} + +/* + * SHA1ProcessMessageBlock + * + * Description: + * This function will process the next 512 bits of the message + * stored in the Message_Block array. + * + * Parameters: + * None. + * + * Returns: + * Nothing. + * + * Comments: + * Many of the variable names in the SHAContext, especially the + * single character names, were used because those were the names + * used in the publication. + * + * + */ +void SHA1ProcessMessageBlock(SHA1Context *context) +{ + const unsigned K[] = /* Constants defined in SHA-1 */ + { + 0x5A827999, + 0x6ED9EBA1, + 0x8F1BBCDC, + 0xCA62C1D6 + }; + int t; /* Loop counter */ + unsigned temp; /* Temporary word value */ + unsigned W[80]; /* Word sequence */ + unsigned A, B, C, D, E; /* Word buffers */ + + /* + * Initialize the first 16 words in the array W + */ + for(t = 0; t < 16; t++) + { + W[t] = ((unsigned) context->Message_Block[t * 4]) << 24; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8; + W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]); + } + + for(t = 16; t < 80; t++) + { + W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]); + } + + A = context->Message_Digest[0]; + B = context->Message_Digest[1]; + C = context->Message_Digest[2]; + D = context->Message_Digest[3]; + E = context->Message_Digest[4]; + + for(t = 0; t < 20; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | ((~B) & D)) + E + W[t] + K[0]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 20; t < 40; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 40; t < 60; t++) + { + temp = SHA1CircularShift(5,A) + + ((B & C) | (B & D) | (C & D)) + E + W[t] + K[2]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + for(t = 60; t < 80; t++) + { + temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3]; + temp &= 0xFFFFFFFF; + E = D; + D = C; + C = SHA1CircularShift(30,B); + B = A; + A = temp; + } + + context->Message_Digest[0] = + (context->Message_Digest[0] + A) & 0xFFFFFFFF; + context->Message_Digest[1] = + (context->Message_Digest[1] + B) & 0xFFFFFFFF; + context->Message_Digest[2] = + (context->Message_Digest[2] + C) & 0xFFFFFFFF; + context->Message_Digest[3] = + (context->Message_Digest[3] + D) & 0xFFFFFFFF; + context->Message_Digest[4] = + (context->Message_Digest[4] + E) & 0xFFFFFFFF; + + context->Message_Block_Index = 0; +} + +/* + * SHA1PadMessage + * + * Description: + * According to the standard, the message must be padded to an even + * 512 bits. The first padding bit must be a '1'. The last 64 + * bits represent the length of the original message. All bits in + * between should be 0. This function will pad the message + * according to those rules by filling the Message_Block array + * accordingly. It will also call SHA1ProcessMessageBlock() + * appropriately. When it returns, it can be assumed that the + * message digest has been computed. + * + * Parameters: + * context: [in/out] + * The context to pad + * + * Returns: + * Nothing. + * + * Comments: + * + */ +void SHA1PadMessage(SHA1Context *context) +{ + /* + * Check to see if the current message block is too small to hold + * the initial padding bits and length. If so, we will pad the + * block, process it, and then continue padding into a second + * block. + */ + if (context->Message_Block_Index > 55) + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 64) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + + SHA1ProcessMessageBlock(context); + + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + else + { + context->Message_Block[context->Message_Block_Index++] = 0x80; + while(context->Message_Block_Index < 56) + { + context->Message_Block[context->Message_Block_Index++] = 0; + } + } + + /* + * Store the message length as the last 8 octets + */ + context->Message_Block[56] = (context->Length_High >> 24) & 0xFF; + context->Message_Block[57] = (context->Length_High >> 16) & 0xFF; + context->Message_Block[58] = (context->Length_High >> 8) & 0xFF; + context->Message_Block[59] = (context->Length_High) & 0xFF; + context->Message_Block[60] = (context->Length_Low >> 24) & 0xFF; + context->Message_Block[61] = (context->Length_Low >> 16) & 0xFF; + context->Message_Block[62] = (context->Length_Low >> 8) & 0xFF; + context->Message_Block[63] = (context->Length_Low) & 0xFF; + + SHA1ProcessMessageBlock(context); +} diff --git a/nand_dump/sha1.h b/nand_dump/sha1.h new file mode 100755 index 0000000..174b582 --- /dev/null +++ b/nand_dump/sha1.h @@ -0,0 +1,40 @@ + +#ifndef _SHA1_H_ +#define _SHA1_H_ + +#ifdef __cplusplus +extern "C" { +#endif + +/* + * This structure will hold context information for the hashing + * operation + */ +typedef struct SHA1Context +{ + unsigned Message_Digest[5]; /* Message Digest (output) */ + + unsigned Length_Low; /* Message length in bits */ + unsigned Length_High; /* Message length in bits */ + + unsigned char Message_Block[64]; /* 512-bit message blocks */ + int Message_Block_Index; /* Index into message block array */ + + int Computed; /* Is the digest computed? */ + int Corrupted; /* Is the message digest corruped? */ +} SHA1Context; + +/* + * Function Prototypes + */ +void SHA1Reset(SHA1Context *); +int SHA1Result(SHA1Context *); +void SHA1Input( SHA1Context *, + const unsigned char *, + unsigned); + +#ifdef __cplusplus +} +#endif + +#endif diff --git a/nand_dump/sharedcontentmap.cpp b/nand_dump/sharedcontentmap.cpp new file mode 100644 index 0000000..51ca696 --- /dev/null +++ b/nand_dump/sharedcontentmap.cpp @@ -0,0 +1,122 @@ +#include "sharedcontentmap.h" +#include "tools.h" + +SharedContentMap::SharedContentMap( QByteArray old ) +{ + data = old; + if( data.size() ) + Check(); +} + +bool SharedContentMap::Check( const QString &path ) +{ + if( !data.size() || data.size() % 28 ) + { + qWarning() << "SharedContentMap::Check -> bad size" << hex << data.size(); + return false; + } + + QByteArray cid; + quint32 cnt = data.size() / 28; + //check that all the cid's can be converted to numbers, and make sure that ther are in numerical order + for( quint32 i = 0; i < cnt; i++ ) + { + bool ok; + cid = data.mid( ( i * 28 ), 8 ); + quint32 num = cid.toInt( &ok, 16 ); + if( !ok ) + { + qWarning() << "SharedContentMap::Check -> error converting" << cid << "to a number"; + return false; + } + if( i != num ) + { + qWarning() << "SharedContentMap::Check -> something is a miss" << num << i; + return false; + } + } + //this is all there is to check right now + if( path.isEmpty() ) + return true; + + QDir dir( path ); + if( !dir.exists() ) + { + qWarning() << "SharedContentMap::Check ->" << path << "doesnt exist"; + return false; + } + + //check hashes for all the contents in the map + for( quint32 i = 0; i < cnt; i++ ) + { + cid = data.mid( ( i * 28 ), 8 ); + QString appName( cid ); + QFileInfo fi = dir.absoluteFilePath( appName + ".app" ); + if( !fi.exists() ) + { + qWarning() << "SharedContentMap::Check -> content in the map isnt found in" << path; + return false; + } + QFile f( fi.absoluteFilePath() ); + if( !f.open( QIODevice::ReadOnly ) ) + { + qWarning() << "SharedContentMap::Check -> cant open" << fi.absoluteFilePath(); + return false; + } + + QByteArray goods = f.readAll(); + f.close(); + + QByteArray expectedHash = data.mid( ( i * 28 ) + 8, 20 ); + QByteArray actualHash = GetSha1( goods ); + if( expectedHash != actualHash ) + { + qWarning() << "SharedContentMap::Check -> hash mismatch in" << fi.absoluteFilePath() << "\n" + << expectedHash << actualHash; + return false; + } + } + return true; +} + +QString SharedContentMap::GetAppFromHash( QByteArray hash ) +{ + quint32 cnt = data.size() / 28; + for( quint32 i = 0; i < cnt; i++ ) + { + QString appName( data.mid( ( i * 28 ), 8 ) ); + QByteArray mapHash = data.mid( ( i * 28 ) + 8, 20 ); + //qDebug() << QString( mapHash ) << hash; + if( mapHash == hash ) + { + return appName; + } + } + //hash not found + return QString(); +} + +QString SharedContentMap::GetNextEmptyCid() +{ + quint32 cnt = data.size() / 28; + quint32 ret = 0; + QListcids; + for( quint32 i = 0; i < cnt; i++ ) + { + QByteArray cid = data.mid( ( i * 28 ), 8 ); + quint32 num = cid.toInt( NULL, 16 ); + cids << num; + } + //find the lowest number cid that isnt used + while( cids.contains( ret ) ) + ret++; + //qDebug() << hex << ret; + return QString( "%1" ).arg( ret, 8, 16, QChar( '0' ) ); +} + +void SharedContentMap::AddEntry( const QString &app, const QByteArray &hash ) +{ + data += app.toAscii() + hash; + //qDebug() << "SharedContentMap::AddEntry -> added entry, rechecking this beast"; + Check(); +} diff --git a/nand_dump/sharedcontentmap.h b/nand_dump/sharedcontentmap.h new file mode 100644 index 0000000..bb4647d --- /dev/null +++ b/nand_dump/sharedcontentmap.h @@ -0,0 +1,27 @@ +#ifndef SHAREDCONTENTMAP_H +#define SHAREDCONTENTMAP_H + +#include "includes.h" + +class SharedContentMap +{ +public: + SharedContentMap( QByteArray old = QByteArray() ); + + //checks that the content map is sane + //size should be correct, contents should be in numerical order + //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() ); + + QString GetAppFromHash( QByteArray hash ); + QString GetNextEmptyCid(); + + void AddEntry( const QString &app, const QByteArray &hash ); + + const QByteArray Data(){ return data; } + +private: + QByteArray data; +}; + +#endif // SHAREDCONTENTMAP_H diff --git a/nand_dump/tiktmd.cpp b/nand_dump/tiktmd.cpp new file mode 100644 index 0000000..1e58346 --- /dev/null +++ b/nand_dump/tiktmd.cpp @@ -0,0 +1,129 @@ +#include "tiktmd.h" +#include "tools.h" +#include "aes.h" + +#include + +Tmd::Tmd( QByteArray stuff ) +{ + data = stuff; + p_tmd = NULL; + if( data.isEmpty() || data.size() < 4 )//maybe 4 is still too low? + return; + + SetPointer(); + //hexdump( stuff ); +} + +quint64 Tmd::Tid() +{ + if( !p_tmd ) + return 0; + return qFromBigEndian( p_tmd->title_id ); +} + +QString Tmd::Cid( quint16 i ) +{ + if( p_tmd && i > qFromBigEndian( p_tmd->num_contents ) ) + return QString(); + return QString( "%1" ).arg( qFromBigEndian( p_tmd->contents[ i ].cid ), 8, 16, QChar( '0' ) ); +} + +QByteArray Tmd::Hash( quint16 i ) +{ + if( p_tmd && i > qFromBigEndian( p_tmd->num_contents ) ) + return QByteArray(); + + return QByteArray( (const char*)&p_tmd->contents[ i ].hash, 20 ); +} + +quint64 Tmd::Size( quint16 i ) +{ + if( p_tmd && i > qFromBigEndian( p_tmd->num_contents ) ) + return 0; + return qFromBigEndian( p_tmd->contents[ i ].size ); +} + +quint16 Tmd::Type( quint16 i ) +{ + if( p_tmd && i > qFromBigEndian( p_tmd->num_contents ) ) + return 0; + return qFromBigEndian( p_tmd->contents[ i ].type ); +} + +quint32 Tmd::SignedSize() +{ + return payLoadOffset + sizeof( tmd ) + ( sizeof( tmd_content ) * qFromBigEndian( p_tmd->num_contents ) ); +} + +void Tmd::SetPointer() +{ + payLoadOffset = 0; + if( data.startsWith( "\x0\x1\x0\x0" ) ) + payLoadOffset = sizeof( sig_rsa2048 ); + + else if( data.startsWith( "\x0\x1\x0\x1" ) ) + payLoadOffset = sizeof( sig_rsa4096 ); + + else if( data.startsWith( "\x0\x1\x0\x2" ) ) + payLoadOffset = sizeof( sig_ecdsa ); + + p_tmd = (tmd*)((quint8*)data.data() + payLoadOffset); +} + +Ticket::Ticket( QByteArray stuff ) +{ + data = stuff; + p_tik = NULL; + if( data.isEmpty() || data.size() < 4 )//maybe 4 is still too low? + return; + + SetPointer(); +} + +quint64 Ticket::Tid() +{ + if( !p_tik ) + return 0; + return qFromBigEndian( p_tik->titleid ); +} + +QByteArray Ticket::DecryptedKey() +{ + quint8 iv[ 16 ]; + quint8 keyin[ 16 ]; + quint8 keyout[ 16 ]; + static quint8 commonkey[ 16 ] = COMMON_KEY; + + quint8 *enc_key = (quint8 *)&p_tik->cipher_title_key; + memcpy( keyin, enc_key, sizeof keyin ); + memset( keyout, 0, sizeof keyout ); + memset( iv, 0, sizeof iv); + memcpy( iv, &p_tik->titleid, sizeof p_tik->titleid ); + + aes_set_key( commonkey ); + aes_decrypt( iv, keyin, keyout, sizeof( keyin ) ); + + return QByteArray( (const char*)&keyout, 16 ); + +} + +quint32 Ticket::SignedSize() +{ + return payLoadOffset + sizeof( tik ); +} + +void Ticket::SetPointer() +{ + payLoadOffset = 0; + if( data.startsWith( "\x0\x1\x0\x0" ) ) + payLoadOffset = sizeof( sig_rsa2048 ); + + else if( data.startsWith( "\x0\x1\x0\x1" ) ) + payLoadOffset = sizeof( sig_rsa4096 ); + + else if( data.startsWith( "\x0\x1\x0\x2" ) ) + payLoadOffset = sizeof( sig_ecdsa ); + + p_tik = (tik*)((quint8*)data.data() + payLoadOffset); +} diff --git a/nand_dump/tiktmd.h b/nand_dump/tiktmd.h new file mode 100644 index 0000000..f88a9f9 --- /dev/null +++ b/nand_dump/tiktmd.h @@ -0,0 +1,231 @@ +#ifndef TIKTMD_H +#define TIKTMD_H + +#include "includes.h" + +#define ES_SIG_RSA4096 0x10000 +#define ES_SIG_RSA2048 0x10001 +#define ES_SIG_ECDSA 0x10002 + +typedef quint32 sigtype; +typedef sigtype sig_header; +typedef sig_header signed_blob; + +typedef quint8 sha1[20]; +typedef quint8 aeskey[16]; + +typedef struct _sig_rsa2048 { + sigtype type; + quint8 sig[256]; + quint8 fill[60]; +} __attribute__((packed)) sig_rsa2048; + +typedef struct _sig_rsa4096 { + sigtype type; + quint8 sig[512]; + quint8 fill[60]; +} __attribute__((packed)) sig_rsa4096; + +typedef struct _sig_ecdsa { + sigtype type; + quint8 sig[60]; + quint8 fill[64]; +} __attribute__((packed)) sig_ecdsa; + +typedef char sig_issuer[0x40]; + +typedef struct _tiklimit { + quint32 tag; + quint32 value; +} __attribute__((packed)) tiklimit; +/* +typedef struct _tikview { + quint32 view; + quint64 ticketid; + quint32 devicetype; + quint64 titleid; + quint16 access_mask; + quint8 reserved[0x3c]; + quint8 cidx_mask[0x40]; + quint16 padding; + tiklimit limits[8]; +} __attribute__((packed)) tikview; +*/ +typedef struct _tik { + sig_issuer issuer; + quint8 fill[63]; //TODO: not really fill + aeskey cipher_title_key; + quint8 fill2; + quint64 ticketid; + quint32 devicetype; + quint64 titleid; + quint16 access_mask; + quint8 reserved[0x3c]; + quint8 cidx_mask[0x40]; + quint16 padding; + tiklimit limits[8]; +} __attribute__((packed)) tik; + +typedef struct _tmd_content { + quint32 cid; + quint16 index; + quint16 type; + quint64 size; + sha1 hash; +} __attribute__((packed)) tmd_content; + +typedef struct _tmd { + sig_issuer issuer; //0x140 + quint8 version; //0x180 + quint8 ca_crl_version; //0x181 + quint8 signer_crl_version; //0x182 + quint8 fill2; //0x183 + quint64 sys_version; //0x184 + quint64 title_id; //0x18c + quint32 title_type; //0x194 + quint16 group_id; //0x198 + quint16 zero; //0x19a + quint16 region; //0x19c + quint8 ratings[16]; //0x19e + quint8 reserved[12]; //0x1ae + quint8 ipc_mask[12]; + quint8 reserved2[18]; + quint32 access_rights; + quint16 title_version; + quint16 num_contents; + quint16 boot_index; + quint16 fill3; + // content records follow + // C99 flexible array + tmd_content contents[]; +} __attribute__((packed)) tmd; + +typedef struct _tmd_view_content +{ + quint32 cid; + quint16 index; + quint16 type; + quint64 size; +} __attribute__((packed)) tmd_view_content; + +typedef struct _tmdview +{ + quint8 version; // 0x0000; + quint8 filler[3]; + quint64 sys_version; //0x0004 + quint64 title_id; // 0x00c + quint32 title_type; //0x0014 + quint16 group_id; //0x0018 + quint8 reserved[0x3e]; //0x001a this is the same reserved 0x3e bytes from the tmd + quint16 title_version; //0x0058 + quint16 num_contents; //0x005a + tmd_view_content contents[]; //0x005c +}__attribute__((packed)) tmd_view; + +typedef struct _cert_header { + sig_issuer issuer; + quint32 cert_type; + char cert_name[64]; + quint32 cert_id; //??? +} __attribute__((packed)) cert_header; + +typedef struct _cert_rsa2048 { + sig_issuer issuer; + quint32 cert_type; + char cert_name[64]; + quint32 cert_id; + quint8 modulus[256]; + quint32 exponent; + quint8 pad[0x34]; +} __attribute__((packed)) cert_rsa2048; + +typedef struct _cert_rsa4096 { + sig_issuer issuer; + quint32 cert_type; + char cert_name[64]; + quint32 cert_id; + quint8 modulus[512]; + quint32 exponent; + quint8 pad[0x34]; +} __attribute__((packed)) cert_rsa4096; + +typedef struct _cert_ecdsa { + sig_issuer issuer; + quint32 cert_type; + char cert_name[64]; + quint32 cert_id; // ng key id + quint8 r[30]; + quint8 s[30]; + quint8 pad[0x3c]; +} __attribute__((packed)) 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 +{ +public: + Ticket( QByteArray stuff ); + + //expose the tik data to the rest of the code so it cas read directly from the p_tmd instead of having to add a function to access all the data + //the pointer should be good until "data" is changed + const tik *payload(){ return p_tik; } + + quint64 Tid(); + + QByteArray DecryptedKey(); + quint32 SignedSize(); + +private: + quint32 payLoadOffset; + + //let data hold the entire tik data + QByteArray data; + + //point the p_tik to the spot in "data" we want + 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 + tik *p_tik; +}; + +class Tmd +{ +public: + Tmd( QByteArray stuff ); + + //expose the tmd data to the rest of the code so it cas read directly from the p_tmd instead of having to add a function to access all the data + //the pointer should be good until "data" is changed + const tmd *payload(){ return p_tmd; } + + //get a string containing the u32 of cid i + QString Cid( quint16 i ); + + //get a btyearray with the hosh of the given conten + QByteArray Hash( quint16 i ); + + //returned in host endian + quint64 Size( quint16 i ); + quint16 Type( quint16 i ); + quint64 Tid(); + + quint32 SignedSize(); +private: + quint32 payLoadOffset; + + //let data hold the entire tmd data + QByteArray data; + + //point the p_tmd to the spot in "data" we want + 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 + tmd *p_tmd; +}; + +#endif // TIKTMD_H diff --git a/nand_dump/tools.cpp b/nand_dump/tools.cpp new file mode 100644 index 0000000..79e6ce4 --- /dev/null +++ b/nand_dump/tools.cpp @@ -0,0 +1,131 @@ +#include "tools.h" +#include "includes.h" +#include "aes.h" +#include "sha1.h" + +QString currentDir; +QString cachePath = "./NUS_cache"; +QString nandPath = "./dump"; + +char ascii( char s ) { + if ( s < 0x20 ) return '.'; + if ( s > 0x7E ) return '.'; + return s; +} + +//using stderr just because qtcreator stows it correctly when mixed with qDebug(), qWarning(), etc +//otherwise it may not be shown in the correct spot in the output due to stdout/stderr caches +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 ); +} + +void hexdump12( const void *d, int len ) { + unsigned char *data; + int i, off; + data = (unsigned char*)d; + fprintf( stderr, "\n"); + for ( off = 0; off < len; off += 12 ) { + fprintf( stderr, "%08x ", off ); + for ( i=0; i<12; 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 < 12; i++ ) + if ( ( i + off) >= len ) fprintf( stderr," "); + else fprintf( stderr,"%c", ascii( data[ off + i ])); + fprintf( stderr,"\n"); + } + fflush( stderr ); +} + +void hexdump12( const QByteArray &d, int from, int len ) +{ + hexdump12( d.data() + from, len == -1 ? d.size() : len ); +} + +QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo ) +{ + QByteArray padding( RU( padTo, orig.size() ) - orig.size(), '\0' ); + //qDebug() << "padding with" << hex << RU( padTo, orig.size() ) << "bytes" << + return orig + padding; +} + +QByteArray AesDecrypt( quint16 index, const QByteArray source ) +{ + static quint8 iv[ 16 ]; + + quint16 beidx = qFromBigEndian( index ); + memset( iv, 0, 16 ); + memcpy( iv, &beidx, 2 ); + QByteArray ret( source.size(), '\0' ); + aes_decrypt( iv, (const quint8*)source.data(), (quint8*)ret.data(), source.size() ); + return ret; +} + +void AesSetKey( const QByteArray key ) +{ + aes_set_key( (const quint8*)key.data() ); +} + +QByteArray GetSha1( QByteArray stuff ) +{ + SHA1Context sha; + SHA1Reset( &sha ); + SHA1Input( &sha, (const unsigned char*)stuff.data(), stuff.size() ); + if( !SHA1Result( &sha ) ) + { + qWarning() << "GetSha1 -> sha error"; + return QByteArray(); + } + QByteArray ret( 20, '\0' ); + quint8 *p = (quint8 *)ret.data(); + for( int i = 0; i < 5 ; i++ ) + { + quint32 part = qFromBigEndian( sha.Message_Digest[ i ] ); + memcpy( p + ( i * 4 ), &part, 4 ); + } + //hexdump( ret ); + return ret; +} diff --git a/nand_dump/tools.h b/nand_dump/tools.h new file mode 100644 index 0000000..34700e6 --- /dev/null +++ b/nand_dump/tools.h @@ -0,0 +1,34 @@ +#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 ) ) + +void hexdump( const void *d, int len ); +void hexdump( const QByteArray &d, int from = 0, int len = -1 ); + +void hexdump12( const void *d, int len ); +void hexdump12( const QByteArray &d, int from = 0, int len = -1 ); + +//simplified functions for crypto shit +void AesSetKey( const QByteArray key ); +QByteArray AesDecrypt( quint16 index, const QByteArray source ); + +QByteArray GetSha1( QByteArray stuff ); + +//get a padded version of the given buffer +QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo ); + +//keep track of the last folder browsed to when looking for files +extern QString currentDir; + +//folder used to cache stuff downloaded from NUS so duplicate titles dont need to be downloaded +extern QString cachePath; + +//folder to use as the base path for the nand +extern QString nandPath; + +#endif // TOOLS_H diff --git a/nand_dump/uidmap.cpp b/nand_dump/uidmap.cpp new file mode 100644 index 0000000..57f4780 --- /dev/null +++ b/nand_dump/uidmap.cpp @@ -0,0 +1,194 @@ +#include "uidmap.h" +#include "tools.h" + +UIDmap::UIDmap( const QByteArray old ) +{ + data = old; + if( data.isEmpty() ) + CreateNew();//no need to add all the factory uids if the nand in never going to be sent back to the big N +} +UIDmap::~UIDmap() +{ + +} + +bool UIDmap::Check() +{ + if( !data.size() || data.size() % 12 ) + { + qWarning() << "UIDmap::Check() bad size:" << hex << data.size(); + return false; + } + + quint64 tid; + quint32 uid; + QBuffer buf( &data ); + buf.open( QIODevice::ReadOnly ); + + buf.read( (char*)&tid, 8 ); + buf.read( (char*)&uid, 4 ); + + tid = qFromBigEndian( tid ); + uid = qFromBigEndian( uid ); + if( tid != 0x100000002ull || uid != 0x1000 )//system menu should be the first entry + { + qWarning() << "UIDmap::Check() system menu entry is messed up:" << hex << tid << uid; + buf.close(); + return false; + } + + QList tids; + QList uids; + //check that there are no duplicate tid or uid and that all uid are as expected + quint32 cnt = data.size() / 12; + for( quint32 i = 1; i < cnt; i++ ) + { + buf.read( (char*)&tid, 8 ); + buf.read( (char*)&uid, 4 ); + tid = qFromBigEndian( tid ); + uid = qFromBigEndian( uid ); + if( tids.contains( tid ) ) + { + qWarning() << "UIDmap::Check() tid is present more than once:" << QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ); + buf.close(); + return false; + } + if( ( uid != 0x1000 + i ) || uids.contains( uid ) ) + { + qWarning() << "UIDmap::Check() uid error:" << QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ) << hex << uid; + buf.close(); + return false; + } + + tids << tid; + uids << uid; + } + + return true; + +} + +quint32 UIDmap::GetUid( quint64 id, bool autoCreate ) +{ + //qDebug() << "UIDmap::GetUid" << hex << id; + quint64 tid; + quint32 uid; + QBuffer buf( &data ); + buf.open( QIODevice::ReadWrite ); + + quint32 cnt = data.size() / 12; + for( quint32 i = 0; i < cnt; i++ ) + { + buf.read( (char*)&tid, 8 ); + buf.read( (char*)&uid, 4 ); + tid = qFromBigEndian( tid ); + if( tid == id ) + { + buf.close(); + return qFromBigEndian( uid ); + } + } + + //not found + if( !autoCreate ) + return 0; + + //add the new entry + tid = qFromBigEndian( id ); + buf.write( (const char*)&tid, 8 ); + uid = qFromBigEndian( qFromBigEndian( uid ) + 1 ); + buf.write( (const char*)&uid, 4 ); + buf.close(); + + //qDebug() << "new uid:"; + //hexdump12( data, ( cnt - 3 ) * 12, 0x30 ); + + return qFromBigEndian( uid ); +} + +void UIDmap::CreateNew( bool addFactorySetupDiscs ) +{ + quint64 tid; + quint32 uid; + QByteArray stuff; + QBuffer buf( &stuff ); + buf.open( QIODevice::WriteOnly ); + + //add the new entry + tid = qFromBigEndian( 0x100000002ull ); + buf.write( (const char*)&tid, 8 ); + uid = qFromBigEndian( 0x1000 ); + buf.write( (const char*)&uid, 4 ); + if( !addFactorySetupDiscs ) + { + buf.close(); + data = stuff; + return; + } + + //add some entries for the factory setup discs, as seen on my nand up until the first retail game + for( quint32 i = 1; i < 0x2f; i++ ) + { + switch( i ) + { + case 0x1:tid = qFromBigEndian( 0x100000004ull ); break; + case 0x2:tid = qFromBigEndian( 0x100000009ull ); break; + case 0x3:tid = qFromBigEndian( 0x100003132334aull ); break; + case 0x4:tid = qFromBigEndian( 0x000100000000deadull ); break; + case 0x5:tid = qFromBigEndian( 0x100000100ull ); break; + case 0x6:tid = qFromBigEndian( 0x100000101ull ); break; + case 0x7:tid = qFromBigEndian( 0x000100003132314aull ); break; + case 0x8:tid = qFromBigEndian( 0x100000015ull ); break; + case 0x9:tid = qFromBigEndian( 0x0001000030303032ull ); break; + case 0xa:tid = qFromBigEndian( 0x100000003ull ); break; + case 0xb:tid = qFromBigEndian( 0x10000000aull ); break; + case 0xc:tid = qFromBigEndian( 0x10000000bull ); break; + case 0xd:tid = qFromBigEndian( 0x10000000cull ); break; + case 0xe:tid = qFromBigEndian( 0x10000000dull ); break; + case 0xf:tid = qFromBigEndian( 0x10000000eull ); break; + case 0x10:tid = qFromBigEndian( 0x10000000full ); break; + case 0x11:tid = qFromBigEndian( 0x100000011ull ); break; + case 0x12:tid = qFromBigEndian( 0x100000014ull ); break; + case 0x13:tid = qFromBigEndian( 0x100000016ull ); break; + case 0x14:tid = qFromBigEndian( 0x10000001cull ); break; + case 0x15:tid = qFromBigEndian( 0x10000001eull ); break; + case 0x16:tid = qFromBigEndian( 0x10000001full ); break; + case 0x17:tid = qFromBigEndian( 0x100000021ull ); break; + case 0x18:tid = qFromBigEndian( 0x100000022ull ); break; + case 0x19:tid = qFromBigEndian( 0x100000023ull ); break; + case 0x1a:tid = qFromBigEndian( 0x100000024ull ); break; + case 0x1b:tid = qFromBigEndian( 0x100000025ull ); break; + case 0x1c:tid = qFromBigEndian( 0x100000026ull ); break; + case 0x1d:tid = qFromBigEndian( 0x100000032ull ); break; + case 0x1e:tid = qFromBigEndian( 0x100000033ull ); break; + case 0x1f:tid = qFromBigEndian( 0x100000035ull ); break; + case 0x20:tid = qFromBigEndian( 0x100000037ull ); break; + case 0x21:tid = qFromBigEndian( 0x1000000feull ); break; + case 0x22:tid = qFromBigEndian( 0x0001000248414341ull ); break; + case 0x23:tid = qFromBigEndian( 0x0001000248414141ull ); break; + case 0x24:tid = qFromBigEndian( 0x0001000248415941ull ); break; + case 0x25:tid = qFromBigEndian( 0x0001000248414641ull ); break; + case 0x26:tid = qFromBigEndian( 0x0001000248414645ull ); break; + case 0x27:tid = qFromBigEndian( 0x0001000248414241ull ); break; + case 0x28:tid = qFromBigEndian( 0x0001000248414741ull ); break; + case 0x29:tid = qFromBigEndian( 0x0001000248414745ull ); break; + case 0x2a:tid = qFromBigEndian( 0x0001000848414b45ull ); break; + case 0x2b:tid = qFromBigEndian( 0x0001000848414c45ull ); break; + case 0x2c:tid = qFromBigEndian( 0x0001000148434745ull ); break; + case 0x2d:tid = qFromBigEndian( 0x0001000031323245ull ); break; + case 0x2e:tid = qFromBigEndian( 0x0001000030303033ull ); break; + default: + qWarning() << "oops" << hex << i; + return; + } + + uid = qFromBigEndian( 0x1000 + i ); + buf.write( (const char*)&tid, 8 ); + buf.write( (const char*)&uid, 4 ); + + } + + buf.close(); + data = stuff; + // hexdump12( data ); +} diff --git a/nand_dump/uidmap.h b/nand_dump/uidmap.h new file mode 100644 index 0000000..b0fe8c9 --- /dev/null +++ b/nand_dump/uidmap.h @@ -0,0 +1,30 @@ +#ifndef UIDMAP_H +#define UIDMAP_H + +#include "includes.h" + +class UIDmap +{ +public: + UIDmap( const QByteArray old = QByteArray() ); + ~UIDmap(); + + //makes sure there are the default entries in the map and that the entries are sane + bool Check(); + + //returns the uid for the given tid + //returns 0 if it is not found. or if autocreate is true, it will be created and the new uid returned + //numbers are in host endian + 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 ) + void CreateNew( bool addFactorySetupDiscs = false ); + + const QByteArray Data(){ return data; } + +private: + QByteArray data; +}; + +#endif // UIDMAP_H diff --git a/nand_dump/wad.cpp b/nand_dump/wad.cpp new file mode 100644 index 0000000..5166840 --- /dev/null +++ b/nand_dump/wad.cpp @@ -0,0 +1,128 @@ +#include "wad.h" +#include "tools.h" +#include "tiktmd.h" + +static QByteArray globalCert; + +Wad::Wad( const QByteArray stuff ) +{ + ok = false; + if( stuff.size() < 0x80 )//less than this and there is definitely nothing there + return; + + QByteArray copy = stuff; + QBuffer b( © ); + b.open( QIODevice::ReadOnly ); + + quint32 tmp; + b.read( (char*)&tmp, 4 ); + if( qFromBigEndian( tmp ) != 0x20 ) + { + b.close(); + Err( "Bad header size" ); + return; + } + b.read( (char*)&tmp, 4 ); + tmp = qFromBigEndian( tmp ); + if( tmp != 0x49730000 && tmp != 0x69620000 && tmp != 0x426b0000 ) + { + b.close(); + Err( "Bad file magic word" ); + return; + } + + quint32 certSize; + quint32 tikSize; + quint32 tmdSize; + quint32 appSize; + quint32 footerSize; + + b.read( (char*)&certSize, 4 ); + certSize = qFromBigEndian( certSize ); + + b.seek( 0x10 ); + b.read( (char*)&tikSize, 4 ); + tikSize = qFromBigEndian( tikSize ); + b.read( (char*)&tmdSize, 4 ); + tmdSize = qFromBigEndian( tmdSize ); + b.read( (char*)&appSize, 4 ); + appSize = qFromBigEndian( appSize ); + b.read( (char*)&footerSize, 4 ); + footerSize = qFromBigEndian( footerSize ); + + b.close();//close the buffer, the rest of the data can be checked without it + + //sanity check this thing + quint32 s = stuff.size(); + if( s < ( RU( 0x40, certSize ) + RU( 0x40, tikSize ) + RU( 0x40, tmdSize ) + RU( 0x40, appSize ) + RU( 0x40, footerSize ) ) ) + { + Err( "Total size is less than the combined sizes of all the parts that it is supposed to contain" ); + return; + } + + quint32 pos = 0x40; + certData = stuff.mid( pos, certSize ); + pos += RU( 0x40, certSize ); + tikData = stuff.mid( pos, tikSize ); + pos += RU( 0x40, tikSize ); + tmdData = stuff.mid( pos, tmdSize ); + pos += RU( 0x40, tmdSize ); + + Ticket ticket( tikData ); + Tmd t( tmdData ); + + if( ticket.Tid() != t.Tid() ) + qWarning() << "wad contains 2 different TIDs"; + + quint32 cnt = qFromBigEndian( t.payload()->num_contents ); + qDebug() << "Wad contains" << hex << cnt << "contents"; + + //another quick sanity check + quint32 totalSize = 0; + for( quint32 i = 0; i < cnt; i++ ) + totalSize += t.Size( i ); + + if( totalSize > appSize ) + { + Err( "Size of all the apps in the tmd is greater than the size in the wad header" ); + return; + } + /* +void AesSetKey( const QByteArray key ); +QByteArray AesDecrypt( quint16 index, const QByteArray source ); +*/ + //read all the contents, check the hash, and remember the data ( still encrypted ) + for( quint32 i = 0; i < cnt; i++ ) + { + quint32 s = RU( 0x40, t.Size( i ) ); + QByteArray encData = stuff.mid( pos, s ); + pos += s; + + //doing this here in case there is some other object that is using the AES that would change the key on us + AesSetKey( ticket.DecryptedKey() ); + + + } + + //Data = stuff.mid( pos, Size ); + //pos += RU( 0x40, Size ); + + + + + + + ok = true; + +} + +Wad::Wad( QList< QByteArray > stuff, bool encrypted ) +{ + +} + +void Wad::Err( QString str ) +{ + errStr = str; + qWarning() << "Wad::Error" << str; +} diff --git a/nand_dump/wad.h b/nand_dump/wad.h new file mode 100644 index 0000000..55f0069 --- /dev/null +++ b/nand_dump/wad.h @@ -0,0 +1,70 @@ +#ifndef WAD_H +#define WAD_H + +#include "includes.h" +class Wad +{ +public: + //create a wad instance from a bytearray containing a wad + Wad( const QByteArray stuff = QByteArray() ); + + //create a wad object from a list of byteArrays. + //the first one should be the tmd, the second one the ticket, and the rest are the contents, in order + //it will use the global cert unless one is given with SetCert + Wad( QList< QByteArray > stuff, bool isEncrypted = true ); + + //check if this wad is valid + bool IsOk(){ return ok; } + + //set the cert for this wad + void SetCert( const QByteArray stuff ); + + //returns the tid of the tmd of this wad( which may be different than the one in the ticket if somebody did something stupid ) + quint64 Tid(); + + //set the tid in the ticket&tmd and fakesign the wad + void SetTid( quint64 tid ); + + //add a new content to this wad and fakesign + //if the data is encrypted, set that arguement to true + //index is the index used for the new entry, default is after all the others + void AddContent( const QByteArray &stuff, quint16 type, bool isEncrypted = false, quint16 index = 0xffff ); + + //remove a content from this wad + void RemoveContent( quint16 index ); + + //set the global cert that will be used for all created + static void SetGlobalCert( const QByteArray &stuff ); + + //pack a wad from the given directory + // use footer to decide if a footer will be added + static QByteArray FromDirectory( QDir dir, bool footer = false ); + + //get a assembled wad from the list of parts + //the first one should be the tmd, the second one the ticket, and the rest are the contents, in order + //it will use the global cert + static QByteArray FromPartList( QList< QByteArray > stuff, bool isEncrypted = true ); + + //get all the parts of this wad put together in a wad ready for writing to disc or whatever + const QByteArray Data(); + + //get the decrypted data from a content + const QByteArray Content( quint16 i ); + + //get the last error encountered while trying to do something + const QString LastError(){ return errStr; } + +private: + bool ok; + QString errStr; + + //keep encrypted parts here + QList< QByteArray > partsEnc; + QByteArray tmdData; + QByteArray tikData; + QByteArray certData; + + void Err( QString str ); +}; + +#endif // WAD_H diff --git a/saveToy/includes.h b/saveToy/includes.h new file mode 100644 index 0000000..d45ded5 --- /dev/null +++ b/saveToy/includes.h @@ -0,0 +1,30 @@ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +//#include +//#include diff --git a/saveToy/main.cpp b/saveToy/main.cpp new file mode 100644 index 0000000..3f61ad5 --- /dev/null +++ b/saveToy/main.cpp @@ -0,0 +1,12 @@ +#include +#include "mainwindow.h" + +int main(int argc, char *argv[]) +{ + Q_INIT_RESOURCE( rc ); + QApplication a(argc, argv); + MainWindow w; + w.show(); + + return a.exec(); +} diff --git a/saveToy/mainwindow.cpp b/saveToy/mainwindow.cpp new file mode 100644 index 0000000..6cd56f3 --- /dev/null +++ b/saveToy/mainwindow.cpp @@ -0,0 +1,179 @@ +#include "mainwindow.h" +#include "ui_mainwindow.h" +#include "tools.h" + +MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), bannerthread( this ) +{ + ui->setupUi(this); + progressBar.setVisible( false ); + ui->statusBar->addPermanentWidget( &progressBar, 0 ); + + //sneekIconTimer.setInterval( 150 );//delay of icon image animation + + connect( &bannerthread, SIGNAL( SendProgress( int ) ), this, SLOT( GetProgressUpdate( int ) ) ); + connect( &bannerthread, SIGNAL( SendDone( int ) ), this, SLOT( LoadThreadIsDone( int ) ) ); + connect( &bannerthread, SIGNAL( SendItem( QByteArray, const QString&, int, int ) ), this, SLOT( ReceiveSaveItem( QByteArray, const QString&, int, int ) ) ); + + connect( &sneekIconTimer, SIGNAL(timeout() ), this, SLOT( ShowNextSneekIcon() ) ); + + + + //GetSavesFromSneek( "/media/WiiFat500" ); + GetSavesFromSneek( sneekPath ); +} + +MainWindow::~MainWindow() +{ + delete ui; +} + +//get the saves from a nand directory +void MainWindow::GetSavesFromSneek( const QString &path ) +{ + sneekPath = path; + ui->listWidget_sneekSaves->clear(); + progressBar.setValue( 0 ); + progressBar.setVisible( true ); + bannerthread.GetBanners( path, LOAD_SNEEK ); +} +/* +//sneek save clicked +void MainWindow::on_listWidget_sneekSaves_itemClicked(QListWidgetItem* item) +{ + if( !item ) + return; + + SaveListItem *i = static_cast< SaveListItem * >( item ); + qDebug() << "item clicked" << i->Tid(); +} + +//sneek save double clicked +void MainWindow::on_listWidget_sneekSaves_itemActivated( QListWidgetItem* item ) +{ + if( !item ) + return; + + SaveListItem *i = static_cast< SaveListItem * >( item ); + qDebug() << "item activated" << i->Tid(); +}*/ +//sneek save item changed +void MainWindow::on_listWidget_sneekSaves_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous) +{ + Q_UNUSED( previous ); + if( !current ) + return; + + SaveListItem *i = static_cast< SaveListItem * >( current ); + + ShowSneekSaveDetails( i ); +} + +//get an item from the thread loading all the data and turn it into a banner +void MainWindow::ReceiveSaveItem( QByteArray stuff, const QString& tid, int type, int size ) +{ + switch( type ) + { + case LOAD_SNEEK: + { + QByteArray copy = stuff; + SaveBanner sb( copy ); + SaveListItem *item = new SaveListItem( sb, tid, size, ui->listWidget_sneekSaves ); + ui->listWidget_sneekSaves->addItem( item ); + } + break; + default: + break; + } + + +} + +//get a pregress update from something that is doing work +void MainWindow::GetProgressUpdate( int i ) +{ + progressBar.setValue( i ); +} + +//something is done working. respond somehow +void MainWindow::LoadThreadIsDone( int type ) +{ + Q_UNUSED( type ); + progressBar.setVisible( false ); +} + +//tools -> set sneek path +void MainWindow::on_actionSet_Sneek_Path_triggered() +{ + QString p = QFileDialog::getExistingDirectory( this, tr( "Select SNEEK root" ), "/media" ); + if( p.isEmpty() ) + return; + + GetSavesFromSneek( p ); +} + +//show the details for a save in the sneek nand +void MainWindow::ShowSneekSaveDetails( SaveListItem *item ) +{ + sneekIconTimer.stop(); + currentSneekIcon = 0; + sneekIcon.clear(); + + SaveBanner *sb = item->Banner(); + + ui->label_sneek_title->setText( sb->Title() ); + + if( !sb->SubTitle().isEmpty() && sb->Title() != sb->SubTitle() ) + ui->label_sneek_title2->setText( sb->SubTitle() ); + else + ui->label_sneek_title2->clear(); + + QString tid = item->Tid(); + tid.insert( 8, "/" ); + tid.prepend( "/" ); + ui->label_sneek_path->setText( tid ); + + QString id; + tid = item->Tid().right( 8 ); + quint32 num = qFromBigEndian( (quint32) tid.toInt( NULL, 16 ) ); + for( int i = 0; i < 4; i++ ) + id += ascii( (char)( num >> ( 8 * i ) ) & 0xff ); + + ui->label_sneek_id->setText( id ); + int size = item->Size(); + QString sizeStr; + if( size < 0x400 ) + sizeStr = tr( "%1 B" ).arg( size, 3 ); + else if( size < 0x100000 ) + { + float kib = (float)size / 1024.00f; + sizeStr = tr( "%1 KiB" ).arg( kib, 3, 'f', 2 ); + } + else//assume there wont be any 1GB saves + { + float mib = (float)size / 1048576.00f; + sizeStr = tr( "%1 MiB" ).arg( mib, 3, 'f', 2 ); + } + int blocks = RU( 0x20000, size) / 0x20000; + QString si = QString( "%1 %2 (%3)").arg( blocks ).arg( blocks == 1 ? tr( "Block" ) : tr( "Blocks" ) ).arg( sizeStr ); + + ui->label_sneek_size->setText( si ); + + foreach( QImage im, sb->IconImgs() ) + sneekIcon << QPixmap::fromImage( im ); + + currentSneekIcon = 0; + ui->label_sneek_icon->setPixmap( sneekIcon.at( 0 ) ); + if( sneekIcon.size() > 1 ) + { + sneekIconTimer.setInterval( 1000 / sneekIcon.size() );//delay of icon image animation + sneekIconTimer.start(); + } +} + +void MainWindow::ShowNextSneekIcon() +{ + if( ++currentSneekIcon >= sneekIcon.size() ) + currentSneekIcon = 0; + + ui->label_sneek_icon->setPixmap( sneekIcon.at( currentSneekIcon ) ); +} diff --git a/saveToy/mainwindow.h b/saveToy/mainwindow.h new file mode 100644 index 0000000..4ea3dc6 --- /dev/null +++ b/saveToy/mainwindow.h @@ -0,0 +1,49 @@ +#ifndef MAINWINDOW_H +#define MAINWINDOW_H + +#include "saveloadthread.h" +#include "savelistitem.h" +#include "savebanner.h" +#include "includes.h" + +namespace Ui { + class MainWindow; +} + +class MainWindow : public QMainWindow +{ + Q_OBJECT + +public: + explicit MainWindow(QWidget *parent = 0); + ~MainWindow(); + +private: + Ui::MainWindow *ui; + QProgressBar progressBar; + void GetSavesFromSneek( const QString &path ); + + SaveLoadThread bannerthread; + + //void ShowSneekSaveDetails( SaveListItem *item ); + int currentSneekIcon; + QTimer sneekIconTimer; + QListsneekIcon; + + +private slots: + void on_actionSet_Sneek_Path_triggered(); + void on_listWidget_sneekSaves_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous); + //void on_listWidget_sneekSaves_itemClicked(QListWidgetItem* item); + //void on_listWidget_sneekSaves_itemActivated( QListWidgetItem* item ); + + void ReceiveSaveItem( QByteArray, const QString&, int, int ); + void GetProgressUpdate( int ); + void LoadThreadIsDone( int ); + + void ShowSneekSaveDetails( SaveListItem *item ); + void ShowNextSneekIcon(); + +}; + +#endif // MAINWINDOW_H diff --git a/saveToy/mainwindow.ui b/saveToy/mainwindow.ui new file mode 100644 index 0000000..71d4357 --- /dev/null +++ b/saveToy/mainwindow.ui @@ -0,0 +1,376 @@ + + + MainWindow + + + + 0 + 0 + 949 + 535 + + + + MainWindow + + + + + 2 + + + + + 1 + + + + Sneek + + + + + + + 620 + 0 + + + + + 620 + 16777215 + + + + QAbstractItemView::ExtendedSelection + + + + 200 + 80 + + + + QListView::Static + + + QListView::IconMode + + + true + + + true + + + true + + + + + + + + + + + + 50 + 50 + + + + + 50 + 50 + + + + TextLabel + + + + + + + + + id + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 193 + 0 + + + + size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + title + + + + + + + title2 + + + + + + + path + + + + + + + Qt::Vertical + + + + 20 + 40 + + + + + + + + Delete + + + + + + + Extract + + + + + + + + + + Tab 2 + + + + + + + 620 + 0 + + + + + 620 + 16777215 + + + + QAbstractItemView::ExtendedSelection + + + + 200 + 80 + + + + QListView::Static + + + QListView::IconMode + + + true + + + true + + + true + + + + + + + + + + + + 50 + 50 + + + + + 50 + 50 + + + + TextLabel + + + + + + + + + id + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + 193 + 0 + + + + size + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + + + + + title + + + + + + + title2 + + + + + + + path + + + + + + + + + + + + + + + Delete + + + + + + + Save Desc. + + + + + + + Install + + + + + + + + + + + + + + + + + 0 + 0 + 949 + 27 + + + + + Tools + + + + + + + + TopToolBarArea + + + false + + + + + + Set Sneek Path... + + + Ctrl+L + + + + + + + diff --git a/saveToy/noBanner.png b/saveToy/noBanner.png new file mode 100644 index 0000000..7352fba Binary files /dev/null and b/saveToy/noBanner.png differ diff --git a/saveToy/noIcon.png b/saveToy/noIcon.png new file mode 100644 index 0000000..7cb3ea5 Binary files /dev/null and b/saveToy/noIcon.png differ diff --git a/saveToy/rc.qrc b/saveToy/rc.qrc new file mode 100644 index 0000000..69684ae --- /dev/null +++ b/saveToy/rc.qrc @@ -0,0 +1,6 @@ + + + noBanner.png + noIcon.png + + diff --git a/saveToy/saveToy.pro b/saveToy/saveToy.pro new file mode 100644 index 0000000..1e2764d --- /dev/null +++ b/saveToy/saveToy.pro @@ -0,0 +1,30 @@ +#------------------------------------------------- +# +# Project created by QtCreator 2010-12-02T02:54:54 +# +#------------------------------------------------- + +QT += core gui + +TARGET = saveToy +TEMPLATE = app + + +SOURCES += main.cpp\ + mainwindow.cpp \ + savebanner.cpp \ + savelistitem.cpp \ + saveloadthread.cpp \ + tools.cpp + +HEADERS += mainwindow.h \ + includes.h \ + savebanner.h \ + savelistitem.h \ + saveloadthread.h \ + tools.h + +FORMS += mainwindow.ui + +RESOURCES += \ + rc.qrc diff --git a/saveToy/savebanner.cpp b/saveToy/savebanner.cpp new file mode 100644 index 0000000..9fe37f2 --- /dev/null +++ b/saveToy/savebanner.cpp @@ -0,0 +1,258 @@ +#include "savebanner.h" +#include "tools.h" + +int TPL_ConvertRGB5A3ToBitMap(quint8* tplbuf, quint8** bitmapdata, quint32 width, quint32 height); + +SaveBanner::SaveBanner() +{ + ok = false; +} + +SaveBanner::SaveBanner( QByteArray stuff ) +{ + ok = false; + QBuffer f( &stuff ); + f.open( QIODevice::ReadOnly ); + quint32 size = f.size(); + if( size < 0x72a0 || ( ( size - 0x60a0 ) % 0x1200 ) )//sanity check the size. must have enough data for the header, names, banner, and 1 icon image + { + qDebug() << "SaveBanner::SaveBanner -> bad filesize" << hex << size; + f.close(); + return; + } + quint32 magic; + f.read( (char*)&magic, 4 ); + if( qFromBigEndian( magic ) != 0x5749424e )//WIBN + { + hexdump( stuff, 0, 0x30 ); + f.close(); + qWarning() << "SaveBanner::SaveBanner -> bad file magic" << hex << qFromBigEndian( magic ); + return; + } + //no clue what this stuff is, dont really need it though + //i suspect instructions for animation ? ( VC icons display forwards and backwards in the system menu ) + //also speed is not always the same + //quint32 tmp; + //f.read( (char*)&tmp, 4 ); + //quint32 tmp2; + //f.read( (char*)&tmp2, 4 ); + f.seek( 0x20 ); + + quint16 name[ 0x20 ]; + quint16 name2[ 0x20 ]; + + f.read( (char*)&name, 0x40 ); + f.read( (char*)&name2, 0x40 ); + + //QString title; + for( int i = 0; i < 0x20 && name[ i ] != 0; i++ ) + saveTitle += QChar( qFromBigEndian( name[ i ] ) ); + + saveTitle = saveTitle.trimmed(); + + //qDebug() << hex << qFromBigEndian( tmp ) << qFromBigEndian( tmp2 ) << saveTitle; + + //QString title2; + for( int i = 0; i < 0x20 && name2[ i ] != 0; i++ ) + saveTitle2 += QChar( qFromBigEndian( name2[ i ] ) ); + + saveTitle2 = saveTitle2.trimmed(); + + //get the banner + f.seek( 0xa0 ); + QByteArray ban = f.read( 0x6000 ); + + //convert to an image we can display + bannerImg = ConvertTextureToImage( ban, 0xc0, 0x40 ); + if( bannerImg.isNull() ) + { + f.close(); + qWarning() << "SaveBanner::SaveBanner -> error converting banner image"; + return; + } + + //get the images that make up the icon + while( f.pos() != size ) + { + QByteArray icn = f.read( 0x1200 ); + //check that there is actually data. some banners use all 0x00 for some images + bool null = true; + for( int i = 0; i < 0x1200; i++ ) + { + if( icn.at( i ) )//this buffer contains at least 1 byte of data, try to turn it into an image + { + null = false; + break; + } + } + if( null ) + { + //qDebug() << "skipping empty image"; + break; + } + + //make this texture int an image + QImage iconImg = ConvertTextureToImage( icn, 0x30, 0x30 ); + if( iconImg.isNull() ) + break; + + //add the image to the list + iconImgs << iconImg; + } + f.close(); + ok = true; + + //qDebug() << hex << QString( "%1 %2").arg( qFromBigEndian( tmp ), 9, 16).arg( qFromBigEndian( tmp2 ), 9, 16) + //<< saveTitle.leftJustified( 0x20 ) << QString( "icons: %1").arg( iconImgs.size(), 1, 16 ) << QString( "banner size: %1" ).arg( size, 4, 16 ); + +} + +SaveBanner::SaveBanner( const QString &bannerPath ) +{ + ok = false; + QFile f( bannerPath ); + if( !f.exists() || !f.open( QIODevice::ReadOnly ) ) + { + qWarning() << "SaveBanner::SaveBanner -> error opening" << bannerPath; + return; + } + quint32 size = f.size(); + if( size < 0x72a0 || ( ( size - 0x60a0 ) % 0x1200 ) )//sanity check the size. must have enough data for the header, names, banner, and 1 icon image + { + qDebug() << "SaveBanner::SaveBanner -> bad filesize" << hex << size; + f.close(); + return; + } + quint32 magic; + f.read( (char*)&magic, 4 ); + if( qFromBigEndian( magic ) != 0x5749424e )//WIBN + { + f.close(); + qWarning() << "SaveBanner::SaveBanner -> bad file magic" << qFromBigEndian( magic ); + return; + } + //get the title of the save + f.seek( 0x20 ); + + quint16 name[ 0x20 ]; + quint16 name2[ 0x20 ]; + + f.read( (char*)&name, 0x40 ); + f.read( (char*)&name2, 0x40 ); + + //QString title; + for( int i = 0; i < 0x20 && name[ i ] != 0; i++ ) + saveTitle += QChar( qFromBigEndian( name[ i ] ) ); + + saveTitle = saveTitle.trimmed(); + + //QString title2; + for( int i = 0; i < 0x20 && name2[ i ] != 0; i++ ) + saveTitle2 += QChar( qFromBigEndian( name2[ i ] ) ); + + saveTitle2 = saveTitle2.trimmed(); + + //get the banner + f.seek( 0xa0 ); + QByteArray ban = f.read( 0x6000 ); + + //convert to an image we can display + bannerImg = ConvertTextureToImage( ban, 0xc0, 0x40 ); + if( bannerImg.isNull() ) + { + f.close(); + qWarning() << "SaveBanner::SaveBanner -> error converting banner image"; + return; + } + + //get the images that make up the icon + while( f.pos() != size ) + { + QByteArray icn = f.read( 0x1200 ); + //check that there is actually data. some banners use all 0x00 for some images + bool null = true; + for( int i = 0; i < 0x1200; i++ ) + { + if( icn.at( i ) )//this buffer contains at least 1 byte of data, try to turn it into an image + { + null = false; + break; + } + } + if( null ) + { + //qDebug() << "skipping empty image"; + break; + } + + //make this texture int an image + QImage iconImg = ConvertTextureToImage( icn, 0x30, 0x30 ); + if( iconImg.isNull() ) + break; + + //add the image to the list + iconImgs << iconImg; + } + f.close(); + ok = true; +} + +QImage SaveBanner::ConvertTextureToImage( const QByteArray &ba, quint32 w, quint32 h ) +{ + //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 ); + if( !ret ) + { + qWarning() << "SaveBanner::ConvertTextureToImage -> error converting image"; + return QImage(); + } + QImage im = QImage( (const uchar*)bitmapdata, w, h, QImage::Format_ARGB32 ); + 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) +{ + quint32 x, y; + quint32 x1, y1; + quint32 iv; + //tplpoint -= width; + *bitmapdata = (quint8*)calloc(width * height, 4); + if(*bitmapdata == NULL) + return -1; + quint32 outsz = width * height * 4; + for(iv = 0, y1 = 0; y1 < height; y1 += 4) { + for(x1 = 0; x1 < width; x1 += 4) { + for(y = y1; y < (y1 + 4); y++) { + for(x = x1; x < (x1 + 4); x++) { + quint16 oldpixel = *(quint16*)(tplbuf + ((iv++) * 2)); + if((x >= width) || (y >= height)) + continue; + oldpixel = qFromBigEndian(oldpixel); + if(oldpixel & (1 << 15)) { + // RGB5 + quint8 b = (((oldpixel >> 10) & 0x1F) * 255) / 31; + quint8 g = (((oldpixel >> 5) & 0x1F) * 255) / 31; + quint8 r = (((oldpixel >> 0) & 0x1F) * 255) / 31; + quint8 a = 255; + quint32 rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24); + (*(quint32**)bitmapdata)[x + (y * width)] = rgba; + }else{ + // RGB4A3 + + quint8 a = (((oldpixel >> 12) & 0x7) * 255) / 7; + quint8 b = (((oldpixel >> 8) & 0xF) * 255) / 15; + quint8 g = (((oldpixel >> 4) & 0xF) * 255) / 15; + quint8 r = (((oldpixel >> 0) & 0xF) * 255) / 15; + quint32 rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24); + (*(quint32**)bitmapdata)[x + (y * width)] = rgba; + } + } + } + } + } + return outsz; +} diff --git a/saveToy/savebanner.h b/saveToy/savebanner.h new file mode 100644 index 0000000..aca4d36 --- /dev/null +++ b/saveToy/savebanner.h @@ -0,0 +1,29 @@ +#ifndef SAVEBANNER_H +#define SAVEBANNER_H + +#include "includes.h" + +class SaveBanner +{ +public: + SaveBanner(); + SaveBanner( const QString &bannerpath ); + SaveBanner( QByteArray stuff ); + + QImage BannerImg(){ return bannerImg; } + QList< QImage > IconImgs() { return iconImgs; } + + QString Title(){ return saveTitle; } + QString SubTitle(){ return saveTitle2; } + +private: + bool ok; + QImage bannerImg; + QList< QImage > iconImgs; + QString saveTitle; + QString saveTitle2; + + QImage ConvertTextureToImage( const QByteArray &ba, quint32 w, quint32 h );//expects tpl texture data in rgb5a3 format +}; + +#endif // SAVEBANNER_H diff --git a/saveToy/savelistitem.cpp b/saveToy/savelistitem.cpp new file mode 100644 index 0000000..2221f73 --- /dev/null +++ b/saveToy/savelistitem.cpp @@ -0,0 +1,23 @@ +#include "savelistitem.h" + +SaveListItem::SaveListItem( SaveBanner banner, const QString &tid, quint32 s, QListWidget * parent ) : QListWidgetItem( parent ), size( s ) +{ + sb = banner; + QImage im = banner.BannerImg(); + if( !im.isNull() ) + { + im.load( ";/noBanner.png" ); + } + QString tex = banner.Title(); + if( tex.isEmpty() ) + tex = "???"; + + this->setText( tex ); + this->setIcon( QIcon( QPixmap::fromImage( im ) ) ); + id = tid; +} + +SaveListItem::~SaveListItem() +{ + //qDebug() << "SaveListItem destroyed"; +} diff --git a/saveToy/savelistitem.h b/saveToy/savelistitem.h new file mode 100644 index 0000000..38e51a8 --- /dev/null +++ b/saveToy/savelistitem.h @@ -0,0 +1,27 @@ +#ifndef SAVELISTITEM_H +#define SAVELISTITEM_H + + +#include "savebanner.h" +#include "includes.h" + + +class SaveListItem : public QListWidgetItem +{ +public: + + SaveListItem( SaveBanner banner, const QString &tid, quint32 s, QListWidget * parent = 0 ); + ~SaveListItem(); + SaveBanner *Banner(){ return &sb; } + QString Tid(){ return id; } + quint32 Size(){ return size; } + //quint32 Blocks(){ return size / 0x20000; } + //quint32 MiB(){ return size / 0x100000; } + +private: + SaveBanner sb; + QString id; + quint32 size; +}; + +#endif // SAVELISTITEM_H diff --git a/saveToy/saveloadthread.cpp b/saveToy/saveloadthread.cpp new file mode 100644 index 0000000..4881278 --- /dev/null +++ b/saveToy/saveloadthread.cpp @@ -0,0 +1,124 @@ +#include "saveloadthread.h" + +SaveLoadThread::SaveLoadThread( QObject *parent ) : QThread( parent ) +{ + abort = false; + //qRegisterMetaType< SaveListItem* >(); +} + +void SaveLoadThread::ForceQuit() +{ + mutex.lock(); + abort = true; + mutex.unlock(); +} + +SaveLoadThread::~SaveLoadThread() +{ + mutex.lock(); + abort = true; + condition.wakeOne(); + mutex.unlock(); + wait(); +} + + +void SaveLoadThread::GetBanners( const QString &bPath, int mode ) +{ + basePath = bPath; + type = mode; + start( NormalPriority ); +} + +void SaveLoadThread::run() +{ + if ( abort ) + { + qDebug( "SaveLoadThread::run -> Thread abort" ); + return; + } + mutex.lock(); + if( basePath.isEmpty() ) + { + qDebug() << "SaveLoadThread::run -> its empty"; + return; + } + mutex.unlock(); + + QFileInfo fi( basePath ); + if( !fi.exists() || !fi.isDir() ) + { + qWarning() << "SaveLoadThread::run ->" << basePath << "is not a directory"; + return; + } + + fi.setFile(basePath + "/title/00010000" ); + if( !fi.exists() || !fi.isDir() ) + { + qWarning() << "SaveLoadThread::run ->" << QString( basePath + "/title/00010000" ) << "is not a directory"; + return; + } + + + QDir subDir( basePath + "/title/00010000" ); + subDir.setFilter( QDir::Dirs | QDir::NoDotAndDotDot ); + QFileInfoList fiL = subDir.entryInfoList(); + quint32 cnt = fiL.size(); + + int i = 0; + subDir.setPath( basePath + "/title/00010001" ); + QFileInfoList fiL2 = subDir.entryInfoList(); + cnt += fiL2.size(); + + foreach( QFileInfo f, fiL ) + { + 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( "00010000" + f.fileName() ), type, size ); + } + foreach( QFileInfo f, fiL2 ) + { + 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 ); + } + + emit SendProgress( 100 ); + emit SendDone( type ); +} + +int SaveLoadThread::GetFolderSize( const QString& path ) +{ + //qDebug() << "SaveLoadThread::GetFolderSize" << path; + quint32 ret = 0; + QDir dir( path ); + dir.setFilter( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot ); + QFileInfoList fiL = dir.entryInfoList(); + foreach( QFileInfo fi, fiL ) + { + if( fi.isDir() ) + ret += GetFolderSize( fi.absoluteFilePath() ); + + else + ret += fi.size(); + } + return ret; +} diff --git a/saveToy/saveloadthread.h b/saveToy/saveloadthread.h new file mode 100644 index 0000000..da6b709 --- /dev/null +++ b/saveToy/saveloadthread.h @@ -0,0 +1,44 @@ +#ifndef SAVELOADTHREAD_H +#define SAVELOADTHREAD_H + +#include "savelistitem.h" +#include "includes.h" +enum +{ + LOAD_SNEEK = 0x111, + LOAD_PC +}; +class SaveLoadThread : public QThread +{ + Q_OBJECT + + public: + SaveLoadThread( QObject *parent = 0 ); + ~SaveLoadThread(); + + void GetBanners( const QString &bPath, int mode ); + void ForceQuit(); + + protected: + void run(); + + signals: + void SendProgress( int ); + void SendDone( int ); + void SendItem( QByteArray, const QString&, int, int ); + + private: + QMutex mutex; + QWaitCondition condition; + QString basePath; + int type; + + bool abort; + + int GetFolderSize( const QString& path ); + }; + +//Q_DECLARE_METATYPE( SaveListItem* ) + + +#endif // SAVELOADTHREAD_H diff --git a/saveToy/tools.cpp b/saveToy/tools.cpp new file mode 100644 index 0000000..f9ee29c --- /dev/null +++ b/saveToy/tools.cpp @@ -0,0 +1,46 @@ +#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 new file mode 100644 index 0000000..30b5d4f --- /dev/null +++ b/saveToy/tools.h @@ -0,0 +1,20 @@ +#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