#include "mainwindow.h" #include "ui_mainwindow.h" #include "../WiiQt/settingtxtdialog.h" #include "../WiiQt/tiktmd.h" #include "../WiiQt/tools.h" #include "../WiiQt/wad.h" MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::MainWindow ), nus ( this ) { ui->setupUi(this); ui->mainToolBar->setVisible( false );//hide toolbar for now //resize buttons to be same size QFontMetrics fm( fontMetrics() ); int max = fm.width( ui->pushButton_CachePathBrowse->text() ); max = MAX( max, fm.width( ui->pushButton_decFolder->text() ) ); max = MAX( max, fm.width( ui->pushButton_GetTitle->text() ) ); max = MAX( max, fm.width( ui->pushButton_nandPath->text() ) ); max = MAX( max, fm.width( ui->pushButton_wad->text() ) ); max += 15; ui->pushButton_CachePathBrowse->setMinimumWidth( max ); ui->pushButton_decFolder->setMinimumWidth( max ); ui->pushButton_GetTitle->setMinimumWidth( max ); ui->pushButton_nandPath->setMinimumWidth( max ); ui->pushButton_wad->setMinimumWidth( max ); Wad::SetGlobalCert( QByteArray( (const char*)&certs_dat, CERTS_DAT_SIZE ) ); //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 &, const NusJob & ) ), this, SLOT( GetError( const QString &, const NusJob & ) ) ); connect( &nus, SIGNAL( SendDone() ), this, SLOT( NusIsDone() ) ); connect( &nus, SIGNAL( SendData( const NusJob & ) ), this, SLOT( ReceiveTitleFromNus( const NusJob & ) ) ); LoadSettings(); } MainWindow::~MainWindow() { SaveSettings(); delete ui; } void MainWindow::SaveSettings() { QSettings s( QSettings::IniFormat, QSettings::UserScope, "WiiQt", "examples", this ); //settings specific to this program s.beginGroup( "nusDownloader" ); //window geometry s.setValue( "size", size() ); s.setValue( "pos", pos() ); //which radio button is selected quint8 val = 0; if( ui->radioButton_folder->isChecked() ) val = 1; else if( ui->radioButton_wad->isChecked() ) val = 2; s.setValue( "radio", val ); s.setValue( "folder", ui->lineEdit_extractPath->text() ); s.setValue( "nuswads", ui->lineEdit_wad->text() ); s.endGroup(); //settings shared in multiple programs //paths s.beginGroup( "paths" ); s.setValue( "nusCache", ui->lineEdit_cachePath->text() ); s.setValue( "sneek", ui->lineEdit_nandPath->text() ); s.endGroup(); } #ifdef Q_WS_WIN #define PATH_PREFIX QString("../..") #else #define PATH_PREFIX QString("..") #endif void MainWindow::LoadSettings() { QSettings s( QSettings::IniFormat, QSettings::UserScope, "WiiQt", "examples", this ); //settings specific to this program s.beginGroup( "nusDownloader" ); resize( s.value("size", QSize( 585, 457 ) ).toSize() ); move( s.value("pos", QPoint( 2, 72 ) ).toPoint() ); quint8 radio = s.value( "radio", 0 ).toInt(); if( radio == 1 ) ui->radioButton_folder->setChecked( true ); else if( radio == 2 ) ui->radioButton_wad->setChecked( true ); ui->lineEdit_extractPath->setText( s.value( "folder", PATH_PREFIX + "/downloaded" ).toString() ); ui->lineEdit_wad->setText( s.value( "nuswads", PATH_PREFIX + "/wads" ).toString() ); s.endGroup(); //settings shared in multiple programs s.beginGroup( "paths" ); QString cachePath = s.value( "nusCache", PATH_PREFIX + "/NUS_cache" ).toString(); QString nandPath = s.value( "sneek" ).toString(); ui->lineEdit_cachePath->setText( cachePath ); ui->lineEdit_nandPath->setText( nandPath ); if( !nandPath.isEmpty() ) nand.SetPath( QFileInfo( nandPath ).absoluteFilePath() ); if( !cachePath.isEmpty() ) nus.SetCachePath( QFileInfo( cachePath ).absoluteFilePath() ); s.endGroup(); } //some slots to respond to the NUS downloader void MainWindow::GetError( const QString &message, const 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 object 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() ) { //check if IOS35 is present in nand dump - needed for sneek QByteArray tmdBA = nand.GetFile( "/title/00000001/00000023/content/title.tmd" ); if( tmdBA.isEmpty() ) { ui->plainTextEdit_log->appendHtml( tr( "IOS35 not found on nand. Getting it now...") ); nus.Get( 0x100000023ull, true ); return; } ui->lineEdit_nandPath->setEnabled( true ); 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 QByteArray set = nand.GetSettingTxt(); if( set.isEmpty() ) { quint8 reg = SETTING_TXT_UNK; if( ui->lineEdit_tid->text().endsWith( "e", Qt::CaseInsensitive ) && ui->lineEdit_tid->text().size() == 4 ) reg = SETTING_TXT_PAL; if( ui->lineEdit_tid->text().endsWith( "j", Qt::CaseInsensitive ) && ui->lineEdit_tid->text().size() == 4 ) reg = SETTING_TXT_JAP; if( ui->lineEdit_tid->text().endsWith( "k", Qt::CaseInsensitive ) && ui->lineEdit_tid->text().size() == 4 ) reg = SETTING_TXT_KOR; set = SettingTxtDialog::Edit( this, QByteArray(), reg ); if( !set.isEmpty() ) nand.SetSettingTxt( set ); } /*QMap< quint64, quint16 > t = nand.GetInstalledTitles(); QMap< quint64, quint16 >::iterator i = t.begin(); while( i != t.end() ) { QString title = QString( "%1v%2" ).arg( i.key(), 16, 16, QChar( '0' ) ).arg( i.value() ); qDebug() << "title:" << title; i++; }*/ } 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( const NusJob &job ) { QString str = tr( "Received a completed download from NUS" ); QString title = QString( "%1v%2" ).arg( job.tid, 16, 16, QChar( '0' ) ).arg( job.version ); ui->plainTextEdit_log->appendHtml( str ); //do something with the data we got if( ui->radioButton_folder->isChecked() )//copy its decrypted contents to a folder { SaveJobToFolder( job ); } else if( ui->radioButton_nand->isChecked() )//install this title to a decrypted nand dump for sneek/dolphin { bool ok = nand.InstallNusItem( job ); if( ok ) ShowMessage( tr( "Installed %1 title to nand" ).arg( title ) ); else ShowMessage( tr( "Error installing %1 title to nand" ).arg( title ) ); } else if( ui->radioButton_wad->isChecked() ) { SaveJobToWad( job ); } } //clicked the button to get a title void MainWindow::on_pushButton_GetTitle_clicked() { bool ok = false; bool wholeUpdate = false; quint64 tid = 0; quint32 ver = 0; if( ui->lineEdit_tid->text().size() == 4 ) { wholeUpdate = true; } else { tid = ui->lineEdit_tid->text().toLongLong( &ok, 16 ); if( !ok ) { ShowMessage( "Error converting \"" + ui->lineEdit_tid->text() + "\" to a hex number." ); return; } ver = TITLE_LATEST_VERSION; 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( nand.GetPath() != ui->lineEdit_nandPath->text() && !nand.SetPath( ui->lineEdit_nandPath->text() ) ) { ShowMessage( tr( "Error setting the basepath of the nand to %1" ) .arg( QFileInfo( ui->lineEdit_nandPath->text() ).absoluteFilePath() ) ); return; } 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( QFileInfo( ui->lineEdit_cachePath->text() ).absoluteFilePath() ); if( wholeUpdate ) { if( !nus.GetUpdate( ui->lineEdit_tid->text(), decrypt ) ) { ShowMessage( tr( "I dont know the titles that were in the %1 update" ).arg( ui->lineEdit_tid->text() ) ); return; } } else { 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() ? QDir::currentPath() : ui->lineEdit_extractPath->text(); QString f = QFileDialog::getExistingDirectory( this, tr( "Select folder to save decrypted titles" ), path ); if( f.isEmpty() ) return; ui->lineEdit_extractPath->setText( f ); } void MainWindow::on_pushButton_wad_clicked() { QString path = ui->lineEdit_wad->text().isEmpty() ? QDir::currentPath() : 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 ); } //nand-dump -> setting.txt void MainWindow::on_actionSetting_txt_triggered() { if( nand.GetPath() != ui->lineEdit_nandPath->text() && !nand.SetPath( ui->lineEdit_nandPath->text() ) ) { ShowMessage( tr( "Error setting the basepath of the nand to %1" ) .arg( QFileInfo( ui->lineEdit_nandPath->text() ).absoluteFilePath() ) ); 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 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 ); } //nand-dump -> flush void MainWindow::on_actionFlush_triggered() { if( !nand.GetPath().isEmpty() ) nand.Flush(); } //nand-dump -> ImportWad void MainWindow::on_actionImportWad_triggered() { if( nand.GetPath() != ui->lineEdit_nandPath->text() && !nand.SetPath( ui->lineEdit_nandPath->text() ) ) { ShowMessage( tr( "Error setting the basepath of the nand to %1" ).arg( QFileInfo( ui->lineEdit_nandPath->text() ).absoluteFilePath() ) ); return; } QString path = ui->lineEdit_wad->text().isEmpty() ? QCoreApplication::applicationDirPath() : ui->lineEdit_wad->text(); QString fn = QFileDialog::getOpenFileName( this, tr("Wad files(*.wad)"), path, tr("WadFiles (*.wad)")); if(fn == "") return; QByteArray data = ReadFile( fn ); if( data.isEmpty() ) return; Wad wad(data); if( !wad.IsOk() ) { ShowMessage( tr( "Wad data not ok" ) );; return; } bool ok = nand.InstallWad( wad ); if( ok ) ShowMessage( tr( "Installed %1 title to nand" ).arg( wad.WadName() ) ); else ShowMessage( tr( "Error %1 title to nand" ).arg( wad.WadName() ) ); } //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 ); QByteArray stuff = job.data.takeFirst(); if( job.decrypt ) { appName += ".app"; //qDebug() << "resizing from" << hex << stuff.size() << "to" << (quint32)t.Size( i ); //stuff.resize( t.Size( i ) ); } if( !WriteFile( d.absoluteFilePath( appName ), stuff ) ) { ShowMessage( "Error writing " + d.absoluteFilePath( appName ) + "!<\b>" ); return; } } ShowMessage( tr( "Wrote title to %1" ).arg( fi.absoluteFilePath() + "/" + newFName ) ); } //save a completed 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 ); } void MainWindow::on_pushButton_CachePathBrowse_clicked() { QString f = QFileDialog::getExistingDirectory( this, tr( "Select NUS Cache base folder" ) ); if( f.isEmpty() ) return; ui->lineEdit_cachePath->setText( f ); nus.SetCachePath( ui->lineEdit_cachePath->text() ); }