#include "mainwindow.h" #include "newnandbin.h" #include "ui_mainwindow.h" #include "../WiiQt/settingtxtdialog.h" #include "../WiiQt/tiktmd.h" #include "../WiiQt/tools.h" #include "../WiiQt/wad.h" //on 1 of my wiis, disc 123J owns the test directory & testlog. this wii came from the factory as 3.2u //#define NAND_TEST_OWNER 0x100003132334aull //on my later wiis, disc 121J owns the test directory & testlog. all came with 4.2u or later #define NAND_TEST_OWNER 0x100003132314aull //the group of the test dir/files seems to always be 4 #define NAND_TEST_GROUP 4 MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::MainWindow ), nus ( this ), nand ( this ) { ui->setupUi(this); nandInited = false; root = NULL; uidDirty = false; sharedDirty = false; nandDirty = false; 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_GetTitle->text() ) ); max = MAX( max, fm.width( ui->pushButton_initNand->text() ) ); max = MAX( max, fm.width( ui->pushButton_nandPath->text() ) ); max += 15; ui->pushButton_CachePathBrowse->setMinimumWidth( max ); ui->pushButton_GetTitle->setMinimumWidth( max ); ui->pushButton_initNand->setMinimumWidth( max ); ui->pushButton_nandPath->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 &, NusJob ) ), this, SLOT( GetError( const QString &, NusJob ) ) ); connect( &nus, SIGNAL( SendDone() ), this, SLOT( NusIsDone() ) ); connect( &nus, SIGNAL( SendData( NusJob ) ), this, SLOT( ReceiveTitleFromNus( NusJob) ) ); //connect to the nand.bin to get text and crap from it connect( &nand, SIGNAL( SendError( const QString & ) ), this, SLOT( GetError( const QString & ) ) ); connect( &nand, SIGNAL( SendText( QString ) ), ui->statusBar, SLOT( showMessage( QString ) ) ); LoadSettings(); ui->lineEdit_nandPath->setText( "./testNand.bin" ); } MainWindow::~MainWindow() { SaveSettings(); delete ui; } void MainWindow::SaveSettings() { QSettings s( QSettings::IniFormat, QSettings::UserScope, "WiiQt", "examples", this ); //settings specific to this program s.beginGroup( "ohneschwanzenegger" ); s.setValue( "size", size() ); s.setValue( "pos", pos() ); s.endGroup(); //settings shared in multiple programs //paths s.beginGroup( "paths" ); s.setValue( "nusCache", ui->lineEdit_cachePath->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( "ohneschwanzenegger" ); resize( s.value("size", QSize( 654, 507 ) ).toSize() ); move( s.value("pos", QPoint( 2, 72 ) ).toPoint() ); s.endGroup(); s.beginGroup( "paths" ); QString cachePath = s.value( "nusCache", PATH_PREFIX + "/NUS_cache" ).toString(); ui->lineEdit_cachePath->setText( cachePath ); if( !cachePath.isEmpty() ) nus.SetCachePath( QFileInfo( cachePath ).absoluteFilePath() ); s.endGroup(); } //some slots to respond to the NUS downloader void MainWindow::GetError( const QString &message, NusJob job ) { QString dataStuff = QString( "%1 items:" ).arg( job.data.size() ); for( int i = 0; i < job.data.size(); i++ ) dataStuff += QString( " %1" ).arg( job.data.at( i ).size(), 0, 16, QChar( ' ' ) ); QString str = tr( "Error getting title from NUS: %1" ).arg( message ); QString j = QString( "NusJob( %1, %2, %3, %4 )
" ) .arg( job.tid, 16, 16, QChar( '0' ) ) .arg( job.version ).arg( job.decrypt ? "decrypted" : "encrypted" ) .arg( dataStuff ); ui->plainTextEdit_log->appendHtml( str ); ui->plainTextEdit_log->appendHtml( j ); } //get error from nand.bin void MainWindow::GetError( const QString &message ) { QString str = tr( "Nand object Error: %1" ).arg( message ); ui->plainTextEdit_log->appendHtml( str ); } 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 ); //make sure there is a setting.txt QTreeWidgetItem *item = ItemFromPath( "/title/00000001/00000002/data/setting.txt" ); if( !item && ItemFromPath( "/title/00000001/00000002/data" ) )//only try to make setting.txt if it is missing and there is a folder to hold it { 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; QByteArray ba = SettingTxtDialog::Edit( this, QByteArray(), reg ); //call a dialog to create a new setting.txt if( !ba.isEmpty() ) //if the dialog returned anything ( cancel wasnt pressed ) write that new setting.txt to the nand { quint16 r = nand.CreateEntry( "/title/00000001/00000002/data/setting.txt", 0x1000, 1, NAND_FILE, NAND_READ, NAND_READ, NAND_READ ); if( !r ) { ShowMessage( "Error creating setting.txt. maybe some folders are missing?" ); } else { if( !nand.SetData( r, ba ) ) { ShowMessage( "Error writing data for setting.txt." ); } else { nandDirty = true; UpdateTree(); } } } } //nand.Delete( "/title/00000001/00000002/content/title.tmd" ); if( nandDirty ) { if( !FlushNand() ) { ShowMessage( "Error flushing nand. Maybe you used too much TP?" ); } nandDirty = false; } } void MainWindow::ReceiveTitleFromNus( 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( InstallNUSItem( job ) ) nandDirty = true; } //clicked the button to get a title void MainWindow::on_pushButton_GetTitle_clicked() { if( !nandInited && !InitNand( ui->lineEdit_nandPath->text() ) ) return; 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; nus.SetCachePath( ui->lineEdit_cachePath->text() ); 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 ); } } //search for a path to use as the nand basepath void MainWindow::on_pushButton_nandPath_clicked() { QString f = QFileDialog::getOpenFileName( this, tr( "Select nand.bin" ) ); if( f.isEmpty() ) return; ui->lineEdit_nandPath->setText( f ); InitNand( ui->lineEdit_nandPath->text() ); } 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() ); } //nand-dump -> setting.txt void MainWindow::on_actionSetting_txt_triggered() { if( !nandInited ) return; QTreeWidgetItem *it = ItemFromPath( "/title/00000001/00000002/data/setting.txt" ); if( !it ) { ShowMessage( tr( "There is no setting.txt found in %1" ) .arg( QFileInfo( ui->lineEdit_nandPath->text() ).absoluteFilePath() ) ); return; } QByteArray ba = nand.GetData( "/title/00000001/00000002/data/setting.txt" ); //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 { if( !nand.SetData( "/title/00000001/00000002/data/setting.txt", ba ) || !nand.WriteMetaData() ) { ShowMessage( tr( "Error writing setting.txt" ) ); } else ShowMessage( tr( "Saved setting.txt" ) ); } } //nand-dump -> flush void MainWindow::on_actionFlush_triggered() { if( nandInited ) FlushNand(); } //nand-dump -> ImportWad void MainWindow::on_actionImportWad_triggered() { if( !nandInited && !InitNand( ui->lineEdit_nandPath->text() ) ) { ShowMessage( tr( "Error setting the basepath of the nand to %1" ).arg( QFileInfo( ui->lineEdit_nandPath->text() ).absoluteFilePath() ) ); return; } //QString fn = QFileDialog::getOpenFileName( this, tr("Wad files(*.wad)"), QCoreApplication::applicationDirPath(), tr("WadFiles (*.wad)") ); QStringList fns = QFileDialog::getOpenFileNames( this, tr("Wad files(*.wad)"), QCoreApplication::applicationDirPath(), tr("WadFiles (*.wad)") ); if( fns.isEmpty() ) return; foreach( QString fn, fns ) { QByteArray data = ReadFile( fn ); if( data.isEmpty() ) return; Wad wad( data ); if( !wad.IsOk() ) { ShowMessage( tr( "Wad data not ok for \"%1\"" ).arg( fn ) ); return; } //work smart, not hard... just turn the wad into a NUSJob and reused the same code to install it NusJob job; job.tid = wad.Tid(); job.data << wad.getTmd(); job.data << wad.getTik(); Tmd t( wad.getTmd() ); job.version = t.Version(); quint16 cnt = t.Count(); for( quint16 i = 0; i < cnt; i++ ) { job.data << wad.Content( i ); } job.decrypt = true; ShowMessage( tr( "Installing %1 to nand" ).arg( fn ) ); InstallNUSItem( job ); } } void MainWindow::on_actionNew_nand_from_keys_triggered() { QString path = NewNandBin::GetNewNandPath( this ); if( path.isEmpty() ) return; InitNand( path ); ui->lineEdit_nandPath->setText( path ); //these titles should be in order ( not really functional, but to emulate better how the wii comes from the factory ) if( !nand.CreateEntry( "/title/00000001", 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_READ ) || !nand.CreateEntry( "/title/00000001/00000004", 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_READ ) || !nand.CreateEntry( "/title/00000001/00000009", 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_READ ) || !nand.CreateEntry( "/title/00000001/00000002", 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_READ ) || !nand.CreateEntry( "/title/00000001/00000100", 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_READ ) || !nand.CreateEntry( "/title/00000001/00000101", 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_READ ) ) { ShowMessage( "Error creating title subdirs<\b>" ); return; } //add some factory test logs and whatnot quint32 _uid = uid.GetUid( NAND_TEST_OWNER, true ); if( !nand.CreateEntry( "/shared2/test", _uid, NAND_TEST_GROUP, NAND_DIR, NAND_RW, NAND_RW, NAND_RW ) || !nand.CreateEntry( "/shared2/sys", _uid, NAND_TEST_GROUP, NAND_DIR, NAND_RW, NAND_RW, NAND_RW ) ) { ShowMessage( "Error creating folder for testlog<\b>" ); return; } quint16 handle = nand.CreateEntry( "/shared2/test/testlog.txt", _uid, NAND_TEST_GROUP, NAND_FILE, NAND_RW, NAND_RW, NAND_RW ); if( !handle ) { ShowMessage( "Error creating testlog<\b>" ); return; } QByteArray tLog = ReadFile( ":/testlog.txt" ); if( !nand.SetData( handle, tLog ) ) { ShowMessage( "Error writing to testlog.txt<\b>" ); return; } UpdateTree(); ShowMessage( "Created /shared2/test/testlog.txt" ); } void MainWindow::on_pushButton_initNand_clicked() { if( ui->lineEdit_nandPath->text().isEmpty() ) { ShowMessage( "Please enter a path for nand.bin<\b>" ); return; } InitNand( ui->lineEdit_nandPath->text() ); } bool MainWindow::InitNand( const QString &path ) { nandInited = false; sharedDirty = false; nandDirty = false; ui->menuContent->setEnabled( false ); if( !nand.SetPath( path ) || !nand.InitNand() ) return false; if( !UpdateTree() ) return false; //setup the uid QTreeWidgetItem *it = ItemFromPath( "/sys/uid.sys" ); if( !it ) { uid.CreateNew();//dont add any UID besides the system menu since we dont know what region it will be if( !nand.CreateEntry( "/sys/uid.sys", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ) ) { ShowMessage( "Error creating new uid.sys" ); return false; } uidDirty = true; } else { QByteArray ba = nand.GetData( "/sys/uid.sys" ); uid = UIDmap( ba ); } //set up the shared map it = ItemFromPath( "/shared1/content.map" ); if( !it ) { if( !nand.CreateEntry( "/shared1/content.map", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ) ) { sharedDirty = true; ShowMessage( "Error creating new content.map" ); return false; } } else { QByteArray ba = nand.GetData( "/shared1/content.map" ); shared = SharedContentMap( ba ); if( !shared.Check() )//i really dont want to create a new one and rewrite all the contents to match it ShowMessage( "Something about the shared map isnt right, but im using it anyways" ); } nandInited = true; ui->menuContent->setEnabled( true ); ShowMessage( "Set path to nand as " + path ); return true; } //this one is kinda important // it is in charge of writing the uid, content map, and all metadata to the nand. //failing to do this will result in any changes not being applied bool MainWindow::FlushNand() { //qDebug() << "MainWindow::FlushNand()"; bool r1 = true; bool r2 = true; if( uidDirty && !nand.SetData( "/sys/uid.sys", uid.Data() ) ) r1 = false; else uidDirty = false; if( sharedDirty && !nand.SetData( "/shared1/content.map", shared.Data() ) ) r2 = false; else sharedDirty = false; return ( nand.WriteMetaData() && r1 && r2 ); } QTreeWidgetItem *MainWindow::FindItem( const QString &s, QTreeWidgetItem *parent ) { int cnt = parent->childCount(); for( int i = 0; i child( i ); if( r->text( 0 ) == s ) { return r; } } return NULL; } QTreeWidgetItem *MainWindow::ItemFromPath( const QString &path ) { QTreeWidgetItem *item = root; if( !path.startsWith( "/" ) || path.contains( "//" )) { return NULL; } int slash = 1; while( slash ) { int nextSlash = path.indexOf( "/", slash + 1 ); QString lookingFor = path.mid( slash, nextSlash - slash ); item = FindItem( lookingFor, item ); if( !item ) { //qWarning() << "ItemFromPath ->item not found" << path; return NULL; } slash = nextSlash + 1; } return item; } QString MainWindow::PathFromItem( QTreeWidgetItem *item ) { QString ret; while( item ) { ret.prepend( "/" + item->text( 0 ) ); item = item->parent(); if( item->text( 0 ) == "/" )// dont add the root break; } return ret; } bool MainWindow::UpdateTree() { //set up the tree so we know what all is in the nand without asking for it every time if( root ) delete root; QTreeWidgetItem *r = nand.GetTree(); if( r->childCount() != 1 || r->child( 0 )->text( 0 ) != "/" ) { ShowMessage( "The nand FS is seriously broken. I Couldn't even find a correct root" ); return false; } root = r->takeChild( 0 ); delete r; return true; } quint16 MainWindow::CreateIfNeeded( const QString &path, quint32 uid, quint16 gid, quint8 attr, quint8 user_perm, quint8 group_perm, quint8 other_perm ) { // qDebug() << "MainWindow::CreateIfNeeded" << path; QTreeWidgetItem *item = ItemFromPath( path ); if( !item ) { quint16 ret = nand.CreateEntry( path, uid, gid, attr, user_perm, group_perm, other_perm ); if( ret && UpdateTree() ) return ret; return 0; } //TODO - if the item already exists, check that its attributes match the expected ones return item->text( 1 ).toInt(); } bool MainWindow::InstallSharedContent( const QByteArray stuff, const QByteArray hash ) { //qDebug() << "MainWindow::InstallSharedContent"; QByteArray h; if( hash.isEmpty() ) h = GetSha1( stuff ); else h = hash; QString cid = shared.GetAppFromHash( hash ); if( !cid.isEmpty() ) //this one is already installed return true; //qDebug() << "will create new"; //get next available cid in the shared map cid = shared.GetNextEmptyCid(); shared.AddEntry( cid, h ); sharedDirty = true; //qDebug() << "next cid" << cid; //create the file quint16 r = CreateIfNeeded( "/shared1/" + cid + ".app", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ); if( !r ) return false; //write the data to the file return nand.SetData( r, stuff ); } bool MainWindow::InstallNUSItem( NusJob job ) { QString title = QString( "%1 v%2" ).arg( job.tid, 16, 16, QChar( '0' ) ).arg( job.version ); qDebug() << "MainWindow::InstallNUSItem" << title; quint16 r; quint32 _uid; quint16 _gid; quint16 cnt; bool deleted = false; QTreeWidgetItem *content; if( !job.tid || !job.data.size() > 2 ) { qWarning() << "bad sizes"; ShowMessage( "Error installing title " + title + " to nand" ); return false; } QString tid = QString( "%1" ).arg( job.tid, 16, 16, QChar( '0' ) ); QString upper = tid.left( 8 ); QString lower = tid.right( 8 ); Tmd t( job.data.takeFirst() ); Ticket ticket( job.data.takeFirst() ); if( t.Tid() != job.tid || ticket.Tid() != job.tid ) { qWarning() << "bad tid"; goto error; } cnt = t.Count(); if( job.data.size() != cnt ) { qWarning() << "content count"; goto error; } //qDebug() << "uidDirty" << uidDirty; if( !uidDirty ) { uidDirty = !uid.GetUid( job.tid, false ); } //qDebug() << "uidDirty" << uidDirty; _uid = uid.GetUid( job.tid ); _gid = t.Gid(); if( !_uid ) { qWarning() << "no uid"; goto error; } //create all the folders if( !CreateIfNeeded( "/ticket/" + upper, 0, 0, NAND_DIR, NAND_RW, NAND_RW, 0 ) ) { qWarning() << "can't create ticket+upper folder";goto error;} if( !CreateIfNeeded( "/title/" + upper, 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_READ ) ) { qWarning() << "can't create title+upper folder";goto error;} if( !CreateIfNeeded( "/title/" + upper + "/" + lower, 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_READ ) ) { qWarning() << "can't create title+upper+lower folder";goto error;} if( !CreateIfNeeded( "/title/" + upper + "/" + lower + "/data", _uid, _gid, NAND_DIR, NAND_RW, 0, 0 ) ) { qWarning() << "can't create data folder";goto error;} if( !CreateIfNeeded( "/title/" + upper + "/" + lower + "/content", 0, 0, NAND_DIR, NAND_RW, NAND_RW, 0 ) ) { qWarning() << "can't create content folder";goto error;} //delete old tmd/.apps and whatever else in the content folder content = ItemFromPath( "/title/" + upper + "/" + lower + "/content" ); cnt = content->childCount(); for ( quint16 i = 0; i < cnt; i++ ) { if( !nand.Delete( "/title/" + upper + "/" + lower + "/content/" + content->child( i )->text( 0 ) ) ) { qWarning() << "error deleting old title"; goto error; } deleted = true; } if( deleted ) { //nand.WriteMetaData(); UpdateTree(); ShowMessage( tr( "Deleted old TMD and private contents for
%1" ).arg( title ) ); } cnt = t.Count(); //install ticket r = CreateIfNeeded( "/ticket/" + upper + "/" + lower + ".tik", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ); if( !r ) { qWarning() << "can't create ticket";goto error;} if( !nand.SetData( r, ticket.Data() ) ) { qWarning() << "can't write to ticket";goto error;} //install tmd r = CreateIfNeeded( "/title/" + upper + "/" + lower + "/content/title.tmd", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ); if( !r ) { qWarning() << "can't create tmd";goto error;} if( !nand.SetData( r, t.Data() ) ) { qWarning() << "can't write to tmd";goto error;} //install contents //qDebug() << "will install" << cnt << "contents"; for( quint16 i = 0; i < cnt; i++ ) { //qDebug() << "installing" << i; //make sure the data is decrypted QByteArray decData; QByteArray hash; if( job.decrypt ) { decData = job.data.takeFirst(); if( (quint32)decData.size() != t.Size( i ) ) { qDebug() << "wtf - size"; decData.resize( t.Size( i ) ); } } else { //decrypt the data QByteArray encData = job.data.takeFirst(); AesSetKey( ticket.DecryptedKey() ); decData = AesDecrypt( i, encData ); decData.resize( t.Size( i ) ); } //check the hash hash = GetSha1( decData ); if( hash != t.Hash( i ) ) { qWarning() << "hash" << i << "\n" << hash.toHex() << "\n" << t.Hash( i ).toHex(); goto error; } //install the content if( t.Type( i ) == 1 )//private { r = CreateIfNeeded( "/title/" + upper + "/" + lower + "/content/" + t.Cid( i ) + ".app" , 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ); if( !r ) { qWarning() << "cant create content" << i; goto error; } if ( !nand.SetData( r, decData ) ) { qWarning() << "cant write content" << i; goto error; } } else if( t.Type( i ) == 0x8001 )//shared { if ( !InstallSharedContent( decData, hash ) ) { qWarning() << "error installing shared" << i << hash.toHex(); goto error; } } else { qWarning() << "type" << hex << t.Type( i ); goto error; } } qDebug() << "done installing"; ShowMessage( "Installed title " + title + " to nand" ); //nand.Delete( "/title/" + upper ); //UpdateTree(); return true; error: ShowMessage( "Error installing title " + title + " to nand" ); return false; } //help -> about void MainWindow::on_actionAbout_triggered() { QString txt = tr( "This is an example program from WiiQt. It is designed to write titles to a nand.bin and even create one from scratch." "

