mirror of
https://github.com/martravi/wiiqt.git
synced 2024-11-25 18:46:56 +01:00
* fix bugs in the nand extractor that caused it not to be able to find the keys.bin
* add fakesigning stuff to the tmd & ticket classes * add functions for changing the tmd & ticket classes ( size, hash, tid... ) * allow replacing contents in wads ( untested ) * other little bug fixes i forgot
This commit is contained in:
parent
43d202a052
commit
68603b95a9
@ -25,6 +25,7 @@ MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::M
|
|||||||
//TODO, really get these paths from settings
|
//TODO, really get these paths from settings
|
||||||
ui->lineEdit_cachePath->setText( cachePath );
|
ui->lineEdit_cachePath->setText( cachePath );
|
||||||
ui->lineEdit_nandPath->setText( nandPath );
|
ui->lineEdit_nandPath->setText( nandPath );
|
||||||
|
ui->lineEdit_extractPath->setText( "./downloaded" );
|
||||||
//nand.SetPath( nandPath );
|
//nand.SetPath( nandPath );
|
||||||
nus.SetCachePath( cachePath );
|
nus.SetCachePath( cachePath );
|
||||||
|
|
||||||
@ -62,7 +63,7 @@ void MainWindow::ShowMessage( const QString& mes )
|
|||||||
|
|
||||||
void MainWindow::NusIsDone()
|
void MainWindow::NusIsDone()
|
||||||
{
|
{
|
||||||
QString str = tr( "NUS ojbect is done working<br>" );
|
QString str = tr( "NUS object is done working<br>" );
|
||||||
ui->plainTextEdit_log->appendHtml( str );
|
ui->plainTextEdit_log->appendHtml( str );
|
||||||
ui->statusBar->showMessage( tr( "Done" ), 5000 );
|
ui->statusBar->showMessage( tr( "Done" ), 5000 );
|
||||||
if( ui->radioButton_folder->isChecked() )
|
if( ui->radioButton_folder->isChecked() )
|
||||||
@ -76,6 +77,7 @@ void MainWindow::NusIsDone()
|
|||||||
ui->pushButton_nandPath->setEnabled( true );
|
ui->pushButton_nandPath->setEnabled( true );
|
||||||
|
|
||||||
//write the uid.sys and content.map to disc
|
//write the uid.sys and content.map to disc
|
||||||
|
ShowMessage( tr( "Flushing nand..." ) );
|
||||||
nand.Flush();
|
nand.Flush();
|
||||||
|
|
||||||
//make sure there is a setting.txt
|
//make sure there is a setting.txt
|
||||||
@ -106,11 +108,11 @@ void MainWindow::ReceiveTitleFromNus( NusJob job )
|
|||||||
ui->plainTextEdit_log->appendHtml( str );
|
ui->plainTextEdit_log->appendHtml( str );
|
||||||
|
|
||||||
//do something with the data we got
|
//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 );
|
bool ok = nand.InstallNusItem( job );
|
||||||
if( ok )
|
if( ok )
|
||||||
@ -120,57 +122,8 @@ void MainWindow::ReceiveTitleFromNus( NusJob job )
|
|||||||
}
|
}
|
||||||
else if( ui->radioButton_wad->isChecked() )
|
else if( ui->radioButton_wad->isChecked() )
|
||||||
{
|
{
|
||||||
Wad wad( job.data );
|
SaveJobToWad( job );
|
||||||
if( !wad.IsOk() )
|
|
||||||
{
|
|
||||||
ShowMessage( "<b>Error making a wad from " + title + "<\b>" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
QFileInfo fi( ui->lineEdit_wad->text() );
|
|
||||||
if( fi.isFile() )
|
|
||||||
{
|
|
||||||
ShowMessage( "<b>" + ui->lineEdit_wad->text() + " is a file. I need a folder<\b>" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if( !fi.exists() )
|
|
||||||
{
|
|
||||||
ShowMessage( "<b>" + fi.absoluteFilePath() + " is not a folder!\nTrying to create it...<\b>" );
|
|
||||||
if( !QDir().mkpath( ui->lineEdit_wad->text() ) )
|
|
||||||
{
|
|
||||||
ShowMessage( "<b>Failed to make the directory!<\b>" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QByteArray w = wad.Data();
|
|
||||||
if( w.isEmpty() )
|
|
||||||
{
|
|
||||||
ShowMessage( "<b>Error creating wad<br>" + 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( "<b>No save name given, aborting<\b>" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
QFile file( fi.absoluteFilePath() + "/" + name );
|
|
||||||
if( !file.open( QIODevice::WriteOnly ) )
|
|
||||||
{
|
|
||||||
ShowMessage( "<b>Cant open " + fi.absoluteFilePath() + "/" + name + " for writing<\b>" );
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
file.write( w );
|
|
||||||
file.close();
|
|
||||||
ShowMessage( "Saved " + title + " to " + fi.absoluteFilePath() + "/" + name );
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//bool r = nand.InstallNusItem( job );
|
|
||||||
//qDebug() << "install:" << r;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//clicked the button to get a title
|
//clicked the button to get a title
|
||||||
@ -304,8 +257,8 @@ void MainWindow::on_pushButton_nandPath_clicked()
|
|||||||
|
|
||||||
void MainWindow::on_pushButton_decFolder_clicked()
|
void MainWindow::on_pushButton_decFolder_clicked()
|
||||||
{
|
{
|
||||||
QString path = ui->lineEdit_extractPath->text().isEmpty() ? "/media" : ui->lineEdit_extractPath->text();
|
QString path = ui->lineEdit_extractPath->text().isEmpty() ? QDir::currentPath() : ui->lineEdit_extractPath->text();
|
||||||
QString f = QFileDialog::getExistingDirectory( this, tr( "Select folder to decrypt this title to" ), path );
|
QString f = QFileDialog::getExistingDirectory( this, tr( "Select folder to save decrypted titles" ), path );
|
||||||
if( f.isEmpty() )
|
if( f.isEmpty() )
|
||||||
return;
|
return;
|
||||||
|
|
||||||
@ -332,7 +285,7 @@ void MainWindow::on_actionSetting_txt_triggered()
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
QByteArray ba = nand.GetSettingTxt(); //read the current setting.txt
|
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
|
if( !ba.isEmpty() ) //if the dialog returned anything ( cancel wasnt pressed ) write that new setting.txt to the nand dump
|
||||||
nand.SetSettingTxt( ba );
|
nand.SetSettingTxt( ba );
|
||||||
}
|
}
|
||||||
@ -343,3 +296,122 @@ void MainWindow::on_actionFlush_triggered()
|
|||||||
if( !nand.GetPath().isEmpty() )
|
if( !nand.GetPath().isEmpty() )
|
||||||
nand.Flush();
|
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( "<b>" + ui->lineEdit_extractPath->text() + " is a file. I need a folder<\b>" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if( !fi.exists() )
|
||||||
|
{
|
||||||
|
ShowMessage( "<b>" + fi.absoluteFilePath() + " is not a folder!\nTrying to create it...<\b>" );
|
||||||
|
if( !QDir().mkpath( fi.absoluteFilePath() ) )
|
||||||
|
{
|
||||||
|
ShowMessage( "<b>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( "<b>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( "<b>Error writing " + d.absoluteFilePath( "title.tmd" ) + "!<\b>" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if( !WriteFile( d.absoluteFilePath( "cetk" ), job.data.takeFirst() ) )
|
||||||
|
{
|
||||||
|
ShowMessage( "<b>Error writing " + d.absoluteFilePath( "cetk" ) + "!<\b>" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Tmd t( tmdDat );
|
||||||
|
quint16 cnt = t.Count();
|
||||||
|
if( job.data.size() != cnt )
|
||||||
|
{
|
||||||
|
ShowMessage( "<b>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( "<b>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( "<b>Error making a wad from " + title + "<\b>" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
QFileInfo fi( ui->lineEdit_wad->text() );
|
||||||
|
if( fi.isFile() )
|
||||||
|
{
|
||||||
|
ShowMessage( "<b>" + ui->lineEdit_wad->text() + " is a file. I need a folder<\b>" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if( !fi.exists() )
|
||||||
|
{
|
||||||
|
ShowMessage( "<b>" + fi.absoluteFilePath() + " is not a folder!\nTrying to create it...<\b>" );
|
||||||
|
if( !QDir().mkpath( ui->lineEdit_wad->text() ) )
|
||||||
|
{
|
||||||
|
ShowMessage( "<b>Failed to make the directory!<\b>" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QByteArray w = wad.Data();
|
||||||
|
if( w.isEmpty() )
|
||||||
|
{
|
||||||
|
ShowMessage( "<b>Error creating wad<br>" + 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( "<b>No save name given, aborting<\b>" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
QFile file( fi.absoluteFilePath() + "/" + name );
|
||||||
|
if( !file.open( QIODevice::WriteOnly ) )
|
||||||
|
{
|
||||||
|
ShowMessage( "<b>Cant open " + fi.absoluteFilePath() + "/" + name + " for writing<\b>" );
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
file.write( w );
|
||||||
|
file.close();
|
||||||
|
ShowMessage( "Saved " + title + " to " + fi.absoluteFilePath() + "/" + name );
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
@ -25,6 +25,10 @@ private:
|
|||||||
|
|
||||||
void ShowMessage( const QString& mes );
|
void ShowMessage( const QString& mes );
|
||||||
|
|
||||||
|
//do something with a completed download
|
||||||
|
void SaveJobToFolder( NusJob job );
|
||||||
|
void SaveJobToWad( NusJob job );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public slots:
|
public slots:
|
||||||
|
@ -207,7 +207,7 @@ bool NusDownloader::SaveDataToCache( const QString &path, const QByteArray &stuf
|
|||||||
QDir d( cachePath );
|
QDir d( cachePath );
|
||||||
if( !d.exists() || !d.mkpath( parent ) )
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
QFile f( path );
|
QFile f( path );
|
||||||
|
@ -60,10 +60,10 @@ public:
|
|||||||
|
|
||||||
//get a list of titles for a given update
|
//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 )
|
//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
|
//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
|
//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
|
//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 > List20u();
|
||||||
static QMap< quint64, quint16 > List30u();
|
static QMap< quint64, quint16 > List30u();
|
||||||
static QMap< quint64, quint16 > List31u();
|
static QMap< quint64, quint16 > List31u();
|
||||||
@ -159,6 +159,7 @@ private:
|
|||||||
quint32 totalTitleSize;
|
quint32 totalTitleSize;
|
||||||
quint32 TitleSizeDownloaded();
|
quint32 TitleSizeDownloaded();
|
||||||
|
|
||||||
|
//remember the ticked key for repeated use
|
||||||
QByteArray decKey;
|
QByteArray decKey;
|
||||||
|
|
||||||
|
|
||||||
@ -166,7 +167,7 @@ private:
|
|||||||
signals:
|
signals:
|
||||||
void SendError( const QString &message, NusJob job );//send an errer and the title the error is about
|
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
|
//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
|
void SendDone();//message that all jobs are done
|
||||||
|
|
||||||
//send progress about the currently downloading job
|
//send progress about the currently downloading job
|
||||||
|
@ -12,6 +12,11 @@ Tmd::Tmd( QByteArray stuff )
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
SetPointer();
|
SetPointer();
|
||||||
|
if( (quint32)data.size() != SignedSize() )
|
||||||
|
{
|
||||||
|
data.resize( SignedSize() );
|
||||||
|
SetPointer();
|
||||||
|
}
|
||||||
//hexdump( stuff );
|
//hexdump( stuff );
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -22,6 +27,15 @@ quint64 Tmd::Tid()
|
|||||||
return qFromBigEndian( p_tmd->title_id );
|
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 )
|
QString Tmd::Cid( quint16 i )
|
||||||
{
|
{
|
||||||
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
|
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 );
|
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()
|
quint16 Tmd::Count()
|
||||||
{
|
{
|
||||||
if( !p_tmd )
|
if( !p_tmd )
|
||||||
@ -53,6 +78,15 @@ quint16 Tmd::Version()
|
|||||||
return qFromBigEndian( p_tmd->title_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 )
|
quint64 Tmd::Size( quint16 i )
|
||||||
{
|
{
|
||||||
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
|
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 );
|
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 )
|
quint16 Tmd::Type( quint16 i )
|
||||||
{
|
{
|
||||||
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
|
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 );
|
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()
|
quint32 Tmd::SignedSize()
|
||||||
{
|
{
|
||||||
if( !p_tmd )
|
if( !p_tmd )
|
||||||
@ -89,6 +141,48 @@ void Tmd::SetPointer()
|
|||||||
p_tmd = (tmd*)((quint8*)data.data() + payLoadOffset);
|
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 )
|
Ticket::Ticket( QByteArray stuff )
|
||||||
{
|
{
|
||||||
data = stuff;
|
data = stuff;
|
||||||
@ -97,6 +191,11 @@ Ticket::Ticket( QByteArray stuff )
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
SetPointer();
|
SetPointer();
|
||||||
|
if( (quint32)data.size() != SignedSize() )
|
||||||
|
{
|
||||||
|
data.resize( SignedSize() );
|
||||||
|
SetPointer();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
quint64 Ticket::Tid()
|
quint64 Ticket::Tid()
|
||||||
@ -106,6 +205,15 @@ quint64 Ticket::Tid()
|
|||||||
return qFromBigEndian( p_tik->titleid );
|
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()
|
QByteArray Ticket::DecryptedKey()
|
||||||
{
|
{
|
||||||
quint8 iv[ 16 ];
|
quint8 iv[ 16 ];
|
||||||
@ -145,3 +253,26 @@ void Ticket::SetPointer()
|
|||||||
|
|
||||||
p_tik = (tik*)((quint8*)data.data() + payLoadOffset);
|
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;
|
||||||
|
}
|
||||||
|
@ -149,9 +149,14 @@ public:
|
|||||||
const tik *payload(){ return p_tik; }
|
const tik *payload(){ return p_tik; }
|
||||||
|
|
||||||
quint64 Tid();
|
quint64 Tid();
|
||||||
|
bool SetTid( quint64 tid );
|
||||||
|
|
||||||
QByteArray DecryptedKey();
|
QByteArray DecryptedKey();
|
||||||
quint32 SignedSize();
|
quint32 SignedSize();
|
||||||
|
bool FakeSign();
|
||||||
|
|
||||||
|
//get the ticket data
|
||||||
|
const QByteArray Data(){ return data; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
quint32 payLoadOffset;
|
quint32 payLoadOffset;
|
||||||
@ -193,6 +198,22 @@ public:
|
|||||||
//title version
|
//title version
|
||||||
quint16 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();
|
quint32 SignedSize();
|
||||||
private:
|
private:
|
||||||
quint32 payLoadOffset;
|
quint32 payLoadOffset;
|
||||||
|
@ -155,6 +155,24 @@ QByteArray ReadFile( const QString &path )
|
|||||||
return ret;
|
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
|
#define CERTS_DAT_SIZE 2560
|
||||||
const quint8 certs_dat[ CERTS_DAT_SIZE ] = {
|
const quint8 certs_dat[ CERTS_DAT_SIZE ] = {
|
||||||
0x00, 0x01, 0x00, 0x01, 0x7D, 0x9D, 0x5E, 0xBA, 0x52, 0x81, 0xDC, 0xA7, 0x06, 0x5D, 0x2F, 0x08,
|
0x00, 0x01, 0x00, 0x01, 0x7D, 0x9D, 0x5E, 0xBA, 0x52, 0x81, 0xDC, 0xA7, 0x06, 0x5D, 0x2F, 0x08,
|
||||||
|
@ -26,6 +26,9 @@ QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo );
|
|||||||
//read a file into a bytearray
|
//read a file into a bytearray
|
||||||
QByteArray ReadFile( const QString &path );
|
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
|
//keep track of the last folder browsed to when looking for files
|
||||||
extern QString currentDir;
|
extern QString currentDir;
|
||||||
|
|
||||||
|
@ -180,6 +180,7 @@ void UIDmap::CreateNew( bool addFactorySetupDiscs )
|
|||||||
default:
|
default:
|
||||||
qWarning() << "oops" << hex << i;
|
qWarning() << "oops" << hex << i;
|
||||||
return;
|
return;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
uid = qFromBigEndian( 0x1000 + i );
|
uid = qFromBigEndian( 0x1000 + i );
|
||||||
|
@ -441,7 +441,7 @@ QString Wad::WadName( quint64 tid, quint16 version, QString path )
|
|||||||
|
|
||||||
QByteArray Wad::FromDirectory( QDir dir )
|
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() )
|
if( tmds.isEmpty() )
|
||||||
{
|
{
|
||||||
qWarning() << "Wad::FromDirectory -> no tmd found in" << dir.absolutePath();
|
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() );
|
QByteArray tmdD = ReadFile( tmds.at( 0 ).absoluteFilePath() );
|
||||||
if( tmdD.isEmpty() )
|
if( tmdD.isEmpty() )
|
||||||
return QByteArray();
|
return QByteArray();
|
||||||
QFileInfoList tiks = dir.entryInfoList( QStringList() << "*.tik", QDir::Files );
|
QFileInfoList tiks = dir.entryInfoList( QStringList() << "*.tik" << "cetk", QDir::Files );
|
||||||
if( tiks.isEmpty() )
|
if( tiks.isEmpty() )
|
||||||
{
|
{
|
||||||
qWarning() << "Wad::FromDirectory -> no tik found in" << dir.absolutePath();
|
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() );
|
QByteArray tikD = ReadFile( tiks.at( 0 ).absoluteFilePath() );
|
||||||
if( tikD.isEmpty() )
|
if( tikD.isEmpty() )
|
||||||
return QByteArray();
|
return QByteArray();
|
||||||
Tmd t(tmdD );
|
|
||||||
QList<QByteArray> datas = QList<QByteArray>()<< 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<QByteArray> datas = QList<QByteArray>()<< tmdP << tikP;
|
||||||
|
|
||||||
quint16 cnt = t.Count();
|
quint16 cnt = t.Count();
|
||||||
for( quint16 i = 0; i < cnt; i++ )
|
for( quint16 i = 0; i < cnt; i++ )
|
||||||
@ -486,3 +495,61 @@ QByteArray Wad::FromDirectory( QDir dir )
|
|||||||
QByteArray ret = wad.Data();
|
QByteArray ret = wad.Data();
|
||||||
return ret;
|
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;
|
||||||
|
}
|
||||||
|
@ -23,15 +23,19 @@ public:
|
|||||||
quint64 Tid();
|
quint64 Tid();
|
||||||
|
|
||||||
//set the tid in the ticket&tmd and fakesign the wad
|
//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
|
//add a new content to this wad and fakesign
|
||||||
//if the data is encrypted, set that arguement to true
|
//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
|
//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
|
//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
|
//set the global cert that will be used for all created
|
||||||
static void SetGlobalCert( const QByteArray &stuff );
|
static void SetGlobalCert( const QByteArray &stuff );
|
||||||
@ -58,9 +62,9 @@ public:
|
|||||||
//returns an empty string if it cant guess the title based on TID
|
//returns an empty string if it cant guess the title based on TID
|
||||||
static QString WadName( quint64 tid, quint16 version, QString path = QString() );
|
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() );
|
QString WadName( QString path = QString() );
|
||||||
|
|
||||||
|
|
||||||
private:
|
private:
|
||||||
bool ok;
|
bool ok;
|
||||||
QString errStr;
|
QString errStr;
|
||||||
|
Loading…
Reference in New Issue
Block a user