* add wad packing to NUS tool

* add more "known updates" to the NUS class.  now your can specify "3.4u" or similar instead of a TID and it will try to download all the titles that would have been in that update.    the lists of titles were taken from wiimpersonator logs and when those were missing, from disc update partitions.  thanks to markhemus and rduke for helping make the lists
* NUS tool will now make sure there is a valid setting.txt in the nand after it is done installing titles to it
This commit is contained in:
giantpune@gmail.com 2010-12-08 18:07:57 +00:00
parent fe18155a2a
commit b0db7bf382
11 changed files with 1512 additions and 871 deletions

View File

@ -6,10 +6,12 @@
#include "tiktmd.h"
#include "tools.h"
#include "aes.h"
#include "wad.h"
MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::MainWindow ), nus ( this )
{
ui->setupUi(this);
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 ) ) );
@ -23,8 +25,10 @@ MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::M
//TODO, really get these paths from settings
ui->lineEdit_cachePath->setText( cachePath );
ui->lineEdit_nandPath->setText( nandPath );
nand.SetPath( nandPath );
//nand.SetPath( nandPath );
nus.SetCachePath( cachePath );
}
MainWindow::~MainWindow()
@ -70,6 +74,18 @@ void MainWindow::NusIsDone()
{
ui->lineEdit_nandPath->setEnabled( true );
ui->pushButton_nandPath->setEnabled( true );
//write the uid.sys and content.map to disc
nand.Flush();
//make sure there is a setting.txt
QByteArray set = nand.GetSettingTxt();
if( set.isEmpty() )
{
set = SettingTxtDialog::Edit( this );
if( !set.isEmpty() )
nand.SetSettingTxt( set );
}
}
else if( ui->radioButton_wad->isChecked() )
{
@ -84,20 +100,11 @@ void MainWindow::NusIsDone()
void MainWindow::ReceiveTitleFromNus( 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( "Received a completed download from NUS" );
QString title = QString( "%1v%2" ).arg( job.tid, 16, 16, QChar( '0' ) ).arg( job.version );
/*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 );
ui->plainTextEdit_log->appendHtml( j );*/
ui->plainTextEdit_log->appendHtml( str );
return;
//do something with the data we got
if( ui->radioButton_folder->isChecked() )
{
@ -113,6 +120,53 @@ return;
}
else if( ui->radioButton_wad->isChecked() )
{
Wad wad( job.data );
if( !wad.IsOk() )
{
ShowMessage( "<b>Error making a wad from " + title + "<\b>" );
return;
}
QFileInfo fi( ui->lineEdit_wad->text() );
if( fi.isFile() )
{
ShowMessage( "<b>" + ui->lineEdit_wad->text() + " is a file. I need a folder<\b>" );
return;
}
if( !fi.exists() )
{
ShowMessage( "<b>" + fi.absoluteFilePath() + " is not a folder!\nTrying to create it...<\b>" );
if( !QDir().mkpath( ui->lineEdit_wad->text() ) )
{
ShowMessage( "<b>Failed to make the directory!<\b>" );
return;
}
}
QByteArray w = wad.Data();
if( w.isEmpty() )
{
ShowMessage( "<b>Error creating wad<br>" + wad.LastError() + "<\b>" );
return;
}
QString name = wad.WadName( fi.absoluteFilePath() );
if( name.isEmpty() )
{
name = QFileDialog::getSaveFileName( this, tr( "Filename for %1" ).arg( title ), fi.absoluteFilePath() );
if( name.isEmpty() )
{
ShowMessage( "<b>No save name given, aborting<\b>" );
return;
}
}
QFile file( fi.absoluteFilePath() + "/" + name );
if( !file.open( QIODevice::WriteOnly ) )
{
ShowMessage( "<b>Cant open " + fi.absoluteFilePath() + "/" + name + " for writing<\b>" );
return;
}
file.write( w );
file.close();
ShowMessage( "Saved " + title + " to " + fi.absoluteFilePath() + "/" + name );
}
//bool r = nand.InstallNusItem( job );
@ -123,25 +177,35 @@ return;
void MainWindow::on_pushButton_GetTitle_clicked()
{
bool ok = false;
quint64 tid = ui->lineEdit_tid->text().toLongLong( &ok, 16 );
if( !ok )
bool wholeUpdate = false;
quint64 tid = 0;
quint32 ver = 0;
if( ui->lineEdit_tid->text().size() == 4 )
{
ShowMessage( "<b>Error converting \"" + ui->lineEdit_tid->text() + "\" to a hex number.</b>" );
return;
wholeUpdate = true;
}
quint32 ver = TITLE_LATEST_VERSION;
if( !ui->lineEdit_version->text().isEmpty() )
else
{
ver = ui->lineEdit_version->text().toInt( &ok, 10 );
tid = ui->lineEdit_tid->text().toLongLong( &ok, 16 );
if( !ok )
{
ShowMessage( "<b>Error converting \"" + ui->lineEdit_version->text() + "\" to a decimal number.</b>" );
ShowMessage( "<b>Error converting \"" + ui->lineEdit_tid->text() + "\" to a hex number.</b>" );
return;
}
if( ver > 0xffff )
ver = TITLE_LATEST_VERSION;
if( !ui->lineEdit_version->text().isEmpty() )
{
ShowMessage( tr( "<b>Version %1 is too high. Max is 65535</b>" ).arg( ver ) );
return;
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
@ -187,7 +251,18 @@ void MainWindow::on_pushButton_GetTitle_clicked()
//ui->progressBar_title->setValue( 0 );
//ui->progressBar_whole->setValue( 0 );
nus.SetCachePath( ui->lineEdit_cachePath->text() );
nus.Get( tid, decrypt, ver );
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;
}
}
else
{
nus.Get( tid, decrypt, ver );
}
}
//ratio buttons toggled
@ -233,10 +308,42 @@ void MainWindow::on_pushButton_decFolder_clicked()
void MainWindow::on_pushButton_wad_clicked()
{
QString path = ui->lineEdit_wad->text().isEmpty() ? "/media" : ui->lineEdit_wad->text();
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 );
}
/*
3.4j-
Error getting title from NUS: Error downloading part of the title.
NusJob( 0001000248414b4a, 2, decrypted, 0 items: )
Error getting title from NUS: Error downloading part of the title.
NusJob( 0001000248414c4a, 2, decrypted, 0 items: )
Received a completed download from NUS
Installed 0001000248415941v2 title to nand
Received a completed download from NUS
Error 000100084843434av0 title to nand
4.0j
Error getting title from NUS: Error downloading part of the title.
NusJob( 0001000248414b4a, 2, decrypted, 0 items: )
Error getting title from NUS: Error downloading part of the title.
NusJob( 0001000248414c4a, 2, decrypted, 0 items: )
Received a completed download from NUS
Installed 0001000248415941v3 title to nand
Error getting title from NUS: Error downloading part of the title.
NusJob( 000100024843434a, 1, decrypted, 0 items: )
Received a completed download from NUS
Error 000100084843434av0 title to nand
*/

View File

@ -25,6 +25,8 @@ private:
void ShowMessage( const QString& mes );
public slots:
//slots for getting info from the NUS downloader
void GetError( const QString &message, NusJob job );

View File

@ -298,7 +298,7 @@ bool NandDump::RecurseDeleteFolder( const QString &path )
bool NandDump::InstallNusItem( NusJob job )
{
if( !job.tid || !job.version || job.data.size() < 3 )
if( !job.tid || job.data.size() < 3 )
{
qWarning() << "NandDump::InstallNusItem -> invalid item";
return false;

File diff suppressed because it is too large Load Diff

View File

@ -53,7 +53,7 @@ public:
//TODO! this function is far from complete
//starts getting all the titles involved for a given update
//expects strings like "4.2u" and "3.3e"
//expects strings like "4.2u" and "3.4e" ( any of the updates listed below should be working )
//returns false if it doesnt know the IOS for the given string
// The boot2 update is not uncluded in this. ask for it separately if you want it
bool GetUpdate( const QString & upd, bool decrypt = true );
@ -63,6 +63,7 @@ public:
//to keep people from bulk DLing and installing and messing something up, any boot2 upudate will not be included
//in the list, ask for it specifically
//lists are created from wiimpersonator logs when available. otherwise they come from examining game update partitions
static QMap< quint64, quint16 > List20u();
static QMap< quint64, quint16 > List30u();
static QMap< quint64, quint16 > List31u();
static QMap< quint64, quint16 > List32u();
@ -73,25 +74,32 @@ public:
static QMap< quint64, quint16 > List42u();
static QMap< quint64, quint16 > List43u();
/* static QMap< quint64, quint16 > List30e();
static QMap< quint64, quint16 > List21e();
static QMap< quint64, quint16 > List30e();
static QMap< quint64, quint16 > List31e();
static QMap< quint64, quint16 > List32e();
static QMap< quint64, quint16 > List33e();
//static QMap< quint64, quint16 > List32e();
//static QMap< quint64, quint16 > List33e();
static QMap< quint64, quint16 > List34e();
static QMap< quint64, quint16 > List40e();
static QMap< quint64, quint16 > List41e();
static QMap< quint64, quint16 > List42e();
static QMap< quint64, quint16 > List43e();
static QMap< quint64, quint16 > List30j();
static QMap< quint64, quint16 > List35k();
static QMap< quint64, quint16 > List41k();
static QMap< quint64, quint16 > List42k();
static QMap< quint64, quint16 > List43k();
static QMap< quint64, quint16 > List20j();
//static QMap< quint64, quint16 > List30j();
static QMap< quint64, quint16 > List31j();
static QMap< quint64, quint16 > List32j();
static QMap< quint64, quint16 > List33j();
//static QMap< quint64, quint16 > List32j();
//static QMap< quint64, quint16 > List33j();
static QMap< quint64, quint16 > List34j();
static QMap< quint64, quint16 > List40j();
static QMap< quint64, quint16 > List41j();
static QMap< quint64, quint16 > List42j();
static QMap< quint64, quint16 > List43j();*/
static QMap< quint64, quint16 > List43j();
private:

View File

@ -24,35 +24,53 @@ quint64 Tmd::Tid()
QString Tmd::Cid( quint16 i )
{
if( p_tmd && i > qFromBigEndian( p_tmd->num_contents ) )
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
return QString();
return QString( "%1" ).arg( qFromBigEndian( p_tmd->contents[ i ].cid ), 8, 16, QChar( '0' ) );
}
QByteArray Tmd::Hash( quint16 i )
{
if( p_tmd && i > qFromBigEndian( p_tmd->num_contents ) )
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
return QByteArray();
return QByteArray( (const char*)&p_tmd->contents[ i ].hash, 20 );
}
quint16 Tmd::Count()
{
if( !p_tmd )
return 0;
return qFromBigEndian( p_tmd->num_contents );
}
quint16 Tmd::Version()
{
if( !p_tmd )
return 0;
return qFromBigEndian( p_tmd->title_version );
}
quint64 Tmd::Size( quint16 i )
{
if( p_tmd && i > qFromBigEndian( p_tmd->num_contents ) )
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
return 0;
return qFromBigEndian( p_tmd->contents[ i ].size );
}
quint16 Tmd::Type( quint16 i )
{
if( p_tmd && i > qFromBigEndian( p_tmd->num_contents ) )
if( !p_tmd || i > qFromBigEndian( p_tmd->num_contents ) )
return 0;
return qFromBigEndian( p_tmd->contents[ i ].type );
}
quint32 Tmd::SignedSize()
{
if( !p_tmd )
return 0;
return payLoadOffset + sizeof( tmd ) + ( sizeof( tmd_content ) * qFromBigEndian( p_tmd->num_contents ) );
}

View File

@ -38,19 +38,7 @@ typedef struct _tiklimit {
quint32 tag;
quint32 value;
} __attribute__((packed)) tiklimit;
/*
typedef struct _tikview {
quint32 view;
quint64 ticketid;
quint32 devicetype;
quint64 titleid;
quint16 access_mask;
quint8 reserved[0x3c];
quint8 cidx_mask[0x40];
quint16 padding;
tiklimit limits[8];
} __attribute__((packed)) tikview;
*/
typedef struct _tik {
sig_issuer issuer;
quint8 fill[63]; //TODO: not really fill
@ -108,20 +96,6 @@ typedef struct _tmd_view_content
quint64 size;
} __attribute__((packed)) tmd_view_content;
typedef struct _tmdview
{
quint8 version; // 0x0000;
quint8 filler[3];
quint64 sys_version; //0x0004
quint64 title_id; // 0x00c
quint32 title_type; //0x0014
quint16 group_id; //0x0018
quint8 reserved[0x3e]; //0x001a this is the same reserved 0x3e bytes from the tmd
quint16 title_version; //0x0058
quint16 num_contents; //0x005a
tmd_view_content contents[]; //0x005c
}__attribute__((packed)) tmd_view;
typedef struct _cert_header {
sig_issuer issuer;
quint32 cert_type;
@ -213,6 +187,12 @@ public:
quint16 Type( quint16 i );
quint64 Tid();
//gets the number of contents
quint16 Count();
//title version
quint16 Version();
quint32 SignedSize();
private:
quint32 payLoadOffset;

View File

@ -104,6 +104,18 @@ QByteArray AesDecrypt( quint16 index, const QByteArray source )
return ret;
}
QByteArray AesEncrypt( quint16 index, const QByteArray source )
{
static quint8 iv[ 16 ];
quint16 beidx = qFromBigEndian( index );
memset( iv, 0, 16 );
memcpy( iv, &beidx, 2 );
QByteArray ret( source.size(), '\0' );
aes_encrypt( iv, (const quint8*)source.data(), (quint8*)ret.data(), source.size() );
return ret;
}
void AesSetKey( const QByteArray key )
{
aes_set_key( (const quint8*)key.data() );
@ -129,3 +141,217 @@ QByteArray GetSha1( QByteArray stuff )
//hexdump( ret );
return ret;
}
QByteArray ReadFile( const QString &path )
{
QFile file( path );
if( !file.exists() || !file.open( QIODevice::ReadOnly ) )
{
qWarning() << "ReadFile -> can't open" << path;
return QByteArray();
}
QByteArray ret = file.readAll();
file.close();
return ret;
}
#define CERTS_DAT_SIZE 2560
const quint8 certs_dat[ CERTS_DAT_SIZE ] = {
0x00, 0x01, 0x00, 0x01, 0x7D, 0x9D, 0x5E, 0xBA, 0x52, 0x81, 0xDC, 0xA7, 0x06, 0x5D, 0x2F, 0x08,
0x68, 0xDB, 0x8A, 0xC7, 0x3A, 0xCE, 0x7E, 0xA9, 0x91, 0xF1, 0x96, 0x9F, 0xE1, 0xD0, 0xF2, 0xC1,
0x1F, 0xAE, 0xC0, 0xC3, 0xF0, 0x1A, 0xDC, 0xB4, 0x46, 0xAD, 0xE5, 0xCA, 0x03, 0xB6, 0x25, 0x21,
0x94, 0x62, 0xC6, 0xE1, 0x41, 0x0D, 0xB9, 0xE6, 0x3F, 0xDE, 0x98, 0xD1, 0xAF, 0x26, 0x3B, 0x4C,
0xB2, 0x87, 0x84, 0x27, 0x82, 0x72, 0xEF, 0x27, 0x13, 0x4B, 0x87, 0xC2, 0x58, 0xD6, 0x7B, 0x62,
0xF2, 0xB5, 0xBF, 0x9C, 0xB6, 0xBA, 0x8C, 0x89, 0x19, 0x2E, 0xC5, 0x06, 0x89, 0xAC, 0x74, 0x24,
0xA0, 0x22, 0x09, 0x40, 0x03, 0xEE, 0x98, 0xA4, 0xBD, 0x2F, 0x01, 0x3B, 0x59, 0x3F, 0xE5, 0x66,
0x6C, 0xD5, 0xEB, 0x5A, 0xD7, 0xA4, 0x93, 0x10, 0xF3, 0x4E, 0xFB, 0xB4, 0x3D, 0x46, 0xCB, 0xF1,
0xB5, 0x23, 0xCF, 0x82, 0xF6, 0x8E, 0xB5, 0x6D, 0xB9, 0x04, 0xA7, 0xC2, 0xA8, 0x2B, 0xE1, 0x1D,
0x78, 0xD3, 0x9B, 0xA2, 0x0D, 0x90, 0xD3, 0x07, 0x42, 0xDB, 0x5E, 0x7A, 0xC1, 0xEF, 0xF2, 0x21,
0x51, 0x09, 0x62, 0xCF, 0xA9, 0x14, 0xA8, 0x80, 0xDC, 0xF4, 0x17, 0xBA, 0x99, 0x93, 0x0A, 0xEE,
0x08, 0xB0, 0xB0, 0xE5, 0x1A, 0x3E, 0x9F, 0xAF, 0xCD, 0xC2, 0xD7, 0xE3, 0xCB, 0xA1, 0x2F, 0x3A,
0xC0, 0x07, 0x90, 0xDE, 0x44, 0x7A, 0xC3, 0xC5, 0x38, 0xA8, 0x67, 0x92, 0x38, 0x07, 0x8B, 0xD4,
0xC4, 0xB2, 0x45, 0xAC, 0x29, 0x16, 0x88, 0x6D, 0x2A, 0x0E, 0x59, 0x4E, 0xED, 0x5C, 0xC8, 0x35,
0x69, 0x8B, 0x4D, 0x62, 0x38, 0xDF, 0x05, 0x72, 0x4D, 0xCC, 0xF6, 0x81, 0x80, 0x8A, 0x70, 0x74,
0x06, 0x59, 0x30, 0xBF, 0xF8, 0x51, 0x41, 0x37, 0xE8, 0x15, 0xFA, 0xBA, 0xA1, 0x72, 0xB8, 0xE0,
0x69, 0x6C, 0x61, 0xE4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x52, 0x6F, 0x6F, 0x74, 0x2D, 0x43, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x58, 0x53, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x33, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xF1, 0xB8, 0x9F, 0xD1, 0xAD, 0x07, 0xA9, 0x37, 0x8A, 0x7B, 0x10, 0x0C,
0x7D, 0xC7, 0x39, 0xBE, 0x9E, 0xDD, 0xB7, 0x32, 0x00, 0x89, 0xAB, 0x25, 0xB1, 0xF8, 0x71, 0xAF,
0x5A, 0xA9, 0xF4, 0x58, 0x9E, 0xD1, 0x83, 0x02, 0x32, 0x8E, 0x81, 0x1A, 0x1F, 0xEF, 0xD0, 0x09,
0xC8, 0x06, 0x36, 0x43, 0xF8, 0x54, 0xB9, 0xE1, 0x3B, 0xBB, 0x61, 0x3A, 0x7A, 0xCF, 0x87, 0x14,
0x85, 0x6B, 0xA4, 0x5B, 0xAA, 0xE7, 0xBB, 0xC6, 0x4E, 0xB2, 0xF7, 0x5D, 0x87, 0xEB, 0xF2, 0x67,
0xED, 0x0F, 0xA4, 0x41, 0xA9, 0x33, 0x66, 0x5E, 0x57, 0x7D, 0x5A, 0xDE, 0xAB, 0xFB, 0x46, 0x2E,
0x76, 0x00, 0xCA, 0x9C, 0xE9, 0x4D, 0xC4, 0xCB, 0x98, 0x39, 0x92, 0xAB, 0x7A, 0x2F, 0xB3, 0xA3,
0x9E, 0xA2, 0xBF, 0x9C, 0x53, 0xEC, 0xD0, 0xDC, 0xFA, 0x6B, 0x8B, 0x5E, 0xB2, 0xCB, 0xA4, 0x0F,
0xFA, 0x40, 0x75, 0xF8, 0xF2, 0xB2, 0xDE, 0x97, 0x38, 0x11, 0x87, 0x2D, 0xF5, 0xE2, 0xA6, 0xC3,
0x8B, 0x2F, 0xDC, 0x8E, 0x57, 0xDD, 0xBD, 0x5F, 0x46, 0xEB, 0x27, 0xD6, 0x19, 0x52, 0xF6, 0xAE,
0xF8, 0x62, 0xB7, 0xEE, 0x9A, 0xC6, 0x82, 0xA2, 0xB1, 0x9A, 0xA9, 0xB5, 0x58, 0xFB, 0xEB, 0xB3,
0x89, 0x2F, 0xBD, 0x50, 0xC9, 0xF5, 0xDC, 0x4A, 0x6E, 0x9C, 0x9B, 0xFE, 0x45, 0x80, 0x34, 0xA9,
0x42, 0x18, 0x2D, 0xDE, 0xB7, 0x5F, 0xE0, 0xD1, 0xB3, 0xDF, 0x0E, 0x97, 0xE3, 0x99, 0x80, 0x87,
0x70, 0x18, 0xC2, 0xB2, 0x83, 0xF1, 0x35, 0x75, 0x7C, 0x5A, 0x30, 0xFC, 0x3F, 0x30, 0x84, 0xA4,
0x9A, 0xAA, 0xC0, 0x1E, 0xE7, 0x06, 0x69, 0x4F, 0x8E, 0x14, 0x48, 0xDA, 0x12, 0x3A, 0xCC, 0x4F,
0xFA, 0x26, 0xAA, 0x38, 0xF7, 0xEF, 0xBF, 0x27, 0x8F, 0x36, 0x97, 0x79, 0x77, 0x5D, 0xB7, 0xC5,
0xAD, 0xC7, 0x89, 0x91, 0xDC, 0xF8, 0x43, 0x8D, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x00, 0xB3, 0xAD, 0xB3, 0x22, 0x6B, 0x3C, 0x3D, 0xFF, 0x1B, 0x4B, 0x40, 0x77,
0x16, 0xFF, 0x4F, 0x7A, 0xD7, 0x64, 0x86, 0xC8, 0x95, 0xAC, 0x56, 0x2D, 0x21, 0xF1, 0x06, 0x01,
0xD4, 0xF6, 0x64, 0x28, 0x19, 0x1C, 0x07, 0x76, 0x8F, 0xDF, 0x1A, 0xE2, 0xCE, 0x7B, 0x27, 0xC9,
0x0F, 0xBC, 0x0A, 0xD0, 0x31, 0x25, 0x78, 0xEC, 0x07, 0x79, 0xB6, 0x57, 0xD4, 0x37, 0x24, 0x13,
0xA7, 0xF8, 0x6F, 0x0C, 0x14, 0xC0, 0xEF, 0x6E, 0x09, 0x41, 0xED, 0x2B, 0x05, 0xEC, 0x39, 0x57,
0x36, 0x07, 0x89, 0x00, 0x4A, 0x87, 0x8D, 0x2E, 0x9D, 0xF8, 0xC7, 0xA5, 0xA9, 0xF8, 0xCA, 0xB3,
0x11, 0xB1, 0x18, 0x79, 0x57, 0xBB, 0xF8, 0x98, 0xE2, 0xA2, 0x54, 0x02, 0xCF, 0x54, 0x39, 0xCF,
0x2B, 0xBF, 0xA0, 0xE1, 0xF8, 0x5C, 0x06, 0x6E, 0x83, 0x9A, 0xE0, 0x94, 0xCA, 0x47, 0xE0, 0x15,
0x58, 0xF5, 0x6E, 0x6F, 0x34, 0xE9, 0x2A, 0xA2, 0xDC, 0x38, 0x93, 0x7E, 0x37, 0xCD, 0x8C, 0x5C,
0x4D, 0xFD, 0x2F, 0x11, 0x4F, 0xE8, 0x68, 0xC9, 0xA8, 0xD9, 0xFE, 0xD8, 0x6E, 0x0C, 0x21, 0x75,
0xA2, 0xBD, 0x7E, 0x89, 0xB9, 0xC7, 0xB5, 0x13, 0xF4, 0x1A, 0x79, 0x61, 0x44, 0x39, 0x10, 0xEF,
0xF9, 0xD7, 0xFE, 0x57, 0x22, 0x18, 0xD5, 0x6D, 0xFB, 0x7F, 0x49, 0x7A, 0xA4, 0xCB, 0x90, 0xD4,
0xF1, 0xAE, 0xB1, 0x76, 0xE4, 0x68, 0x5D, 0xA7, 0x94, 0x40, 0x60, 0x98, 0x2F, 0x04, 0x48, 0x40,
0x1F, 0xCF, 0xC6, 0xBA, 0xEB, 0xDA, 0x16, 0x30, 0xB4, 0x73, 0xB4, 0x15, 0x23, 0x35, 0x08, 0x07,
0x0A, 0x9F, 0x4F, 0x89, 0x78, 0xE6, 0x2C, 0xEC, 0x5E, 0x92, 0x46, 0xA5, 0xA8, 0xBD, 0xA0, 0x85,
0x78, 0x68, 0x75, 0x0C, 0x3A, 0x11, 0x2F, 0xAF, 0x95, 0xE8, 0x38, 0xC8, 0x99, 0x0E, 0x87, 0xB1,
0x62, 0xCD, 0x10, 0xDA, 0xB3, 0x31, 0x96, 0x65, 0xEF, 0x88, 0x9B, 0x54, 0x1B, 0xB3, 0x36, 0xBB,
0x67, 0x53, 0x9F, 0xAF, 0xC2, 0xAE, 0x2D, 0x0A, 0x2E, 0x75, 0xC0, 0x23, 0x74, 0xEA, 0x4E, 0xAC,
0x8D, 0x99, 0x50, 0x7F, 0x59, 0xB9, 0x53, 0x77, 0x30, 0x5F, 0x26, 0x35, 0xC6, 0x08, 0xA9, 0x90,
0x93, 0xAC, 0x8F, 0xC6, 0xDE, 0x23, 0xB9, 0x7A, 0xEA, 0x70, 0xB4, 0xC4, 0xCF, 0x66, 0xB3, 0x0E,
0x58, 0x32, 0x0E, 0xC5, 0xB6, 0x72, 0x04, 0x48, 0xCE, 0x3B, 0xB1, 0x1C, 0x53, 0x1F, 0xCB, 0x70,
0x28, 0x7C, 0xB5, 0xC2, 0x7C, 0x67, 0x4F, 0xBB, 0xFD, 0x8C, 0x7F, 0xC9, 0x42, 0x20, 0xA4, 0x73,
0x23, 0x1D, 0x58, 0x7E, 0x5A, 0x1A, 0x1A, 0x82, 0xE3, 0x75, 0x79, 0xA1, 0xBB, 0x82, 0x6E, 0xCE,
0x01, 0x71, 0xC9, 0x75, 0x63, 0x47, 0x4B, 0x1D, 0x46, 0xE6, 0x79, 0xB2, 0x82, 0x37, 0x62, 0x11,
0xCD, 0xC7, 0x00, 0x2F, 0x46, 0x87, 0xC2, 0x3C, 0x6D, 0xC0, 0xD5, 0xB5, 0x78, 0x6E, 0xE1, 0xF2,
0x73, 0xFF, 0x01, 0x92, 0x50, 0x0F, 0xF4, 0xC7, 0x50, 0x6A, 0xEE, 0x72, 0xB6, 0xF4, 0x3D, 0xF6,
0x08, 0xFE, 0xA5, 0x83, 0xA1, 0xF9, 0x86, 0x0F, 0x87, 0xAF, 0x52, 0x44, 0x54, 0xBB, 0x47, 0xC3,
0x06, 0x0C, 0x94, 0xE9, 0x9B, 0xF7, 0xD6, 0x32, 0xA7, 0xC8, 0xAB, 0x4B, 0x4F, 0xF5, 0x35, 0x21,
0x1F, 0xC1, 0x80, 0x47, 0xBB, 0x7A, 0xFA, 0x5A, 0x2B, 0xD7, 0xB8, 0x84, 0xAD, 0x8E, 0x56, 0x4F,
0x5B, 0x89, 0xFF, 0x37, 0x97, 0x37, 0xF1, 0xF5, 0x01, 0x3B, 0x1F, 0x9E, 0xC4, 0x18, 0x6F, 0x92,
0x2A, 0xD5, 0xC4, 0xB3, 0xC0, 0xD5, 0x87, 0x0B, 0x9C, 0x04, 0xAF, 0x1A, 0xB5, 0xF3, 0xBC, 0x6D,
0x0A, 0xF1, 0x7D, 0x47, 0x08, 0xE4, 0x43, 0xE9, 0x73, 0xF7, 0xB7, 0x70, 0x77, 0x54, 0xBA, 0xF3,
0xEC, 0xD2, 0xAC, 0x49, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x52, 0x6F, 0x6F, 0x74, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x43, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x5B, 0xFA, 0x7D, 0x5C, 0xB2, 0x79, 0xC9, 0xE2, 0xEE, 0xE1, 0x21, 0xC6,
0xEA, 0xF4, 0x4F, 0xF6, 0x39, 0xF8, 0x8F, 0x07, 0x8B, 0x4B, 0x77, 0xED, 0x9F, 0x95, 0x60, 0xB0,
0x35, 0x82, 0x81, 0xB5, 0x0E, 0x55, 0xAB, 0x72, 0x11, 0x15, 0xA1, 0x77, 0x70, 0x3C, 0x7A, 0x30,
0xFE, 0x3A, 0xE9, 0xEF, 0x1C, 0x60, 0xBC, 0x1D, 0x97, 0x46, 0x76, 0xB2, 0x3A, 0x68, 0xCC, 0x04,
0xB1, 0x98, 0x52, 0x5B, 0xC9, 0x68, 0xF1, 0x1D, 0xE2, 0xDB, 0x50, 0xE4, 0xD9, 0xE7, 0xF0, 0x71,
0xE5, 0x62, 0xDA, 0xE2, 0x09, 0x22, 0x33, 0xE9, 0xD3, 0x63, 0xF6, 0x1D, 0xD7, 0xC1, 0x9F, 0xF3,
0xA4, 0xA9, 0x1E, 0x8F, 0x65, 0x53, 0xD4, 0x71, 0xDD, 0x7B, 0x84, 0xB9, 0xF1, 0xB8, 0xCE, 0x73,
0x35, 0xF0, 0xF5, 0x54, 0x05, 0x63, 0xA1, 0xEA, 0xB8, 0x39, 0x63, 0xE0, 0x9B, 0xE9, 0x01, 0x01,
0x1F, 0x99, 0x54, 0x63, 0x61, 0x28, 0x70, 0x20, 0xE9, 0xCC, 0x0D, 0xAB, 0x48, 0x7F, 0x14, 0x0D,
0x66, 0x26, 0xA1, 0x83, 0x6D, 0x27, 0x11, 0x1F, 0x20, 0x68, 0xDE, 0x47, 0x72, 0x14, 0x91, 0x51,
0xCF, 0x69, 0xC6, 0x1B, 0xA6, 0x0E, 0xF9, 0xD9, 0x49, 0xA0, 0xF7, 0x1F, 0x54, 0x99, 0xF2, 0xD3,
0x9A, 0xD2, 0x8C, 0x70, 0x05, 0x34, 0x82, 0x93, 0xC4, 0x31, 0xFF, 0xBD, 0x33, 0xF6, 0xBC, 0xA6,
0x0D, 0xC7, 0x19, 0x5E, 0xA2, 0xBC, 0xC5, 0x6D, 0x20, 0x0B, 0xAF, 0x6D, 0x06, 0xD0, 0x9C, 0x41,
0xDB, 0x8D, 0xE9, 0xC7, 0x20, 0x15, 0x4C, 0xA4, 0x83, 0x2B, 0x69, 0xC0, 0x8C, 0x69, 0xCD, 0x3B,
0x07, 0x3A, 0x00, 0x63, 0x60, 0x2F, 0x46, 0x2D, 0x33, 0x80, 0x61, 0xA5, 0xEA, 0x6C, 0x91, 0x5C,
0xD5, 0x62, 0x35, 0x79, 0xC3, 0xEB, 0x64, 0xCE, 0x44, 0xEF, 0x58, 0x6D, 0x14, 0xBA, 0xAA, 0x88,
0x34, 0x01, 0x9B, 0x3E, 0xEB, 0xEE, 0xD3, 0x79, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x01, 0x00, 0x01, 0x4E, 0x00, 0x5F, 0xF1, 0x3F, 0x86, 0x75, 0x8D, 0xB6, 0x9C, 0x45, 0x63,
0x0F, 0xD4, 0x9B, 0xF4, 0xCC, 0x5D, 0x54, 0xCF, 0xCC, 0x22, 0x34, 0x72, 0x57, 0xAB, 0xA4, 0xBA,
0x53, 0xD2, 0xB3, 0x3D, 0xE6, 0xEC, 0x9E, 0xA1, 0x57, 0x54, 0x53, 0xAE, 0x5F, 0x93, 0x3D, 0x96,
0xBF, 0xF7, 0xCC, 0x7A, 0x79, 0x56, 0x6E, 0x84, 0x7B, 0x1B, 0x60, 0x77, 0xC2, 0xA9, 0x38, 0x71,
0x30, 0x1A, 0x8C, 0xD3, 0xC9, 0x3D, 0x4D, 0xB3, 0x26, 0xE9, 0x87, 0x92, 0x66, 0xE9, 0xD3, 0xBA,
0x9F, 0x79, 0xBC, 0x46, 0x38, 0xFA, 0x2D, 0x20, 0xA0, 0x3A, 0x70, 0x67, 0xA4, 0x11, 0xA7, 0xA0,
0xB7, 0xD9, 0x12, 0xAD, 0x11, 0x6A, 0x3A, 0xC4, 0x6E, 0x32, 0x42, 0x47, 0xC2, 0x08, 0xBA, 0xB4,
0x94, 0x9C, 0xC5, 0x2E, 0xD0, 0x2F, 0x19, 0xF6, 0x51, 0xE0, 0xDF, 0x2E, 0x36, 0x53, 0xAA, 0xAF,
0x97, 0xA6, 0x92, 0xBB, 0xA9, 0x1D, 0xD8, 0x6E, 0x24, 0x2E, 0xB3, 0x08, 0x77, 0x55, 0x11, 0xCE,
0x98, 0xF6, 0xA2, 0xF4, 0x26, 0xC9, 0x27, 0x04, 0xD0, 0xFC, 0x8D, 0xD4, 0x80, 0x9E, 0xD7, 0x61,
0xBD, 0x11, 0xB7, 0x85, 0x94, 0x8C, 0xD6, 0xD0, 0x7A, 0xDB, 0xA4, 0x08, 0xD0, 0xF0, 0x86, 0xF6,
0x5A, 0xAE, 0x19, 0x14, 0xB2, 0x88, 0x9A, 0xA8, 0xAE, 0x4A, 0xA2, 0xAA, 0xC7, 0x61, 0xA9, 0x0D,
0x41, 0x2C, 0xB1, 0x50, 0x09, 0xAB, 0x3E, 0x93, 0xFC, 0xA9, 0x24, 0xDE, 0xCE, 0x4F, 0x7C, 0x06,
0xAB, 0xDC, 0x2E, 0x60, 0x9D, 0x68, 0xBE, 0x00, 0x73, 0xFA, 0x80, 0x57, 0x6A, 0x14, 0x5E, 0xED,
0xC4, 0x8B, 0x74, 0x32, 0x87, 0x07, 0x93, 0xC8, 0xFC, 0xA6, 0xD8, 0x3E, 0x09, 0x6E, 0xC5, 0xF2,
0xA9, 0xC4, 0x21, 0xE7, 0x48, 0xB3, 0x73, 0x40, 0x5B, 0xE2, 0xFA, 0x8A, 0xE1, 0x58, 0x78, 0xE9,
0xD5, 0x23, 0x88, 0x75, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x52, 0x6F, 0x6F, 0x74, 0x2D, 0x43, 0x41, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x31, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x01, 0x43, 0x50, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x30, 0x34, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0xF1, 0xB8, 0xA0, 0x64, 0xC1, 0x6D, 0xF3, 0x83, 0x29, 0x55, 0xC3, 0x29,
0x5B, 0x72, 0xF0, 0x33, 0x2E, 0x97, 0xEF, 0x14, 0x84, 0x8A, 0x68, 0x04, 0x9C, 0xA6, 0x8E, 0xAC,
0xDE, 0x14, 0x50, 0x33, 0xB8, 0x6C, 0x10, 0x8D, 0x48, 0x33, 0x5C, 0x5D, 0x0C, 0xAB, 0x77, 0x04,
0x62, 0x54, 0x47, 0x55, 0x45, 0x2A, 0x90, 0x00, 0x70, 0xB1, 0x56, 0x92, 0x5C, 0x17, 0x86, 0xE2,
0xCD, 0x20, 0x6D, 0xCC, 0xDC, 0x2C, 0x2E, 0x37, 0x6E, 0x27, 0xFC, 0xB4, 0x20, 0x66, 0xCC, 0x0A,
0x8C, 0xE9, 0xFE, 0xE8, 0x57, 0x04, 0xE6, 0xCA, 0x63, 0x1A, 0x2E, 0x7E, 0x91, 0x7E, 0x94, 0x7C,
0x39, 0x91, 0x77, 0x36, 0x29, 0xD1, 0x55, 0x61, 0x85, 0xBB, 0xD7, 0xB7, 0x73, 0xCA, 0x37, 0x47,
0x9E, 0x5F, 0xAA, 0xA3, 0xB6, 0x05, 0xE0, 0x01, 0xE1, 0xAC, 0xE5, 0x8D, 0xD8, 0xF8, 0x47, 0x82,
0xD6, 0x45, 0xFC, 0xE3, 0xA1, 0xCD, 0x03, 0xAB, 0x36, 0xF0, 0xF3, 0x86, 0xB1, 0xA2, 0xD1, 0x37,
0x40, 0xA1, 0x94, 0x8A, 0x53, 0xBA, 0x1B, 0x0D, 0x8C, 0x48, 0x63, 0xCD, 0x6B, 0x2C, 0x2E, 0x20,
0x64, 0x94, 0x80, 0x4C, 0x62, 0xFA, 0xA9, 0x3A, 0x7E, 0x33, 0xA9, 0xEA, 0x78, 0x6B, 0x59, 0xCA,
0xE3, 0xAB, 0x36, 0x45, 0xF4, 0xCB, 0x8F, 0xD7, 0x90, 0x6B, 0x82, 0x68, 0xCD, 0xAC, 0xF1, 0x7B,
0x3A, 0xEC, 0x46, 0x83, 0x1B, 0x91, 0xF6, 0xDE, 0x18, 0x61, 0x83, 0xBC, 0x4B, 0x32, 0x67, 0x93,
0xC7, 0x2E, 0x50, 0xD9, 0x1E, 0x36, 0xA0, 0xDC, 0xE2, 0xB9, 0x7D, 0xA0, 0x21, 0x3E, 0x46, 0x96,
0x02, 0x1F, 0x33, 0x1C, 0xBE, 0xAE, 0x8D, 0xFC, 0x92, 0x87, 0x32, 0xAA, 0x44, 0xDC, 0x78, 0xE7,
0x19, 0x9A, 0x3D, 0xDD, 0x57, 0x22, 0x7E, 0x9E, 0x77, 0xDE, 0x32, 0x63, 0x86, 0x93, 0x6C, 0x11,
0xAC, 0xA7, 0x0F, 0x81, 0x19, 0xD3, 0x3A, 0x99, 0x00, 0x01, 0x00, 0x01, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
};
// public root cert
const quint8 root_dat[] = {
0xF8, 0x24, 0x6C, 0x58, 0xBA, 0xE7, 0x50, 0x03, 0x01, 0xFB, 0xB7, 0xC2, 0xEB, 0xE0, 0x01, 0x05,
0x71, 0xDA, 0x92, 0x23, 0x78, 0xF0, 0x51, 0x4E, 0xC0, 0x03, 0x1D, 0xD0, 0xD2, 0x1E, 0xD3, 0xD0,
0x7E, 0xFC, 0x85, 0x20, 0x69, 0xB5, 0xDE, 0x9B, 0xB9, 0x51, 0xA8, 0xBC, 0x90, 0xA2, 0x44, 0x92,
0x6D, 0x37, 0x92, 0x95, 0xAE, 0x94, 0x36, 0xAA, 0xA6, 0xA3, 0x02, 0x51, 0x0C, 0x7B, 0x1D, 0xED,
0xD5, 0xFB, 0x20, 0x86, 0x9D, 0x7F, 0x30, 0x16, 0xF6, 0xBE, 0x65, 0xD3, 0x83, 0xA1, 0x6D, 0xB3,
0x32, 0x1B, 0x95, 0x35, 0x18, 0x90, 0xB1, 0x70, 0x02, 0x93, 0x7E, 0xE1, 0x93, 0xF5, 0x7E, 0x99,
0xA2, 0x47, 0x4E, 0x9D, 0x38, 0x24, 0xC7, 0xAE, 0xE3, 0x85, 0x41, 0xF5, 0x67, 0xE7, 0x51, 0x8C,
0x7A, 0x0E, 0x38, 0xE7, 0xEB, 0xAF, 0x41, 0x19, 0x1B, 0xCF, 0xF1, 0x7B, 0x42, 0xA6, 0xB4, 0xED,
0xE6, 0xCE, 0x8D, 0xE7, 0x31, 0x8F, 0x7F, 0x52, 0x04, 0xB3, 0x99, 0x0E, 0x22, 0x67, 0x45, 0xAF,
0xD4, 0x85, 0xB2, 0x44, 0x93, 0x00, 0x8B, 0x08, 0xC7, 0xF6, 0xB7, 0xE5, 0x6B, 0x02, 0xB3, 0xE8,
0xFE, 0x0C, 0x9D, 0x85, 0x9C, 0xB8, 0xB6, 0x82, 0x23, 0xB8, 0xAB, 0x27, 0xEE, 0x5F, 0x65, 0x38,
0x07, 0x8B, 0x2D, 0xB9, 0x1E, 0x2A, 0x15, 0x3E, 0x85, 0x81, 0x80, 0x72, 0xA2, 0x3B, 0x6D, 0xD9,
0x32, 0x81, 0x05, 0x4F, 0x6F, 0xB0, 0xF6, 0xF5, 0xAD, 0x28, 0x3E, 0xCA, 0x0B, 0x7A, 0xF3, 0x54,
0x55, 0xE0, 0x3D, 0xA7, 0xB6, 0x83, 0x26, 0xF3, 0xEC, 0x83, 0x4A, 0xF3, 0x14, 0x04, 0x8A, 0xC6,
0xDF, 0x20, 0xD2, 0x85, 0x08, 0x67, 0x3C, 0xAB, 0x62, 0xA2, 0xC7, 0xBC, 0x13, 0x1A, 0x53, 0x3E,
0x0B, 0x66, 0x80, 0x6B, 0x1C, 0x30, 0x66, 0x4B, 0x37, 0x23, 0x31, 0xBD, 0xC4, 0xB0, 0xCA, 0xD8,
0xD1, 0x1E, 0xE7, 0xBB, 0xD9, 0x28, 0x55, 0x48, 0xAA, 0xEC, 0x1F, 0x66, 0xE8, 0x21, 0xB3, 0xC8,
0xA0, 0x47, 0x69, 0x00, 0xC5, 0xE6, 0x88, 0xE8, 0x0C, 0xCE, 0x3C, 0x61, 0xD6, 0x9C, 0xBB, 0xA1,
0x37, 0xC6, 0x60, 0x4F, 0x7A, 0x72, 0xDD, 0x8C, 0x7B, 0x3E, 0x3D, 0x51, 0x29, 0x0D, 0xAA, 0x6A,
0x59, 0x7B, 0x08, 0x1F, 0x9D, 0x36, 0x33, 0xA3, 0x46, 0x7A, 0x35, 0x61, 0x09, 0xAC, 0xA7, 0xDD,
0x7D, 0x2E, 0x2F, 0xB2, 0xC1, 0xAE, 0xB8, 0xE2, 0x0F, 0x48, 0x92, 0xD8, 0xB9, 0xF8, 0xB4, 0x6F,
0x4E, 0x3C, 0x11, 0xF4, 0xF4, 0x7D, 0x8B, 0x75, 0x7D, 0xFE, 0xFE, 0xA3, 0x89, 0x9C, 0x33, 0x59,
0x5C, 0x5E, 0xFD, 0xEB, 0xCB, 0xAB, 0xE8, 0x41, 0x3E, 0x3A, 0x9A, 0x80, 0x3C, 0x69, 0x35, 0x6E,
0xB2, 0xB2, 0xAD, 0x5C, 0xC4, 0xC8, 0x58, 0x45, 0x5E, 0xF5, 0xF7, 0xB3, 0x06, 0x44, 0xB4, 0x7C,
0x64, 0x06, 0x8C, 0xDF, 0x80, 0x9F, 0x76, 0x02, 0x5A, 0x2D, 0xB4, 0x46, 0xE0, 0x3D, 0x7C, 0xF6,
0x2F, 0x34, 0xE7, 0x02, 0x45, 0x7B, 0x02, 0xA4, 0xCF, 0x5D, 0x9D, 0xD5, 0x3C, 0xA5, 0x3A, 0x7C,
0xA6, 0x29, 0x78, 0x8C, 0x67, 0xCA, 0x08, 0xBF, 0xEC, 0xCA, 0x43, 0xA9, 0x57, 0xAD, 0x16, 0xC9,
0x4E, 0x1C, 0xD8, 0x75, 0xCA, 0x10, 0x7D, 0xCE, 0x7E, 0x01, 0x18, 0xF0, 0xDF, 0x6B, 0xFE, 0xE5,
0x1D, 0xDB, 0xD9, 0x91, 0xC2, 0x6E, 0x60, 0xCD, 0x48, 0x58, 0xAA, 0x59, 0x2C, 0x82, 0x00, 0x75,
0xF2, 0x9F, 0x52, 0x6C, 0x91, 0x7C, 0x6F, 0xE5, 0x40, 0x3E, 0xA7, 0xD4, 0xA5, 0x0C, 0xEC, 0x3B,
0x73, 0x84, 0xDE, 0x88, 0x6E, 0x82, 0xD2, 0xEB, 0x4D, 0x4E, 0x42, 0xB5, 0xF2, 0xB1, 0x49, 0xA8,
0x1E, 0xA7, 0xCE, 0x71, 0x44, 0xDC, 0x29, 0x94, 0xCF, 0xC4, 0x4E, 0x1F, 0x91, 0xCB, 0xD4, 0x95,
0x00, 0x01, 0x00, 0x01
};

View File

@ -16,12 +16,16 @@ void hexdump12( const QByteArray &d, int from = 0, int len = -1 );
//simplified functions for crypto shit
void AesSetKey( const QByteArray key );
QByteArray AesDecrypt( quint16 index, const QByteArray source );
QByteArray AesEncrypt( quint16 index, const QByteArray source );
QByteArray GetSha1( QByteArray stuff );
//get a padded version of the given buffer
QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo );
//read a file into a bytearray
QByteArray ReadFile( const QString &path );
//keep track of the last folder browsed to when looking for files
extern QString currentDir;
@ -31,4 +35,8 @@ extern QString cachePath;
//folder to use as the base path for the nand
extern QString nandPath;
#define CERTS_DAT_SIZE 2560
extern const quint8 certs_dat[ CERTS_DAT_SIZE ];
extern const quint8 root_dat[];
#endif // TOOLS_H

View File

@ -8,7 +8,10 @@ Wad::Wad( const QByteArray stuff )
{
ok = false;
if( stuff.size() < 0x80 )//less than this and there is definitely nothing there
{
Err( "Size is < 0x80" );
return;
}
QByteArray copy = stuff;
QBuffer b( &copy );
@ -27,6 +30,7 @@ Wad::Wad( const QByteArray stuff )
if( tmp != 0x49730000 && tmp != 0x69620000 && tmp != 0x426b0000 )
{
b.close();
hexdump( stuff, 0, 0x40 );
Err( "Bad file magic word" );
return;
}
@ -71,11 +75,14 @@ Wad::Wad( const QByteArray stuff )
Ticket ticket( tikData );
Tmd t( tmdData );
//hexdump( tikData );
//hexdump( tmdData );
if( ticket.Tid() != t.Tid() )
qWarning() << "wad contains 2 different TIDs";
quint32 cnt = qFromBigEndian( t.payload()->num_contents );
qDebug() << "Wad contains" << hex << cnt << "contents";
quint32 cnt = t.Count();
//qDebug() << "Wad contains" << hex << cnt << "contents";
//another quick sanity check
quint32 totalSize = 0;
@ -87,42 +94,395 @@ Wad::Wad( const QByteArray stuff )
Err( "Size of all the apps in the tmd is greater than the size in the wad header" );
return;
}
/*
void AesSetKey( const QByteArray key );
QByteArray AesDecrypt( quint16 index, const QByteArray source );
*/
//read all the contents, check the hash, and remember the data ( still encrypted )
for( quint32 i = 0; i < cnt; i++ )
{
quint32 s = RU( 0x40, t.Size( i ) );
//qDebug() << "content" << i << "is at" << hex << pos << "with size" << s;
QByteArray encData = stuff.mid( pos, s );
pos += s;
//doing this here in case there is some other object that is using the AES that would change the key on us
AesSetKey( ticket.DecryptedKey() );
QByteArray decData = AesDecrypt( i, encData );
decData.resize( t.Size( i ) );
QByteArray realHash = GetSha1( decData );
if( realHash != t.Hash( i ) )
{
Err( QString( "hash doesnt match for content %1" ).arg( i ) );
}
partsEnc << encData;
}
//Data = stuff.mid( pos, Size );
//pos += RU( 0x40, Size );
//wtf? some VC titles have a full banner as the footer? maybe somebody's wad packer is busted
//QByteArray footer = stuff.mid( pos, stuff.size() - pos );
//qDebug() << "footer";
//hexdump( footer );
ok = true;
}
Wad::Wad( QList< QByteArray > stuff, bool encrypted )
{
if( stuff.size() < 3 )
{
Err( "Cant treate a wad with < 3 items" );
return;
}
tmdData = stuff.at( 0 );
tikData = stuff.at( 1 );
Ticket ticket( tikData );
Tmd t( tmdData );
quint16 cnt = stuff.size() - 2;
if( cnt != t.Count() )
{
Err( "The number of items given doesnt match the number in the tmd" );
return;
}
for( quint16 i = 0; i < cnt; i++ )
{
QByteArray encData;
if( encrypted )
{
encData = stuff.at( i + 2 );
}
else
{
QByteArray decDataPadded = PaddedByteArray( stuff.at( i + 2 ), 0x40 );
//doing this here in case there is some other object that is using the AES that would change the key on us
AesSetKey( ticket.DecryptedKey() );
encData = AesEncrypt( i, decDataPadded );
}
partsEnc << encData;
}
ok = true;
}
void Wad::SetCert( const QByteArray stuff )
{
certData = stuff;
}
quint64 Wad::Tid()
{
if( !tmdData.size() )
{
Err( "There is no data in the TMD" );
return 0;
}
Tmd t( tmdData );
return t.Tid();
}
void Wad::SetGlobalCert( const QByteArray &stuff )
{
globalCert = stuff;
}
const QByteArray Wad::Content( quint16 i )
{
if( tmdData.isEmpty() || tikData.isEmpty() )
{
Err( "Can't decryte data without a TMD and ticket" );
return QByteArray();
}
Ticket ticket( tikData );
Tmd t( tmdData );
if( partsEnc.size() != t.Count() || i >= partsEnc.size() )
{
Err( "I dont know whats going on some number is out of range and i dont like it" );
return QByteArray();
}
QByteArray encData = partsEnc.at( i );
AesSetKey( ticket.DecryptedKey() );
QByteArray decData = AesDecrypt( i, encData );
decData.resize( t.Size( i ) );
QByteArray realHash = GetSha1( decData );
if( realHash != t.Hash( i ) )
{
Err( QString( "hash doesnt match for content %1" ).arg( i ) );
return QByteArray();
}
return decData;
}
void Wad::Err( QString str )
{
ok = false;
errStr = str;
qWarning() << "Wad::Error" << str;
}
const QByteArray Wad::Data( quint32 magicWord, const QByteArray footer )
{
//qDebug() << "Wad::Data" << hex << magicWord << footer.size();
if( !partsEnc.size() || tmdData.isEmpty() || tikData.isEmpty() || ( certData.isEmpty() && globalCert.isEmpty() ) )
{
Err( "Dont have all the parts to make a wad" );
return QByteArray();
}
//do some brief checks to make sure that wad is good
Ticket ticket( tikData );
Tmd t( tmdData );
if( t.Tid() != ticket.Tid() )
{
Err( "Ticket and TMD have different TID" );
return QByteArray();
}
if( partsEnc.size() != t.Count() )
{
Err( "Dont have enough contents according to the TMD" );
return QByteArray();
}
//everything seems in order, try to make the thing
QByteArray cert = certData.isEmpty() ? globalCert : certData;
quint32 certSize = cert.size();
quint32 tikSize = ticket.SignedSize();
quint32 tmdSize = t.SignedSize();
quint32 appSize = 0;
quint32 footerSize = footer.size();
//add all the app sizes together and check that they match the TMD
quint16 cnt = t.Count();
for( quint16 i = 0; i < cnt; i++ )
{
quint32 s = RU( 0x40, partsEnc.at( i ).size() );
if( RU( 0x40, t.Size( i ) ) != s )
{
Err( QString( "Size of content %1 is bad ( %2, %3, %4 )" ).arg( i )
.arg( t.Size( i ), 0, 16 )
.arg( RU( 0x40, t.Size( i ) ), 0, 16 )
.arg( s, 0, 16 ) );
return QByteArray();
}
appSize += s;
}
QByteArray header( 0x20, '\0' );
QBuffer buf( &header );
buf.open( QIODevice::WriteOnly );
quint32 tmp = qFromBigEndian( 0x20 );//header size
buf.write( (const char*)&tmp, 4 );
tmp = qFromBigEndian( magicWord );//magic word
buf.write( (const char*)&tmp, 4 );
tmp = qFromBigEndian( certSize );
buf.write( (const char*)&tmp, 4 );
buf.seek( 0x10 );
tmp = qFromBigEndian( tikSize );
buf.write( (const char*)&tmp, 4 );
tmp = qFromBigEndian( tmdSize );
buf.write( (const char*)&tmp, 4 );
tmp = qFromBigEndian( appSize );
buf.write( (const char*)&tmp, 4 );
tmp = qFromBigEndian( footerSize );
buf.write( (const char*)&tmp, 4 );
buf.close();
//hexdump( header, 0, 0x20 );
QByteArray ret = PaddedByteArray( header, 0x40 ) + PaddedByteArray( cert, 0x40 );
//make sure we dont have the huge ticket and TMD that come when downloading from NUS
QByteArray tik = tikData;
tik.resize( tikSize );
tik = PaddedByteArray( tik, 0x40 );
QByteArray tm = tmdData;
tm.resize( tmdSize );
tm = PaddedByteArray( tm, 0x40 );
//hexdump( tik );
//hexdump( tm );
ret += tik + tm;
for( quint16 i = 0; i < cnt; i++ )
{
ret += PaddedByteArray( partsEnc.at( i ), 0x40 );
}
ret += footer;
return ret;
}
QString Wad::WadName( QString path )
{
if( !tmdData.size() )
{
Err( "There is no data in the TMD" );
return QString();
}
Tmd t( tmdData );
return WadName( t.Tid(), t.Version(), path );
}
QString Wad::WadName( quint64 tid, quint16 version, QString path )
{
quint32 type = (quint32)( ( tid >> 32 ) & 0xffffffff );
quint32 base = (quint32)( tid & 0xffffffff );
quint8 reg = (quint8)( tid & 0xff );
quint32 regionFree = ( ( base >> 8 ) & 0xffffff ) << 8;
QString region;
switch( reg )
{
case 0x45:
region = "US";
break;
case 0x50:
region = "EU";
break;
case 0x4A:
region = "JP";
break;
case 0x4B:
region = "KO";//is this correct?? i have no korean games
break;
default:
break;
}
QString name;
switch( type )
{
case 1:
switch( base )
{
case 1:
name = QString( "BOOT2-v%1-64" ).arg( version );
break;
case 2:
name = QString( "RVL-WiiSystemmenu-v%1" ).arg( version );
break;
case 0x100:
name = QString( "RVL-bc-v%1" ).arg( version );
break;
case 0x101:
name = QString( "RVL-mios-v%1" ).arg( version );
break;
default:
if( base > 0xff )
break;
name = QString( "IOS%1-64-v%2" ).arg( base ).arg( version );
break;
}
break;
case 0x10002:
switch( base )
{
case 0x48414141://HAAA
name = QString( "RVL-photo-v%1" ).arg( version );
break;
case 0x48415941://HAYA
name = QString( "RVL-photo2-v%1" ).arg( version );
break;
case 0x48414241://HABA
name = QString( "RVL-Shopping-v%1" ).arg( version );
break;
case 0x48414341://HACA
name = QString( "RVL-NigaoeNR-v%1" ).arg( version );//mii channel
break;
case 0x48414741://HAGA
name = QString( "RVL-News-v%1" ).arg( version );
break;
case 0x48414641://HAFA
name = QString( "RVL-Weather-v%1" ).arg( version );
break;
default:
switch( regionFree )
{
case 0x48414600://HAF?
name = QString( "RVL-Forecast_%1-v%2" ).arg( region ).arg( version );
break;
case 0x48414700://HAG?
name = QString( "RVL-News_%1-v%2" ).arg( region ).arg( version );
break;
default:
break;
}
break;
}
break;
case 0x10008:
switch( regionFree )
{
case 0x48414b00://HAK?
name = QString( "RVL-Eulav_%1-v%2" ).arg( region ).arg( version );
break;
case 0x48414c00://HAL?
name = QString( "RVL-Rgnsel_%1-v%2" ).arg( region ).arg( version );
break;
default:
break;
}
break;
default:
break;
}
if( name.isEmpty() )
return QString();
if( path.isEmpty() )
return name + ".wad.out.wad";
QString ret = name + ".wad.out.wad";
int i = 1;
while( QFile::exists( path + "/" + ret ) )
{
ret = name + QString( "(copy %1)" ).arg( i ) + ".wad.out.wad";
i++;
}
return ret;
}
QByteArray Wad::FromDirectory( QDir dir )
{
QFileInfoList tmds = dir.entryInfoList( QStringList() << "*.tmd", QDir::Files );
if( tmds.isEmpty() )
{
qWarning() << "Wad::FromDirectory -> no tmd found in" << dir.absolutePath();
return QByteArray();
}
QByteArray tmdD = ReadFile( tmds.at( 0 ).absoluteFilePath() );
if( tmdD.isEmpty() )
return QByteArray();
QFileInfoList tiks = dir.entryInfoList( QStringList() << "*.tik", QDir::Files );
if( tiks.isEmpty() )
{
qWarning() << "Wad::FromDirectory -> no tik found in" << dir.absolutePath();
return QByteArray();
}
QByteArray tikD = ReadFile( tiks.at( 0 ).absoluteFilePath() );
if( tikD.isEmpty() )
return QByteArray();
Tmd t(tmdD );
QList<QByteArray> datas = QList<QByteArray>()<< tmdD << tikD;
quint16 cnt = t.Count();
for( quint16 i = 0; i < cnt; i++ )
{
QByteArray appD = ReadFile( dir.absoluteFilePath( t.Cid( i ) + ".app" ) );
if( appD.isEmpty() )
return QByteArray();
datas << appD;
}
Wad wad( datas, false );
if( !wad.IsOk() )
return QByteArray();
QFileInfoList certs = dir.entryInfoList( QStringList() << "*.cert", QDir::Files );
if( !certs.isEmpty() )
{
QByteArray certD = ReadFile( certs.at( 0 ).absoluteFilePath() );
if( !certD.isEmpty() )
wad.SetCert( certD );
}
QByteArray ret = wad.Data();
return ret;
}

View File

@ -37,8 +37,7 @@ public:
static void SetGlobalCert( const QByteArray &stuff );
//pack a wad from the given directory
// use footer to decide if a footer will be added
static QByteArray FromDirectory( QDir dir, bool footer = false );
static QByteArray FromDirectory( QDir dir );
//get a assembled wad from the list of parts
//the first one should be the tmd, the second one the ticket, and the rest are the contents, in order
@ -46,7 +45,7 @@ public:
static QByteArray FromPartList( QList< QByteArray > stuff, bool isEncrypted = true );
//get all the parts of this wad put together in a wad ready for writing to disc or whatever
const QByteArray Data();
const QByteArray Data( quint32 magicWord = 0x49730000, const QByteArray footer = QByteArray() );
//get the decrypted data from a content
const QByteArray Content( quint16 i );
@ -54,6 +53,14 @@ public:
//get the last error encountered while trying to do something
const QString LastError(){ return errStr; }
//get a name for a wad as it would be seen in a wii disc update partition
//if a path is given, it will check that path for existing wads with the name and append a number to the end "(1)" to avoid duplicate files
//returns an empty string if it cant guess the title based on TID
static QString WadName( quint64 tid, quint16 version, QString path = QString() );
QString WadName( QString path = QString() );
private:
bool ok;
QString errStr;