PLEASE BE AWARE, THIS IS NOT VERY WELL TESTED AND AS OF RIGHT NOW." " IT SHOULD ONLY BE USED BY PEOPLE THAT KNOW HOW TO VERIFY THE FILES IT PRODUCES. AND HAVE A WAY TO FIX A BRICKED WII SHOULD THIS PROGRAM HAVE BUGS" "

YOU HAVE BEEN WARNED" "
giantpune" ); QMessageBox::critical( this, tr( "About" ), txt ); } #if 0 //add a default settings file if there is one laying around void MainWindow::TryToAddDefaultSettings() { if( ItemFromPath( "/shared2/sys/SYSCONF" ) ) return; QByteArray stuff = ReadFile( "./default_SYSCONF" ); if( stuff.isEmpty() ) return; quint32 uiD = uid.GetUid( NAND_TEST_OWNER ); if( !CreateIfNeeded( "/shared2/sys", uiD, NAND_TEST_GROUP, NAND_DIR, NAND_RW, NAND_RW, NAND_RW ) ) return; quint16 handle = nand.CreateEntry( "/shared2/sys/SYSCONF", uiD, NAND_TEST_GROUP, NAND_FILE, NAND_RW, NAND_RW, NAND_RW ); if( !handle || !nand.SetData( handle, stuff ) || !nand.WriteMetaData() ) { ShowMessage( "Error adding the default settings" ); return; } UpdateTree(); ShowMessage( "Wrote /shared2/sys/SYSCONF" ); } #endif //AFAIK, the stuff in the "/meta" folder is put there by the disc that installs the IOS & system menu at the factory. //i have not been able to get the wii to create this data on its own using any officially release system menu or other program void MainWindow::AddStuffToMetaFolder() { if( !ItemFromPath( "/meta" ) ) return; if( !CreateIfNeeded( "/meta/00000001", 0x1000, 1, NAND_DIR, NAND_RW, NAND_RW, NAND_RW ) ) { ShowMessage( "Cannot create folder for metacrap" ); return; } bool written = false; //these are the bare minimum metadata files ive seen on a nand //i have seen some where there are full banners for the 0x10002 titles, but this is not always the case for( quint16 i = 0; i < 3; i++ ) { quint64 tid; quint16 ver; QString desc; switch( i ) { case 0: tid = 0x100000004ull; ver = 3; desc = "sd_os1_1.64"; break; case 1: tid = 0x100000009ull; ver = 1; desc = "sd_os1_1.64"; break; case 2: tid = 0x100000002ull; ver = 0; desc = "systemmenu.rvl.0.4"; break; } QString tidStr = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ); tidStr.insert( 8, "/" ); QString path = "/meta/" + tidStr + "/title.met"; if( ItemFromPath( path ) )//already have this metadata continue; //create subfolder path = "/meta/" + tidStr; if( !CreateIfNeeded( path, 0x1000, 1, NAND_DIR, NAND_RW, NAND_RW, NAND_RW ) ) { ShowMessage( "Cannot create " + path + " for metacrap" ); return; } path = "/meta/" + tidStr + "/title.met"; //generate metacrap QByteArray stuff = GenMeta( desc, tid, ver ); quint16 handle = nand.CreateEntry( path, 0x1000, 1, NAND_FILE, NAND_RW, NAND_RW, NAND_RW ); if( !handle || !nand.SetData( handle, stuff ) || !UpdateTree() ) { ShowMessage( "Error writing data for " + path + " " ); return; } written = true; } if( !written ) { ShowMessage( "Nothing to write in \"/meta\"" ); return; } if( !nand.WriteMetaData() ) { ShowMessage( "Error writing nand matedata for \"/meta\"" ); return; } ShowMessage( "Wrote entries for \"/meta\"" ); } QByteArray MainWindow::GenMeta( const QString &desc, quint64 tid, quint16 version ) { QByteArray ret( 0x40, '\0' ); QBuffer buf( &ret ); buf.open( QIODevice::WriteOnly ); buf.write( desc.toLatin1().data() ); tid = qFromBigEndian( tid ); buf.seek( 0x20 ); buf.write( (const char*)&tid, 8 ); version = qFromBigEndian( version ); buf.write( (const char*)&version, 4 ); buf.close(); return ret; } void MainWindow::on_actionWrite_meta_entries_triggered() { AddStuffToMetaFolder(); } //content -> format void MainWindow::on_actionFormat_triggered() { if( nand.FilePath().isEmpty() ) return; if( QMessageBox::warning( this, tr( "Format" ), \ tr( "You are about to format
%1.

