diff --git a/nand_dump/mainwindow.cpp b/nand_dump/mainwindow.cpp
index 062ec67..930afe4 100644
--- a/nand_dump/mainwindow.cpp
+++ b/nand_dump/mainwindow.cpp
@@ -25,6 +25,7 @@ MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::M
//TODO, really get these paths from settings
ui->lineEdit_cachePath->setText( cachePath );
ui->lineEdit_nandPath->setText( nandPath );
+ ui->lineEdit_extractPath->setText( "./downloaded" );
//nand.SetPath( nandPath );
nus.SetCachePath( cachePath );
@@ -62,7 +63,7 @@ void MainWindow::ShowMessage( const QString& mes )
void MainWindow::NusIsDone()
{
- QString str = tr( "NUS ojbect is done working
" );
+ QString str = tr( "NUS object is done working
" );
ui->plainTextEdit_log->appendHtml( str );
ui->statusBar->showMessage( tr( "Done" ), 5000 );
if( ui->radioButton_folder->isChecked() )
@@ -76,6 +77,7 @@ void MainWindow::NusIsDone()
ui->pushButton_nandPath->setEnabled( true );
//write the uid.sys and content.map to disc
+ ShowMessage( tr( "Flushing nand..." ) );
nand.Flush();
//make sure there is a setting.txt
@@ -106,11 +108,11 @@ void MainWindow::ReceiveTitleFromNus( NusJob job )
ui->plainTextEdit_log->appendHtml( str );
//do something with the data we got
- if( ui->radioButton_folder->isChecked() )
+ if( ui->radioButton_folder->isChecked() )//copy its decrypted contents to a folder
{
-
+ SaveJobToFolder( job );
}
- else if( ui->radioButton_nand->isChecked() )
+ else if( ui->radioButton_nand->isChecked() )//install this title to a decrypted nand dump for sneek/dolphin
{
bool ok = nand.InstallNusItem( job );
if( ok )
@@ -120,57 +122,8 @@ void MainWindow::ReceiveTitleFromNus( NusJob job )
}
else if( ui->radioButton_wad->isChecked() )
{
- Wad wad( job.data );
- if( !wad.IsOk() )
- {
- ShowMessage( "Error making a wad from " + title + "<\b>" );
- return;
- }
- QFileInfo fi( ui->lineEdit_wad->text() );
- if( fi.isFile() )
- {
- ShowMessage( "" + ui->lineEdit_wad->text() + " is a file. I need a folder<\b>" );
- return;
- }
- if( !fi.exists() )
- {
- ShowMessage( "" + fi.absoluteFilePath() + " is not a folder!\nTrying to create it...<\b>" );
- if( !QDir().mkpath( ui->lineEdit_wad->text() ) )
- {
- ShowMessage( "Failed to make the directory!<\b>" );
- return;
- }
- }
- QByteArray w = wad.Data();
- if( w.isEmpty() )
- {
- ShowMessage( "Error creating wad
" + wad.LastError() + "<\b>" );
- return;
- }
-
- QString name = wad.WadName( fi.absoluteFilePath() );
- if( name.isEmpty() )
- {
- name = QFileDialog::getSaveFileName( this, tr( "Filename for %1" ).arg( title ), fi.absoluteFilePath() );
- if( name.isEmpty() )
- {
- ShowMessage( "No save name given, aborting<\b>" );
- return;
- }
- }
- QFile file( fi.absoluteFilePath() + "/" + name );
- if( !file.open( QIODevice::WriteOnly ) )
- {
- ShowMessage( "Cant open " + fi.absoluteFilePath() + "/" + name + " for writing<\b>" );
- return;
- }
- file.write( w );
- file.close();
- ShowMessage( "Saved " + title + " to " + fi.absoluteFilePath() + "/" + name );
+ SaveJobToWad( job );
}
-
- //bool r = nand.InstallNusItem( job );
- //qDebug() << "install:" << r;
}
//clicked the button to get a title
@@ -304,8 +257,8 @@ void MainWindow::on_pushButton_nandPath_clicked()
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 );
+ QString path = ui->lineEdit_extractPath->text().isEmpty() ? QDir::currentPath() : ui->lineEdit_extractPath->text();
+ QString f = QFileDialog::getExistingDirectory( this, tr( "Select folder to save decrypted titles" ), path );
if( f.isEmpty() )
return;
@@ -332,7 +285,7 @@ void MainWindow::on_actionSetting_txt_triggered()
return;
}
QByteArray ba = nand.GetSettingTxt(); //read the current setting.txt
- ba = SettingTxtDialog::Edit( this, ba ); //call a dialog to edit that existing file and store the result in teh same bytearray
+ ba = SettingTxtDialog::Edit( this, ba ); //call a dialog to edit that existing file and store the result in the same bytearray
if( !ba.isEmpty() ) //if the dialog returned anything ( cancel wasnt pressed ) write that new setting.txt to the nand dump
nand.SetSettingTxt( ba );
}
@@ -343,3 +296,122 @@ void MainWindow::on_actionFlush_triggered()
if( !nand.GetPath().isEmpty() )
nand.Flush();
}
+
+//save a NUS job to a folder
+void MainWindow::SaveJobToFolder( NusJob job )
+{
+ QString title = QString( "%1v%2" ).arg( job.tid, 16, 16, QChar( '0' ) ).arg( job.version );
+ QFileInfo fi( ui->lineEdit_extractPath->text() );
+ if( fi.isFile() )
+ {
+ ShowMessage( "" + ui->lineEdit_extractPath->text() + " is a file. I need a folder<\b>" );
+ return;
+ }
+ if( !fi.exists() )
+ {
+ ShowMessage( "" + fi.absoluteFilePath() + " is not a folder!\nTrying to create it...<\b>" );
+ if( !QDir().mkpath( fi.absoluteFilePath() ) )
+ {
+ ShowMessage( "Failed to make the directory!<\b>" );
+ return;
+ }
+ }
+ QString newFName = title;
+ int i = 1;
+ while( QFileInfo( fi.absoluteFilePath() + "/" + newFName ).exists() )//find a folder that doesnt exist and try to create it
+ {
+ newFName = QString( "%1 (copy%2)" ).arg( title ).arg( i++ );
+ }
+ if( !QDir().mkpath( fi.absoluteFilePath() + "/" + newFName ) )
+ {
+ ShowMessage( "Can't create" + fi.absoluteFilePath() + "/" + newFName + " to save this title into!<\b>" );
+ return;
+ }
+ //start writing all this stuff to the HDD
+ QDir d( fi.absoluteFilePath() + "/" + newFName );
+ QByteArray tmdDat = job.data.takeFirst(); //remember the tmd and use it for getting the names of the .app files
+ if( !WriteFile( d.absoluteFilePath( "title.tmd" ), tmdDat ) )
+ {
+ ShowMessage( "Error writing " + d.absoluteFilePath( "title.tmd" ) + "!<\b>" );
+ return;
+ }
+ if( !WriteFile( d.absoluteFilePath( "cetk" ), job.data.takeFirst() ) )
+ {
+ ShowMessage( "Error writing " + d.absoluteFilePath( "cetk" ) + "!<\b>" );
+ return;
+ }
+ Tmd t( tmdDat );
+ quint16 cnt = t.Count();
+ if( job.data.size() != cnt )
+ {
+ ShowMessage( "Error! Number of contents in the TMD dont match the number received from NUS!<\b>" );
+ return;
+ }
+ for( quint16 i = 0; i < cnt; i++ )//write all the contents in the new folder. if the job is decrypted, append ".app" to the end of their names
+ {
+ QString appName = t.Cid( i );
+ if( job.decrypt )
+ appName += ".app";
+ if( !WriteFile( d.absoluteFilePath( appName ), job.data.takeFirst() ) )
+ {
+ ShowMessage( "Error writing " + d.absoluteFilePath( appName ) + "!<\b>" );
+ return;
+ }
+ }
+ ShowMessage( tr( "Wrote title to %1" ).arg( fi.absoluteFilePath() + "/" + newFName ) );
+}
+
+//save a conpleted job to wad
+void MainWindow::SaveJobToWad( NusJob job )
+{
+ QString title = QString( "%1v%2" ).arg( job.tid, 16, 16, QChar( '0' ) ).arg( job.version );
+ Wad wad( job.data );
+ if( !wad.IsOk() )
+ {
+ ShowMessage( "Error making a wad from " + title + "<\b>" );
+ return;
+ }
+ QFileInfo fi( ui->lineEdit_wad->text() );
+ if( fi.isFile() )
+ {
+ ShowMessage( "" + ui->lineEdit_wad->text() + " is a file. I need a folder<\b>" );
+ return;
+ }
+ if( !fi.exists() )
+ {
+ ShowMessage( "" + fi.absoluteFilePath() + " is not a folder!\nTrying to create it...<\b>" );
+ if( !QDir().mkpath( ui->lineEdit_wad->text() ) )
+ {
+ ShowMessage( "Failed to make the directory!<\b>" );
+ return;
+ }
+ }
+ QByteArray w = wad.Data();
+ if( w.isEmpty() )
+ {
+ ShowMessage( "Error creating wad
" + wad.LastError() + "<\b>" );
+ return;
+ }
+
+ QString name = wad.WadName( fi.absoluteFilePath() );
+ if( name.isEmpty() )
+ {
+ name = QFileDialog::getSaveFileName( this, tr( "Filename for %1" ).arg( title ), fi.absoluteFilePath() );
+ if( name.isEmpty() )
+ {
+ ShowMessage( "No save name given, aborting<\b>" );
+ return;
+ }
+ }
+ QFile file( fi.absoluteFilePath() + "/" + name );
+ if( !file.open( QIODevice::WriteOnly ) )
+ {
+ ShowMessage( "Cant open " + fi.absoluteFilePath() + "/" + name + " for writing<\b>" );
+ return;
+ }
+ file.write( w );
+ file.close();
+ ShowMessage( "Saved " + title + " to " + fi.absoluteFilePath() + "/" + name );
+
+
+}
diff --git a/nand_dump/mainwindow.h b/nand_dump/mainwindow.h
index 529f19d..c8ac63d 100644
--- a/nand_dump/mainwindow.h
+++ b/nand_dump/mainwindow.h
@@ -25,6 +25,10 @@ private:
void ShowMessage( const QString& mes );
+ //do something with a completed download
+ void SaveJobToFolder( NusJob job );
+ void SaveJobToWad( NusJob job );
+
public slots:
diff --git a/nand_dump/nusdownloader.cpp b/nand_dump/nusdownloader.cpp
index e3140fe..5e08010 100644
--- a/nand_dump/nusdownloader.cpp
+++ b/nand_dump/nusdownloader.cpp
@@ -207,7 +207,7 @@ bool NusDownloader::SaveDataToCache( const QString &path, const QByteArray &stuf
QDir d( cachePath );
if( !d.exists() || !d.mkpath( parent ) )
{
- qWarning() << "NusDownloader::SaveDataToCache -> cant create directory" << d.absolutePath();
+ qWarning() << "NusDownloader::SaveDataToCache -> cant create directory" << QString( d.absolutePath() + "/" + path );
return false;
}
QFile f( path );
diff --git a/nand_dump/nusdownloader.h b/nand_dump/nusdownloader.h
index f58cad2..ceeb713 100644
--- a/nand_dump/nusdownloader.h
+++ b/nand_dump/nusdownloader.h
@@ -60,10 +60,10 @@ public:
//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
+ //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. IOS35 is added in all updates for use in sneek
//lists are created from wiimpersonator logs when available. otherwise they come from examining game update partitions
- //for the 2.x updates, IOS35 is added for use in sneek
+
static QMap< quint64, quint16 > List20u();
static QMap< quint64, quint16 > List30u();
static QMap< quint64, quint16 > List31u();
@@ -159,6 +159,7 @@ private:
quint32 totalTitleSize;
quint32 TitleSizeDownloaded();
+ //remember the ticked key for repeated use
QByteArray decKey;
@@ -166,7 +167,7 @@ private:
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 SendFatalErrorError( const QString &message, NusJob job );//currently not used
void SendDone();//message that all jobs are done
//send progress about the currently downloading job
diff --git a/nand_dump/tiktmd.cpp b/nand_dump/tiktmd.cpp
index 96b099a..abdcbf3 100644
--- a/nand_dump/tiktmd.cpp
+++ b/nand_dump/tiktmd.cpp
@@ -12,6 +12,11 @@ Tmd::Tmd( QByteArray stuff )
return;
SetPointer();
+ if( (quint32)data.size() != SignedSize() )
+ {
+ data.resize( SignedSize() );
+ SetPointer();
+ }
//hexdump( stuff );
}
@@ -22,6 +27,15 @@ quint64 Tmd::Tid()
return qFromBigEndian( p_tmd->title_id );
}
+bool Tmd::SetTid( quint64 tid )
+{
+ if( !p_tmd )
+ return false;
+
+ p_tmd->title_id = qFromBigEndian( tid );
+ return true;
+}
+
QString Tmd::Cid( quint16 i )
{
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
@@ -37,6 +51,17 @@ QByteArray Tmd::Hash( quint16 i )
return QByteArray( (const char*)&p_tmd->contents[ i ].hash, 20 );
}
+bool Tmd::SetHash( quint16 cid, const QByteArray hash )
+{
+ if( !p_tmd || cid >= qFromBigEndian( p_tmd->num_contents ) || hash.size() != 20 )
+ return false;
+
+ const char* h = hash.data();
+ for( quint8 i = 0; i < 20; i++ )
+ p_tmd->contents[ cid ].hash[ i ] = h[ i ];
+ return true;
+}
+
quint16 Tmd::Count()
{
if( !p_tmd )
@@ -53,6 +78,15 @@ quint16 Tmd::Version()
return qFromBigEndian( p_tmd->title_version );
}
+bool Tmd::SetVersion( quint16 v )
+{
+ if( !p_tmd )
+ return false;
+
+ p_tmd->title_version = qFromBigEndian( v );
+ return true;
+}
+
quint64 Tmd::Size( quint16 i )
{
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
@@ -60,6 +94,15 @@ quint64 Tmd::Size( quint16 i )
return qFromBigEndian( p_tmd->contents[ i ].size );
}
+bool Tmd::SetSize( quint16 cid, quint32 size )
+{
+ if( !p_tmd || cid >= qFromBigEndian( p_tmd->num_contents ) )
+ return false;
+
+ p_tmd->contents[ cid ].size = qFromBigEndian( size );
+ return true;
+}
+
quint16 Tmd::Type( quint16 i )
{
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
@@ -67,6 +110,15 @@ quint16 Tmd::Type( quint16 i )
return qFromBigEndian( p_tmd->contents[ i ].type );
}
+bool Tmd::SetType( quint16 cid, quint16 type )
+{
+ if( !p_tmd || cid >= qFromBigEndian( p_tmd->num_contents ) )
+ return false;
+
+ p_tmd->contents[ cid ].type = qFromBigEndian( type );
+ return true;
+}
+
quint32 Tmd::SignedSize()
{
if( !p_tmd )
@@ -89,6 +141,48 @@ void Tmd::SetPointer()
p_tmd = (tmd*)((quint8*)data.data() + payLoadOffset);
}
+void Tmd::Dbg()
+{
+ if( !p_tmd )
+ return;
+
+ QString contents;
+ quint16 cnt = Count();
+ for( quint16 i = 0; i < cnt; i++ )
+ {
+ contents += QString( "%1 %2 %3 %4 " ).arg( i, 2, 16 ).arg( Type( i ), 4, 16 ).arg( Cid( i ) ).arg( Size( i ), 8, 16 )
+ + Hash( i ).toHex() + "\n";
+ }
+
+ QString s = QString( "TMD Dbg:\ntid:: %1\ncnt:: %2\n" )
+ .arg( Tid(), 16, 16, QChar( '0' ) )
+ .arg( cnt ) + contents;
+ qDebug() << s;
+}
+
+bool Tmd::FakeSign()
+{
+ if( !p_tmd || payLoadOffset < 5 )
+ return false;
+
+ quint32 size = SignedSize();
+ memset( (void*)( data.data() + 4 ), 0, payLoadOffset - 4 );//zero the RSA
+
+ quint16 i = 0;
+ bool ret = false;//brute force the sha1
+ do
+ {
+ p_tmd->zero = i;//no need to worry about endian here
+ if( GetSha1( data.mid( payLoadOffset, size ) ).startsWith( '\0' ) )
+ {
+ ret = true;
+ break;
+ }
+ }
+ while( ++i );
+ return ret;
+}
+
Ticket::Ticket( QByteArray stuff )
{
data = stuff;
@@ -97,6 +191,11 @@ Ticket::Ticket( QByteArray stuff )
return;
SetPointer();
+ if( (quint32)data.size() != SignedSize() )
+ {
+ data.resize( SignedSize() );
+ SetPointer();
+ }
}
quint64 Ticket::Tid()
@@ -106,6 +205,15 @@ quint64 Ticket::Tid()
return qFromBigEndian( p_tik->titleid );
}
+bool Ticket::SetTid( quint64 tid )
+{
+ if( !p_tik )
+ return false;
+
+ p_tik->titleid = qFromBigEndian( tid );
+ return true;
+}
+
QByteArray Ticket::DecryptedKey()
{
quint8 iv[ 16 ];
@@ -145,3 +253,26 @@ void Ticket::SetPointer()
p_tik = (tik*)((quint8*)data.data() + payLoadOffset);
}
+
+bool Ticket::FakeSign()
+{
+ if( !p_tik || payLoadOffset < 5 )
+ return false;
+
+ quint32 size = SignedSize();
+ memset( (void*)( data.data() + 4 ), 0, payLoadOffset - 4 );//zero the RSA
+
+ quint16 i = 0;
+ bool ret = false;//brute force the sha1
+ do
+ {
+ p_tik->padding = i;//no need to worry about endian here
+ if( GetSha1( data.mid( payLoadOffset, size ) ).startsWith( '\0' ) )
+ {
+ ret = true;
+ break;
+ }
+ }
+ while( ++i );
+ return ret;
+}
diff --git a/nand_dump/tiktmd.h b/nand_dump/tiktmd.h
index e659694..c81f751 100644
--- a/nand_dump/tiktmd.h
+++ b/nand_dump/tiktmd.h
@@ -149,9 +149,14 @@ public:
const tik *payload(){ return p_tik; }
quint64 Tid();
+ bool SetTid( quint64 tid );
QByteArray DecryptedKey();
quint32 SignedSize();
+ bool FakeSign();
+
+ //get the ticket data
+ const QByteArray Data(){ return data; }
private:
quint32 payLoadOffset;
@@ -193,6 +198,22 @@ public:
//title version
quint16 Version();
+ //functions to edit the TMD
+ bool SetTid( quint64 tid );
+ bool SetVersion( quint16 v );
+ bool SetType( quint16 cid, quint16 type );
+ bool SetSize( quint16 cid, quint32 size );
+ bool SetHash( quint16 cid, const QByteArray hash );
+
+ bool FakeSign();
+
+ //get the tmd data
+ const QByteArray Data(){ return data; }
+
+ //print the tmd info to qDebug()
+ void Dbg();
+
+
quint32 SignedSize();
private:
quint32 payLoadOffset;
diff --git a/nand_dump/tools.cpp b/nand_dump/tools.cpp
index cb36232..b943e01 100644
--- a/nand_dump/tools.cpp
+++ b/nand_dump/tools.cpp
@@ -155,6 +155,24 @@ QByteArray ReadFile( const QString &path )
return ret;
}
+bool WriteFile( const QString &path, const QByteArray ba )
+{
+ QFile file( path );
+ if( !file.open( QIODevice::WriteOnly ) )
+ {
+ qWarning() << "WriteFile -> can't open" << path;
+ return false;
+ }
+ if( file.write( ba ) != ba.size() )
+ {
+ file.close();
+ qWarning() << "WriteFile -> can't write all the data to" << path;
+ return false;
+ }
+ file.close();
+ return true;
+}
+
#define CERTS_DAT_SIZE 2560
const quint8 certs_dat[ CERTS_DAT_SIZE ] = {
0x00, 0x01, 0x00, 0x01, 0x7D, 0x9D, 0x5E, 0xBA, 0x52, 0x81, 0xDC, 0xA7, 0x06, 0x5D, 0x2F, 0x08,
diff --git a/nand_dump/tools.h b/nand_dump/tools.h
index 0796ea0..4d1e668 100644
--- a/nand_dump/tools.h
+++ b/nand_dump/tools.h
@@ -26,6 +26,9 @@ QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo );
//read a file into a bytearray
QByteArray ReadFile( const QString &path );
+//save a file to disc
+bool WriteFile( const QString &path, const QByteArray ba );
+
//keep track of the last folder browsed to when looking for files
extern QString currentDir;
diff --git a/nand_dump/uidmap.cpp b/nand_dump/uidmap.cpp
index 57f4780..6990707 100644
--- a/nand_dump/uidmap.cpp
+++ b/nand_dump/uidmap.cpp
@@ -180,6 +180,7 @@ void UIDmap::CreateNew( bool addFactorySetupDiscs )
default:
qWarning() << "oops" << hex << i;
return;
+ break;
}
uid = qFromBigEndian( 0x1000 + i );
diff --git a/nand_dump/wad.cpp b/nand_dump/wad.cpp
index 1334c27..61ef38f 100644
--- a/nand_dump/wad.cpp
+++ b/nand_dump/wad.cpp
@@ -441,7 +441,7 @@ QString Wad::WadName( quint64 tid, quint16 version, QString path )
QByteArray Wad::FromDirectory( QDir dir )
{
- QFileInfoList tmds = dir.entryInfoList( QStringList() << "*.tmd", QDir::Files );
+ QFileInfoList tmds = dir.entryInfoList( QStringList() << "*.tmd" << "tmd.*", QDir::Files );
if( tmds.isEmpty() )
{
qWarning() << "Wad::FromDirectory -> no tmd found in" << dir.absolutePath();
@@ -450,7 +450,7 @@ QByteArray Wad::FromDirectory( QDir dir )
QByteArray tmdD = ReadFile( tmds.at( 0 ).absoluteFilePath() );
if( tmdD.isEmpty() )
return QByteArray();
- QFileInfoList tiks = dir.entryInfoList( QStringList() << "*.tik", QDir::Files );
+ QFileInfoList tiks = dir.entryInfoList( QStringList() << "*.tik" << "cetk", QDir::Files );
if( tiks.isEmpty() )
{
qWarning() << "Wad::FromDirectory -> no tik found in" << dir.absolutePath();
@@ -459,8 +459,17 @@ QByteArray Wad::FromDirectory( QDir dir )
QByteArray tikD = ReadFile( tiks.at( 0 ).absoluteFilePath() );
if( tikD.isEmpty() )
return QByteArray();
- Tmd t(tmdD );
- QList datas = QList()<< tmdD << tikD;
+
+ Tmd t( tmdD );
+ Ticket ticket( tikD );
+
+ //make sure to only add the tmd & ticket without all the cert mumbo jumbo
+ QByteArray tmdP = tmdD;
+ tmdP.resize( t.SignedSize() );
+ QByteArray tikP = tikD;
+ tikP.resize( ticket.SignedSize() );
+
+ QList datas = QList()<< tmdP << tikP;
quint16 cnt = t.Count();
for( quint16 i = 0; i < cnt; i++ )
@@ -486,3 +495,61 @@ QByteArray Wad::FromDirectory( QDir dir )
QByteArray ret = wad.Data();
return ret;
}
+
+bool Wad::SetTid( quint64 tid )
+{
+ if( !tmdData.size() || !tikData.size() )
+ {
+ Err( "Mising parts of the wad" );
+ return false;
+ }
+ Tmd t( tmdData );
+ Ticket ti( tikData );
+
+ t.SetTid( tid );
+ ti.SetTid( tid );
+
+ if( !t.FakeSign() )
+ {
+ Err( "Error signing TMD" );
+ return false;
+ }
+ if( !ti.FakeSign() )
+ {
+ Err( "Error signing ticket" );
+ return false;
+ }
+ tmdData = t.Data();
+ tikData = ti.Data();
+ return true;
+}
+
+bool Wad::ReplaceContent( quint16 idx, const QByteArray ba )
+{
+ if( idx >= partsEnc.size() || !tmdData.size() || !tikData.size() )
+ {
+ Err( "Mising parts of the wad" );
+ return false;
+ }
+ QByteArray hash = GetSha1( ba );
+ quint32 size = ba.size();
+
+ Tmd t( tmdData );
+ t.SetHash( idx, hash );
+ t.SetSize( idx, size );
+ if( !t.FakeSign() )
+ {
+ Err( "Error signing the tmd" );
+ return false;
+ }
+ tmdData = t.Data();
+
+ Ticket ti( tikData );
+ AesSetKey( ti.DecryptedKey() );
+ QByteArray decDataPadded = PaddedByteArray( ba, 0x40 );
+
+ QByteArray encData = AesEncrypt( idx, decDataPadded );
+ partsEnc.replace( idx, encData );
+
+ return true;
+}
diff --git a/nand_dump/wad.h b/nand_dump/wad.h
index 47a3cb4..908455a 100644
--- a/nand_dump/wad.h
+++ b/nand_dump/wad.h
@@ -23,15 +23,19 @@ public:
quint64 Tid();
//set the tid in the ticket&tmd and fakesign the wad
- void SetTid( quint64 tid );
+ bool SetTid( quint64 tid );
+
+ //replace a content of this wad, update the size & hash in the tmd and sign it
+ //ba should be decrypted
+ bool ReplaceContent( quint16 idx, const QByteArray ba );
//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 );
+ //void AddContent( const QByteArray &stuff, quint16 type, bool isEncrypted = false, quint16 index = 0xffff );
//remove a content from this wad
- void RemoveContent( quint16 index );
+ //void RemoveContent( quint16 index );
//set the global cert that will be used for all created
static void SetGlobalCert( const QByteArray &stuff );
@@ -58,9 +62,9 @@ public:
//returns an empty string if it cant guess the title based on TID
static QString WadName( quint64 tid, quint16 version, QString path = QString() );
+ //get this Wad's name as it would appear on a disc update partition
QString WadName( QString path = QString() );
-
private:
bool ok;
QString errStr;