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;