* fix bug in nand.bin class: when creating a new nand.bin, initialize fst_pos in fst entries

* insert new entries in nand.bin at the start of the chain instead of at the end ( to closer mimic IOS FS behavior )
* oneswanzenegger: create /sys/cert.sys when creating a new nand.bin
This commit is contained in:
giantpune@gmail.com 2011-01-03 04:02:00 +00:00
parent fe3ec0de0a
commit 8e3d07daad
4 changed files with 288 additions and 260 deletions

View File

@ -90,6 +90,9 @@ bool NandBin::CreateNew( const QString &path, const QByteArray &keys, const QByt
fats.clear();
memset( &fsts, 0, sizeof( fst_t ) * 0x17ff );
for( quint16 i = 0; i < 0x17ff; i++ )
fsts[ i ].fst_pos = i;
//reserve blocks 0 - 7
for( quint16 i = 0; i < 0x40; i++ )
{
@ -1193,7 +1196,12 @@ quint16 NandBin::CreateEntry( const QString &path, quint32 uid, quint16 gid, qui
if( !ret )
return 0;
fsts[ entryNo ].sib = ret;
//method 1: this works, and the nand is bootable. but doesnt mimic the IOS FS driver. ( my entries appear in reversed order when walking the FS )
//fsts[ entryNo ].sib = ret;
//method 2: trying to mimic the IOS FS driver ( insert new entries at the start of the chain, instead of the end )
fsts[ ret ].sib = fsts[ parIdx ].sub;
fsts[ parIdx ].sub = ret;
}
QTreeWidgetItem *child = CreateItem( par, name, 0, ret, uid, gid, 0, fsts[ ret ].attr, 0 );
if( attr == NAND_FILE )

View File

@ -56,13 +56,13 @@ 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( ' ' ) );
dataStuff += QString( " %1" ).arg( job.data.at( i ).size(), 0, 16, QChar( ' ' ) );
QString str = tr( "<b>Error getting title from NUS: %1</b>" ).arg( message );
QString j = QString( "NusJob( %1, %2, %3, %4 )<br>" )
.arg( job.tid, 16, 16, QChar( '0' ) )
.arg( job.version ).arg( job.decrypt ? "decrypted" : "encrypted" )
.arg( dataStuff );
.arg( job.tid, 16, 16, QChar( '0' ) )
.arg( job.version ).arg( job.decrypt ? "decrypted" : "encrypted" )
.arg( dataStuff );
ui->plainTextEdit_log->appendHtml( str );
@ -92,44 +92,44 @@ void MainWindow::NusIsDone()
QTreeWidgetItem *item = ItemFromPath( "/title/00000001/00000002/data/setting.txt" );
if( !item )
{
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( "<b>Error creating setting.txt. maybe some folders are missing?</b>" );
}
else
{
if( !nand.SetData( r, ba) )
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
{
ShowMessage( "<b>Error writing data for setting.txt.</b>" );
quint16 r = nand.CreateEntry( "/title/00000001/00000002/data/setting.txt", 0x1000, 1, NAND_FILE, NAND_READ, NAND_READ, NAND_READ );
if( !r )
{
ShowMessage( "<b>Error creating setting.txt. maybe some folders are missing?</b>" );
}
else
{
if( !nand.SetData( r, ba) )
{
ShowMessage( "<b>Error writing data for setting.txt.</b>" );
}
else
{
nandDirty = true;
UpdateTree();
}
}
}
else
{
nandDirty = true;
UpdateTree();
}
}
}
}
//nand.Delete( "/title/00000001/00000002/content/title.tmd" );
if( nandDirty )
{
if( !FlushNand() )
{
ShowMessage( "<b>Error flushing nand. Maybe you used too much TP?</b>" );
}
nandDirty = false;
if( !FlushNand() )
{
ShowMessage( "<b>Error flushing nand. Maybe you used too much TP?</b>" );
}
nandDirty = false;
}
}
@ -142,7 +142,7 @@ void MainWindow::ReceiveTitleFromNus( NusJob job )
//do something with the data we got
if( InstallNUSItem( job ) )
nandDirty = true;
nandDirty = true;
}
@ -150,7 +150,7 @@ void MainWindow::ReceiveTitleFromNus( NusJob job )
void MainWindow::on_pushButton_GetTitle_clicked()
{
if( !nandInited && !InitNand( ui->lineEdit_nandPath->text() ) )
return;
return;
bool ok = false;
bool wholeUpdate = false;
@ -158,46 +158,46 @@ void MainWindow::on_pushButton_GetTitle_clicked()
quint32 ver = 0;
if( ui->lineEdit_tid->text().size() == 4 )
{
wholeUpdate = true;
wholeUpdate = true;
}
else
{
tid = ui->lineEdit_tid->text().toLongLong( &ok, 16 );
if( !ok )
{
ShowMessage( "<b>Error converting \"" + ui->lineEdit_tid->text() + "\" to a hex number.</b>" );
return;
}
ver = TITLE_LATEST_VERSION;
if( !ui->lineEdit_version->text().isEmpty() )
{
ver = ui->lineEdit_version->text().toInt( &ok, 10 );
if( !ok )
{
ShowMessage( "<b>Error converting \"" + ui->lineEdit_version->text() + "\" to a decimal number.</b>" );
return;
}
if( ver > 0xffff )
{
ShowMessage( tr( "<b>Version %1 is too high. Max is 65535</b>" ).arg( ver ) );
return;
}
}
tid = ui->lineEdit_tid->text().toLongLong( &ok, 16 );
if( !ok )
{
ShowMessage( "<b>Error converting \"" + ui->lineEdit_tid->text() + "\" to a hex number.</b>" );
return;
}
ver = TITLE_LATEST_VERSION;
if( !ui->lineEdit_version->text().isEmpty() )
{
ver = ui->lineEdit_version->text().toInt( &ok, 10 );
if( !ok )
{
ShowMessage( "<b>Error converting \"" + ui->lineEdit_version->text() + "\" to a decimal number.</b>" );
return;
}
if( ver > 0xffff )
{
ShowMessage( tr( "<b>Version %1 is too high. Max is 65535</b>" ).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( "<b>I dont know the titles that were in the %1 update</b>" ).arg( ui->lineEdit_tid->text() ) );
return;
}
if( !nus.GetUpdate( ui->lineEdit_tid->text(), decrypt ) )
{
ShowMessage( tr( "<b>I dont know the titles that were in the %1 update</b>" ).arg( ui->lineEdit_tid->text() ) );
return;
}
}
else
{
nus.Get( tid, decrypt, ver );
nus.Get( tid, decrypt, ver );
}
}
@ -206,7 +206,7 @@ void MainWindow::on_pushButton_nandPath_clicked()
{
QString f = QFileDialog::getOpenFileName( this, tr( "Select nand.bin" ) );
if( f.isEmpty() )
return;
return;
ui->lineEdit_nandPath->setText( f );
}
@ -215,7 +215,7 @@ void MainWindow::on_pushButton_CachePathBrowse_clicked()
{
QString f = QFileDialog::getExistingDirectory( this, tr( "Select NUS Cache base folder" ) );
if( f.isEmpty() )
return;
return;
ui->lineEdit_cachePath->setText( f );
nus.SetCachePath( ui->lineEdit_cachePath->text() );
@ -225,26 +225,26 @@ void MainWindow::on_pushButton_CachePathBrowse_clicked()
void MainWindow::on_actionSetting_txt_triggered()
{
if( !nandInited )
return;
return;
QTreeWidgetItem *it = ItemFromPath( "/title/00000001/00000002/data/setting.txt" );
if( !it )
{
ShowMessage( tr( "<b>There is no setting.txt found in %1</b>" )
.arg( QFileInfo( ui->lineEdit_nandPath->text() ).absoluteFilePath() ) );
return;
ShowMessage( tr( "<b>There is no setting.txt found in %1</b>" )
.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
nand.SetData( "/title/00000001/00000002/data/setting.txt", ba );
nand.SetData( "/title/00000001/00000002/data/setting.txt", ba );
}
//nand-dump -> flush
void MainWindow::on_actionFlush_triggered()
{
if( !nandInited )
FlushNand();
FlushNand();
}
//nand-dump -> ImportWad
@ -252,23 +252,23 @@ void MainWindow::on_actionImportWad_triggered()
{
if( !nandInited && !InitNand( ui->lineEdit_nandPath->text() ) )
{
ShowMessage( tr( "<b>Error setting the basepath of the nand to %1</b>" ).arg( QFileInfo( ui->lineEdit_nandPath->text() ).absoluteFilePath() ) );
return;
ShowMessage( tr( "<b>Error setting the basepath of the nand to %1</b>" ).arg( QFileInfo( ui->lineEdit_nandPath->text() ).absoluteFilePath() ) );
return;
}
QString fn = QFileDialog::getOpenFileName( this, tr("Wad files(*.wad)"), QCoreApplication::applicationDirPath(), tr("WadFiles (*.wad)") );
if( fn.isEmpty() )
return;
return;
QByteArray data = ReadFile( fn );
if( data.isEmpty() )
return;
return;
Wad wad( data );
if( !wad.IsOk() )
{
ShowMessage( tr( "Wad data not ok for \"%1\"" ).arg( fn ) );
return;
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
@ -282,7 +282,7 @@ void MainWindow::on_actionImportWad_triggered()
quint16 cnt = t.Count();
for( quint16 i = 0; i < cnt; i++ )
{
job.data << wad.Content( i );
job.data << wad.Content( i );
}
job.decrypt = true;
@ -295,7 +295,7 @@ void MainWindow::on_actionNew_nand_from_keys_triggered()
{
QString path = NewNandBin::GetNewNandPath( this );
if( path.isEmpty() )
return;
return;
InitNand( path );
ui->lineEdit_nandPath->setText( path );
}
@ -304,8 +304,8 @@ void MainWindow::on_pushButton_initNand_clicked()
{
if( ui->lineEdit_nandPath->text().isEmpty() )
{
ShowMessage( "<b>Please enter a path for nand.bin<\b>" );
return;
ShowMessage( "<b>Please enter a path for nand.bin<\b>" );
return;
}
InitNand( ui->lineEdit_nandPath->text() );
}
@ -316,45 +316,45 @@ bool MainWindow::InitNand( const QString &path )
sharedDirty = false;
nandDirty = false;
if( !nand.SetPath( path ) || !nand.InitNand() )
return false;
return false;
if( !UpdateTree() )
return false;
return false;
//setup the uid
QTreeWidgetItem *it = ItemFromPath( "/sys/uid.sys" );
if( !it )
{
uid.CreateNew( true );
if( !nand.CreateEntry( "/sys/uid.sys", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ) )
{
ShowMessage( "<b>Error creating new uid.sys</b>" );
return false;
}
uidDirty = true;
uid.CreateNew( true );
if( !nand.CreateEntry( "/sys/uid.sys", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ) )
{
ShowMessage( "<b>Error creating new uid.sys</b>" );
return false;
}
uidDirty = true;
}
else
{
QByteArray ba = nand.GetData( "/sys/uid.sys" );
uid = UIDmap( ba );
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( "<b>Error creating new content.map</b>" );
return false;
}
if( !nand.CreateEntry( "/shared1/content.map", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 ) )
{
sharedDirty = true;
ShowMessage( "<b>Error creating new content.map</b>" );
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( "<b>Something about the shared map isnt right, but im using it anyways</b>" );
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( "<b>Something about the shared map isnt right, but im using it anyways</b>" );
}
//nand.Delete( "/title/00000001/00000002/content/title.tmd" );
@ -372,15 +372,15 @@ bool MainWindow::FlushNand()
bool r1 = true;
bool r2 = true;
if( uidDirty && !nand.SetData( "/sys/uid.sys", uid.Data() ) )
r1 = false;
r1 = false;
else
uidDirty = false;
uidDirty = false;
if( sharedDirty && !nand.SetData( "/shared1/content.map", shared.Data() ) )
r2 = false;
r2 = false;
else
sharedDirty = false;
sharedDirty = false;
return ( nand.WriteMetaData() && r1 && r2 );
}
@ -390,11 +390,11 @@ QTreeWidgetItem *MainWindow::FindItem( const QString &s, QTreeWidgetItem *parent
int cnt = parent->childCount();
for( int i = 0; i <cnt; i++ )
{
QTreeWidgetItem *r = parent->child( i );
if( r->text( 0 ) == s )
{
return r;
}
QTreeWidgetItem *r = parent->child( i );
if( r->text( 0 ) == s )
{
return r;
}
}
return NULL;
}
@ -404,20 +404,20 @@ QTreeWidgetItem *MainWindow::ItemFromPath( const QString &path )
QTreeWidgetItem *item = root;
if( !path.startsWith( "/" ) || path.contains( "//" ))
{
return NULL;
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;
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;
}
@ -427,10 +427,10 @@ 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;
ret.prepend( "/" + item->text( 0 ) );
item = item->parent();
if( item->text( 0 ) == "/" )// dont add the root
break;
}
return ret;
}
@ -439,12 +439,12 @@ 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;
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;
ShowMessage( "The nand FS is seriously broken. I Couldn't even find a correct root" );
return false;
}
root = r->takeChild( 0 );
delete r;
@ -454,14 +454,14 @@ bool MainWindow::UpdateTree()
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;
// 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;
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();
@ -472,13 +472,13 @@ bool MainWindow::InstallSharedContent( const QByteArray stuff, const QByteArray
//qDebug() << "MainWindow::InstallSharedContent";
QByteArray h;
if( hash.isEmpty() )
h = GetSha1( stuff );
h = GetSha1( stuff );
else
h = hash;
h = hash;
QString cid = shared.GetAppFromHash( hash );
if( !cid.isEmpty() ) //this one is already installed
return true;
return true;
//qDebug() << "will create new";
//get next available cid in the shared map
@ -490,7 +490,7 @@ bool MainWindow::InstallSharedContent( const QByteArray stuff, const QByteArray
//create the file
quint16 r = CreateIfNeeded( "/shared1/" + cid + ".app", 0, 0, NAND_FILE, NAND_RW, NAND_RW, 0 );
if( !r )
return false;
return false;
//write the data to the file
return nand.SetData( r, stuff );
@ -509,9 +509,9 @@ bool MainWindow::InstallNUSItem( NusJob job )
QTreeWidgetItem *content;
if( !job.tid || !job.data.size() > 2 )
{
qWarning() << "bad sizes";
ShowMessage( "<b>Error installing title " + title + " to nand</b>" );
return false;
qWarning() << "bad sizes";
ShowMessage( "<b>Error installing title " + title + " to nand</b>" );
return false;
}
QString tid = QString( "%1" ).arg( job.tid, 16, 16, QChar( '0' ) );
QString upper = tid.left( 8 );
@ -521,19 +521,19 @@ bool MainWindow::InstallNUSItem( NusJob job )
Ticket ticket( job.data.takeFirst() );
if( t.Tid() != job.tid || ticket.Tid() != job.tid )
{
qWarning() << "bad tid";
goto error;
qWarning() << "bad tid";
goto error;
}
cnt = t.Count();
if( job.data.size() != cnt )
{
qWarning() << "content count";
goto error;
qWarning() << "content count";
goto error;
}
//qDebug() << "uidDirty" << uidDirty;
if( !uidDirty )
{
uidDirty = !uid.GetUid( job.tid, false );
uidDirty = !uid.GetUid( job.tid, false );
}
//qDebug() << "uidDirty" << uidDirty;
_uid = uid.GetUid( job.tid );
@ -541,8 +541,8 @@ bool MainWindow::InstallNUSItem( NusJob job )
_gid = t.Gid();
if( !_uid )
{
qWarning() << "no uid";
goto error;
qWarning() << "no uid";
goto error;
}
//create all the folders
@ -566,15 +566,15 @@ bool MainWindow::InstallNUSItem( NusJob job )
cnt = content->childCount();
for ( quint16 i = 0; i < cnt; i++ )
{
if( !nand.Delete( "/title/" + upper + "/" + lower + "/content/" + content->child( i )->text( 0 ) ) )
if( !nand.Delete( "/title/" + upper + "/" + lower + "/content/" + content->child( i )->text( 0 ) ) )
{ qWarning() << "error deleting old title"; goto error; }
deleted = true;
deleted = true;
}
if( deleted )
{
//nand.WriteMetaData();
UpdateTree();
ShowMessage( tr( "Deleted old TMD and private contents for<br>%1" ).arg( title ) );
//nand.WriteMetaData();
UpdateTree();
ShowMessage( tr( "Deleted old TMD and private contents for<br>%1" ).arg( title ) );
}
cnt = t.Count();
@ -597,64 +597,64 @@ bool MainWindow::InstallNUSItem( NusJob job )
//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 ) );
}
//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;
}
//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;
}
//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" );
@ -662,7 +662,7 @@ bool MainWindow::InstallNUSItem( NusJob job )
//nand.Delete( "/title/" + upper );
//UpdateTree();
return true;
error:
error:
ShowMessage( "<b>Error installing title " + title + " to nand</b>" );
return false;
}
@ -671,9 +671,9 @@ error:
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."
"<br><br>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"
"<br><br>YOU HAVE BEEN WARNED"
"<br>giantpune" );
"<br><br>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"
"<br><br>YOU HAVE BEEN WARNED"
"<br>giantpune" );
QMessageBox::critical( this, tr( "About" ), txt );
}

