mirror of
https://github.com/martravi/wiiqt.git
synced 2024-11-16 14:19:21 +01:00
0e671a6764
* use unsigned int since HBC's TID wont fit in a signed 32 bit int * add key index fixing in the ticket class
964 lines
27 KiB
C++
964 lines
27 KiB
C++
#include "mainwindow.h"
|
|
#include "ui_mainwindow.h"
|
|
#include "ngdialog.h"
|
|
#include "textdialog.h"
|
|
|
|
#include "../WiiQt/tools.h"
|
|
#include "../WiiQt/savebanner.h"
|
|
#include "../WiiQt/savedatabin.h"
|
|
|
|
#include "quazip.h"
|
|
#include "quazipfile.h"
|
|
|
|
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), bannerthread( this )
|
|
{
|
|
ui->setupUi(this);
|
|
#ifdef Q_WS_MAC
|
|
ui->listWidget_sneekSaves->setFixedWidth( 630 );
|
|
ui->listWidget_pcSaves->setFixedWidth( 630 );
|
|
#endif
|
|
ClearSneekGuiInfo();
|
|
ClearPcGuiInfo();
|
|
progressBar.setVisible( false );
|
|
ui->statusBar->addPermanentWidget( &progressBar, 0 );
|
|
LoadSettings();
|
|
|
|
connect( &bannerthread, SIGNAL( SendProgress( int ) ), this, SLOT( GetProgressUpdate( int ) ) );
|
|
connect( &bannerthread, SIGNAL( SendDone( int ) ), this, SLOT( LoadThreadIsDone( int ) ) );
|
|
connect( &bannerthread, SIGNAL( SendSneekItem( QByteArray, const QString&, int ) ), this, SLOT( ReceiveSneekBanner( QByteArray, const QString&, int ) ) );
|
|
connect( &bannerthread, SIGNAL( SendPcItem( PcSaveInfo ) ), this, SLOT( ReceivePcItem( PcSaveInfo ) ) );
|
|
|
|
connect( &sneekIconTimer, SIGNAL( timeout() ), this, SLOT( ShowNextSneekIcon() ) );
|
|
connect( &pcIconTimer, SIGNAL( timeout() ), this, SLOT( ShowNextPcIcon() ) );
|
|
|
|
//start loading saves if there is a path
|
|
if( !sneekPath.isEmpty() )
|
|
{
|
|
initialStartup = true;
|
|
GetSavesFromSneek( sneekPath );
|
|
}
|
|
else if( !pcPath.isEmpty() )
|
|
GetSavesFromPC( pcPath );
|
|
}
|
|
|
|
MainWindow::~MainWindow()
|
|
{
|
|
SaveSettings();
|
|
delete ui;
|
|
}
|
|
|
|
//save/load settings
|
|
void MainWindow::SaveSettings()
|
|
{
|
|
QSettings s( QSettings::IniFormat, QSettings::UserScope, "WiiQt", "examples", this );
|
|
|
|
//settings specific to this program
|
|
s.beginGroup( "savetoy" );
|
|
//window geometry
|
|
s.setValue( "size", size() );
|
|
s.setValue( "pos", pos() );
|
|
|
|
//save backup location
|
|
s.setValue( "pcPath", pcPath );
|
|
|
|
//current view
|
|
s.setValue( "tab", ui->tabWidget->currentIndex() );
|
|
s.endGroup();
|
|
|
|
//settings shared in multiple programs
|
|
//keys
|
|
s.beginGroup( "keys" );
|
|
s.setValue( "ngID", ngID );
|
|
s.setValue( "ngKeyID", ngKeyID );
|
|
s.setValue( "ngSig", ngSig.toHex() );
|
|
s.setValue( "ngMac", ngMac.toHex() );
|
|
s.setValue( "ngPriv", ngPriv.toHex() );
|
|
s.endGroup();
|
|
|
|
//paths
|
|
s.beginGroup( "paths" );
|
|
s.setValue( "sneek", sneekPath );
|
|
s.endGroup();
|
|
}
|
|
|
|
void MainWindow::LoadSettings()
|
|
{
|
|
QSettings s( QSettings::IniFormat, QSettings::UserScope, "WiiQt", "examples", this );
|
|
|
|
//settings specific to this program
|
|
s.beginGroup( "savetoy" );
|
|
//window geometry
|
|
resize( s.value("size", QSize( 949, 535 ) ).toSize() );
|
|
move( s.value("pos", QPoint( 284, 295 ) ).toPoint() );
|
|
|
|
//save backup location
|
|
pcPath = s.value( "pcPath" ).toString();
|
|
ui->tabWidget->setCurrentIndex( s.value( "tab", 0 ).toInt() );
|
|
s.endGroup();
|
|
|
|
//settings shared in multiple programs
|
|
//keys
|
|
s.beginGroup( "keys" );
|
|
ngID = s.value( "ngID", 0 ).toInt();
|
|
ngKeyID = s.value( "ngKeyID", 0 ).toInt();
|
|
ngSig = QByteArray::fromHex( s.value( "ngSig", QByteArray() ).toString().toLatin1() );
|
|
ngMac = QByteArray::fromHex( s.value( "ngMac", QByteArray() ).toString().toLatin1() );
|
|
ngPriv = QByteArray::fromHex( s.value( "ngPriv", QByteArray() ).toString().toLatin1() );
|
|
s.endGroup();
|
|
|
|
//paths
|
|
s.beginGroup( "paths" );
|
|
sneekPath = s.value( "sneek" ).toString();
|
|
s.endGroup();
|
|
}
|
|
|
|
//get the saves from a nand directory
|
|
void MainWindow::GetSavesFromSneek( const QString &path )
|
|
{
|
|
//qDebug() << "MainWindow::GetSavesFromSneek" << path;
|
|
if( !QFileInfo( path ).exists() )
|
|
return;
|
|
|
|
if( bannerthread.isRunning() )
|
|
{
|
|
ui->statusBar->showMessage( tr( "Wait for the current job to finish" ) );
|
|
return;
|
|
}
|
|
sneekPath = path;
|
|
ui->listWidget_sneekSaves->clear();
|
|
progressBar.setValue( 0 );
|
|
progressBar.setVisible( true );
|
|
if( !bannerthread.SetNandPath( path ) )
|
|
{
|
|
qWarning() << "MainWindow::GetSavesFromSneek -> error sotteng path" << path;
|
|
return;
|
|
}
|
|
bannerthread.GetBanners();
|
|
}
|
|
|
|
void MainWindow::GetSavesFromPC( const QString &path )
|
|
{
|
|
pcPath = path;
|
|
if( !QFileInfo( path ).exists() )
|
|
return;
|
|
|
|
if( bannerthread.isRunning() )
|
|
{
|
|
ui->statusBar->showMessage( tr( "Wait for the current job to finish" ) );
|
|
return;
|
|
}
|
|
//remove all currently loaded saves
|
|
ui->listWidget_pcSaves->clear();
|
|
pcInfos.clear();
|
|
progressBar.setValue( 0 );
|
|
progressBar.setVisible( true );
|
|
bannerthread.GetBanners( pcPath );
|
|
}
|
|
|
|
void MainWindow::ReceivePcItem( PcSaveInfo info )
|
|
{
|
|
//qDebug() << "received a pc save";
|
|
if( !info.sizes.size() || info.sizes.size() != info.descriptions.size() || info.sizes.size() != info.paths.size() )//invalid
|
|
return;
|
|
SaveBanner sb( info.banner );
|
|
new SaveListItem( sb, info.tid, 0, ui->listWidget_pcSaves );
|
|
pcInfos << info;
|
|
}
|
|
|
|
//sneek save item changed
|
|
void MainWindow::on_listWidget_sneekSaves_currentItemChanged( QListWidgetItem* current, QListWidgetItem* previous )
|
|
{
|
|
Q_UNUSED( previous );
|
|
if( !current )
|
|
{
|
|
ClearSneekGuiInfo();
|
|
return;
|
|
}
|
|
|
|
SaveListItem *i = static_cast< SaveListItem * >( current );
|
|
ShowSneekSaveDetails( i );
|
|
}
|
|
|
|
//get an item from the thread loading all the data and turn it into a banner
|
|
void MainWindow::ReceiveSneekBanner( QByteArray stuff, const QString& tid, int size )
|
|
{
|
|
QByteArray copy = stuff;
|
|
SaveBanner sb( copy );
|
|
new SaveListItem( sb, tid, size, ui->listWidget_sneekSaves );
|
|
}
|
|
|
|
//get a pregress update from something that is doing work
|
|
void MainWindow::GetProgressUpdate( int i )
|
|
{
|
|
progressBar.setValue( i );
|
|
}
|
|
|
|
//something is done working. respond somehow
|
|
void MainWindow::LoadThreadIsDone( int type )
|
|
{
|
|
switch( type )
|
|
{
|
|
case LOAD_SNEEK:
|
|
if( initialStartup )
|
|
{
|
|
initialStartup = false;
|
|
if( !pcPath.isEmpty() )
|
|
GetSavesFromPC( pcPath );
|
|
}
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
progressBar.setVisible( false );
|
|
}
|
|
|
|
//tools -> set sneek path
|
|
void MainWindow::on_actionSet_Sneek_Path_triggered()
|
|
{
|
|
QString p = QFileDialog::getExistingDirectory( this, tr( "Select SNEEK root" ), "/media" );
|
|
if( p.isEmpty() )
|
|
return;
|
|
|
|
ui->listWidget_sneekSaves->clear();
|
|
GetSavesFromSneek( p );
|
|
}
|
|
|
|
//show the details for a save in the sneek nand
|
|
void MainWindow::ShowSneekSaveDetails( SaveListItem *item )
|
|
{
|
|
sneekIconTimer.stop();
|
|
currentSneekIcon = 0;
|
|
sneekIcon.clear();
|
|
|
|
SaveBanner *sb = item->Banner();
|
|
|
|
ui->label_sneek_title->setText( sb->Title() );
|
|
|
|
if( !sb->SubTitle().isEmpty() && sb->Title() != sb->SubTitle() )
|
|
ui->label_sneek_title2->setText( sb->SubTitle() );
|
|
else
|
|
ui->label_sneek_title2->clear();
|
|
|
|
QString tid = item->Tid();
|
|
tid.insert( 8, "/" );
|
|
tid.prepend( "/" );
|
|
ui->label_sneek_path->setText( tid );
|
|
|
|
QString id;
|
|
tid = item->Tid().right( 8 );
|
|
quint32 num = qFromBigEndian( (quint32) tid.toInt( NULL, 16 ) );
|
|
for( int i = 0; i < 4; i++ )
|
|
id += ascii( (char)( num >> ( 8 * i ) ) & 0xff );
|
|
|
|
ui->label_sneek_id->setText( id );
|
|
int size = item->Size();
|
|
QString sizeStr;
|
|
if( size < 0x400 )
|
|
sizeStr = tr( "%1 B" ).arg( size, 3 );
|
|
else if( size < 0x100000 )
|
|
{
|
|
float kib = (float)size / 1024.00f;
|
|
sizeStr = tr( "%1 KiB" ).arg( kib, 3, 'f', 2 );
|
|
}
|
|
else//assume there wont be any 1GB saves
|
|
{
|
|
float mib = (float)size / 1048576.00f;
|
|
sizeStr = tr( "%1 MiB" ).arg( mib, 3, 'f', 2 );
|
|
}
|
|
int blocks = RU( size, 0x20000 ) / 0x20000;
|
|
QString si = QString( "%1 %2 (%3)").arg( blocks ).arg( blocks == 1 ? tr( "Block" ) : tr( "Blocks" ) ).arg( sizeStr );
|
|
|
|
ui->label_sneek_size->setText( si );
|
|
|
|
foreach( QImage im, sb->IconImgs() )
|
|
sneekIcon << QPixmap::fromImage( im );
|
|
|
|
currentSneekIcon = 0;
|
|
ui->label_sneek_icon->setPixmap( sneekIcon.at( 0 ) );
|
|
if( sneekIcon.size() > 1 )
|
|
{
|
|
sneekIconTimer.setInterval( 1000 / sneekIcon.size() );//delay of icon image animation
|
|
sneekIconTimer.start();
|
|
}
|
|
}
|
|
|
|
//slots for cycling animated icons
|
|
void MainWindow::ShowNextSneekIcon()
|
|
{
|
|
if( ++currentSneekIcon >= sneekIcon.size() )
|
|
currentSneekIcon = 0;
|
|
|
|
ui->label_sneek_icon->setPixmap( sneekIcon.at( currentSneekIcon ) );
|
|
}
|
|
|
|
void MainWindow::ShowNextPcIcon()
|
|
{
|
|
if( ++currentPcIcon >= pcIcon.size() )
|
|
currentPcIcon = 0;
|
|
|
|
ui->label_PC_icon->setPixmap( pcIcon.at( currentPcIcon ) );
|
|
}
|
|
|
|
//clicked button to delete save from sneek
|
|
void MainWindow::on_pushButton_sneekDelete_clicked()
|
|
{
|
|
QList<QListWidgetItem*>selected = ui->listWidget_sneekSaves->selectedItems();
|
|
quint16 cnt = selected.size();
|
|
if( !cnt )
|
|
return;
|
|
|
|
if( QMessageBox::question( this, tr( "Are you sure?" ), \
|
|
tr( "You are about to delete %1 %2 from the SNEEK nand" ).arg( cnt )\
|
|
.arg( cnt == 1 ? tr( "save" ) : tr( "saves" ) ),
|
|
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel ) != QMessageBox::Ok )
|
|
return;
|
|
|
|
quint16 del = 0;
|
|
for( quint16 i = 0; i < cnt; i++ )
|
|
{
|
|
QListWidgetItem* item = selected.takeFirst();
|
|
SaveListItem *si = static_cast< SaveListItem * >( item );
|
|
if( !si )
|
|
{
|
|
qDebug() << "MainWindow::on_pushButton_sneekDelete_clicked() -> error casting" << item->text();
|
|
delete item;
|
|
continue;
|
|
}
|
|
|
|
bool ok = false;
|
|
quint64 tid = si->Tid().toLongLong( &ok, 16 );
|
|
if( !ok )
|
|
{
|
|
qDebug() << "MainWindow::on_pushButton_sneekDelete_clicked() -> error converting" << si->Tid() << "to int";
|
|
delete item;
|
|
continue;
|
|
}
|
|
delete item;
|
|
if( !bannerthread.DeleteSaveFromSneekNand( tid ) )
|
|
qDebug() << "MainWindow::on_pushButton_sneekDelete_clicked() -> error deleting" << tid;
|
|
|
|
else
|
|
del++;
|
|
|
|
}
|
|
|
|
ui->statusBar->showMessage( tr( "Deleted %1 of %2 saves" ).arg( del ).arg( cnt ), 5000 );
|
|
}
|
|
|
|
QString MainWindow::GetSaveName( quint64 tid )
|
|
{
|
|
if( pcPath.isEmpty() )
|
|
return QString();
|
|
|
|
QString tidStr = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
|
|
tidStr.insert( 8, "/" );
|
|
|
|
QString parent = pcPath + "/" + tidStr;
|
|
QFileInfo fi( parent );
|
|
if( fi.exists() && fi.isFile() )
|
|
{
|
|
qWarning() << "MainWindow::GetSaveName ->" << fi.absoluteFilePath() << "is a file";
|
|
return QString();
|
|
}
|
|
if( !fi.exists() && !QDir().mkpath( fi.absoluteFilePath() ) )
|
|
{
|
|
qWarning() << "MainWindow::GetSaveName -> error creating" << fi.absoluteFilePath();
|
|
return QString();
|
|
}
|
|
quint32 i = 1;
|
|
QString name = QString( "1.zip" );
|
|
QDir dir( fi.absoluteFilePath() );
|
|
while( dir.exists( name ) )
|
|
{
|
|
name = QString( "%1.zip" ).arg( ++i );
|
|
}
|
|
return fi.absoluteFilePath() + "/" + name;
|
|
}
|
|
|
|
//quick sanity checks on the NG stuff
|
|
bool MainWindow::NG_Ok()
|
|
{
|
|
return ( ngID && ngKeyID && ngSig.size() == 60 && ngMac.size() == 6 && ngPriv.size() == 30 );
|
|
}
|
|
|
|
//button to extract a save from sneek clicked
|
|
void MainWindow::on_pushButton_sneekExtract_clicked()
|
|
{
|
|
if( pcPath.isEmpty() )
|
|
{
|
|
ui->statusBar->showMessage( tr( "Set a path to extract saves first" ) );
|
|
return;
|
|
}
|
|
QList<QListWidgetItem*>selected = ui->listWidget_sneekSaves->selectedItems();
|
|
quint16 cnt = selected.size();
|
|
if( !cnt )
|
|
{
|
|
ui->statusBar->showMessage( tr( "No saves are selected to extract" ) );
|
|
return;
|
|
}
|
|
if( !NG_Ok() )
|
|
{
|
|
on_actionSet_NG_Keys_triggered();
|
|
if( !NG_Ok() )
|
|
{
|
|
qDebug() << "invalid keys. can't create data.bins";
|
|
ui->statusBar->showMessage( tr( "I Need valid keys to extract saves" ) );
|
|
return;
|
|
}
|
|
}
|
|
|
|
bool promptForDetails = true;
|
|
//keep from prompting for details for multiple saves
|
|
if( cnt > 1 && QMessageBox::question( this, tr( "Enter Details?" ), \
|
|
tr( "You are about to extract %1 saves from the SNEEK nand.<br>Do you want to enter details for each of them?" )
|
|
.arg( cnt ), QMessageBox::No | QMessageBox::Yes, QMessageBox::No ) == QMessageBox::No )
|
|
promptForDetails = false;
|
|
|
|
quint16 done = 0;
|
|
for( quint16 i = 0; i < cnt; i++ )
|
|
{
|
|
QListWidgetItem* item = selected.at( i );
|
|
SaveListItem *si = static_cast< SaveListItem * >( item );
|
|
if( !si )
|
|
{
|
|
qDebug() << "MainWindow::on_pushButton_sneekExtract_clicked() -> error casting" << item->text();
|
|
delete item;
|
|
continue;
|
|
}
|
|
|
|
bool ok = false;
|
|
quint64 tid = si->Tid().toLongLong( &ok, 16 );
|
|
if( !ok )
|
|
{
|
|
qDebug() << "MainWindow::on_pushButton_sneekExtract_clicked() -> error converting" << si->Tid() << "to int";
|
|
continue;
|
|
}
|
|
|
|
//get a save destination
|
|
QString fn = GetSaveName( tid );
|
|
if( fn.isEmpty() )
|
|
continue;
|
|
|
|
//extract the save
|
|
SaveGame sg = bannerthread.GetSave( tid );
|
|
if( !IsValidSave( sg ) )
|
|
{
|
|
ui->statusBar->showMessage( tr( "Error extracting save for %1").arg( tid, 16, 16, QChar( '0' ) ) );
|
|
qDebug() << "MainWindow::on_pushButton_sneekExtract_clicked() -> invalid save" << QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
|
|
continue;
|
|
}
|
|
|
|
//convert to data.bin
|
|
QByteArray ba = SaveDataBin::DataBinFromSaveStruct( sg, ngPriv, ngSig, ngMac, ngID, ngKeyID );
|
|
if( ba.isEmpty() )
|
|
{
|
|
ui->statusBar->showMessage( tr( "Error encoding save for %1").arg( tid, 16, 16, QChar( '0' ) ) );
|
|
qDebug() << "MainWindow::on_pushButton_sneekExtract_clicked() -> error converting" << QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
|
|
continue;
|
|
}
|
|
|
|
QString fullDesc = QString( "SaveInfo\nversion=%1\ntid=%2\n" ).arg( DESC_VERSION ).arg( tid, 16, 16, QChar( '0' ) );
|
|
QDate date = QDate::currentDate();
|
|
QTime time = QTime::currentTime();
|
|
QString desc = QString( "title=%1\ndate=%2\ntime=%3\n" ).arg( si->Banner()->Title() ).arg( date.toString() ).arg( time.toString() );
|
|
|
|
if( promptForDetails )
|
|
{
|
|
fullDesc += "desc=\n" + TextDialog::GetText( this, desc ) + "\nend_desc\n";
|
|
}
|
|
else
|
|
{
|
|
fullDesc += "desc=\n" + desc + "\nend_desc\n";
|
|
}
|
|
//qDebug() << "description:\n" << fullDesc;
|
|
|
|
//write to file
|
|
if( !WriteZipFile( ba, fullDesc.toLatin1(), fn ) )
|
|
{
|
|
ui->statusBar->showMessage( tr( "Error writing save for %1").arg( tid, 16, 16, QChar( '0' ) ) );
|
|
qDebug() << "MainWindow::on_pushButton_sneekExtract_clicked() -> error writing" << QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
|
|
continue;
|
|
}
|
|
//item extracted ok, now add the info to the tab displaying the PC
|
|
SaveBanner banner = *si->Banner();
|
|
AddNewPCSave( fullDesc, si->Tid(), si->Size(), fn, banner );
|
|
done++;
|
|
|
|
}
|
|
|
|
ui->statusBar->showMessage( tr( "Extracted %1 of %2 saves" ).arg( done ).arg( cnt ), 5000 );
|
|
}
|
|
|
|
void MainWindow::AddNewPCSave( const QString &desc, const QString &tid, quint32 size, const QString &path, SaveBanner banner )
|
|
{
|
|
//look to see if there is already some pc saves from the same game
|
|
quint32 cnt = pcInfos.size();
|
|
for( quint32 i = 0; i < cnt; i++ )
|
|
{
|
|
if( tid == pcInfos.at( i ).tid )
|
|
{
|
|
PcSaveInfo info = pcInfos.at( i );
|
|
info.sizes << size;
|
|
info.descriptions << desc;
|
|
info.paths << path;
|
|
|
|
pcInfos.replace( i, info );
|
|
if( currentPcSave == i )
|
|
{
|
|
QString version = path;
|
|
version.remove( 0, version.lastIndexOf( "/" ) + 1 );
|
|
ui->comboBox_pcSelect->addItem( version );
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
//not found, create a new one
|
|
PcSaveInfo info;
|
|
info.tid = tid;
|
|
info.sizes << size;
|
|
info.descriptions << desc;
|
|
info.paths << path;
|
|
|
|
pcInfos << info;
|
|
new SaveListItem( banner, tid, 0, ui->listWidget_pcSaves );
|
|
}
|
|
|
|
//try to write a zip file with a data.bin and a descriptive text file
|
|
bool MainWindow::WriteZipFile( const QByteArray &dataBin, const QByteArray &desc, const QString &path )
|
|
{
|
|
qDebug() << "MainWindow::WriteZipFile" << path;
|
|
QuaZip zip( path );
|
|
if( !zip.open(QuaZip::mdCreate ) )
|
|
{
|
|
qWarning( "error creating zip file: %d", zip.getZipError() );
|
|
return false;
|
|
}
|
|
zip.setComment( "Created with giantpune's saveToy" );
|
|
QuaZipFile outFile( &zip );
|
|
for( quint8 i = 0; i < 2; i++ )
|
|
{
|
|
QString fn;
|
|
QByteArray stuff;
|
|
if( i == 0 )
|
|
{
|
|
stuff = dataBin;
|
|
fn = "data.bin";
|
|
}
|
|
else
|
|
{
|
|
stuff = desc;
|
|
fn = "info.txt";
|
|
}
|
|
|
|
if( !outFile.open( QIODevice::WriteOnly, QuaZipNewInfo( fn, fn ) ) )
|
|
{
|
|
qWarning("MainWindow::WriteZipFile: outFile.open(): %d", outFile.getZipError() );
|
|
goto error;
|
|
}
|
|
if( outFile.write( stuff ) != stuff.size() )
|
|
{
|
|
qWarning() << "MainWindow::WriteZipFile: wrong size written to zip file";
|
|
goto error;
|
|
}
|
|
outFile.close();
|
|
if( outFile.getZipError() !=UNZ_OK )
|
|
{
|
|
qWarning("MainWindow::WriteZipFile: outFile.close(): %d", outFile.getZipError() );
|
|
goto error;
|
|
}
|
|
}
|
|
zip.close();
|
|
if( zip.getZipError() != 0 )
|
|
{
|
|
qWarning("MainWindow::WriteZipFile: zip.close(): %d", zip.getZipError());
|
|
goto error;
|
|
}
|
|
return true;
|
|
|
|
error:
|
|
zip.close();
|
|
if( QFile::exists( path ) )
|
|
QFile::remove( path );
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
//tools -> set ng keys
|
|
void MainWindow::on_actionSet_NG_Keys_triggered()
|
|
{
|
|
qDebug() << hex << ngID;
|
|
NgDialog d( this );
|
|
|
|
d.ngID = ngID;
|
|
d.ngKeyID = ngKeyID;
|
|
d.ngMac = ngMac;
|
|
d.ngPriv = ngPriv;
|
|
d.ngSig = ngSig;
|
|
|
|
if( !d.exec() )
|
|
{
|
|
//qDebug() << "not accepted";
|
|
return;
|
|
}
|
|
ngID = d.ngID;
|
|
ngKeyID = d.ngKeyID;
|
|
ngMac = d.ngMac;
|
|
ngPriv = d.ngPriv;
|
|
ngSig = d.ngSig;
|
|
qDebug() << "accepted";
|
|
//qDebug() << hex << d.ngID
|
|
// << "\n" << d.ngKeyID
|
|
// << "\n" << d.ngMac.toHex()
|
|
// << "\n" << d.ngPriv.toHex()
|
|
// << "\n" << d.ngSig.toHex();
|
|
}
|
|
|
|
//PC list item changed
|
|
void MainWindow::on_listWidget_pcSaves_currentItemChanged( QListWidgetItem* current, QListWidgetItem* previous )
|
|
{
|
|
Q_UNUSED( previous );
|
|
if( !current )
|
|
{
|
|
ClearPcGuiInfo();
|
|
return;
|
|
}
|
|
|
|
SaveListItem *i = static_cast< SaveListItem * >( current );
|
|
ShowPCSaveDetails( i );
|
|
}
|
|
|
|
//show detials for a save backed up on the PC
|
|
void MainWindow::ShowPCSaveDetails( SaveListItem *item )
|
|
{
|
|
//qDebug() << "MainWindow::ShowPCSaveDetails";
|
|
pcIconTimer.stop();
|
|
currentPcIcon = 0;
|
|
pcIcon.clear();
|
|
ui->comboBox_pcSelect->clear();
|
|
//find the item in the list of infos that matches this item
|
|
currentPcSave = 0xffffffff;
|
|
quint32 cnt = pcInfos.size();
|
|
for( quint32 i = 0; i < cnt; i++ )
|
|
{
|
|
if( item->Tid() == pcInfos.at( i ).tid )
|
|
{
|
|
currentPcSave = i;
|
|
break;
|
|
}
|
|
}
|
|
if( currentPcSave == 0xffffffff )
|
|
{
|
|
qWarning() << "MainWindow::ShowPCSaveDetails -> tid not found";
|
|
return;
|
|
}
|
|
|
|
SaveBanner *sb = item->Banner();
|
|
|
|
ui->label_pc_title->setText( sb->Title() );
|
|
|
|
if( !sb->SubTitle().isEmpty() && sb->Title() != sb->SubTitle() )
|
|
ui->label_pc_title2->setText( sb->SubTitle() );
|
|
else
|
|
ui->label_pc_title2->clear();
|
|
|
|
QString tid = item->Tid();
|
|
tid.insert( 8, "/" );
|
|
tid.prepend( "/" );
|
|
//ui->label_sneek_path->setText( tid );
|
|
|
|
QString id;
|
|
tid = item->Tid().right( 8 );
|
|
quint32 num = qFromBigEndian( (quint32) tid.toInt( NULL, 16 ) );
|
|
for( int i = 0; i < 4; i++ )
|
|
id += ascii( (char)( num >> ( 8 * i ) ) & 0xff );
|
|
|
|
ui->label_pc_id->setText( id );
|
|
|
|
foreach( QImage im, sb->IconImgs() )
|
|
pcIcon << QPixmap::fromImage( im );
|
|
|
|
currentPcIcon = 0;
|
|
ui->label_PC_icon->setPixmap( pcIcon.at( 0 ) );
|
|
if( pcIcon.size() > 1 )
|
|
{
|
|
pcIconTimer.setInterval( 1000 / pcIcon.size() );//delay of icon image animation
|
|
pcIconTimer.start();
|
|
}
|
|
|
|
//add combobox entries for each of the different saves for this game
|
|
cnt = pcInfos.at( currentPcSave ).sizes.size();
|
|
for( quint32 i = 0; i < cnt; i++ )
|
|
{
|
|
QString version = pcInfos.at( currentPcSave ).paths.at( i );
|
|
version.remove( 0, version.lastIndexOf( "/" ) + 1 );
|
|
ui->comboBox_pcSelect->addItem( version );
|
|
}
|
|
}
|
|
|
|
//pc combobox index changed
|
|
void MainWindow::on_comboBox_pcSelect_currentIndexChanged( int index )
|
|
{
|
|
//qDebug() << "MainWindow::on_comboBox_pcSelect_currentIndexChanged" << index;
|
|
if( index < 0 )
|
|
return;
|
|
ui->plainTextEdit_pcDesc->clear();
|
|
if( currentPcSave >= (quint32)pcInfos.size() || index >= pcInfos.at( currentPcSave ).sizes.size() )
|
|
{
|
|
qWarning() << "MainWindow::on_comboBox_pc_date_currentIndexChanged -> index is out of range";
|
|
return;
|
|
}
|
|
|
|
int size = pcInfos.at( currentPcSave ).sizes.at( index );
|
|
QString sizeStr;
|
|
if( size < 0x400 )
|
|
sizeStr = tr( "%1 B" ).arg( size, 3 );
|
|
else if( size < 0x100000 )
|
|
{
|
|
float kib = (float)size / 1024.00f;
|
|
sizeStr = tr( "%1 KiB" ).arg( kib, 3, 'f', 2 );
|
|
}
|
|
else//assume there wont be any 1GB saves
|
|
{
|
|
float mib = (float)size / 1048576.00f;
|
|
sizeStr = tr( "%1 MiB" ).arg( mib, 3, 'f', 2 );
|
|
}
|
|
int blocks = RU( size, 0x20000 ) / 0x20000;
|
|
QString si = QString( "%1 %2 (%3)").arg( blocks ).arg( blocks == 1 ? tr( "Block" ) : tr( "Blocks" ) ).arg( sizeStr );
|
|
|
|
ui->label_pc_size->setText( si );
|
|
QString path = pcInfos.at( currentPcSave ).paths.at( index );
|
|
if( path.size() >= 30 )
|
|
{
|
|
path = path.right( 27 );
|
|
path.prepend( "..." );
|
|
}
|
|
ui->label_pc_path->setText( path );
|
|
ui->plainTextEdit_pcDesc->clear();
|
|
ui->plainTextEdit_pcDesc->insertPlainText( pcInfos.at( currentPcSave ).descriptions.at( index ) );
|
|
}
|
|
|
|
//delete PC save button clicked
|
|
void MainWindow::on_pushButton_pcDelete_clicked()
|
|
{
|
|
QList<QListWidgetItem*>selected = ui->listWidget_pcSaves->selectedItems();
|
|
quint32 cnt = selected.size();
|
|
if( cnt != 1 )
|
|
return;
|
|
|
|
SaveListItem *si = static_cast< SaveListItem * >( selected.at( 0 ) );
|
|
|
|
int i = ui->comboBox_pcSelect->currentIndex();
|
|
|
|
//find the item in the list of infos that matches this item
|
|
currentPcSave = 0xffffffff;
|
|
cnt = pcInfos.size();
|
|
for( quint32 i = 0; i < cnt; i++ )
|
|
{
|
|
if( si->Tid() == pcInfos.at( i ).tid )
|
|
{
|
|
currentPcSave = i;
|
|
break;
|
|
}
|
|
}
|
|
if( currentPcSave == 0xffffffff )
|
|
{
|
|
qWarning() << "MainWindow::on_pushButton_pcDelete_clicked() -> tid not found";
|
|
return;
|
|
}
|
|
|
|
if( QMessageBox::question( this, tr( "Are you sure?" ), \
|
|
tr( "You are about to delete the backed up save<br>\"%1\"?" ).arg( pcInfos.at( currentPcSave ).paths.at( i ) ),
|
|
QMessageBox::Ok | QMessageBox::Cancel, QMessageBox::Cancel ) != QMessageBox::Ok )
|
|
return;
|
|
|
|
//delete the file
|
|
if( !QFile::remove( pcInfos.at( currentPcSave ).paths.at( ui->comboBox_pcSelect->currentIndex() ) ) )
|
|
{
|
|
ui->statusBar->showMessage( tr( "Error deleting file" ), 5000 );
|
|
return;
|
|
}
|
|
if( pcInfos.at( currentPcSave ).sizes.size() == 1 )//there are no more saves for this game
|
|
{
|
|
pcInfos.takeAt( currentPcSave ); //remove it entirely from the list of knowns
|
|
si = static_cast< SaveListItem * >( selected.takeFirst() );//delete its banner from the list
|
|
delete si;
|
|
}
|
|
else //theres more saves for this game on the PC, just remove the selected one
|
|
{
|
|
//remove entries for this save from the list
|
|
pcInfos[ currentPcSave ].descriptions.takeAt( i );
|
|
pcInfos[ currentPcSave ].paths.takeAt( i );
|
|
pcInfos[ currentPcSave ].sizes.takeAt( i );
|
|
ui->comboBox_pcSelect->removeItem( i ); //remove it from the combobox last
|
|
}
|
|
ui->statusBar->showMessage( tr( "Deleted 1 save from the PC" ), 5000 );
|
|
|
|
}
|
|
|
|
void MainWindow::on_actionSet_Local_Path_triggered()
|
|
{
|
|
QString p = QFileDialog::getExistingDirectory( this, tr( "Select Save Backup path" ), pcPath );
|
|
if( p.isEmpty() )
|
|
return;
|
|
GetSavesFromPC( p );
|
|
}
|
|
|
|
void MainWindow::ClearSneekGuiInfo()
|
|
{
|
|
ui->label_sneek_icon->clear();
|
|
ui->label_sneek_id->clear();
|
|
ui->label_sneek_path->clear();
|
|
ui->label_sneek_size->clear();
|
|
ui->label_sneek_title->clear();
|
|
ui->label_sneek_title2->clear();
|
|
}
|
|
|
|
void MainWindow::ClearPcGuiInfo()
|
|
{
|
|
ui->label_PC_icon->clear();
|
|
ui->label_pc_id->clear();
|
|
ui->label_pc_path->clear();
|
|
ui->label_pc_size->clear();
|
|
ui->label_pc_title->clear();
|
|
ui->label_pc_title2->clear();
|
|
ui->plainTextEdit_pcDesc->clear();
|
|
ui->comboBox_pcSelect->clear();
|
|
}
|
|
|
|
//button to install PC save clicked
|
|
void MainWindow::on_pushButton_pcInstall_clicked()
|
|
{
|
|
QString nPath = bannerthread.NandBasePath();
|
|
if( nPath.isEmpty() )
|
|
{
|
|
return;
|
|
}
|
|
QList<QListWidgetItem*>selected = ui->listWidget_pcSaves->selectedItems();
|
|
quint32 cnt = selected.size();
|
|
if( cnt != 1 )
|
|
return;
|
|
|
|
SaveListItem *si = static_cast< SaveListItem * >( selected.at( 0 ) );
|
|
|
|
int i = ui->comboBox_pcSelect->currentIndex();
|
|
|
|
//find the item in the list of infos that matches this item
|
|
currentPcSave = 0xffffffff;
|
|
cnt = pcInfos.size();
|
|
for( quint32 i = 0; i < cnt; i++ )
|
|
{
|
|
if( si->Tid() == pcInfos.at( i ).tid )
|
|
{
|
|
currentPcSave = i;
|
|
break;
|
|
}
|
|
}
|
|
if( currentPcSave == 0xffffffff )
|
|
{
|
|
qWarning() << "MainWindow::on_pushButton_pcInstall_clicked() -> tid not found";
|
|
return;
|
|
}
|
|
if( i < 0 || i >= pcInfos.at( currentPcSave ).sizes.size() )
|
|
{
|
|
qWarning() << "MainWindow::on_pushButton_pcInstall_clicked() -> index is out of range";
|
|
return;
|
|
}
|
|
|
|
//read datad.bin from the zip file
|
|
QString zipPath = pcInfos.at( currentPcSave ).paths.at( i );
|
|
QByteArray dataBin;
|
|
QuaZip zip( zipPath );
|
|
if( !zip.open( QuaZip::mdUnzip ) )
|
|
{
|
|
qWarning("on_pushButton_pcInstall_clicked(): zip.open(): %d", zip.getZipError() );
|
|
return;
|
|
}
|
|
zip.setFileNameCodec("IBM866");
|
|
QuaZipFile file(&zip);
|
|
QString name;
|
|
for( bool more = zip.goToFirstFile(); more; more = zip.goToNextFile() )
|
|
{
|
|
if( !file.open( QIODevice::ReadOnly ) )
|
|
{
|
|
qWarning("on_pushButton_pcInstall_clicked(): file.open(): %d", file.getZipError() );
|
|
zip.close();
|
|
return;
|
|
}
|
|
name = file.getActualFileName();
|
|
if( file.getZipError() != UNZ_OK )
|
|
{
|
|
qWarning("on_pushButton_pcInstall_clicked(): file.getFileName(): %d", file.getZipError());
|
|
file.close();
|
|
zip.close();
|
|
return;
|
|
}
|
|
if( name != "data.bin" )
|
|
{
|
|
file.close();
|
|
continue;
|
|
}
|
|
dataBin = file.readAll();
|
|
if( file.getZipError() != UNZ_OK )
|
|
{
|
|
qWarning("on_pushButton_pcInstall_clicked(): file.getFileName(): %d", file.getZipError());
|
|
file.close();
|
|
zip.close();
|
|
return;
|
|
}
|
|
file.close();
|
|
break;
|
|
}
|
|
zip.close();
|
|
if( dataBin.isEmpty() )
|
|
{
|
|
qWarning() << "no data.bin found in the archive" << zipPath;
|
|
return;
|
|
}
|
|
SaveGame save = SaveDataBin::StructFromDataBin( dataBin );
|
|
if( !IsValidSave( save ) )
|
|
{
|
|
qWarning() << "got an invalid save from the data.bin in" << zipPath;
|
|
return;
|
|
}
|
|
|
|
//see if the sneek nand and already has a save for this game
|
|
cnt = ui->listWidget_sneekSaves->count();
|
|
for( quint32 i = 0; i < cnt; i++ )
|
|
{
|
|
QListWidgetItem* item = ui->listWidget_sneekSaves->item( i );
|
|
si = static_cast< SaveListItem * >( item );
|
|
bool ok = false;
|
|
quint64 t = si->Tid().toLongLong( &ok, 16 );
|
|
if( !ok || t != save.tid )
|
|
continue;
|
|
|
|
//delete old save from sneek nand
|
|
if( !bannerthread.DeleteSaveFromSneekNand( save.tid ) )
|
|
{
|
|
qWarning() << "error deleting the old save";
|
|
return;
|
|
}
|
|
|
|
//delete old save from sneek browser
|
|
si = static_cast< SaveListItem * >( ui->listWidget_sneekSaves->takeItem( i ) );
|
|
delete si;
|
|
si = NULL;
|
|
|
|
break;
|
|
}
|
|
bool success = bannerthread.InstallSaveToSneekNand( save );
|
|
if( !success )
|
|
{
|
|
qWarning() << "error installing the save";
|
|
return;
|
|
}
|
|
//add this new item to the sneek list view
|
|
quint32 size = SaveItemSize( save );
|
|
QByteArray bnr = DataFromSave( save, "/banner.bin" );
|
|
SaveBanner sb( bnr );
|
|
new SaveListItem( sb, QString( "%1" ).arg( save.tid, 16, 16, QChar( '0' ) ) , size, ui->listWidget_sneekSaves );
|
|
ui->statusBar->showMessage( tr( "Installed save to extracted nand" ), 5000 );
|
|
}
|