This cannot be undone. Are you sure you want to do it?" ).arg( nand.FilePath() ),\ QMessageBox::Yes | QMessageBox::No, QMessageBox::No ) != QMessageBox::Yes ) return; ShowMessage( "Formatting nand..." ); if( !nand.Format() ) { ShowMessage( "Error! This nand may be broken now :(" ); return; } //add folders to root if( !nand.CreateEntry( "/sys", 0, 0, NAND_DIR, NAND_RW, NAND_RW, 0 ) || !nand.CreateEntry( "/ticket", 0, 0, NAND_DIR, NAND_RW, NAND_RW, 0 ) || !nand.CreateEntry( "/title", 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_READ ) || !nand.CreateEntry( "/shared1", 0, 0, NAND_DIR, NAND_RW, NAND_RW, 0 ) || !nand.CreateEntry( "/shared2", 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_RW ) || !nand.CreateEntry( "/import", 0, 0, NAND_DIR, NAND_RW, NAND_RW, 0 ) || !nand.CreateEntry( "/meta", 0x1000, 1, NAND_DIR, NAND_RW, NAND_RW, NAND_RW ) || !nand.CreateEntry( "/tmp", 0, 0, NAND_DIR, NAND_RW, NAND_RW, NAND_RW ) ) { ShowMessage( "Error! Can't create base folders in the new nand." ); return; } //add cert.sys quint16 handle = nand.CreateEntry( "/sys/cert.sys", 0, 0, NAND_FILE, NAND_RW, NAND_RW, NAND_READ ); if( !handle || !nand.SetData( handle, QByteArray( (const char*)&certs_dat, CERTS_DAT_SIZE ) ) ) { ShowMessage( "Error! Can't cert.sys." ); return; } //wipe all user-created entries from uid.sys QByteArray uidData = uid.Data(); QBuffer buf( &uidData ); buf.open( QIODevice::ReadWrite ); quint64 tid; quint16 titles = 0; quint32 cnt = uidData.size() / 12; for( quint32 i = 0; i < cnt; i++ ) { buf.seek( i * 12 ); buf.read( (char*)&tid, 8 ); tid = qFromBigEndian( tid ); quint32 upper = ( ( tid >> 32 ) & 0xffffffff ); quint32 lower = ( tid & 0xffffffff ); //qDebug() << hex << i << QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ) << upper << lower << QChar( ( lower >> 24 ) & 0xff ) << ( lower & 0xffffff00 ); if( ( upper == 0x10001 && ( ( lower >> 24 ) & 0xff ) != 0x48 ) || //a channel, not starting with 'H' lower == 0x48415858 || //original HBC ( upper == 0x10000 && ( ( lower & 0xffffff00 ) == 0x555000 ) ) ) //a disc update partition break; titles++; } buf.close(); uidData.resize( 12 * titles ); //hexdump12( uidData ); uid = UIDmap( uidData ); uidDirty = true; sharedDirty = true; shared = SharedContentMap(); if( !nand.CreateEntry( "/sys/uid.sys", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ) ) { ShowMessage( "Error! Can't create /sys/uid.sys" ); return; } //clear content.map if( !nand.CreateEntry( "/shared1/content.map", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ) ) { ShowMessage( "Error! Can't create /shared1/content.map" ); return; } //commit if( !nand.WriteMetaData() || !UpdateTree() ) { ShowMessage( "Error finalizing formatting!" ); return; } ShowMessage( "Done!" ); }