View File

@ -8,9 +8,9 @@ NewNandBin::NewNandBin( QWidget *parent, QList<quint16> badBlocks ) : QDialog(pa
ui->setupUi(this);
foreach( quint16 block, badBlocks )
{
QString txt = QString( "%1" ).arg( block );
if( !ui->listWidget_badBlocks->findItems( txt, Qt::MatchExactly ).isEmpty() )
ui->listWidget_badBlocks->addItem( txt );
QString txt = QString( "%1" ).arg( block );
if( !ui->listWidget_badBlocks->findItems( txt, Qt::MatchExactly ).isEmpty() )
ui->listWidget_badBlocks->addItem( txt );
}
}
@ -23,7 +23,7 @@ void NewNandBin::on_pushButton_keys_clicked()
{
QString f = QFileDialog::getOpenFileName( this, tr( "Select Keys.bin" ), dir );
if( f.isEmpty() )
return;
return;
ui->lineEdit_keys->setText( f );
dir = QFileInfo( f ).canonicalPath();
}
@ -32,7 +32,7 @@ void NewNandBin::on_pushButton_boot_clicked()
{
QString f = QFileDialog::getOpenFileName( this, tr( "Select Boot 1 & 2" ), dir );
if( f.isEmpty() )
return;
return;
ui->lineEdit_boot->setText( f );
dir = QFileInfo( f ).canonicalPath();
}
@ -41,7 +41,7 @@ void NewNandBin::on_pushButton_dest_clicked()
{
QString f = QFileDialog::getSaveFileName( this, tr( "Output file" ), dir );
if( f.isEmpty() )
return;
return;
ui->lineEdit_dest->setText( f );
dir = QFileInfo( f ).canonicalPath();
}
@ -52,10 +52,10 @@ QList<quint16> NewNandBin::BadBlocks()
quint16 cnt = ui->listWidget_badBlocks->count();
for( quint16 i = 0; i < cnt; i++ )
{
bool ok = false;
quint16 num = ui->listWidget_badBlocks->item( i )->text().toInt( &ok );
if( ok )
ret << num;
bool ok = false;
quint16 num = ui->listWidget_badBlocks->item( i )->text().toInt( &ok );
if( ok )
ret << num;
}
return ret;
}
@ -65,7 +65,7 @@ void NewNandBin::on_pushButton_bb_add_clicked()
quint16 val = ui->spinBox->value();
if( !BadBlocks().contains( val ) )
{
ui->listWidget_badBlocks->addItem( QString( "%1" ).arg( val ) );
ui->listWidget_badBlocks->addItem( QString( "%1" ).arg( val ) );
}
}
@ -74,8 +74,8 @@ void NewNandBin::on_pushButton_bb_rm_clicked()
QList<QListWidgetItem *> items = ui->listWidget_badBlocks->selectedItems();
foreach( QListWidgetItem *item, items )
{
ui->listWidget_badBlocks->removeItemWidget( item );
delete item;
ui->listWidget_badBlocks->removeItemWidget( item );
delete item;
}
}
@ -84,35 +84,48 @@ void NewNandBin::on_buttonBox_accepted()
{
if( ui->lineEdit_keys->text().isEmpty() || ui->lineEdit_boot->text().isEmpty() || ui->lineEdit_dest->text().isEmpty() )
{
QMessageBox::warning( this, tr( "Error" ), tr( "Required feilds are empty" ) );
return;
QMessageBox::warning( this, tr( "Error" ), tr( "Required feilds are empty" ) );
return;
}
QByteArray keys = ReadFile( ui->lineEdit_keys->text() );
QByteArray boots = ReadFile( ui->lineEdit_boot->text() );
if( keys.size() != 0x400 || boots.size() != 0x108000 )
{
QMessageBox::warning( this, tr( "Error" ), tr( "The keys or boot1/2 is not correct" ) );
return;
QMessageBox::warning( this, tr( "Error" ), tr( "The keys or boot1/2 is not correct" ) );
return;
}
if( !nand.CreateNew( ui->lineEdit_dest->text(), keys, boots, BadBlocks() ) )
{
qDebug() << "error creating nand.bin";
return;
qDebug() << "error creating nand.bin";
return;
}
//qDebug() << "created nand, trying to add default entries";
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 )
|| !nand.WriteMetaData() )
|| !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 )
|| !nand.WriteMetaData() )
{
qWarning() << "NewNandBin::on_buttonBox_accepted -> error creating the new nand";
return;
qWarning() << "NewNandBin::on_buttonBox_accepted -> error creating directories 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( "/sys/cert.sys", QByteArray( (const char*)&certs_dat, CERTS_DAT_SIZE ) ) )
{
qWarning() << "NewNandBin::on_buttonBox_accepted -> error creating cert in the new nand";
return;
}
//commit changes to metadata
if( !nand.WriteMetaData() )
{
qWarning() << "NewNandBin::on_buttonBox_accepted -> error writing metadata";
return;
}
ret = ui->lineEdit_dest->text();
}
@ -121,7 +134,7 @@ QString NewNandBin::GetNewNandPath( QWidget *parent, QList<quint16> badBlocks )
{
NewNandBin d( parent, badBlocks );
if( !d.exec() )
return QString();
return QString();
return d.ret;
}
@ -130,13 +143,13 @@ void NewNandBin::on_pushButton_badBlockFile_clicked()
{
QString f = QFileDialog::getOpenFileName( this, tr( "Select File with Bad Block List" ), dir );
if( f.isEmpty() )
return;
return;
dir = QFileInfo( f ).canonicalPath();
QString str = QString( ReadFile( f ) );
if( str.isEmpty() )
{
qWarning() << "NewNandBin::on_pushButton_badBlockFile_clicked -> error reading file";
return;
qWarning() << "NewNandBin::on_pushButton_badBlockFile_clicked -> error reading file";
return;
}
ui->listWidget_badBlocks->clear();
@ -144,17 +157,17 @@ void NewNandBin::on_pushButton_badBlockFile_clicked()
QStringList lines = str.split( "\n", QString::SkipEmptyParts );
foreach( QString line, lines )
{
if( line.size() > 5 )
continue;
bool ok = false;
if( line.size() > 5 )
continue;
bool ok = false;
if( ui->listWidget_badBlocks->findItems( line, Qt::MatchExactly ).size() )//this one is already in the list
continue;
if( ui->listWidget_badBlocks->findItems( line, Qt::MatchExactly ).size() )//this one is already in the list
continue;
quint16 bb = line.toInt( &ok );
if( !ok || bb < 8 || bb > 4079 )
continue;
quint16 bb = line.toInt( &ok );
if( !ok || bb < 8 || bb > 4079 )
continue;
ui->listWidget_badBlocks->addItem( line );
ui->listWidget_badBlocks->addItem( line );
}
}

View File

@ -1,4 +1,11 @@
this is a tool to list saves in an extracted nand dump
this is a tool to list/extract/delete saves to/from an extracted nand dump. saves are extracted, packed into a data.bin, zipped up with a txt file
that says a bit about the save, and then stored in a folder. you must designate a folder and provide the necessary keys before extracting saves
will work. i chose to go with data.bin as it is a way to store the entire save in 1 file and also allow the save to be installed using the
system menu. and i went with the zip format 1) to save space, and 2) to keep a save and the description all in 1 compact file.
saves are extracted to "<the folder you specify>/<TID upper>/<TID lower>/#.zip". the # is chosen by the lowest available number starting with 1.
there is no need for the saves to be in numerical order, if you have 10 zips for the same game and you decide to delete "3.zip", it will cause
no issues, and next time you extract a save for this game, it will use the filename "3.zip".
TODO:
add some sort of extract/delete/install functionality
installing saves back to sneek nand still isnt done