* adding a few different projects. some of them have hardcoded paths and whatnot that may need to be changed or removed

This commit is contained in:
giantpune@gmail.com 2010-12-08 07:26:18 +00:00
parent d226d57450
commit 00a8bfcb56
58 changed files with 7942 additions and 0 deletions

57
.gitattributes vendored
View File

@ -1 +1,58 @@
* text=auto !eol
nandExtract/aes.c -text
nandExtract/aes.h -text
nandExtract/includes.h -text
nandExtract/main.cpp -text
nandExtract/nandExtract.pro -text
nandExtract/nandExtract.pro.user -text
nandExtract/nandbin.cpp -text
nandExtract/nandbin.h -text
nandExtract/nandwindow.cpp -text
nandExtract/nandwindow.h -text
nandExtract/nandwindow.ui -text
nandExtract/tools.cpp -text
nandExtract/tools.h -text
nand_dump/aes.c -text
nand_dump/aes.h -text
nand_dump/includes.h -text
nand_dump/main.cpp -text
nand_dump/mainwindow.cpp -text
nand_dump/mainwindow.h -text
nand_dump/mainwindow.ui -text
nand_dump/nand.pro -text
nand_dump/nanddump.cpp -text
nand_dump/nanddump.h -text
nand_dump/nusdownloader.cpp -text
nand_dump/nusdownloader.h -text
nand_dump/settingtxtdialog.cpp -text
nand_dump/settingtxtdialog.h -text
nand_dump/settingtxtdialog.ui -text
nand_dump/sha1.c -text
nand_dump/sha1.h -text
nand_dump/sharedcontentmap.cpp -text
nand_dump/sharedcontentmap.h -text
nand_dump/tiktmd.cpp -text
nand_dump/tiktmd.h -text
nand_dump/tools.cpp -text
nand_dump/tools.h -text
nand_dump/uidmap.cpp -text
nand_dump/uidmap.h -text
nand_dump/wad.cpp -text
nand_dump/wad.h -text
saveToy/includes.h -text
saveToy/main.cpp -text
saveToy/mainwindow.cpp -text
saveToy/mainwindow.h -text
saveToy/mainwindow.ui -text
saveToy/noBanner.png -text
saveToy/noIcon.png -text
saveToy/rc.qrc -text
saveToy/saveToy.pro -text
saveToy/savebanner.cpp -text
saveToy/savebanner.h -text
saveToy/savelistitem.cpp -text
saveToy/savelistitem.h -text
saveToy/saveloadthread.cpp -text
saveToy/saveloadthread.h -text
saveToy/tools.cpp -text
saveToy/tools.h -text

400
nandExtract/aes.c Normal file
View File

@ -0,0 +1,400 @@
/* Rijndael Block Cipher - aes.c
Written by Mike Scott 21st April 1999
mike@compapp.dcu.ie
Permission for free direct or derivative use is granted subject
to compliance with any conditions that the originators of the
algorithm place on its exploitation.
*/
//#include <stdio.h>
//#include <stdlib.h>
#include <string.h>
#define u8 unsigned char /* 8 bits */
#define u32 unsigned long /* 32 bits */
#define u64 unsigned long long
/* rotates x one bit to the left */
#define ROTL(x) (((x)>>7)|((x)<<1))
/* Rotates 32-bit word left by 1, 2 or 3 byte */
#define ROTL8(x) (((x)<<8)|((x)>>24))
#define ROTL16(x) (((x)<<16)|((x)>>16))
#define ROTL24(x) (((x)<<24)|((x)>>8))
/* Fixed Data */
static u8 InCo[4]={0xB,0xD,0x9,0xE}; /* Inverse Coefficients */
static u8 fbsub[256];
static u8 rbsub[256];
static u8 ptab[256],ltab[256];
static u32 ftable[256];
static u32 rtable[256];
static u32 rco[30];
/* Parameter-dependent data */
int Nk,Nb,Nr;
u8 fi[24],ri[24];
u32 fkey[120];
u32 rkey[120];
static u32 pack(u8 *b)
{ /* pack bytes into a 32-bit Word */
return ((u32)b[3]<<24)|((u32)b[2]<<16)|((u32)b[1]<<8)|(u32)b[0];
}
static void unpack(u32 a,u8 *b)
{ /* unpack bytes from a word */
b[0]=(u8)a;
b[1]=(u8)(a>>8);
b[2]=(u8)(a>>16);
b[3]=(u8)(a>>24);
}
static u8 xtime(u8 a)
{
u8 b;
if (a&0x80) b=0x1B;
else b=0;
a<<=1;
a^=b;
return a;
}
static u8 bmul(u8 x,u8 y)
{ /* x.y= AntiLog(Log(x) + Log(y)) */
if (x && y) return ptab[(ltab[x]+ltab[y])%255];
else return 0;
}
static u32 SubByte(u32 a)
{
u8 b[4];
unpack(a,b);
b[0]=fbsub[b[0]];
b[1]=fbsub[b[1]];
b[2]=fbsub[b[2]];
b[3]=fbsub[b[3]];
return pack(b);
}
static u8 product(u32 x,u32 y)
{ /* dot product of two 4-byte arrays */
u8 xb[4],yb[4];
unpack(x,xb);
unpack(y,yb);
return bmul(xb[0],yb[0])^bmul(xb[1],yb[1])^bmul(xb[2],yb[2])^bmul(xb[3],yb[3]);
}
static u32 InvMixCol(u32 x)
{ /* matrix Multiplication */
u32 y,m;
u8 b[4];
m=pack(InCo);
b[3]=product(m,x);
m=ROTL24(m);
b[2]=product(m,x);
m=ROTL24(m);
b[1]=product(m,x);
m=ROTL24(m);
b[0]=product(m,x);
y=pack(b);
return y;
}
u8 ByteSub(u8 x)
{
u8 y=ptab[255-ltab[x]]; /* multiplicative inverse */
x=y; x=ROTL(x);
y^=x; x=ROTL(x);
y^=x; x=ROTL(x);
y^=x; x=ROTL(x);
y^=x; y^=0x63;
return y;
}
void gentables(void)
{ /* generate tables */
int i;
u8 y,b[4];
/* use 3 as primitive root to generate power and log tables */
ltab[0]=0;
ptab[0]=1; ltab[1]=0;
ptab[1]=3; ltab[3]=1;
for (i=2;i<256;i++)
{
ptab[i]=ptab[i-1]^xtime(ptab[i-1]);
ltab[ptab[i]]=i;
}
/* affine transformation:- each bit is xored with itself shifted one bit */
fbsub[0]=0x63;
rbsub[0x63]=0;
for (i=1;i<256;i++)
{
y=ByteSub((u8)i);
fbsub[i]=y; rbsub[y]=i;
}
for (i=0,y=1;i<30;i++)
{
rco[i]=y;
y=xtime(y);
}
/* calculate forward and reverse tables */
for (i=0;i<256;i++)
{
y=fbsub[i];
b[3]=y^xtime(y); b[2]=y;
b[1]=y; b[0]=xtime(y);
ftable[i]=pack(b);
y=rbsub[i];
b[3]=bmul(InCo[0],y); b[2]=bmul(InCo[1],y);
b[1]=bmul(InCo[2],y); b[0]=bmul(InCo[3],y);
rtable[i]=pack(b);
}
}
void gkey(int nb,int nk,u8 *key)
{ /* blocksize=32*nb bits. Key=32*nk bits */
/* currently nb,bk = 4, 6 or 8 */
/* key comes as 4*Nk bytes */
/* Key Scheduler. Create expanded encryption key */
int i,j,k,m,N;
int C1,C2,C3;
u32 CipherKey[8];
Nb=nb; Nk=nk;
/* Nr is number of rounds */
if (Nb>=Nk) Nr=6+Nb;
else Nr=6+Nk;
C1=1;
if (Nb<8) { C2=2; C3=3; }
else { C2=3; C3=4; }
/* pre-calculate forward and reverse increments */
for (m=j=0;j<nb;j++,m+=3)
{
fi[m]=(j+C1)%nb;
fi[m+1]=(j+C2)%nb;
fi[m+2]=(j+C3)%nb;
ri[m]=(nb+j-C1)%nb;
ri[m+1]=(nb+j-C2)%nb;
ri[m+2]=(nb+j-C3)%nb;
}
N=Nb*(Nr+1);
for (i=j=0;i<Nk;i++,j+=4)
{
CipherKey[i]=pack(key+j);
}
for (i=0;i<Nk;i++) fkey[i]=CipherKey[i];
for (j=Nk,k=0;j<N;j+=Nk,k++)
{
fkey[j]=fkey[j-Nk]^SubByte(ROTL24(fkey[j-1]))^rco[k];
if (Nk<=6)
{
for (i=1;i<Nk && (i+j)<N;i++)
fkey[i+j]=fkey[i+j-Nk]^fkey[i+j-1];
}
else
{
for (i=1;i<4 &&(i+j)<N;i++)
fkey[i+j]=fkey[i+j-Nk]^fkey[i+j-1];
if ((j+4)<N) fkey[j+4]=fkey[j+4-Nk]^SubByte(fkey[j+3]);
for (i=5;i<Nk && (i+j)<N;i++)
fkey[i+j]=fkey[i+j-Nk]^fkey[i+j-1];
}
}
/* now for the expanded decrypt key in reverse order */
for (j=0;j<Nb;j++) rkey[j+N-Nb]=fkey[j];
for (i=Nb;i<N-Nb;i+=Nb)
{
k=N-Nb-i;
for (j=0;j<Nb;j++) rkey[k+j]=InvMixCol(fkey[i+j]);
}
for (j=N-Nb;j<N;j++) rkey[j-N+Nb]=fkey[j];
}
/* There is an obvious time/space trade-off possible here. *
* Instead of just one ftable[], I could have 4, the other *
* 3 pre-rotated to save the ROTL8, ROTL16 and ROTL24 overhead */
void encrypt(u8 *buff)
{
int i,j,k,m;
u32 a[8],b[8],*x,*y,*t;
for (i=j=0;i<Nb;i++,j+=4)
{
a[i]=pack(buff+j);
a[i]^=fkey[i];
}
k=Nb;
x=a; y=b;
/* State alternates between a and b */
for (i=1;i<Nr;i++)
{ /* Nr is number of rounds. May be odd. */
/* if Nb is fixed - unroll this next
loop and hard-code in the values of fi[] */
for (m=j=0;j<Nb;j++,m+=3)
{ /* deal with each 32-bit element of the State */
/* This is the time-critical bit */
y[j]=fkey[k++]^ftable[(u8)x[j]]^
ROTL8(ftable[(u8)(x[fi[m]]>>8)])^
ROTL16(ftable[(u8)(x[fi[m+1]]>>16)])^
ROTL24(ftable[(u8)(x[fi[m+2]]>>24)]);
}
t=x; x=y; y=t; /* swap pointers */
}
/* Last Round - unroll if possible */
for (m=j=0;j<Nb;j++,m+=3)
{
y[j]=fkey[k++]^(u32)fbsub[(u8)x[j]]^
ROTL8((u32)fbsub[(u8)(x[fi[m]]>>8)])^
ROTL16((u32)fbsub[(u8)(x[fi[m+1]]>>16)])^
ROTL24((u32)fbsub[(u8)(x[fi[m+2]]>>24)]);
}
for (i=j=0;i<Nb;i++,j+=4)
{
unpack(y[i],(u8 *)&buff[j]);
x[i]=y[i]=0; /* clean up stack */
}
return;
}
void decrypt(u8 *buff)
{
int i,j,k,m;
u32 a[8],b[8],*x,*y,*t;
for (i=j=0;i<Nb;i++,j+=4)
{
a[i]=pack(buff+j);
a[i]^=rkey[i];
}
k=Nb;
x=a; y=b;
/* State alternates between a and b */
for (i=1;i<Nr;i++)
{ /* Nr is number of rounds. May be odd. */
/* if Nb is fixed - unroll this next
loop and hard-code in the values of ri[] */
for (m=j=0;j<Nb;j++,m+=3)
{ /* This is the time-critical bit */
y[j]=rkey[k++]^rtable[(u8)x[j]]^
ROTL8(rtable[(u8)(x[ri[m]]>>8)])^
ROTL16(rtable[(u8)(x[ri[m+1]]>>16)])^
ROTL24(rtable[(u8)(x[ri[m+2]]>>24)]);
}
t=x; x=y; y=t; /* swap pointers */
}
/* Last Round - unroll if possible */
for (m=j=0;j<Nb;j++,m+=3)
{
y[j]=rkey[k++]^(u32)rbsub[(u8)x[j]]^
ROTL8((u32)rbsub[(u8)(x[ri[m]]>>8)])^
ROTL16((u32)rbsub[(u8)(x[ri[m+1]]>>16)])^
ROTL24((u32)rbsub[(u8)(x[ri[m+2]]>>24)]);
}
for (i=j=0;i<Nb;i++,j+=4)
{
unpack(y[i],(u8 *)&buff[j]);
x[i]=y[i]=0; /* clean up stack */
}
return;
}
void aes_set_key(u8 *key) {
gentables();
gkey(4, 4, key);
}
// CBC mode decryption
void aes_decrypt(u8 *iv, u8 *inbuf, u8 *outbuf, unsigned long long len) {
u8 block[16];
unsigned int blockno = 0, i;
// debug_printf("aes_decrypt(%p, %p, %p, %lld)\n", iv, inbuf, outbuf, len);
for (blockno = 0; blockno <= (len / sizeof(block)); blockno++) {
unsigned int fraction;
if (blockno == (len / sizeof(block))) { // last block
fraction = len % sizeof(block);
if (fraction == 0) break;
memset(block, 0, sizeof(block));
} else fraction = 16;
// debug_printf("block %d: fraction = %d\n", blockno, fraction);
memcpy(block, inbuf + blockno * sizeof(block), fraction);
decrypt(block);
u8 *ctext_ptr;
if (blockno == 0) ctext_ptr = iv;
else ctext_ptr = inbuf + (blockno-1) * sizeof(block);
for(i=0; i < fraction; i++)
outbuf[blockno * sizeof(block) + i] =
ctext_ptr[i] ^ block[i];
// debug_printf("Block %d output: ", blockno);
// hexdump(outbuf + blockno*sizeof(block), 16);
}
}
// CBC mode encryption
void aes_encrypt(u8 *iv, u8 *inbuf, u8 *outbuf, unsigned long long len) {
u8 block[16];
unsigned int blockno = 0, i;
// debug_printf("aes_decrypt(%p, %p, %p, %lld)\n", iv, inbuf, outbuf, len);
for (blockno = 0; blockno <= (len / sizeof(block)); blockno++) {
unsigned int fraction;
if (blockno == (len / sizeof(block))) { // last block
fraction = len % sizeof(block);
if (fraction == 0) break;
memset(block, 0, sizeof(block));
} else fraction = 16;
// debug_printf("block %d: fraction = %d\n", blockno, fraction);
memcpy(block, inbuf + blockno * sizeof(block), fraction);
for(i=0; i < fraction; i++)
block[i] = inbuf[blockno * sizeof(block) + i] ^ iv[i];
encrypt(block);
memcpy(iv, block, sizeof(block));
memcpy(outbuf + blockno * sizeof(block), block, sizeof(block));
// debug_printf("Block %d output: ", blockno);
// hexdump(outbuf + blockno*sizeof(block), 16);
}
}

18
nandExtract/aes.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef __AES_H_
#define __AES_H_
#include "includes.h"
#ifdef __cplusplus
extern "C" {
#endif
void aes_encrypt( quint8 *iv, const quint8 *inbuf, quint8 *outbuf, unsigned long long len );
void aes_decrypt( quint8 *iv, const quint8 *inbuf, quint8 *outbuf, unsigned long long len );
void aes_set_key( const quint8 *key );
#ifdef __cplusplus
}
#endif
#endif //__AES_H_

35
nandExtract/includes.h Executable file
View File

@ -0,0 +1,35 @@
#ifndef INCLUDES_H
#define INCLUDES_H
#include <QAction>
#include <QDialog>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QLayout>
#include <QList>
#include <QMainWindow>
#include <QMenu>
#include <QMessageBox>
//#include <QNetworkAccessManager>
//#include <QNetworkReply>
#include <QObject>
#include <QProcess>
#include <QQueue>
#include <QSettings>
#include <QtDebug>
#include <QTimer>
#include <QtGui>
#include <QtGui/QMainWindow>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <qpushbutton.h>
#include <QWidget>
//#include <QDomDocument>
//#include <QDomElement>
#endif // INCLUDES_H

10
nandExtract/main.cpp Executable file
View File

@ -0,0 +1,10 @@
#include <QtGui/QApplication>
#include "nandwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
NandWindow w;
w.show();
return a.exec();
}

16
nandExtract/nandExtract.pro Executable file
View File

@ -0,0 +1,16 @@
# -------------------------------------------------
# Project created by QtCreator 2010-12-06T03:40:50
# -------------------------------------------------
TARGET = nandExtract
TEMPLATE = app
SOURCES += main.cpp \
nandwindow.cpp \
nandbin.cpp \
tools.cpp \
aes.c
HEADERS += nandwindow.h \
nandbin.h \
includes.h \
tools.h \
aes.h
FORMS += nandwindow.ui

View File

@ -0,0 +1,113 @@
<!DOCTYPE QtCreatorProject>
<qtcreator>
<data>
<variable>ProjectExplorer.Project.ActiveTarget</variable>
<value type="int">0</value>
</data>
<data>
<variable>ProjectExplorer.Project.EditorSettings</variable>
<valuemap type="QVariantMap">
<value key="EditorConfiguration.Codec" type="QByteArray">System</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.Target.0</variable>
<valuemap type="QVariantMap">
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Desktop</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.Target.DesktopTarget</value>
<value key="ProjectExplorer.Target.ActiveBuildConfiguration" type="int">1</value>
<value key="ProjectExplorer.Target.ActiveRunConfiguration" type="int">0</value>
<valuemap key="ProjectExplorer.Target.BuildConfiguration.0" type="QVariantMap">
<valuemap key="ProjectExplorer.BuildConfiguration.BuildStep.0" type="QVariantMap">
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">qmake</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">QtProjectManager.QMakeBuildStep</value>
<valuelist key="QtProjectManager.QMakeBuildStep.QMakeArguments" type="QVariantList"/>
</valuemap>
<valuemap key="ProjectExplorer.BuildConfiguration.BuildStep.1" type="QVariantMap">
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Make</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.MakeStep</value>
<value key="Qt4ProjectManager.MakeStep.Clean" type="bool">false</value>
<valuelist key="Qt4ProjectManager.MakeStep.MakeArguments" type="QVariantList"/>
<value key="Qt4ProjectManager.MakeStep.MakeCommand" type="QString"></value>
</valuemap>
<value key="ProjectExplorer.BuildConfiguration.BuildStepsCount" type="int">2</value>
<valuemap key="ProjectExplorer.BuildConfiguration.CleanStep.0" type="QVariantMap">
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Make</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.MakeStep</value>
<value key="Qt4ProjectManager.MakeStep.Clean" type="bool">true</value>
<valuelist key="Qt4ProjectManager.MakeStep.MakeArguments" type="QVariantList">
<value type="QString">clean</value>
</valuelist>
<value key="Qt4ProjectManager.MakeStep.MakeCommand" type="QString"></value>
</valuemap>
<value key="ProjectExplorer.BuildConfiguration.CleanStepsCount" type="int">1</value>
<value key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment" type="bool">false</value>
<valuelist key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges" type="QVariantList"/>
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Debug</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration" type="int">2</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.BuildDirectory" type="QString">/home/j/c/QtWii/nandExtract-build-desktop</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.QtVersionId" type="int">8</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.ToolChain" type="int">0</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild" type="bool">true</value>
</valuemap>
<valuemap key="ProjectExplorer.Target.BuildConfiguration.1" type="QVariantMap">
<valuemap key="ProjectExplorer.BuildConfiguration.BuildStep.0" type="QVariantMap">
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">qmake</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">QtProjectManager.QMakeBuildStep</value>
<valuelist key="QtProjectManager.QMakeBuildStep.QMakeArguments" type="QVariantList"/>
</valuemap>
<valuemap key="ProjectExplorer.BuildConfiguration.BuildStep.1" type="QVariantMap">
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Make</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.MakeStep</value>
<value key="Qt4ProjectManager.MakeStep.Clean" type="bool">false</value>
<valuelist key="Qt4ProjectManager.MakeStep.MakeArguments" type="QVariantList"/>
<value key="Qt4ProjectManager.MakeStep.MakeCommand" type="QString"></value>
</valuemap>
<value key="ProjectExplorer.BuildConfiguration.BuildStepsCount" type="int">2</value>
<valuemap key="ProjectExplorer.BuildConfiguration.CleanStep.0" type="QVariantMap">
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Make</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.MakeStep</value>
<value key="Qt4ProjectManager.MakeStep.Clean" type="bool">true</value>
<valuelist key="Qt4ProjectManager.MakeStep.MakeArguments" type="QVariantList">
<value type="QString">clean</value>
</valuelist>
<value key="Qt4ProjectManager.MakeStep.MakeCommand" type="QString"></value>
</valuemap>
<value key="ProjectExplorer.BuildConfiguration.CleanStepsCount" type="int">1</value>
<value key="ProjectExplorer.BuildConfiguration.ClearSystemEnvironment" type="bool">false</value>
<valuelist key="ProjectExplorer.BuildConfiguration.UserEnvironmentChanges" type="QVariantList"/>
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">Release</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.Qt4BuildConfiguration</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.BuildConfiguration" type="int">0</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.BuildDirectory" type="QString">/home/j/c/QtWii/nandExtract</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.QtVersionId" type="int">8</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.ToolChain" type="int">0</value>
<value key="Qt4ProjectManager.Qt4BuildConfiguration.UseShadowBuild" type="bool">true</value>
</valuemap>
<value key="ProjectExplorer.Target.BuildConfigurationCount" type="int">2</value>
<valuemap key="ProjectExplorer.Target.RunConfiguration.0" type="QVariantMap">
<value key="ProjectExplorer.ProjectConfiguration.DisplayName" type="QString">nandExtract</value>
<value key="ProjectExplorer.ProjectConfiguration.Id" type="QString">Qt4ProjectManager.Qt4RunConfiguration</value>
<value key="Qt4ProjectManager.Qt4RunConfiguration.BaseEnvironmentBase" type="int">2</value>
<valuelist key="Qt4ProjectManager.Qt4RunConfiguration.CommandLineArguments" type="QVariantList"/>
<value key="Qt4ProjectManager.Qt4RunConfiguration.ProFile" type="QString">nandExtract.pro</value>
<value key="Qt4ProjectManager.Qt4RunConfiguration.UseDyldImageSuffix" type="bool">false</value>
<value key="Qt4ProjectManager.Qt4RunConfiguration.UseTerminal" type="bool">false</value>
<valuelist key="Qt4ProjectManager.Qt4RunConfiguration.UserEnvironmentChanges" type="QVariantList"/>
<value key="Qt4ProjectManager.Qt4RunConfiguration.UserSetName" type="bool">false</value>
<value key="Qt4ProjectManager.Qt4RunConfiguration.UserSetWorkingDirectory" type="bool">false</value>
<value key="Qt4ProjectManager.Qt4RunConfiguration.UserWorkingDirectory" type="QString"></value>
</valuemap>
<value key="ProjectExplorer.Target.RunConfigurationCount" type="int">1</value>
</valuemap>
</data>
<data>
<variable>ProjectExplorer.Project.TargetCount</variable>
<value type="int">1</value>
</data>
<data>
<variable>ProjectExplorer.Project.Updater.FileVersion</variable>
<value type="int">4</value>
</data>
</qtcreator>

536
nandExtract/nandbin.cpp Executable file
View File

@ -0,0 +1,536 @@
#include "nandbin.h"
#include "tools.h"
NandBin::NandBin( QObject * parent, const QString &path ) : QObject( parent )
{
type = -1;
fatNames = false;
root = NULL;
if( !path.isEmpty() )
SetPath( path );
}
NandBin::~NandBin()
{
if( f.isOpen() )
f.close();
if( root )
delete root;
}
bool NandBin::SetPath( const QString &path )
{
nandPath = path;
if( f.isOpen() )
f.close();
f.setFileName( path );
bool ret = !f.exists() || !f.open( QIODevice::ReadOnly );
if( ret )
{
emit SendError( tr( "Cant open %1" ).arg( path ) );
}
return !ret;
}
QTreeWidgetItem *NandBin::GetTree()
{
return root->clone();
}
bool NandBin::ExtractToDir( QTreeWidgetItem *item, const QString &path )
{
if( !item )
return false;
bool ok = false;
quint16 entry = item->text( 1 ).toInt( &ok );
if( !ok )
{
emit SendError( tr( "Error converting entry(%1) to a number" ).arg( item->text( 1 ) ) );
return false;
}
return ExtractFST( entry, path );
return true;
}
bool NandBin::AddChildren( QTreeWidgetItem *parent, quint16 entry )
{
if( entry >= 0x17ff )
{
emit SendError( tr( "entry is above 0x17ff mmmmkay [ 0x%1 ]" ).arg( entry, 0, 16 ) );
return false;
}
fst_t fst = GetFST( entry );
if( !fst.filename[ 0 ] )//something is amiss, better quit now
return false;
if( fst.sib != 0xffff )
{
if( !AddChildren( parent, fst.sib ) )
return false;
}
QStringList text;
QString name = FstName( fst );
QString en = QString( "%1" ).arg( entry );
QString size = QString( "%1" ).arg( fst.size, 0, 16 );
QString uid = QString( "%1" ).arg( fst.uid, 8, 16, QChar( '0' ) );
QString gid = QString( "%1 (\"%2%3\")" ).arg( fst.gid, 4, 16, QChar( '0' ) )
.arg( QChar( ascii( (char)( (fst.gid >> 8) & 0xff ) ) ) )
.arg( QChar( ascii( (char)( (fst.gid) & 0xff ) ) ) );
QString x3 = QString( "%1" ).arg( fst.x3, 8, 16, QChar( '0' ) );
QString mode = QString( "%1" ).arg( fst.mode, 2, 16, QChar( '0' ) );
QString attr = QString( "%1" ).arg( fst.attr, 2, 16, QChar( '0' ) );
text << name << en << size << uid << gid << x3 << mode << attr;
QTreeWidgetItem *child = new QTreeWidgetItem( parent, text );
//try to add subfolder contents to the tree
if( !fst.mode && fst.sub != 0xffff && !AddChildren( child, fst.sub ) )
return false;
return true;
}
QString NandBin::FstName( fst_t fst )
{
QByteArray ba( (char*)fst.filename, 0xc );
QString ret = QString( ba );
if( fatNames )
ret.replace( ":", "-" );
return ret;
}
bool NandBin::ExtractFST( quint16 entry, const QString &path )
{
//qDebug() << "NandBin::ExtractFST(" << hex << entry << "," << path << ")";
fst_t fst = GetFST( entry );
if( !fst.filename[ 0 ] )//something is amiss, better quit now
return false;
if( fst.sib != 0xffff && !ExtractFST( fst.sib, path ) )
return false;
switch( fst.mode )
{
case 0:
if( !ExtractDir( fst, path ) )
return false;
break;
case 1:
if( !ExtractFile( fst, path ) )
return false;
break;
default://wtf
emit SendError( tr( "Unknown fst mode. Bailing out" ) );
return false;
break;
}
return true;
}
bool NandBin::ExtractDir( fst_t fst, QString parent )
{
//qDebug() << "NandBin::ExtractDir(" << parent << ")";
QByteArray ba( (char*)fst.filename, 0xc );
QString filename( ba );
QFileInfo fi( parent + "/" + filename );
if( filename != "/" && !fi.exists() && !QDir().mkpath( fi.absoluteFilePath() ) )
return false;
if( fst.sub != 0xffff && !ExtractFST( fst.sub, fi.absoluteFilePath() ) )
return false;
return true;
}
bool NandBin::ExtractFile( fst_t fst, QString parent )
{
QByteArray ba( (char*)fst.filename, 0xc );
QString filename( ba );
QFileInfo fi( parent + "/" + filename );
qDebug() << "extract" << fi.absoluteFilePath();
emit SendText( tr( "Extracting \"%1\"" ).arg( fi.absoluteFilePath() ) );
QByteArray data = GetFile( fst );
if( fst.size && !data.size() )//dont worry if files dont have anything in them anyways
return false;
QFile out( fi.absoluteFilePath() );
if( out.exists() )// !! delete existing files
out.remove();
if( !out.open( QIODevice::WriteOnly ) )
{
emit SendError( tr( "Can't open \"%1\" for writing" ).arg( fi.absoluteFilePath() ) );
return false;
}
out.write( data );
out.close();
return true;
}
bool NandBin::InitNand()
{
type = GetDumpType( f.size() );
if( type < 0 || type > 3 )
return false;
//qDebug() << "dump type:" << type;
if( !GetKey( type ) )
return false;
loc_super = FindSuperblock();
if( loc_super < 0 )
return false;
quint32 n_fatlen[] = { 0x010000, 0x010800, 0x010800 };
loc_fat = loc_super;
loc_fst = loc_fat + 0x0C + n_fatlen[ type ];
if( root )
delete root;
root = new QTreeWidgetItem( QStringList() << nandPath );
AddChildren( root, 0 );
ShowInfo();
return true;
}
int NandBin::GetDumpType( quint64 fileSize )
{
quint64 sizes[] = { 536870912, // type 0 | 536870912 == no ecc
553648128, // type 1 | 553648128 == ecc
553649152 }; // type 2 | 553649152 == old bootmii
for( int i = 0; i < 3; i++ )
{
if( sizes[ i ] == fileSize )
return i;
}
emit SendError( tr( "Can't tell what type of nand dump this is" ) );
return -1;
}
bool NandBin::GetKey( int type )
{
switch( type )
{
case 0:
case 1:
{
QString keyPath = nandFile;
int sl = keyPath.lastIndexOf( "/" );
if( sl == -1 )
{
emit SendError( tr( "Error getting path of keys.bin" ) );
return false;
}
keyPath.resize( sl );
keyPath += "keys.bin";
key = ReadKeyfile( keyPath );
if( key.isEmpty() )
return false;
}
break;
case 2:
{
if( !f.isOpen() )
{
emit SendError( tr( "Tried to read keys from unopened file" ) );
return false;
}
f.seek( 0x21000158 );
key = f.read( 16 );
}
break;
default:
emit SendError( tr( "Tried to read keys for unknown dump type" ) );
return false;
break;
}
return true;
}
QByteArray NandBin::ReadKeyfile( QString path )
{
QByteArray retval;
QFile f( path );
if( !f.exists() || !f.open( QIODevice::ReadOnly ) )
{
emit SendError( tr( "Can't open %1!" ).arg( path ) );
return retval;
}
if( f.size() < 0x16e )
{
f.close();
emit SendError( tr( "keys.bin is too small!" ) );
return retval;
}
f.seek( 0x158 );
retval = f.read( 16 );
f.close();
return retval;
}
qint32 NandBin::FindSuperblock()
{
if( type < 0 || type > 3 )
{
emit SendError( tr( "Tried to get superblock of unknown dump type" ) );
return -1;
}
if( !f.isOpen() )
{
emit SendError( tr( "Tried to get superblock of unopened dump" ) );
return -1;
}
quint32 loc = 0, current = 0, last = 0;
quint32 n_start[] = { 0x1FC00000, 0x20BE0000, 0x20BE0000 },
n_end[] = { 0x20000000, 0x21000000, 0x21000000 },
n_len[] = { 0x40000, 0x42000, 0x42000 };
for( loc = n_start[ type ]; loc < n_end[ type ]; loc += n_len[ type ] )
{
f.read( (char*)&current, 4 );
current = qFromBigEndian( current );
//qDebug() << "superblock" << hex << current;
if( current > last )
last = current;
else
{
//qDebug() << "superblock loc" << hex << loc - n_len[ type ];
return loc - n_len[ type ];
}
f.seek( n_len[ type ] - 4 );
}
return -1;
}
fst_t NandBin::GetFST( quint16 entry )
{
//qDebug() << "NandBin::GetFST(" << hex << entry << ")";
fst_t fst;
if( entry >= 0x17FF )
{
emit SendError( tr( "Tried to get entry above 0x17ff [ 0x%1 ]" ).arg( entry, 0, 16 ) );
fst.filename[ 0 ] = '\0';
return fst;
}
// compensate for 64 bytes of ecc data every 64 fst entries
quint32 n_fst[] = { 0, 2, 2 };
int loc_entry = ( ( ( entry / 0x40 ) * n_fst[ type ] ) + entry ) * 0x20;
if( (quint32)f.size() < loc_fst + loc_entry + sizeof( fst_t ) )
{
emit SendError( tr( "Tried to read fst_t beyond size of nand.bin" ) );
fst.filename[ 0 ] = '\0';
return fst;
}
f.seek( loc_fst + loc_entry );
f.read( (char*)&fst.filename, 0xc );
f.read( (char*)&fst.mode, 1 );
f.read( (char*)&fst.attr, 1 );
f.read( (char*)&fst.sub, 2 );
f.read( (char*)&fst.sib, 2 );
if( type && ( entry + 1 ) % 64 == 0 )//bug in other nand.bin extracterizers. the entry for every 64th fst item is inturrupeted by some ecc shit
{
f.read( (char*)&fst.size, 2 );
f.seek( f.pos() + 0x40 );
f.read( (char*)(&fst.size) + 2, 2 );
}
else
f.read( (char*)&fst.size, 4 );
f.read( (char*)&fst.uid, 4 );
f.read( (char*)&fst.gid, 2 );
f.read( (char*)&fst.x3, 4 );
fst.sub = qFromBigEndian( fst.sub );
fst.sib = qFromBigEndian( fst.sib );
fst.size = qFromBigEndian( fst.size );
fst.uid = qFromBigEndian( fst.uid );
fst.gid = qFromBigEndian( fst.gid );
fst.x3 = qFromBigEndian( fst.x3 );
fst.mode &= 1;
return fst;
}
quint16 NandBin::GetFAT( quint16 fat_entry )
{
/*
* compensate for "off-16" storage at beginning of superblock
* 53 46 46 53 XX XX XX XX 00 00 00 00
* S F F S "version" padding?
* 1 2 3 4 5 6*/
fat_entry += 6;
// location in fat of cluster chain
quint32 n_fat[] = { 0, 0x20, 0x20 };
int loc = loc_fat + ((((fat_entry / 0x400) * n_fat[type]) + fat_entry) * 2);
if( (quint32)f.size() < loc + sizeof( quint16 ) )
{
emit SendError( tr( "Tried to read FAT entry beyond size of nand.bin" ) );
return 0;
}
f.seek( loc );
quint16 ret;
f.read( (char*)&ret, 2 );
ret = qFromBigEndian( ret );
return ret;
}
QByteArray NandBin::GetCluster( quint16 cluster_entry, bool decrypt )
{
//qDebug() << "NandBin::GetCluster" << hex << cluster_entry;
quint32 n_clusterlen[] = { 0x4000, 0x4200, 0x4200 };
quint32 n_pagelen[] = { 0x800, 0x840, 0x840 };
if( f.size() < ( cluster_entry * n_clusterlen[ type ] ) + ( 8 * n_pagelen[ type ] ) )
{
emit SendError( tr( "Tried to read cluster past size of nand.bin" ) );
return QByteArray();
}
QByteArray cluster;
for( int i = 0; i < 8; i++ )
{
f.seek( ( cluster_entry * n_clusterlen[ type ] ) + ( i * n_pagelen[ type ] ) ); //seek to the beginning of the page to read
//QByteArray page = f.read( n_pagelen[ type ] ); //read the page, with ecc
QByteArray page = f.read( 0x800 ); //read the page, skip the ecc
cluster += page;
}
if( cluster.size() != 0x4000 )
{
qDebug() << "actual cluster size" << hex << cluster.size();
emit SendError( tr( "Error reading cluster" ) );
return QByteArray();
}
if( !decrypt )
return cluster;
//really redundant to do this for ever AES decryption, but the AES code only lets
//1 key set at a time and it may be changed if some other object is decrypting something else
AesSetKey( key );
QByteArray ret = AesDecrypt( 0, cluster );//TODO... is IV really always 0?
return ret;
}
QByteArray NandBin::GetFile( quint16 entry )
{
fst_t fst = GetFST( entry );
if( !fst.filename[ 0 ] )//something is amiss, better quit now
return QByteArray();
return GetFile( fst );
}
QByteArray NandBin::GetFile( fst_t fst )
{
if( !fst.size )
return QByteArray();
quint16 fat = fst.sub;
//int cluster_span = (int)( fst.size / 0x4000) + 1;
QByteArray data;
for (int i = 0; fat < 0xFFF0; i++)
{
QByteArray cluster = GetCluster( fat );
if( cluster.size() != 0x4000 )
return QByteArray();
data += cluster;
fat = GetFAT( fat );
}
//this check doesnt really seem to matter, it always appears to be 1 extra cluster added to the end
//of the file and that extra bit is dropped in this function before the data is returned.
/*if( data.size() != cluster_span * 0x4000 )
{
qDebug() << "data.size() != cluster_span * 0x4000 :: "
<< hex << data.size()
<< cluster_span
<< ( cluster_span * 0x4000 )
<< "expected size:" << hex << fst.size;
emit SendError( tr( "Error reading file [ block size is not a as expected ] %1" ).arg( FstName( fst ) ) );
}*/
if( (quint32)data.size() < fst.size )
{
qDebug() << "(quint32)data.size() < fst.size :: "
<< hex << data.size()
<< "expected size:" << hex << fst.size;
emit SendError( tr( "Error reading file [ returned data size is less that the size in the fst ]" ) );
return QByteArray();
}
if( (quint32)data.size() > fst.size )
data.resize( fst.size );//dont need to give back all the data, only up to the expected size
return data;
}
void NandBin::SetFixNamesForFAT( bool fix )
{
fatNames = fix;
}
/*
* 0xFFFB - last cluster within a chain
* 0xFFFC - reserved cluster
* 0xFFFD - bad block (marked at factory) -- you should always see these in groups of 8 (8 clusters per NAND block)
* 0xFFFE - empty (unused / available) space
*/
void NandBin::ShowInfo()
{
quint16 badBlocks = 0;
quint16 reserved = 0;
quint16 freeBlocks = 0;
QList<quint16>badOnes;
for( quint16 i = 0; i < 0x8000; i++ )
{
quint16 fat = GetFAT( i );
if( 0xfffc == fat )
reserved++;
else if( 0xfffd == fat )
{
badBlocks++;
if( i % 8 == 0 )
{
badOnes << ( i / 8 );
}
}
else if( 0xfffe == fat )
freeBlocks++;
}
if( badBlocks )
badBlocks /= 8;
if( reserved )
reserved /= 8;
if( freeBlocks )
freeBlocks /= 8;
qDebug() << "free blocks:" << hex << freeBlocks
<< "\nbadBlocks:" << hex << badBlocks << badOnes
<< "\nreserved:" << hex << reserved;
}

85
nandExtract/nandbin.h Executable file
View File

@ -0,0 +1,85 @@
#ifndef NANDBIN_H
#define NANDBIN_H
#include "includes.h"
struct fst_t
{
quint8 filename[ 0xc ];//showmii wads has size 0xb here but reads 0xc bytes into name??
quint8 mode;
quint8 attr;
quint16 sub;
quint16 sib;
quint32 size;
quint32 uid;
quint16 gid;
quint32 x3;
};
// class to deal with an encrypted wii nand dump
// basic usage... create an object, set a path, call InitNand. then you can get the detailed list of entries with GetTree()
// extract files with GetFile()
class NandBin : public QObject
{
Q_OBJECT
public:
NandBin( QObject * parent = 0, const QString &path = QString() );
~NandBin();
bool SetPath( const QString &path );
bool InitNand();
//get a root item containing children that are actually entries in the nand dump
//the root itself is just a container to hold them all and can be deleted once its children are taken
QTreeWidgetItem *GetTree();
//returns the data that makes up the file of a given entry#
QByteArray GetFile( quint16 entry );
//extracts an item( and all its children ) to a directory
//this function is BLOCKING and will block the current thread, so if done in the gui thread, it will freeze your GUI till it returns
bool ExtractToDir( QTreeWidgetItem *item, const QString &path );
//print a little info about the free space
void ShowInfo();
//set this to change ":" in names to "-" on etracting.
//theres more illegal characters in FAT, but thes seems to be the only one that happens on the nand FS
void SetFixNamesForFAT( bool fix = true );
private:
QByteArray key;
qint32 loc_super;
qint32 loc_fat;
qint32 loc_fst;
QString extractPath;
QString nandFile;
QFile f;
int type;
bool fatNames;
int GetDumpType( quint64 fileSize );
bool GetKey( int type );
QByteArray ReadKeyfile( QString path );
qint32 FindSuperblock();
quint16 GetFAT( quint16 fat_entry );
fst_t GetFST( quint16 entry );
QByteArray GetCluster( quint16 cluster_entry, bool decrypt = true );
//QByteArray ReadPage( quint32 i, bool withEcc = false );
QByteArray GetFile( fst_t fst );
QString FstName( fst_t fst );
bool ExtractFST( quint16 entry, const QString &path );
bool ExtractDir( fst_t fst, QString parent );
bool ExtractFile( fst_t fst, QString parent );
QTreeWidgetItem *root;
bool AddChildren( QTreeWidgetItem *parent, quint16 entry );
signals:
void SendError( QString );
void SendText( QString );
};
#endif // NANDBIN_H

119
nandExtract/nandwindow.cpp Executable file
View File

@ -0,0 +1,119 @@
#include "nandwindow.h"
#include "ui_nandwindow.h"
#include "tools.h"
NandWindow::NandWindow(QWidget *parent) :
QMainWindow(parent),
ui(new Ui::NandWindow),
nandBin( this )
{
ui->setupUi(this);
ui->treeWidget->header()->resizeSection( 0, 300 );//name
connect( &nandBin, SIGNAL( SendError( QString ) ), this, SLOT( GetError( QString ) ) );
connect( &nandBin, SIGNAL( SendText( QString ) ), this, SLOT( GetStatusUpdate( QString ) ) );
}
NandWindow::~NandWindow()
{
delete ui;
}
void NandWindow::changeEvent(QEvent *e)
{
QMainWindow::changeEvent(e);
switch (e->type()) {
case QEvent::LanguageChange:
ui->retranslateUi(this);
break;
default:
break;
}
}
void NandWindow::GetStatusUpdate( QString s )
{
ui->statusBar->showMessage( s );
}
void NandWindow::GetError( QString str )
{
qWarning() << str;
}
void NandWindow::ExtractShit()
{
ui->statusBar->showMessage( "Trying to extract..." );
nandBin.ExtractToDir( exItem, exPath );//who cares if it returns false? not me. thats what the qDebug() info is for
ui->statusBar->showMessage( "Done", 5000 );
}
//nand window right-clicked
void NandWindow::on_treeWidget_customContextMenuRequested( QPoint pos )
{
QPoint globalPos = ui->treeWidget->viewport()->mapToGlobal( pos );
QTreeWidgetItem* item = ui->treeWidget->itemAt( pos );
if( !item )//right-clicked in the partition window, but not on an item
{
qDebug() << "no item selected";
return;
}
QMenu myMenu( this );
QAction extractA( tr( "Extract" ), &myMenu );
myMenu.addAction( &extractA );
QAction* s = myMenu.exec( globalPos );
//respond to what was selected
if( s )
{
// something was chosen, do stuff
if( s == &extractA )//extract a file
{
QString path = QFileDialog::getExistingDirectory( this, tr("Select a destination") );
if( path.isEmpty() )
return;
exPath = path;
exItem = item;
//ghetto, but gives the dialog box time to dissappear before the gui freezes as it extracts the nand
QTimer::singleShot( 250, this, SLOT( ExtractShit() ) );
}
}
}
void NandWindow::on_actionOpen_Nand_triggered()
{
QString path = QFileDialog::getOpenFileName( this, tr( "Select a Nand to open" ) );
if( path.isEmpty() )
return;
if( !nandBin.SetPath( path ) )
{
qDebug() << " error in nandBin.SetPath";
ui->statusBar->showMessage( "Error setting path to " + path );
return;
}
ui->statusBar->showMessage( "Loading " + path );
if( !nandBin.InitNand() )
{
qDebug() << " error in nandBin.InitNand()";
ui->statusBar->showMessage( "Error reading " + path );
return;
}
ui->treeWidget->clear();
//get an item holding a tree with all the items of the nand
QTreeWidgetItem* tree = nandBin.GetTree();
//take the actual contents of the nand from the made up root and add them to the gui widget
ui->treeWidget->addTopLevelItems( tree->takeChildren() );
//delete the made up root item
delete tree;
ui->statusBar->showMessage( "Loaded " + path, 5000 );
}

40
nandExtract/nandwindow.h Executable file
View File

@ -0,0 +1,40 @@
#ifndef NANDWINDOW_H
#define NANDWINDOW_H
#include "includes.h"
#include "nandbin.h"
namespace Ui {
class NandWindow;
}
class NandWindow : public QMainWindow {
Q_OBJECT
public:
NandWindow(QWidget *parent = 0);
~NandWindow();
protected:
void changeEvent(QEvent *e);
private:
Ui::NandWindow *ui;
NandBin nandBin;
//just put these here and create a slot to make the GUI not
//look so ugly when it hangs while stuff is extracting
QString exPath;
QTreeWidgetItem* exItem;
public slots:
void GetError( QString );
void GetStatusUpdate( QString );
private slots:
void on_actionOpen_Nand_triggered();
void on_treeWidget_customContextMenuRequested(QPoint pos);
void ExtractShit();
};
#endif // NANDWINDOW_H

105
nandExtract/nandwindow.ui Executable file
View File

@ -0,0 +1,105 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>NandWindow</class>
<widget class="QMainWindow" name="NandWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>657</width>
<height>499</height>
</rect>
</property>
<property name="windowTitle">
<string>NandWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QTreeWidget" name="treeWidget">
<property name="contextMenuPolicy">
<enum>Qt::CustomContextMenu</enum>
</property>
<column>
<property name="text">
<string>Name</string>
</property>
</column>
<column>
<property name="text">
<string>Entry</string>
</property>
</column>
<column>
<property name="text">
<string>Size</string>
</property>
</column>
<column>
<property name="text">
<string>uid</string>
</property>
</column>
<column>
<property name="text">
<string>gid</string>
</property>
</column>
<column>
<property name="text">
<string>x3</string>
</property>
</column>
<column>
<property name="text">
<string>Mode</string>
</property>
</column>
<column>
<property name="text">
<string>Attr</string>
</property>
</column>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>657</width>
<height>27</height>
</rect>
</property>
<widget class="QMenu" name="menuFile">
<property name="title">
<string>File</string>
</property>
<addaction name="actionOpen_Nand"/>
</widget>
<addaction name="menuFile"/>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionOpen_Nand">
<property name="text">
<string>Open Nand...</string>
</property>
<property name="shortcut">
<string>Ctrl+O</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

131
nandExtract/tools.cpp Normal file
View File

@ -0,0 +1,131 @@
#include "tools.h"
#include "includes.h"
#include "aes.h"
//#include "sha1.h"
QString currentDir;
QString cachePath = "./NUS_cache";
QString nandPath = "./dump";
char ascii( char s ) {
if ( s < 0x20 ) return '.';
if ( s > 0x7E ) return '.';
return s;
}
//using stderr just because qtcreator stows it correctly when mixed with qDebug(), qWarning(), etc
//otherwise it may not be shown in the correct spot in the output due to stdout/stderr caches
void hexdump( const void *d, int len ) {
unsigned char *data;
int i, off;
data = (unsigned char*)d;
fprintf( stderr, "\n");
for ( off = 0; off < len; off += 16 ) {
fprintf( stderr, "%08x ", off );
for ( i=0; i<16; i++ )
{
if( ( i + 1 ) % 4 )
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x",data[ off + i ]);
}
else
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x ",data[ off + i ]);
}
}
fprintf( stderr, " " );
for ( i = 0; i < 16; i++ )
if ( ( i + off) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%c", ascii( data[ off + i ]));
fprintf( stderr,"\n");
}
fflush( stderr );
}
void hexdump( const QByteArray &d, int from, int len )
{
hexdump( d.data() + from, len == -1 ? d.size() : len );
}
void hexdump12( const void *d, int len ) {
unsigned char *data;
int i, off;
data = (unsigned char*)d;
fprintf( stderr, "\n");
for ( off = 0; off < len; off += 12 ) {
fprintf( stderr, "%08x ", off );
for ( i=0; i<12; i++ )
{
if( ( i + 1 ) % 4 )
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x",data[ off + i ]);
}
else
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x ",data[ off + i ]);
}
}
fprintf( stderr, " " );
for ( i = 0; i < 12; i++ )
if ( ( i + off) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%c", ascii( data[ off + i ]));
fprintf( stderr,"\n");
}
fflush( stderr );
}
void hexdump12( const QByteArray &d, int from, int len )
{
hexdump12( d.data() + from, len == -1 ? d.size() : len );
}
QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo )
{
QByteArray padding( RU( padTo, orig.size() ) - orig.size(), '\0' );
//qDebug() << "padding with" << hex << RU( padTo, orig.size() ) << "bytes" <<
return orig + padding;
}
QByteArray AesDecrypt( 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_decrypt( iv, (const quint8*)source.data(), (quint8*)ret.data(), source.size() );
return ret;
}
void AesSetKey( const QByteArray key )
{
aes_set_key( (const quint8*)key.data() );
}
/*
QByteArray GetSha1( QByteArray stuff )
{
SHA1Context sha;
SHA1Reset( &sha );
SHA1Input( &sha, (const unsigned char*)stuff.data(), stuff.size() );
if( !SHA1Result( &sha ) )
{
qWarning() << "GetSha1 -> sha error";
return QByteArray();
}
QByteArray ret( 20, '\0' );
quint8 *p = (quint8 *)ret.data();
for( int i = 0; i < 5 ; i++ )
{
quint32 part = qFromBigEndian( sha.Message_Digest[ i ] );
memcpy( p + ( i * 4 ), &part, 4 );
}
//hexdump( ret );
return ret;
}*/

36
nandExtract/tools.h Normal file
View File

@ -0,0 +1,36 @@
#ifndef TOOLS_H
#define TOOLS_H
#include "includes.h"
#define RU(x,n) (-(-(x) & -(n))) //round up
#define MIN( x, y ) ( ( x ) < ( y ) ? ( x ) : ( y ) )
#define MAX( x, y ) ( ( x ) > ( y ) ? ( x ) : ( y ) )
char ascii( char s );
void hexdump( const void *d, int len );
void hexdump( const QByteArray &d, int from = 0, int len = -1 );
void hexdump12( const void *d, int len );
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 GetSha1( QByteArray stuff );
//get a padded version of the given buffer
QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo );
//keep track of the last folder browsed to when looking for files
extern QString currentDir;
//folder used to cache stuff downloaded from NUS so duplicate titles dont need to be downloaded
extern QString cachePath;
//folder to use as the base path for the nand
extern QString nandPath;
#endif // TOOLS_H

400
nand_dump/aes.c Normal file
View File

@ -0,0 +1,400 @@
/* Rijndael Block Cipher - aes.c
Written by Mike Scott 21st April 1999
mike@compapp.dcu.ie
Permission for free direct or derivative use is granted subject
to compliance with any conditions that the originators of the
algorithm place on its exploitation.
*/
/*
#include <stdio.h>
#include <stdlib.h>
#include <string.h>*/
#define u8 unsigned char /* 8 bits */
#define u32 unsigned long /* 32 bits */
#define u64 unsigned long long
/* rotates x one bit to the left */
#define ROTL(x) (((x)>>7)|((x)<<1))
/* Rotates 32-bit word left by 1, 2 or 3 byte */
#define ROTL8(x) (((x)<<8)|((x)>>24))
#define ROTL16(x) (((x)<<16)|((x)>>16))
#define ROTL24(x) (((x)<<24)|((x)>>8))
/* Fixed Data */
static u8 InCo[4]={0xB,0xD,0x9,0xE}; /* Inverse Coefficients */
static u8 fbsub[256];
static u8 rbsub[256];
static u8 ptab[256],ltab[256];
static u32 ftable[256];
static u32 rtable[256];
static u32 rco[30];
/* Parameter-dependent data */
int Nk,Nb,Nr;
u8 fi[24],ri[24];
u32 fkey[120];
u32 rkey[120];
static u32 pack(u8 *b)
{ /* pack bytes into a 32-bit Word */
return ((u32)b[3]<<24)|((u32)b[2]<<16)|((u32)b[1]<<8)|(u32)b[0];
}
static void unpack(u32 a,u8 *b)
{ /* unpack bytes from a word */
b[0]=(u8)a;
b[1]=(u8)(a>>8);
b[2]=(u8)(a>>16);
b[3]=(u8)(a>>24);
}
static u8 xtime(u8 a)
{
u8 b;
if (a&0x80) b=0x1B;
else b=0;
a<<=1;
a^=b;
return a;
}
static u8 bmul(u8 x,u8 y)
{ /* x.y= AntiLog(Log(x) + Log(y)) */
if (x && y) return ptab[(ltab[x]+ltab[y])%255];
else return 0;
}
static u32 SubByte(u32 a)
{
u8 b[4];
unpack(a,b);
b[0]=fbsub[b[0]];
b[1]=fbsub[b[1]];
b[2]=fbsub[b[2]];
b[3]=fbsub[b[3]];
return pack(b);
}
static u8 product(u32 x,u32 y)
{ /* dot product of two 4-byte arrays */
u8 xb[4],yb[4];
unpack(x,xb);
unpack(y,yb);
return bmul(xb[0],yb[0])^bmul(xb[1],yb[1])^bmul(xb[2],yb[2])^bmul(xb[3],yb[3]);
}
static u32 InvMixCol(u32 x)
{ /* matrix Multiplication */
u32 y,m;
u8 b[4];
m=pack(InCo);
b[3]=product(m,x);
m=ROTL24(m);
b[2]=product(m,x);
m=ROTL24(m);
b[1]=product(m,x);
m=ROTL24(m);
b[0]=product(m,x);
y=pack(b);
return y;
}
u8 ByteSub(u8 x)
{
u8 y=ptab[255-ltab[x]]; /* multiplicative inverse */
x=y; x=ROTL(x);
y^=x; x=ROTL(x);
y^=x; x=ROTL(x);
y^=x; x=ROTL(x);
y^=x; y^=0x63;
return y;
}
void gentables(void)
{ /* generate tables */
int i;
u8 y,b[4];
/* use 3 as primitive root to generate power and log tables */
ltab[0]=0;
ptab[0]=1; ltab[1]=0;
ptab[1]=3; ltab[3]=1;
for (i=2;i<256;i++)
{
ptab[i]=ptab[i-1]^xtime(ptab[i-1]);
ltab[ptab[i]]=i;
}
/* affine transformation:- each bit is xored with itself shifted one bit */
fbsub[0]=0x63;
rbsub[0x63]=0;
for (i=1;i<256;i++)
{
y=ByteSub((u8)i);
fbsub[i]=y; rbsub[y]=i;
}
for (i=0,y=1;i<30;i++)
{
rco[i]=y;
y=xtime(y);
}
/* calculate forward and reverse tables */
for (i=0;i<256;i++)
{
y=fbsub[i];
b[3]=y^xtime(y); b[2]=y;
b[1]=y; b[0]=xtime(y);
ftable[i]=pack(b);
y=rbsub[i];
b[3]=bmul(InCo[0],y); b[2]=bmul(InCo[1],y);
b[1]=bmul(InCo[2],y); b[0]=bmul(InCo[3],y);
rtable[i]=pack(b);
}
}
void gkey(int nb,int nk,u8 *key)
{ /* blocksize=32*nb bits. Key=32*nk bits */
/* currently nb,bk = 4, 6 or 8 */
/* key comes as 4*Nk bytes */
/* Key Scheduler. Create expanded encryption key */
int i,j,k,m,N;
int C1,C2,C3;
u32 CipherKey[8];
Nb=nb; Nk=nk;
/* Nr is number of rounds */
if (Nb>=Nk) Nr=6+Nb;
else Nr=6+Nk;
C1=1;
if (Nb<8) { C2=2; C3=3; }
else { C2=3; C3=4; }
/* pre-calculate forward and reverse increments */
for (m=j=0;j<nb;j++,m+=3)
{
fi[m]=(j+C1)%nb;
fi[m+1]=(j+C2)%nb;
fi[m+2]=(j+C3)%nb;
ri[m]=(nb+j-C1)%nb;
ri[m+1]=(nb+j-C2)%nb;
ri[m+2]=(nb+j-C3)%nb;
}
N=Nb*(Nr+1);
for (i=j=0;i<Nk;i++,j+=4)
{
CipherKey[i]=pack(key+j);
}
for (i=0;i<Nk;i++) fkey[i]=CipherKey[i];
for (j=Nk,k=0;j<N;j+=Nk,k++)
{
fkey[j]=fkey[j-Nk]^SubByte(ROTL24(fkey[j-1]))^rco[k];
if (Nk<=6)
{
for (i=1;i<Nk && (i+j)<N;i++)
fkey[i+j]=fkey[i+j-Nk]^fkey[i+j-1];
}
else
{
for (i=1;i<4 &&(i+j)<N;i++)
fkey[i+j]=fkey[i+j-Nk]^fkey[i+j-1];
if ((j+4)<N) fkey[j+4]=fkey[j+4-Nk]^SubByte(fkey[j+3]);
for (i=5;i<Nk && (i+j)<N;i++)
fkey[i+j]=fkey[i+j-Nk]^fkey[i+j-1];
}
}
/* now for the expanded decrypt key in reverse order */
for (j=0;j<Nb;j++) rkey[j+N-Nb]=fkey[j];
for (i=Nb;i<N-Nb;i+=Nb)
{
k=N-Nb-i;
for (j=0;j<Nb;j++) rkey[k+j]=InvMixCol(fkey[i+j]);
}
for (j=N-Nb;j<N;j++) rkey[j-N+Nb]=fkey[j];
}
/* There is an obvious time/space trade-off possible here. *
* Instead of just one ftable[], I could have 4, the other *
* 3 pre-rotated to save the ROTL8, ROTL16 and ROTL24 overhead */
void encrypt(u8 *buff)
{
int i,j,k,m;
u32 a[8],b[8],*x,*y,*t;
for (i=j=0;i<Nb;i++,j+=4)
{
a[i]=pack(buff+j);
a[i]^=fkey[i];
}
k=Nb;
x=a; y=b;
/* State alternates between a and b */
for (i=1;i<Nr;i++)
{ /* Nr is number of rounds. May be odd. */
/* if Nb is fixed - unroll this next
loop and hard-code in the values of fi[] */
for (m=j=0;j<Nb;j++,m+=3)
{ /* deal with each 32-bit element of the State */
/* This is the time-critical bit */
y[j]=fkey[k++]^ftable[(u8)x[j]]^
ROTL8(ftable[(u8)(x[fi[m]]>>8)])^
ROTL16(ftable[(u8)(x[fi[m+1]]>>16)])^
ROTL24(ftable[(u8)(x[fi[m+2]]>>24)]);
}
t=x; x=y; y=t; /* swap pointers */
}
/* Last Round - unroll if possible */
for (m=j=0;j<Nb;j++,m+=3)
{
y[j]=fkey[k++]^(u32)fbsub[(u8)x[j]]^
ROTL8((u32)fbsub[(u8)(x[fi[m]]>>8)])^
ROTL16((u32)fbsub[(u8)(x[fi[m+1]]>>16)])^
ROTL24((u32)fbsub[(u8)(x[fi[m+2]]>>24)]);
}
for (i=j=0;i<Nb;i++,j+=4)
{
unpack(y[i],(u8 *)&buff[j]);
x[i]=y[i]=0; /* clean up stack */
}
return;
}
void decrypt(u8 *buff)
{
int i,j,k,m;
u32 a[8],b[8],*x,*y,*t;
for (i=j=0;i<Nb;i++,j+=4)
{
a[i]=pack(buff+j);
a[i]^=rkey[i];
}
k=Nb;
x=a; y=b;
/* State alternates between a and b */
for (i=1;i<Nr;i++)
{ /* Nr is number of rounds. May be odd. */
/* if Nb is fixed - unroll this next
loop and hard-code in the values of ri[] */
for (m=j=0;j<Nb;j++,m+=3)
{ /* This is the time-critical bit */
y[j]=rkey[k++]^rtable[(u8)x[j]]^
ROTL8(rtable[(u8)(x[ri[m]]>>8)])^
ROTL16(rtable[(u8)(x[ri[m+1]]>>16)])^
ROTL24(rtable[(u8)(x[ri[m+2]]>>24)]);
}
t=x; x=y; y=t; /* swap pointers */
}
/* Last Round - unroll if possible */
for (m=j=0;j<Nb;j++,m+=3)
{
y[j]=rkey[k++]^(u32)rbsub[(u8)x[j]]^
ROTL8((u32)rbsub[(u8)(x[ri[m]]>>8)])^
ROTL16((u32)rbsub[(u8)(x[ri[m+1]]>>16)])^
ROTL24((u32)rbsub[(u8)(x[ri[m+2]]>>24)]);
}
for (i=j=0;i<Nb;i++,j+=4)
{
unpack(y[i],(u8 *)&buff[j]);
x[i]=y[i]=0; /* clean up stack */
}
return;
}
void aes_set_key(u8 *key) {
gentables();
gkey(4, 4, key);
}
// CBC mode decryption
void aes_decrypt(u8 *iv, u8 *inbuf, u8 *outbuf, unsigned long long len) {
u8 block[16];
unsigned int blockno = 0, i;
// debug_printf("aes_decrypt(%p, %p, %p, %lld)\n", iv, inbuf, outbuf, len);
for (blockno = 0; blockno <= (len / sizeof(block)); blockno++) {
unsigned int fraction;
if (blockno == (len / sizeof(block))) { // last block
fraction = len % sizeof(block);
if (fraction == 0) break;
memset(block, 0, sizeof(block));
} else fraction = 16;
// debug_printf("block %d: fraction = %d\n", blockno, fraction);
memcpy(block, inbuf + blockno * sizeof(block), fraction);
decrypt(block);
u8 *ctext_ptr;
if (blockno == 0) ctext_ptr = iv;
else ctext_ptr = inbuf + (blockno-1) * sizeof(block);
for(i=0; i < fraction; i++)
outbuf[blockno * sizeof(block) + i] =
ctext_ptr[i] ^ block[i];
// debug_printf("Block %d output: ", blockno);
// hexdump(outbuf + blockno*sizeof(block), 16);
}
}
// CBC mode encryption
void aes_encrypt(u8 *iv, u8 *inbuf, u8 *outbuf, unsigned long long len) {
u8 block[16];
unsigned int blockno = 0, i;
// debug_printf("aes_decrypt(%p, %p, %p, %lld)\n", iv, inbuf, outbuf, len);
for (blockno = 0; blockno <= (len / sizeof(block)); blockno++) {
unsigned int fraction;
if (blockno == (len / sizeof(block))) { // last block
fraction = len % sizeof(block);
if (fraction == 0) break;
memset(block, 0, sizeof(block));
} else fraction = 16;
// debug_printf("block %d: fraction = %d\n", blockno, fraction);
memcpy(block, inbuf + blockno * sizeof(block), fraction);
for(i=0; i < fraction; i++)
block[i] = inbuf[blockno * sizeof(block) + i] ^ iv[i];
encrypt(block);
memcpy(iv, block, sizeof(block));
memcpy(outbuf + blockno * sizeof(block), block, sizeof(block));
// debug_printf("Block %d output: ", blockno);
// hexdump(outbuf + blockno*sizeof(block), 16);
}
}

18
nand_dump/aes.h Normal file
View File

@ -0,0 +1,18 @@
#ifndef __AES_H_
#define __AES_H_
#include "includes.h"
#ifdef __cplusplus
extern "C" {
#endif
void aes_encrypt( quint8 *iv, const quint8 *inbuf, quint8 *outbuf, unsigned long long len );
void aes_decrypt( quint8 *iv, const quint8 *inbuf, quint8 *outbuf, unsigned long long len );
void aes_set_key( const quint8 *key );
#ifdef __cplusplus
}
#endif
#endif //__AES_H_

35
nand_dump/includes.h Normal file
View File

@ -0,0 +1,35 @@
#ifndef INCLUDES_H
#define INCLUDES_H
#include <QAction>
#include <QBuffer>
#include <QDialog>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QLayout>
#include <QList>
#include <QMainWindow>
#include <QMenu>
#include <QMessageBox>
#include <QNetworkAccessManager>
#include <QNetworkReply>
#include <QObject>
#include <QProcess>
#include <QQueue>
#include <QRegExp>
#include <QSettings>
#include <QtDebug>
#include <QTimer>
#include <QtGui>
#include <QtGui/QMainWindow>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <qpushbutton.h>
#include <QWidget>
#endif // INCLUDES_H

11
nand_dump/main.cpp Normal file
View File

@ -0,0 +1,11 @@
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

242
nand_dump/mainwindow.cpp Normal file
View File

@ -0,0 +1,242 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "settingtxtdialog.h"
#include "uidmap.h"
#include "sha1.h"
#include "tiktmd.h"
#include "tools.h"
#include "aes.h"
MainWindow::MainWindow( QWidget *parent ) : QMainWindow( parent ), ui( new Ui::MainWindow ), nus ( this )
{
ui->setupUi(this);
//connect to the nus object so we can respond to what it is saying with pretty stuff in the gui
connect( &nus, SIGNAL( SendDownloadProgress( int ) ), ui->progressBar_dl, SLOT( setValue( int ) ) );
connect( &nus, SIGNAL( SendTitleProgress( int ) ), ui->progressBar_title, SLOT( setValue( int ) ) );
connect( &nus, SIGNAL( SendTotalProgress( int ) ), ui->progressBar_whole, SLOT( setValue( int ) ) );
connect( &nus, SIGNAL( SendText( QString ) ), ui->statusBar, SLOT( showMessage( QString ) ) );
connect( &nus, SIGNAL( SendError( const QString &, NusJob ) ), this, SLOT( GetError( const QString &, NusJob ) ) );
connect( &nus, SIGNAL( SendDone() ), this, SLOT( NusIsDone() ) );
connect( &nus, SIGNAL( SendData( NusJob ) ), this, SLOT( ReceiveTitleFromNus( NusJob) ) );
//TODO, really get these paths from settings
ui->lineEdit_cachePath->setText( cachePath );
ui->lineEdit_nandPath->setText( nandPath );
nand.SetPath( nandPath );
nus.SetCachePath( cachePath );
}
MainWindow::~MainWindow()
{
delete ui;
}
//some slots to respond to the NUS downloader
void MainWindow::GetError( const QString &message, NusJob job )
{
QString dataStuff = QString( "%1 items:" ).arg( job.data.size() );
for( int i = 0; i < job.data.size(); i++ )
dataStuff += QString( " %1" ).arg( job.data.at( i ).size(), 0, 16, QChar( ' ' ) );
QString str = tr( "<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 );
ui->plainTextEdit_log->appendHtml( str );
ui->plainTextEdit_log->appendHtml( j );
}
void MainWindow::ShowMessage( const QString& mes )
{
QString str = mes + "<br>";
ui->plainTextEdit_log->appendHtml( str );
}
void MainWindow::NusIsDone()
{
QString str = tr( "NUS ojbect is done working<br>" );
ui->plainTextEdit_log->appendHtml( str );
ui->statusBar->showMessage( tr( "Done" ), 5000 );
if( ui->radioButton_folder->isChecked() )
{
ui->lineEdit_extractPath->setEnabled( true );
ui->pushButton_decFolder->setEnabled( true );
}
else if( ui->radioButton_nand->isChecked() )
{
ui->lineEdit_nandPath->setEnabled( true );
ui->pushButton_nandPath->setEnabled( true );
}
else if( ui->radioButton_wad->isChecked() )
{
ui->lineEdit_wad->setEnabled( true );
ui->pushButton_wad->setEnabled( true );
}
ui->radioButton_folder->setEnabled( true );
ui->radioButton_nand->setEnabled( true );
ui->radioButton_wad->setEnabled( true );
}
void MainWindow::ReceiveTitleFromNus( 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() )
{
}
else if( ui->radioButton_nand->isChecked() )
{
bool ok = nand.InstallNusItem( job );
if( ok )
ShowMessage( tr( "Installed %1 title to nand" ).arg( title ) );
else
ShowMessage( tr( "<b>Error %1 title to nand</b>" ).arg( title ) );
}
else if( ui->radioButton_wad->isChecked() )
{
}
//bool r = nand.InstallNusItem( job );
//qDebug() << "install:" << r;
}
//clicked the button to get a title
void MainWindow::on_pushButton_GetTitle_clicked()
{
bool ok = false;
quint64 tid = ui->lineEdit_tid->text().toLongLong( &ok, 16 );
if( !ok )
{
ShowMessage( "<b>Error converting \"" + ui->lineEdit_tid->text() + "\" to a hex number.</b>" );
return;
}
quint32 ver = 0;
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;
if( ui->radioButton_folder->isChecked() )
{
if( ui->lineEdit_extractPath->text().isEmpty() )
{
ShowMessage( tr( "<b>No path given to save downloads in.</b>" ) );
return;
}
ui->lineEdit_extractPath->setEnabled( false );
ui->pushButton_decFolder->setEnabled( false );
}
else if( ui->radioButton_nand->isChecked() )
{
if( ui->lineEdit_nandPath->text().isEmpty() )
{
ShowMessage( tr( "<b>No path given for nand dump base.</b>" ) );
return;
}
ui->lineEdit_nandPath->setEnabled( false );
ui->pushButton_nandPath->setEnabled( false );
}
else if( ui->radioButton_wad->isChecked() )
{
if( ui->lineEdit_wad->text().isEmpty() )
{
ShowMessage( tr( "<b>No path given to save wads in.</b>" ) );
return;
}
decrypt = false;
ui->lineEdit_wad->setEnabled( false );
ui->pushButton_wad->setEnabled( false );
}
ui->radioButton_folder->setEnabled( false );
ui->radioButton_nand->setEnabled( false );
ui->radioButton_wad->setEnabled( false );
//dont set these to 0 in case the button is pressed while something else is already being downloaded
//ui->progressBar_dl->setValue( 0 );
//ui->progressBar_title->setValue( 0 );
//ui->progressBar_whole->setValue( 0 );
nus.SetCachePath( ui->lineEdit_cachePath->text() );
nus.Get( tid, decrypt, ver );
}
//ratio buttons toggled
void MainWindow::on_radioButton_nand_toggled( bool checked )
{
ui->lineEdit_nandPath->setEnabled( checked );
ui->pushButton_nandPath->setEnabled( checked );
}
void MainWindow::on_radioButton_folder_toggled( bool checked )
{
ui->lineEdit_extractPath->setEnabled( checked );
ui->pushButton_decFolder->setEnabled( checked );
}
void MainWindow::on_radioButton_wad_toggled( bool checked )
{
ui->lineEdit_wad->setEnabled( checked );
ui->pushButton_wad->setEnabled( checked );
}
//search for a path to use as the nand basepath
void MainWindow::on_pushButton_nandPath_clicked()
{
QString path = ui->lineEdit_nandPath->text().isEmpty() ? "/media" : ui->lineEdit_nandPath->text();
QString f = QFileDialog::getExistingDirectory( this, tr( "Select Nand Base Folder" ), path );
if( f.isEmpty() )
return;
ui->lineEdit_nandPath->setText( f );
nus.SetCachePath( ui->lineEdit_cachePath->text() );
}
void MainWindow::on_pushButton_decFolder_clicked()
{
QString path = ui->lineEdit_extractPath->text().isEmpty() ? "/media" : ui->lineEdit_extractPath->text();
QString f = QFileDialog::getExistingDirectory( this, tr( "Select folder to decrypt this title to" ), path );
if( f.isEmpty() )
return;
ui->lineEdit_extractPath->setText( f );
}
void MainWindow::on_pushButton_wad_clicked()
{
QString path = ui->lineEdit_wad->text().isEmpty() ? "/media" : 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 );
}

45
nand_dump/mainwindow.h Normal file
View File

@ -0,0 +1,45 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "includes.h"
#include "nusdownloader.h"
#include "nanddump.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow( QWidget *parent = 0 );
~MainWindow();
private:
Ui::MainWindow *ui;
NusDownloader nus;
NandDump nand;
void ShowMessage( const QString& mes );
public slots:
//slots for getting info from the NUS downloader
void GetError( const QString &message, NusJob job );
void NusIsDone();
void ReceiveTitleFromNus( NusJob job );
private slots:
void on_pushButton_wad_clicked();
void on_pushButton_decFolder_clicked();
void on_pushButton_nandPath_clicked();
void on_radioButton_wad_toggled(bool checked);
void on_radioButton_folder_toggled(bool checked);
void on_radioButton_nand_toggled(bool checked);
void on_pushButton_GetTitle_clicked();
};
#endif // MAINWINDOW_H

229
nand_dump/mainwindow.ui Normal file
View File

@ -0,0 +1,229 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>585</width>
<height>457</height>
</rect>
</property>
<property name="windowTitle">
<string>QtShitGetter</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLineEdit" name="lineEdit_tid">
<property name="text">
<string>0000000100000002</string>
</property>
<property name="maxLength">
<number>16</number>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_4_version">
<property name="text">
<string>v</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="lineEdit_version">
<property name="maximumSize">
<size>
<width>107</width>
<height>16777215</height>
</size>
</property>
<property name="text">
<string>513</string>
</property>
<property name="maxLength">
<number>5</number>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_GetTitle">
<property name="text">
<string>Get It!</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLineEdit" name="lineEdit_cachePath"/>
</item>
<item>
<widget class="QPushButton" name="pushButton_CachePathBrowse">
<property name="text">
<string>Local Cache</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_progDl">
<property name="text">
<string>Download</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QProgressBar" name="progressBar_dl">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_progTitle">
<property name="text">
<string>Title</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QProgressBar" name="progressBar_title">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_progTotal">
<property name="text">
<string>Total</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QProgressBar" name="progressBar_whole">
<property name="value">
<number>0</number>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QRadioButton" name="radioButton_nand">
<property name="text">
<string>Nand</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
<property name="autoExclusive">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_nandPath"/>
</item>
<item row="0" column="2">
<widget class="QPushButton" name="pushButton_nandPath">
<property name="text">
<string>Search...</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QRadioButton" name="radioButton_folder">
<property name="text">
<string>Folder</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_extractPath">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="1" column="2">
<widget class="QPushButton" name="pushButton_decFolder">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Search...</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QRadioButton" name="radioButton_wad">
<property name="text">
<string>Wad</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_wad">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
<item row="2" column="2">
<widget class="QPushButton" name="pushButton_wad">
<property name="enabled">
<bool>false</bool>
</property>
<property name="text">
<string>Search...</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QPlainTextEdit" name="plainTextEdit_log"/>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>585</width>
<height>27</height>
</rect>
</property>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

41
nand_dump/nand.pro Normal file
View File

@ -0,0 +1,41 @@
#-------------------------------------------------
#
# Project created by QtCreator 2010-12-02T23:30:12
#
#-------------------------------------------------
QT += core gui\
network
TARGET = nand
TEMPLATE = app
SOURCES += main.cpp\
mainwindow.cpp \
tools.cpp \
uidmap.cpp \
sharedcontentmap.cpp \
sha1.c \
tiktmd.cpp \
aes.c \
nusdownloader.cpp \
nanddump.cpp \
settingtxtdialog.cpp \
wad.cpp
HEADERS += mainwindow.h \
tools.h \
includes.h \
uidmap.h \
sharedcontentmap.h \
sha1.h \
tiktmd.h \
aes.h \
nusdownloader.h \
nanddump.h \
settingtxtdialog.h \
wad.h
FORMS += mainwindow.ui \
settingtxtdialog.ui

393
nand_dump/nanddump.cpp Normal file
View File

@ -0,0 +1,393 @@
#include "nanddump.h"
#include "tiktmd.h"
#include "tools.h"
NandDump::NandDump( const QString &path )
{
uidDirty = true;
cmDirty = true;
if( !path.isEmpty() )
SetPath( path );
}
NandDump::~NandDump()
{
if( !basePath.isEmpty() )
Flush();//no need to check the return, the class is destructing and we wouldnt do anything to fix it
}
bool NandDump::Flush()
{
bool ret = FlushUID();
return FlushContentMap() && ret;
}
bool NandDump::SetPath( const QString &path )
{
//check what is already in this path and create stuff that is missing
QFileInfo fi( path );
basePath = fi.absoluteFilePath();
if( fi.exists() && fi.isFile() )
{
qWarning() << "NandDump::SetPath ->" << path << "is a file";
return false;
}
if( !fi.exists() && !QDir().mkpath( path ) )
{
qWarning() << "NandDump::SetPath -> cant create" << path;
return false;
}
//make sure some subfolders are there
QDir d( path );
if( ( !d.exists( "title" ) && !d.mkdir( "title" ) )//these should be good enough
|| ( !d.exists( "ticket" ) && !d.mkdir( "ticket" ) )
|| ( !d.exists( "shared1" ) && !d.mkdir( "shared1" ) )
|| ( !d.exists( "shared2" ) && !d.mkdir( "shared2" ) )
|| ( !d.exists( "sys" ) && !d.mkdir( "sys" ) ) )
{
qWarning() << "NandDump::SetPath -> error creating subfolders in" << path;
return false;
}
//make sure there is a valid uid.sys
QString uidPath = fi.absoluteFilePath() + "/sys/uid.sys";
fi.setFile( uidPath );
if( !fi.exists() && !FlushUID() )
{
qWarning() << "NandDump::SetPath -> can\'t create new uid.sys";
return false;
}
else
{
QFile f( uidPath );
if( !f.open( QIODevice::ReadOnly ) )
{
qWarning() << "NandDump::SetPath -> error opening existing uid.sys" << uidPath;
return false;
}
QByteArray u = f.readAll();
f.close();
uidMap = UIDmap( u );
uidMap.Check();//not really taking any action, but it will spit out errors in the debug output
uidDirty = false;
}
//make sure there is a valid content.map
QString cmPath = basePath + "/shared1/content.map";
fi.setFile( cmPath );
if( !fi.exists() && !FlushContentMap() )
{
qWarning() << "NandDump::SetPath -> can\'t create new content map";
return false;
}
else
{
QFile f( cmPath );
if( !f.open( QIODevice::ReadOnly ) )
{
qWarning() << "NandDump::SetPath -> error opening existing content.map" << cmPath;
return false;
}
QByteArray u = f.readAll();
f.close();
cMap = SharedContentMap( u );//checked automatically by the constructor
cMap.Check( basePath + "/shared1" );//just checking to make sure everything is ok.
cmDirty = false;
}
//TODO - need a setting.txt up in here
return true;
}
//write the uid to the HDD
bool NandDump::FlushUID()
{
if( uidDirty )
uidDirty = !SaveData( uidMap.Data(), "/sys/uid.sys" );
return !uidDirty;
}
//write the shared content map to the HDD
bool NandDump::FlushContentMap()
{
if( cmDirty )
cmDirty = !SaveData( cMap.Data(), "/shared1/content.map" );
return !cmDirty;
}
QByteArray NandDump::GetSettingTxt()
{
return GetFile( "/title/00000001/00000002/data/setting.txt" );
}
bool NandDump::SetSettingTxt( const QByteArray ba )
{
QString path = basePath + "/title/00000001/00000002/data";
if( !QFileInfo( path ).exists() && !QDir().mkpath( path ) )
return false;
return SaveData( ba, "/title/00000001/00000002/data/setting.txt" );
}
const QByteArray NandDump::GetFile( const QString &path )
{
QFile f( basePath + path );
if( !f.open( QIODevice::ReadOnly ) )
{
qWarning() << "NandDump::GetFile -> cant open file for reading" << path;
return QByteArray();
}
QByteArray ret = f.readAll();
f.close();
return ret;
}
//write some file to the nand
bool NandDump::SaveData( const QByteArray ba, const QString& path )
{
qDebug() << "NandDump::SaveData" << path << hex << ba.size();
QFile f( basePath + path );
if( !f.open( QIODevice::WriteOnly ) )
{
qWarning() << "NandDump::SaveData -> cant open file for writing" << path;
return false;
}
f.write( ba );
f.close();
return true;
}
//delete a file from the nand
void NandDump::DeleteData( const QString & path )
{
qDebug() << "NandDump::DeleteData" << path;
QFile::remove( basePath + path );
}
bool NandDump::InstallTicket( const QByteArray ba, quint64 tid )
{
Ticket t( ba );
if( t.Tid() != tid )
{
qWarning() << "NandDump::InstallTicket -> bad tid" << hex << tid << t.Tid();
return false;
}
//only write the first chunk of the ticket to the nand
QByteArray start = ba.left( t.SignedSize() );
if( start.size() != 0x2a4 )
{
qWarning() << "NandDump::InstallTicket -> ticket size" << hex << start.size();
}
QString p = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
p.insert( 8 ,"/" );
p.prepend( "/ticket/" );
QString folder = p;
folder.resize( 17 );
folder.prepend( basePath );
if( !QFileInfo( folder ).exists() && !QDir().mkpath( folder ) )
return false;
p.append( ".tik" );
return SaveData( start, p );
}
bool NandDump::InstallTmd( const QByteArray ba, quint64 tid )
{
Tmd t( ba );
if( t.Tid() != tid )
{
qWarning() << "NandDump::InstallTmd -> bad tid" << hex << tid << t.Tid();
return false;
}
//only write the first chunk of the ticket to the nand
QByteArray start = ba.left( t.SignedSize() );
QString p = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
p.insert( 8 ,"/" );
p.prepend( "/title/" );
p.append( "/content/title.tmd" );
return SaveData( start, p );
}
bool NandDump::InstallSharedContent( const QByteArray ba, const QByteArray hash )
{
QByteArray h = hash;
if( h.isEmpty() )
h = GetSha1( ba );
if( !cMap.GetAppFromHash( h ).isEmpty() )//already have this one
return true;
//qDebug() << "adding shared content";
QString appName = cMap.GetNextEmptyCid();
QString p = "/shared1/" + appName + ".app";
if( !SaveData( ba, p ) )
return false;
cMap.AddEntry( appName, hash );
cmDirty = true;
return true;
}
bool NandDump::InstallPrivateContent( const QByteArray ba, quint64 tid, const QString &cid )
{
QString p = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
p.insert( 8 ,"/" );
p.prepend( "/title/" );
p.append( "/content/" + cid + ".app" );
return SaveData( ba, p );
}
//delete all .app and .tmd files in the content folder for this tid
void NandDump::AbortInstalling( quint64 tid )
{
QString p = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
p.insert( 8 ,"/" );
p.prepend( "/title" );
p.append( "/content" );
QDir d( p );
if( !d.exists() )
return;
QFileInfoList fiL = d.entryInfoList( QStringList() << "*.app" << ".tmd", QDir::Files );
foreach( QFileInfo fi, fiL )
QFile::remove( fi.absoluteFilePath() );
}
bool NandDump::DeleteTitle( quint64 tid, bool deleteData )
{
QString tidStr = QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
tidStr.insert( 8 ,"/" );
QString tikPath = tidStr;
tikPath.prepend( "/ticket/" );
tikPath.append( ".tik" );
DeleteData( tikPath );
if( !deleteData )
{
AbortInstalling( tid );
return true;
}
QString tPath = basePath + "/title/" + tidStr;
return RecurseDeleteFolder( tPath );
}
//go through and delete all the stuff in a given folder and then delete the folder itself
//this function expects an absolute path, not a relitive one inside the nand dump
bool NandDump::RecurseDeleteFolder( const QString &path )
{
QDir d( path );
QFileInfoList fiL = d.entryInfoList( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot );
foreach( QFileInfo fi, fiL )
{
if( fi.isFile() && !QFile::remove( fi.absoluteFilePath() ) )
{
qWarning() << "NandDump::RecurseDeleteFolder -> error deleting" << fi.absoluteFilePath();
return false;
}
if( fi.isDir() && !RecurseDeleteFolder( fi.absoluteFilePath() ) )
{
qWarning() << "NandDump::RecurseDeleteFolder -> error deleting" << fi.absoluteFilePath();
return false;
}
}
return QDir().rmdir( path );
}
bool NandDump::InstallNusItem( NusJob job )
{
if( !job.tid || !job.version || job.data.size() < 3 )
{
qWarning() << "NandDump::InstallNusItem -> invalid item";
return false;
}
if( !uidDirty )
{
uidDirty = uidMap.GetUid( job.tid, false ) != 0;//only frag the uid as dirty if it has to be, this way it is only flushed if needed
}
uidMap.GetUid( job.tid );
QString p = QString( "%1" ).arg( job.tid, 16, 16, QChar( '0' ) );
p.insert( 8 ,"/" );
p.prepend( "/title/" );
QString path = basePath + p + "/content";
if( !QFileInfo( path ).exists() && !QDir().mkpath( path ) )
return false;
path = basePath + p + "/data";
if( !QFileInfo( path ).exists() && !QDir().mkpath( path ) )
return false;
QByteArray ba = job.data.takeFirst();
if( !InstallTmd( ba, job.tid ) )
return false;
Tmd t( ba );
ba = job.data.takeFirst();
Ticket ti( ba );
if( !InstallTicket( ba, job.tid ) )
{
AbortInstalling( job.tid );
return false;
}
quint32 cnt = qFromBigEndian( t.payload()->num_contents );
if( cnt != (quint32)job.data.size() )
{
AbortInstalling( job.tid );
return false;
}
for( quint32 i = 0; i < cnt; i++ )
{
//make sure the content is not encrypted
QByteArray decData;
if( job.decrypt )
{
decData = job.data.takeFirst();
}
else
{
//seems like a waste to keep setting the key, but for now im doing it this way
//so multiple objects can be decrypting titles at the same time
AesSetKey( ti.DecryptedKey() );
QByteArray paddedEncrypted = PaddedByteArray( job.data.takeFirst(), 0x40 );
decData = AesDecrypt( i, paddedEncrypted );
decData.resize( t.Size( i ) );
QByteArray realHash = GetSha1( decData );
if( realHash != t.Hash( i ) )
{
qWarning() << "NandDump::InstallNusItem -> hash doesnt match for content" << hex << i;
hexdump( realHash );
hexdump( t.Hash( i ) );
AbortInstalling( job.tid );
return false;
}
}
if( t.Type( i ) == 0x8001 )
{
if( !InstallSharedContent( decData, t.Hash( i ) ) )
{
AbortInstalling( job.tid );
return false;
}
}
else if( t.Type( i ) == 1 )
{
if( !InstallPrivateContent( decData, job.tid, t.Cid( i ) ) )
{
AbortInstalling( job.tid );
return false;
}
}
else//unknown content type
{
qWarning() << "NandDump::InstallNusItem -> unknown content type";
AbortInstalling( job.tid );
return false;
}
}
return true;
}

67
nand_dump/nanddump.h Normal file
View File

@ -0,0 +1,67 @@
#ifndef NANDDUMP_H
#define NANDDUMP_H
#include "nusdownloader.h"
#include "includes.h"
#include "sharedcontentmap.h"
#include "uidmap.h"
class NandDump
{
public:
NandDump( const QString &path = QString() );
~NandDump();
//sets the basepath for this nand
//if it doesnt exist, the function will try to create it
//also creates the normal folders in the nand
bool SetPath( const QString &path );
//installs a title to the nand dump from an already existing NusJob
//returns false if something went wrong
bool InstallNusItem( NusJob job );
//tries to delete a title from the nand dump
//deleteData gives the option to just delete the title and leave behind its data
bool DeleteTitle( quint64 tid, bool deleteData = false );
//check what version a given title is on this nand, returns 0 if it isnt installed
quint16 GetTitleVersion( quint64 tid );
//write the current uid & content.map to the PC
//failure to make sure this is done can end up with a broken nand
bool Flush();
QByteArray GetSettingTxt();
bool SetSettingTxt( const QByteArray ba );
const QByteArray GetFile( const QString &path );
bool SaveData( const QByteArray ba, const QString& path );
void DeleteData( const QString & path );
private:
QString basePath;
SharedContentMap cMap;
UIDmap uidMap;
//write the current uid.sys to disc
bool uidDirty;
bool FlushUID();
//write the content.map to disc
bool cmDirty;
bool FlushContentMap();
bool InstallTicket( const QByteArray ba, quint64 tid );
bool InstallTmd( const QByteArray ba, quint64 tid );
bool InstallSharedContent( const QByteArray ba, const QByteArray hash = QByteArray() );
bool InstallPrivateContent( const QByteArray ba, quint64 tid, const QString &cid );
void AbortInstalling( quint64 tid );
//go through and delete all the stuff in a given folder and then delete the folder itself
//this function expects an absolute path, not a relitive one inside the nand dump
bool RecurseDeleteFolder( const QString &path );
};
#endif // NANDDUMP_H

1476
nand_dump/nusdownloader.cpp Normal file

File diff suppressed because it is too large Load Diff

194
nand_dump/nusdownloader.h Normal file
View File

@ -0,0 +1,194 @@
#ifndef NUSDOWNLOADER_H
#define NUSDOWNLOADER_H
#include "includes.h"
#include "tiktmd.h"
#define SHOPPING_USER_AGENT "Opera/9.00 (Nintendo Wii; U; ; 1038-58; Wii Shop Channel/1.0; en)"
#define UPDATING_USER_AGENT "wii libnup/1.0"
#define VIRTUAL_CONSOLE_USER_AGENT "libec-3.0.7.06111123"
#define WIICONNECT24_USER_AGENT "WiiConnect24/1.0FC4plus1 (build 061114161108)"
#define NUS_BASE_URL "http://ccs.shop.wii.com/ccs/download/"
enum
{
IDX_CETK = 0x9000,
IDX_TMD
};
//struct used to keep all the data about a NUS request together
//when a finished job is returned, the data list will be the TMD, then the ticket, then all the contents
struct NusJob
{
quint64 tid;
quint16 version;
bool decrypt;
QList<QByteArray> data;
};
struct downloadJob
{
QString tid;
QString name;
quint16 index;
QByteArray data;
};
//class to download titles from nintendo update servers
class NusDownloader : public QObject
{
Q_OBJECT
public:
explicit NusDownloader( QObject *parent = 0, const QString &cPath = QString() );
//set the path used to cache files
void SetCachePath( const QString &cPath = QString() );
//get a title from NUS. if version is 0, get the latest one
void GetTitle( NusJob job );
void GetTitles( QList<NusJob> jobs );
void Get( quint64 tid, bool decrypt, quint16 version = 0 );
//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"
//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 );
//get a list of titles for a given update
//if a title is not available on NUS, a substitute is given instead ( a later version of the same title )
//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 > List30u();
static QMap< quint64, quint16 > List31u();
static QMap< quint64, quint16 > List32u();
static QMap< quint64, quint16 > List33u();
static QMap< quint64, quint16 > List34u();
static QMap< quint64, quint16 > List40u();
static QMap< quint64, quint16 > List41u();
static QMap< quint64, quint16 > List42u();
static QMap< quint64, quint16 > List43u();
/* static QMap< quint64, quint16 > List30e();
static QMap< quint64, quint16 > List31e();
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 > List31j();
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();*/
private:
//helper function to create a job
downloadJob CreateJob( QString name, quint16 index );
//path on the PC to try and get files from and save to to avoid downloading deplicates from NUS
QString cachePath;
//get data from the cache and put it in the job's data
//uses the version from currentJob
QByteArray GetDataFromCache( downloadJob job );
//saves downloaded data to the HDD for later use
bool SaveDataToCache( const QString &path, const QByteArray &stuff );
//check the tmd and try to ticket
void ReadTmdAndGetTicket( QByteArray ba );
bool DecryptCheckHashAndAppendData( const QByteArray &encData, quint16 idx );
//triggered when a file is done downloading
void FileIsFinishedDownloading( downloadJob job );
//send a fail message about the current job and skip to the next
void CurrentJobErrored( const QString &str );
//print info about a job to qDebug()
void DbgJoB( NusJob job );
//get the path for a file in the local cache
QString GetCachePath( quint32 idx );
//list of stuff to do
QList<NusJob>jobList;
//variables to keep track of the current downloading title
NusJob currentJob;
Tmd curTmd;
//variables for actually downloading something from the interwebz
QNetworkAccessManager manager;
QQueue< downloadJob > downloadQueue;
QNetworkReply *currentDownload;
QTime downloadTime;
QString currentJobText;
downloadJob dlJob;
//should be true if this object is busy getting a title and false if all work is done, or a fatal error
bool running;
//used to help in sending the overall progress done, should be reset on fatal error or when all jobs are done
quint32 totalJobs;
//used to help in sending the progress for the current title
quint32 totalTitleSize;
quint32 TitleSizeDownloaded();
QByteArray decKey;
signals:
void SendError( const QString &message, NusJob job );//send an errer and the title the error is about
//send an errer and the title the error is about, no more jobs will be done, and the SendDone signal will not be emited
void SendFatalErrorError( const QString &message, NusJob job );
void SendDone();//message that all jobs are done
//send progress about the currently downloading job
void SendDownloadProgress( int );
//send progress about the overall progress
void SendTotalProgress( int );
//send progress info about the current title
void SendTitleProgress( int );
//sends a completed job to whoever is listening
void SendData( NusJob );
//a file is done downloading
void finished();
//send pretty text about what is happening
void SendText( QString );
//maybe this one is redundant
//void SendJobFinished( downloadJob );
public slots:
void StartNextJob();
void GetNextItemForCurrentTitle();
//slots for actually downloading something from online
void StartDownload();
void downloadProgress( qint64 bytesReceived, qint64 bytesTotal );
void downloadFinished();
void downloadReadyRead();
};
#endif // NUSDOWNLOADER_H

View File

@ -0,0 +1,116 @@
#include "settingtxtdialog.h"
#include "ui_settingtxtdialog.h"
#include "tools.h"
SettingTxtDialog::SettingTxtDialog( QWidget *parent, const QByteArray &old ) : QDialog(parent), ui(new Ui::SettingTxtDialog)
{
ui->setupUi( this );
if( !old.isEmpty() )
{
QByteArray copy = old;
copy = LolCrypt( copy );
QString str( copy );
str.replace( "\r\n", "\n" );//maybe not needed to do this in 2 steps, but there may be some reason the file only uses "\n", so do it this way to be safe
QStringList parts = str.split( "\n", QString::SkipEmptyParts );
foreach( QString part, parts )
{
QString p = part;
if( part.startsWith( "AREA=" ) )
{
p.remove( 0, 5 );
ui->lineEdit_area->setText( p );
}
else if( part.startsWith( "MODEL=" ) )
{
p.remove( 0, 6 );
ui->lineEdit_model->setText( p );
}
else if( part.startsWith( "DVD=" ) )
{
p.remove( 0, 4 );
ui->lineEdit_dvd->setText( p );
}
else if( part.startsWith( "MPCH=" ) )
{
p.remove( 0, 5 );
ui->lineEdit_mpch->setText( p );
}
else if( part.startsWith( "CODE=" ) )
{
p.remove( 0, 5 );
ui->lineEdit_code->setText( p );
}
else if( part.startsWith( "SERNO=" ) )
{
p.remove( 0, 6 );
ui->lineEdit_serno->setText( p );
}
else if( part.startsWith( "VIDEO=" ) )
{
p.remove( 0, 6 );
ui->lineEdit_video->setText( p );
}
else if( part.startsWith( "GAME=" ) )
{
p.remove( 0, 5 );
ui->lineEdit_game->setText( p );
}
else
{
qDebug() << "SettingTxtDialog::SettingTxtDialog -> unhandled shit" << p;
}
}
}
}
SettingTxtDialog::~SettingTxtDialog()
{
delete ui;
}
//ok button clicked
void SettingTxtDialog::on_buttonBox_accepted()
{
QString s = "AREA=" + ui->lineEdit_area->text() + "\r\n" +
"MODEL=" + ui->lineEdit_model->text() + "\r\n" +
"DVD=" + ui->lineEdit_dvd->text() + "\r\n" +
"MPCH=" + ui->lineEdit_mpch->text() + "\r\n" +
"CODE=" + ui->lineEdit_code->text() + "\r\n" +
"SERNO=" + ui->lineEdit_serno->text() + "\r\n" +
"VIDEO=" + ui->lineEdit_video->text() + "\r\n" +
"GAME=" + ui->lineEdit_game->text();
ret = s.toAscii();
ret = PaddedByteArray( ret, 0x100 );
//hexdump( ret );
ret = LolCrypt( ret );
//hexdump( ret );
}
QByteArray SettingTxtDialog::LolCrypt( QByteArray ba )
{
int s;
for( s = ba.size() - 1; s > 0; s-- )
if( ba.at( s ) != '\0' )
break;
QByteArray ret = ba;
quint32 key = 0x73b5dbfa;
quint8 * stuff = (quint8 *)ret.data();
for( int i = 0; i < s + 1; i++ )
{
*stuff ^= ( key & 0xff );
stuff++;
key = ( ( key << 1 ) | ( key >> 31 ) );
}
return ret;
}
QByteArray SettingTxtDialog::Edit( QWidget *parent, const QByteArray &old )
{
SettingTxtDialog d( parent, old );
if( d.exec() )
return d.ret;
return QByteArray();
}

View File

@ -0,0 +1,31 @@
#ifndef SETTINGTXTDIALOG_H
#define SETTINGTXTDIALOG_H
#include "includes.h"
namespace Ui {
class SettingTxtDialog;
}
class SettingTxtDialog : public QDialog
{
Q_OBJECT
public:
explicit SettingTxtDialog( QWidget *parent = 0, const QByteArray &old = QByteArray() );
~SettingTxtDialog();
static QByteArray Edit( QWidget *parent = 0, const QByteArray &old = QByteArray() );
static QByteArray LolCrypt( QByteArray ba );
private:
Ui::SettingTxtDialog *ui;
QByteArray ret;
private slots:
void on_buttonBox_accepted();
};
#endif // SETTINGTXTDIALOG_H

View File

@ -0,0 +1,183 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>SettingTxtDialog</class>
<widget class="QDialog" name="SettingTxtDialog">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>333</width>
<height>295</height>
</rect>
</property>
<property name="windowTitle">
<string>Dialog</string>
</property>
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<layout class="QFormLayout" name="formLayout">
<item row="0" column="0">
<widget class="QLabel" name="label_area">
<property name="text">
<string>Area</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QLineEdit" name="lineEdit_area">
<property name="text">
<string>USA</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label_model">
<property name="text">
<string>Model</string>
</property>
</widget>
</item>
<item row="1" column="1">
<widget class="QLineEdit" name="lineEdit_model">
<property name="text">
<string>RVL-001(USA)</string>
</property>
</widget>
</item>
<item row="2" column="0">
<widget class="QLabel" name="label_dvd">
<property name="text">
<string>DVD</string>
</property>
</widget>
</item>
<item row="2" column="1">
<widget class="QLineEdit" name="lineEdit_dvd">
<property name="text">
<string>0</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="label_mpch">
<property name="text">
<string>MPCH</string>
</property>
</widget>
</item>
<item row="3" column="1">
<widget class="QLineEdit" name="lineEdit_mpch">
<property name="text">
<string>0x7FFE</string>
</property>
</widget>
</item>
<item row="4" column="0">
<widget class="QLabel" name="label_code">
<property name="text">
<string>Code</string>
</property>
</widget>
</item>
<item row="4" column="1">
<widget class="QLineEdit" name="lineEdit_code">
<property name="text">
<string>LU</string>
</property>
</widget>
</item>
<item row="5" column="0">
<widget class="QLabel" name="label_SerNo">
<property name="text">
<string>SerNo</string>
</property>
</widget>
</item>
<item row="5" column="1">
<widget class="QLineEdit" name="lineEdit_serno">
<property name="text">
<string>123456789</string>
</property>
<property name="maxLength">
<number>9</number>
</property>
</widget>
</item>
<item row="6" column="0">
<widget class="QLabel" name="label_video">
<property name="text">
<string>Video</string>
</property>
</widget>
</item>
<item row="6" column="1">
<widget class="QLineEdit" name="lineEdit_video">
<property name="text">
<string>NTSC</string>
</property>
</widget>
</item>
<item row="7" column="0">
<widget class="QLabel" name="label_game">
<property name="text">
<string>Game</string>
</property>
</widget>
</item>
<item row="7" column="1">
<widget class="QLineEdit" name="lineEdit_game">
<property name="text">
<string>US</string>
</property>
</widget>
</item>
</layout>
</item>
<item row="1" column="0">
<widget class="QDialogButtonBox" name="buttonBox">
<property name="orientation">
<enum>Qt::Horizontal</enum>
</property>
<property name="standardButtons">
<set>QDialogButtonBox::Cancel|QDialogButtonBox::Ok</set>
</property>
</widget>
</item>
</layout>
</widget>
<resources/>
<connections>
<connection>
<sender>buttonBox</sender>
<signal>accepted()</signal>
<receiver>SettingTxtDialog</receiver>
<slot>accept()</slot>
<hints>
<hint type="sourcelabel">
<x>248</x>
<y>254</y>
</hint>
<hint type="destinationlabel">
<x>157</x>
<y>274</y>
</hint>
</hints>
</connection>
<connection>
<sender>buttonBox</sender>
<signal>rejected()</signal>
<receiver>SettingTxtDialog</receiver>
<slot>reject()</slot>
<hints>
<hint type="sourcelabel">
<x>316</x>
<y>260</y>
</hint>
<hint type="destinationlabel">
<x>286</x>
<y>274</y>
</hint>
</hints>
</connection>
</connections>
</ui>

371
nand_dump/sha1.c Executable file
View File

@ -0,0 +1,371 @@
/*
* sha1.c
*
* Copyright (C) 1998, 2009
* Paul E. Jones <paulej@packetizer.com>
* All Rights Reserved
*
*****************************************************************************
* $Id: sha1.c 12 2009-06-22 19:34:25Z paulej $
*****************************************************************************
*
* Description:
* This file implements the Secure Hashing Standard as defined
* in FIPS PUB 180-1 published April 17, 1995.
*
* The Secure Hashing Standard, which uses the Secure Hashing
* Algorithm (SHA), produces a 160-bit message digest for a
* given data stream. In theory, it is highly improbable that
* two messages will produce the same message digest. Therefore,
* this algorithm can serve as a means of providing a "fingerprint"
* for a message.
*
* Portability Issues:
* SHA-1 is defined in terms of 32-bit "words". This code was
* written with the expectation that the processor has at least
* a 32-bit machine word size. If the machine word size is larger,
* the code should still function properly. One caveat to that
* is that the input functions taking characters and character
* arrays assume that only 8 bits of information are stored in each
* character.
*
* Caveats:
* SHA-1 is designed to work with messages less than 2^64 bits
* long. Although SHA-1 allows a message digest to be generated for
* messages of any number of bits less than 2^64, this
* implementation only works with messages with a length that is a
* multiple of the size of an 8-bit character.
*
*/
#include "sha1.h"
/*
* Define the circular shift macro
*/
#define SHA1CircularShift(bits,word) \
((((word) << (bits)) & 0xFFFFFFFF) | \
((word) >> (32-(bits))))
/* Function prototypes */
void SHA1ProcessMessageBlock(SHA1Context *);
void SHA1PadMessage(SHA1Context *);
/*
* SHA1Reset
*
* Description:
* This function will initialize the SHA1Context in preparation
* for computing a new message digest.
*
* Parameters:
* context: [in/out]
* The context to reset.
*
* Returns:
* Nothing.
*
* Comments:
*
*/
void SHA1Reset(SHA1Context *context)
{
context->Length_Low = 0;
context->Length_High = 0;
context->Message_Block_Index = 0;
context->Message_Digest[0] = 0x67452301;
context->Message_Digest[1] = 0xEFCDAB89;
context->Message_Digest[2] = 0x98BADCFE;
context->Message_Digest[3] = 0x10325476;
context->Message_Digest[4] = 0xC3D2E1F0;
context->Computed = 0;
context->Corrupted = 0;
}
/*
* SHA1Result
*
* Description:
* This function will return the 160-bit message digest into the
* Message_Digest array within the SHA1Context provided
*
* Parameters:
* context: [in/out]
* The context to use to calculate the SHA-1 hash.
*
* Returns:
* 1 if successful, 0 if it failed.
*
* Comments:
*
*/
int SHA1Result(SHA1Context *context)
{
if (context->Corrupted)
{
return 0;
}
if (!context->Computed)
{
SHA1PadMessage(context);
context->Computed = 1;
}
return 1;
}
/*
* SHA1Input
*
* Description:
* This function accepts an array of octets as the next portion of
* the message.
*
* Parameters:
* context: [in/out]
* The SHA-1 context to update
* message_array: [in]
* An array of characters representing the next portion of the
* message.
* length: [in]
* The length of the message in message_array
*
* Returns:
* Nothing.
*
* Comments:
*
*/
void SHA1Input( SHA1Context *context,
const unsigned char *message_array,
unsigned length)
{
if (!length)
{
return;
}
if (context->Computed || context->Corrupted)
{
context->Corrupted = 1;
return;
}
while(length-- && !context->Corrupted)
{
context->Message_Block[context->Message_Block_Index++] =
(*message_array & 0xFF);
context->Length_Low += 8;
/* Force it to 32 bits */
context->Length_Low &= 0xFFFFFFFF;
if (context->Length_Low == 0)
{
context->Length_High++;
/* Force it to 32 bits */
context->Length_High &= 0xFFFFFFFF;
if (context->Length_High == 0)
{
/* Message is too long */
context->Corrupted = 1;
}
}
if (context->Message_Block_Index == 64)
{
SHA1ProcessMessageBlock(context);
}
message_array++;
}
}
/*
* SHA1ProcessMessageBlock
*
* Description:
* This function will process the next 512 bits of the message
* stored in the Message_Block array.
*
* Parameters:
* None.
*
* Returns:
* Nothing.
*
* Comments:
* Many of the variable names in the SHAContext, especially the
* single character names, were used because those were the names
* used in the publication.
*
*
*/
void SHA1ProcessMessageBlock(SHA1Context *context)
{
const unsigned K[] = /* Constants defined in SHA-1 */
{
0x5A827999,
0x6ED9EBA1,
0x8F1BBCDC,
0xCA62C1D6
};
int t; /* Loop counter */
unsigned temp; /* Temporary word value */
unsigned W[80]; /* Word sequence */
unsigned A, B, C, D, E; /* Word buffers */
/*
* Initialize the first 16 words in the array W
*/
for(t = 0; t < 16; t++)
{
W[t] = ((unsigned) context->Message_Block[t * 4]) << 24;
W[t] |= ((unsigned) context->Message_Block[t * 4 + 1]) << 16;
W[t] |= ((unsigned) context->Message_Block[t * 4 + 2]) << 8;
W[t] |= ((unsigned) context->Message_Block[t * 4 + 3]);
}
for(t = 16; t < 80; t++)
{
W[t] = SHA1CircularShift(1,W[t-3] ^ W[t-8] ^ W[t-14] ^ W[t-16]);
}
A = context->Message_Digest[0];
B = context->Message_Digest[1];
C = context->Message_Digest[2];
D = context->Message_Digest[3];
E = context->Message_Digest[4];
for(t = 0; t < 20; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | ((~B) & D)) + E + W[t] + K[0];
temp &= 0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 20; t < 40; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[1];
temp &= 0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 40; t < 60; t++)
{
temp = SHA1CircularShift(5,A) +
((B & C) | (B & D) | (C & D)) + E + W[t] + K[2];
temp &= 0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
for(t = 60; t < 80; t++)
{
temp = SHA1CircularShift(5,A) + (B ^ C ^ D) + E + W[t] + K[3];
temp &= 0xFFFFFFFF;
E = D;
D = C;
C = SHA1CircularShift(30,B);
B = A;
A = temp;
}
context->Message_Digest[0] =
(context->Message_Digest[0] + A) & 0xFFFFFFFF;
context->Message_Digest[1] =
(context->Message_Digest[1] + B) & 0xFFFFFFFF;
context->Message_Digest[2] =
(context->Message_Digest[2] + C) & 0xFFFFFFFF;
context->Message_Digest[3] =
(context->Message_Digest[3] + D) & 0xFFFFFFFF;
context->Message_Digest[4] =
(context->Message_Digest[4] + E) & 0xFFFFFFFF;
context->Message_Block_Index = 0;
}
/*
* SHA1PadMessage
*
* Description:
* According to the standard, the message must be padded to an even
* 512 bits. The first padding bit must be a '1'. The last 64
* bits represent the length of the original message. All bits in
* between should be 0. This function will pad the message
* according to those rules by filling the Message_Block array
* accordingly. It will also call SHA1ProcessMessageBlock()
* appropriately. When it returns, it can be assumed that the
* message digest has been computed.
*
* Parameters:
* context: [in/out]
* The context to pad
*
* Returns:
* Nothing.
*
* Comments:
*
*/
void SHA1PadMessage(SHA1Context *context)
{
/*
* Check to see if the current message block is too small to hold
* the initial padding bits and length. If so, we will pad the
* block, process it, and then continue padding into a second
* block.
*/
if (context->Message_Block_Index > 55)
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 64)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
SHA1ProcessMessageBlock(context);
while(context->Message_Block_Index < 56)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
}
else
{
context->Message_Block[context->Message_Block_Index++] = 0x80;
while(context->Message_Block_Index < 56)
{
context->Message_Block[context->Message_Block_Index++] = 0;
}
}
/*
* Store the message length as the last 8 octets
*/
context->Message_Block[56] = (context->Length_High >> 24) & 0xFF;
context->Message_Block[57] = (context->Length_High >> 16) & 0xFF;
context->Message_Block[58] = (context->Length_High >> 8) & 0xFF;
context->Message_Block[59] = (context->Length_High) & 0xFF;
context->Message_Block[60] = (context->Length_Low >> 24) & 0xFF;
context->Message_Block[61] = (context->Length_Low >> 16) & 0xFF;
context->Message_Block[62] = (context->Length_Low >> 8) & 0xFF;
context->Message_Block[63] = (context->Length_Low) & 0xFF;
SHA1ProcessMessageBlock(context);
}

40
nand_dump/sha1.h Executable file
View File

@ -0,0 +1,40 @@
#ifndef _SHA1_H_
#define _SHA1_H_
#ifdef __cplusplus
extern "C" {
#endif
/*
* This structure will hold context information for the hashing
* operation
*/
typedef struct SHA1Context
{
unsigned Message_Digest[5]; /* Message Digest (output) */
unsigned Length_Low; /* Message length in bits */
unsigned Length_High; /* Message length in bits */
unsigned char Message_Block[64]; /* 512-bit message blocks */
int Message_Block_Index; /* Index into message block array */
int Computed; /* Is the digest computed? */
int Corrupted; /* Is the message digest corruped? */
} SHA1Context;
/*
* Function Prototypes
*/
void SHA1Reset(SHA1Context *);
int SHA1Result(SHA1Context *);
void SHA1Input( SHA1Context *,
const unsigned char *,
unsigned);
#ifdef __cplusplus
}
#endif
#endif

View File

@ -0,0 +1,122 @@
#include "sharedcontentmap.h"
#include "tools.h"
SharedContentMap::SharedContentMap( QByteArray old )
{
data = old;
if( data.size() )
Check();
}
bool SharedContentMap::Check( const QString &path )
{
if( !data.size() || data.size() % 28 )
{
qWarning() << "SharedContentMap::Check -> bad size" << hex << data.size();
return false;
}
QByteArray cid;
quint32 cnt = data.size() / 28;
//check that all the cid's can be converted to numbers, and make sure that ther are in numerical order
for( quint32 i = 0; i < cnt; i++ )
{
bool ok;
cid = data.mid( ( i * 28 ), 8 );
quint32 num = cid.toInt( &ok, 16 );
if( !ok )
{
qWarning() << "SharedContentMap::Check -> error converting" << cid << "to a number";
return false;
}
if( i != num )
{
qWarning() << "SharedContentMap::Check -> something is a miss" << num << i;
return false;
}
}
//this is all there is to check right now
if( path.isEmpty() )
return true;
QDir dir( path );
if( !dir.exists() )
{
qWarning() << "SharedContentMap::Check ->" << path << "doesnt exist";
return false;
}
//check hashes for all the contents in the map
for( quint32 i = 0; i < cnt; i++ )
{
cid = data.mid( ( i * 28 ), 8 );
QString appName( cid );
QFileInfo fi = dir.absoluteFilePath( appName + ".app" );
if( !fi.exists() )
{
qWarning() << "SharedContentMap::Check -> content in the map isnt found in" << path;
return false;
}
QFile f( fi.absoluteFilePath() );
if( !f.open( QIODevice::ReadOnly ) )
{
qWarning() << "SharedContentMap::Check -> cant open" << fi.absoluteFilePath();
return false;
}
QByteArray goods = f.readAll();
f.close();
QByteArray expectedHash = data.mid( ( i * 28 ) + 8, 20 );
QByteArray actualHash = GetSha1( goods );
if( expectedHash != actualHash )
{
qWarning() << "SharedContentMap::Check -> hash mismatch in" << fi.absoluteFilePath() << "\n"
<< expectedHash << actualHash;
return false;
}
}
return true;
}
QString SharedContentMap::GetAppFromHash( QByteArray hash )
{
quint32 cnt = data.size() / 28;
for( quint32 i = 0; i < cnt; i++ )
{
QString appName( data.mid( ( i * 28 ), 8 ) );
QByteArray mapHash = data.mid( ( i * 28 ) + 8, 20 );
//qDebug() << QString( mapHash ) << hash;
if( mapHash == hash )
{
return appName;
}
}
//hash not found
return QString();
}
QString SharedContentMap::GetNextEmptyCid()
{
quint32 cnt = data.size() / 28;
quint32 ret = 0;
QList<quint32>cids;
for( quint32 i = 0; i < cnt; i++ )
{
QByteArray cid = data.mid( ( i * 28 ), 8 );
quint32 num = cid.toInt( NULL, 16 );
cids << num;
}
//find the lowest number cid that isnt used
while( cids.contains( ret ) )
ret++;
//qDebug() << hex << ret;
return QString( "%1" ).arg( ret, 8, 16, QChar( '0' ) );
}
void SharedContentMap::AddEntry( const QString &app, const QByteArray &hash )
{
data += app.toAscii() + hash;
//qDebug() << "SharedContentMap::AddEntry -> added entry, rechecking this beast";
Check();
}

View File

@ -0,0 +1,27 @@
#ifndef SHAREDCONTENTMAP_H
#define SHAREDCONTENTMAP_H
#include "includes.h"
class SharedContentMap
{
public:
SharedContentMap( QByteArray old = QByteArray() );
//checks that the content map is sane
//size should be correct, contents should be in numerical order
//if a path is given, it will check that the hashes in the map match up with the contents in the folder
bool Check( const QString &path = QString() );
QString GetAppFromHash( QByteArray hash );
QString GetNextEmptyCid();
void AddEntry( const QString &app, const QByteArray &hash );
const QByteArray Data(){ return data; }
private:
QByteArray data;
};
#endif // SHAREDCONTENTMAP_H

129
nand_dump/tiktmd.cpp Normal file
View File

@ -0,0 +1,129 @@
#include "tiktmd.h"
#include "tools.h"
#include "aes.h"
#include <iostream>
Tmd::Tmd( QByteArray stuff )
{
data = stuff;
p_tmd = NULL;
if( data.isEmpty() || data.size() < 4 )//maybe 4 is still too low?
return;
SetPointer();
//hexdump( stuff );
}
quint64 Tmd::Tid()
{
if( !p_tmd )
return 0;
return qFromBigEndian( p_tmd->title_id );
}
QString Tmd::Cid( quint16 i )
{
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 ) )
return QByteArray();
return QByteArray( (const char*)&p_tmd->contents[ i ].hash, 20 );
}
quint64 Tmd::Size( quint16 i )
{
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 ) )
return 0;
return qFromBigEndian( p_tmd->contents[ i ].type );
}
quint32 Tmd::SignedSize()
{
return payLoadOffset + sizeof( tmd ) + ( sizeof( tmd_content ) * qFromBigEndian( p_tmd->num_contents ) );
}
void Tmd::SetPointer()
{
payLoadOffset = 0;
if( data.startsWith( "\x0\x1\x0\x0" ) )
payLoadOffset = sizeof( sig_rsa2048 );
else if( data.startsWith( "\x0\x1\x0\x1" ) )
payLoadOffset = sizeof( sig_rsa4096 );
else if( data.startsWith( "\x0\x1\x0\x2" ) )
payLoadOffset = sizeof( sig_ecdsa );
p_tmd = (tmd*)((quint8*)data.data() + payLoadOffset);
}
Ticket::Ticket( QByteArray stuff )
{
data = stuff;
p_tik = NULL;
if( data.isEmpty() || data.size() < 4 )//maybe 4 is still too low?
return;
SetPointer();
}
quint64 Ticket::Tid()
{
if( !p_tik )
return 0;
return qFromBigEndian( p_tik->titleid );
}
QByteArray Ticket::DecryptedKey()
{
quint8 iv[ 16 ];
quint8 keyin[ 16 ];
quint8 keyout[ 16 ];
static quint8 commonkey[ 16 ] = COMMON_KEY;
quint8 *enc_key = (quint8 *)&p_tik->cipher_title_key;
memcpy( keyin, enc_key, sizeof keyin );
memset( keyout, 0, sizeof keyout );
memset( iv, 0, sizeof iv);
memcpy( iv, &p_tik->titleid, sizeof p_tik->titleid );
aes_set_key( commonkey );
aes_decrypt( iv, keyin, keyout, sizeof( keyin ) );
return QByteArray( (const char*)&keyout, 16 );
}
quint32 Ticket::SignedSize()
{
return payLoadOffset + sizeof( tik );
}
void Ticket::SetPointer()
{
payLoadOffset = 0;
if( data.startsWith( "\x0\x1\x0\x0" ) )
payLoadOffset = sizeof( sig_rsa2048 );
else if( data.startsWith( "\x0\x1\x0\x1" ) )
payLoadOffset = sizeof( sig_rsa4096 );
else if( data.startsWith( "\x0\x1\x0\x2" ) )
payLoadOffset = sizeof( sig_ecdsa );
p_tik = (tik*)((quint8*)data.data() + payLoadOffset);
}

231
nand_dump/tiktmd.h Normal file
View File

@ -0,0 +1,231 @@
#ifndef TIKTMD_H
#define TIKTMD_H
#include "includes.h"
#define ES_SIG_RSA4096 0x10000
#define ES_SIG_RSA2048 0x10001
#define ES_SIG_ECDSA 0x10002
typedef quint32 sigtype;
typedef sigtype sig_header;
typedef sig_header signed_blob;
typedef quint8 sha1[20];
typedef quint8 aeskey[16];
typedef struct _sig_rsa2048 {
sigtype type;
quint8 sig[256];
quint8 fill[60];
} __attribute__((packed)) sig_rsa2048;
typedef struct _sig_rsa4096 {
sigtype type;
quint8 sig[512];
quint8 fill[60];
} __attribute__((packed)) sig_rsa4096;
typedef struct _sig_ecdsa {
sigtype type;
quint8 sig[60];
quint8 fill[64];
} __attribute__((packed)) sig_ecdsa;
typedef char sig_issuer[0x40];
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
aeskey cipher_title_key;
quint8 fill2;
quint64 ticketid;
quint32 devicetype;
quint64 titleid;
quint16 access_mask;
quint8 reserved[0x3c];
quint8 cidx_mask[0x40];
quint16 padding;
tiklimit limits[8];
} __attribute__((packed)) tik;
typedef struct _tmd_content {
quint32 cid;
quint16 index;
quint16 type;
quint64 size;
sha1 hash;
} __attribute__((packed)) tmd_content;
typedef struct _tmd {
sig_issuer issuer; //0x140
quint8 version; //0x180
quint8 ca_crl_version; //0x181
quint8 signer_crl_version; //0x182
quint8 fill2; //0x183
quint64 sys_version; //0x184
quint64 title_id; //0x18c
quint32 title_type; //0x194
quint16 group_id; //0x198
quint16 zero; //0x19a
quint16 region; //0x19c
quint8 ratings[16]; //0x19e
quint8 reserved[12]; //0x1ae
quint8 ipc_mask[12];
quint8 reserved2[18];
quint32 access_rights;
quint16 title_version;
quint16 num_contents;
quint16 boot_index;
quint16 fill3;
// content records follow
// C99 flexible array
tmd_content contents[];
} __attribute__((packed)) tmd;
typedef struct _tmd_view_content
{
quint32 cid;
quint16 index;
quint16 type;
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;
char cert_name[64];
quint32 cert_id; //???
} __attribute__((packed)) cert_header;
typedef struct _cert_rsa2048 {
sig_issuer issuer;
quint32 cert_type;
char cert_name[64];
quint32 cert_id;
quint8 modulus[256];
quint32 exponent;
quint8 pad[0x34];
} __attribute__((packed)) cert_rsa2048;
typedef struct _cert_rsa4096 {
sig_issuer issuer;
quint32 cert_type;
char cert_name[64];
quint32 cert_id;
quint8 modulus[512];
quint32 exponent;
quint8 pad[0x34];
} __attribute__((packed)) cert_rsa4096;
typedef struct _cert_ecdsa {
sig_issuer issuer;
quint32 cert_type;
char cert_name[64];
quint32 cert_id; // ng key id
quint8 r[30];
quint8 s[30];
quint8 pad[0x3c];
} __attribute__((packed)) cert_ecdsa;
#define COMMON_KEY {0xeb, 0xe4, 0x2a, 0x22, 0x5e, 0x85, 0x93, 0xe4, 0x48, 0xd9, 0xc5, 0x45, 0x73, 0x81, 0xaa, 0xf7}
#define TITLE_IDH(x) ((u32)(((u64)(x))>>32))
#define TITLE_IDL(x) ((u32)(((u64)(x))))
//just a quick class to try to keep the rest of the code from getting full of the same shit over and over
class Ticket
{
public:
Ticket( QByteArray stuff );
//expose the tik data to the rest of the code so it cas read directly from the p_tmd instead of having to add a function to access all the data
//the pointer should be good until "data" is changed
const tik *payload(){ return p_tik; }
quint64 Tid();
QByteArray DecryptedKey();
quint32 SignedSize();
private:
quint32 payLoadOffset;
//let data hold the entire tik data
QByteArray data;
//point the p_tik to the spot in "data" we want
void SetPointer();
//this is just a pointer to the actual good stuff in "data".
//whenever data is changes, this pointer will become invalid and needs to be reset
tik *p_tik;
};
class Tmd
{
public:
Tmd( QByteArray stuff );
//expose the tmd data to the rest of the code so it cas read directly from the p_tmd instead of having to add a function to access all the data
//the pointer should be good until "data" is changed
const tmd *payload(){ return p_tmd; }
//get a string containing the u32 of cid i
QString Cid( quint16 i );
//get a btyearray with the hosh of the given conten
QByteArray Hash( quint16 i );
//returned in host endian
quint64 Size( quint16 i );
quint16 Type( quint16 i );
quint64 Tid();
quint32 SignedSize();
private:
quint32 payLoadOffset;
//let data hold the entire tmd data
QByteArray data;
//point the p_tmd to the spot in "data" we want
void SetPointer();
//this is just a pointer to the actual good stuff in "data".
//whenever data is changes, this pointer will become invalid and needs to be reset
tmd *p_tmd;
};
#endif // TIKTMD_H

131
nand_dump/tools.cpp Normal file
View File

@ -0,0 +1,131 @@
#include "tools.h"
#include "includes.h"
#include "aes.h"
#include "sha1.h"
QString currentDir;
QString cachePath = "./NUS_cache";
QString nandPath = "./dump";
char ascii( char s ) {
if ( s < 0x20 ) return '.';
if ( s > 0x7E ) return '.';
return s;
}
//using stderr just because qtcreator stows it correctly when mixed with qDebug(), qWarning(), etc
//otherwise it may not be shown in the correct spot in the output due to stdout/stderr caches
void hexdump( const void *d, int len ) {
unsigned char *data;
int i, off;
data = (unsigned char*)d;
fprintf( stderr, "\n");
for ( off = 0; off < len; off += 16 ) {
fprintf( stderr, "%08x ", off );
for ( i=0; i<16; i++ )
{
if( ( i + 1 ) % 4 )
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x",data[ off + i ]);
}
else
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x ",data[ off + i ]);
}
}
fprintf( stderr, " " );
for ( i = 0; i < 16; i++ )
if ( ( i + off) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%c", ascii( data[ off + i ]));
fprintf( stderr,"\n");
}
fflush( stderr );
}
void hexdump( const QByteArray &d, int from, int len )
{
hexdump( d.data() + from, len == -1 ? d.size() : len );
}
void hexdump12( const void *d, int len ) {
unsigned char *data;
int i, off;
data = (unsigned char*)d;
fprintf( stderr, "\n");
for ( off = 0; off < len; off += 12 ) {
fprintf( stderr, "%08x ", off );
for ( i=0; i<12; i++ )
{
if( ( i + 1 ) % 4 )
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x",data[ off + i ]);
}
else
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x ",data[ off + i ]);
}
}
fprintf( stderr, " " );
for ( i = 0; i < 12; i++ )
if ( ( i + off) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%c", ascii( data[ off + i ]));
fprintf( stderr,"\n");
}
fflush( stderr );
}
void hexdump12( const QByteArray &d, int from, int len )
{
hexdump12( d.data() + from, len == -1 ? d.size() : len );
}
QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo )
{
QByteArray padding( RU( padTo, orig.size() ) - orig.size(), '\0' );
//qDebug() << "padding with" << hex << RU( padTo, orig.size() ) << "bytes" <<
return orig + padding;
}
QByteArray AesDecrypt( 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_decrypt( iv, (const quint8*)source.data(), (quint8*)ret.data(), source.size() );
return ret;
}
void AesSetKey( const QByteArray key )
{
aes_set_key( (const quint8*)key.data() );
}
QByteArray GetSha1( QByteArray stuff )
{
SHA1Context sha;
SHA1Reset( &sha );
SHA1Input( &sha, (const unsigned char*)stuff.data(), stuff.size() );
if( !SHA1Result( &sha ) )
{
qWarning() << "GetSha1 -> sha error";
return QByteArray();
}
QByteArray ret( 20, '\0' );
quint8 *p = (quint8 *)ret.data();
for( int i = 0; i < 5 ; i++ )
{
quint32 part = qFromBigEndian( sha.Message_Digest[ i ] );
memcpy( p + ( i * 4 ), &part, 4 );
}
//hexdump( ret );
return ret;
}

34
nand_dump/tools.h Normal file
View File

@ -0,0 +1,34 @@
#ifndef TOOLS_H
#define TOOLS_H
#include "includes.h"
#define RU(x,n) (-(-(x) & -(n))) //round up
#define MIN( x, y ) ( ( x ) < ( y ) ? ( x ) : ( y ) )
#define MAX( x, y ) ( ( x ) > ( y ) ? ( x ) : ( y ) )
void hexdump( const void *d, int len );
void hexdump( const QByteArray &d, int from = 0, int len = -1 );
void hexdump12( const void *d, int len );
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 GetSha1( QByteArray stuff );
//get a padded version of the given buffer
QByteArray PaddedByteArray( const QByteArray &orig, quint32 padTo );
//keep track of the last folder browsed to when looking for files
extern QString currentDir;
//folder used to cache stuff downloaded from NUS so duplicate titles dont need to be downloaded
extern QString cachePath;
//folder to use as the base path for the nand
extern QString nandPath;
#endif // TOOLS_H

194
nand_dump/uidmap.cpp Normal file
View File

@ -0,0 +1,194 @@
#include "uidmap.h"
#include "tools.h"
UIDmap::UIDmap( const QByteArray old )
{
data = old;
if( data.isEmpty() )
CreateNew();//no need to add all the factory uids if the nand in never going to be sent back to the big N
}
UIDmap::~UIDmap()
{
}
bool UIDmap::Check()
{
if( !data.size() || data.size() % 12 )
{
qWarning() << "UIDmap::Check() bad size:" << hex << data.size();
return false;
}
quint64 tid;
quint32 uid;
QBuffer buf( &data );
buf.open( QIODevice::ReadOnly );
buf.read( (char*)&tid, 8 );
buf.read( (char*)&uid, 4 );
tid = qFromBigEndian( tid );
uid = qFromBigEndian( uid );
if( tid != 0x100000002ull || uid != 0x1000 )//system menu should be the first entry
{
qWarning() << "UIDmap::Check() system menu entry is messed up:" << hex << tid << uid;
buf.close();
return false;
}
QList<quint64> tids;
QList<quint32> uids;
//check that there are no duplicate tid or uid and that all uid are as expected
quint32 cnt = data.size() / 12;
for( quint32 i = 1; i < cnt; i++ )
{
buf.read( (char*)&tid, 8 );
buf.read( (char*)&uid, 4 );
tid = qFromBigEndian( tid );
uid = qFromBigEndian( uid );
if( tids.contains( tid ) )
{
qWarning() << "UIDmap::Check() tid is present more than once:" << QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) );
buf.close();
return false;
}
if( ( uid != 0x1000 + i ) || uids.contains( uid ) )
{
qWarning() << "UIDmap::Check() uid error:" << QString( "%1" ).arg( tid, 16, 16, QChar( '0' ) ) << hex << uid;
buf.close();
return false;
}
tids << tid;
uids << uid;
}
return true;
}
quint32 UIDmap::GetUid( quint64 id, bool autoCreate )
{
//qDebug() << "UIDmap::GetUid" << hex << id;
quint64 tid;
quint32 uid;
QBuffer buf( &data );
buf.open( QIODevice::ReadWrite );
quint32 cnt = data.size() / 12;
for( quint32 i = 0; i < cnt; i++ )
{
buf.read( (char*)&tid, 8 );
buf.read( (char*)&uid, 4 );
tid = qFromBigEndian( tid );
if( tid == id )
{
buf.close();
return qFromBigEndian( uid );
}
}
//not found
if( !autoCreate )
return 0;
//add the new entry
tid = qFromBigEndian( id );
buf.write( (const char*)&tid, 8 );
uid = qFromBigEndian( qFromBigEndian( uid ) + 1 );
buf.write( (const char*)&uid, 4 );
buf.close();
//qDebug() << "new uid:";
//hexdump12( data, ( cnt - 3 ) * 12, 0x30 );
return qFromBigEndian( uid );
}
void UIDmap::CreateNew( bool addFactorySetupDiscs )
{
quint64 tid;
quint32 uid;
QByteArray stuff;
QBuffer buf( &stuff );
buf.open( QIODevice::WriteOnly );
//add the new entry
tid = qFromBigEndian( 0x100000002ull );
buf.write( (const char*)&tid, 8 );
uid = qFromBigEndian( 0x1000 );
buf.write( (const char*)&uid, 4 );
if( !addFactorySetupDiscs )
{
buf.close();
data = stuff;
return;
}
//add some entries for the factory setup discs, as seen on my nand up until the first retail game
for( quint32 i = 1; i < 0x2f; i++ )
{
switch( i )
{
case 0x1:tid = qFromBigEndian( 0x100000004ull ); break;
case 0x2:tid = qFromBigEndian( 0x100000009ull ); break;
case 0x3:tid = qFromBigEndian( 0x100003132334aull ); break;
case 0x4:tid = qFromBigEndian( 0x000100000000deadull ); break;
case 0x5:tid = qFromBigEndian( 0x100000100ull ); break;
case 0x6:tid = qFromBigEndian( 0x100000101ull ); break;
case 0x7:tid = qFromBigEndian( 0x000100003132314aull ); break;
case 0x8:tid = qFromBigEndian( 0x100000015ull ); break;
case 0x9:tid = qFromBigEndian( 0x0001000030303032ull ); break;
case 0xa:tid = qFromBigEndian( 0x100000003ull ); break;
case 0xb:tid = qFromBigEndian( 0x10000000aull ); break;
case 0xc:tid = qFromBigEndian( 0x10000000bull ); break;
case 0xd:tid = qFromBigEndian( 0x10000000cull ); break;
case 0xe:tid = qFromBigEndian( 0x10000000dull ); break;
case 0xf:tid = qFromBigEndian( 0x10000000eull ); break;
case 0x10:tid = qFromBigEndian( 0x10000000full ); break;
case 0x11:tid = qFromBigEndian( 0x100000011ull ); break;
case 0x12:tid = qFromBigEndian( 0x100000014ull ); break;
case 0x13:tid = qFromBigEndian( 0x100000016ull ); break;
case 0x14:tid = qFromBigEndian( 0x10000001cull ); break;
case 0x15:tid = qFromBigEndian( 0x10000001eull ); break;
case 0x16:tid = qFromBigEndian( 0x10000001full ); break;
case 0x17:tid = qFromBigEndian( 0x100000021ull ); break;
case 0x18:tid = qFromBigEndian( 0x100000022ull ); break;
case 0x19:tid = qFromBigEndian( 0x100000023ull ); break;
case 0x1a:tid = qFromBigEndian( 0x100000024ull ); break;
case 0x1b:tid = qFromBigEndian( 0x100000025ull ); break;
case 0x1c:tid = qFromBigEndian( 0x100000026ull ); break;
case 0x1d:tid = qFromBigEndian( 0x100000032ull ); break;
case 0x1e:tid = qFromBigEndian( 0x100000033ull ); break;
case 0x1f:tid = qFromBigEndian( 0x100000035ull ); break;
case 0x20:tid = qFromBigEndian( 0x100000037ull ); break;
case 0x21:tid = qFromBigEndian( 0x1000000feull ); break;
case 0x22:tid = qFromBigEndian( 0x0001000248414341ull ); break;
case 0x23:tid = qFromBigEndian( 0x0001000248414141ull ); break;
case 0x24:tid = qFromBigEndian( 0x0001000248415941ull ); break;
case 0x25:tid = qFromBigEndian( 0x0001000248414641ull ); break;
case 0x26:tid = qFromBigEndian( 0x0001000248414645ull ); break;
case 0x27:tid = qFromBigEndian( 0x0001000248414241ull ); break;
case 0x28:tid = qFromBigEndian( 0x0001000248414741ull ); break;
case 0x29:tid = qFromBigEndian( 0x0001000248414745ull ); break;
case 0x2a:tid = qFromBigEndian( 0x0001000848414b45ull ); break;
case 0x2b:tid = qFromBigEndian( 0x0001000848414c45ull ); break;
case 0x2c:tid = qFromBigEndian( 0x0001000148434745ull ); break;
case 0x2d:tid = qFromBigEndian( 0x0001000031323245ull ); break;
case 0x2e:tid = qFromBigEndian( 0x0001000030303033ull ); break;
default:
qWarning() << "oops" << hex << i;
return;
}
uid = qFromBigEndian( 0x1000 + i );
buf.write( (const char*)&tid, 8 );
buf.write( (const char*)&uid, 4 );
}
buf.close();
data = stuff;
// hexdump12( data );
}

30
nand_dump/uidmap.h Normal file
View File

@ -0,0 +1,30 @@
#ifndef UIDMAP_H
#define UIDMAP_H
#include "includes.h"
class UIDmap
{
public:
UIDmap( const QByteArray old = QByteArray() );
~UIDmap();
//makes sure there are the default entries in the map and that the entries are sane
bool Check();
//returns the uid for the given tid
//returns 0 if it is not found. or if autocreate is true, it will be created and the new uid returned
//numbers are in host endian
quint32 GetUid( quint64 tid, bool autoCreate = true );
//creates a new uid.sys with the system menu entry.
//if addFactorySetupDiscs is true, it will add some entries for the setup discs used in the wii factory ( serve no purpose other than to just exist )
void CreateNew( bool addFactorySetupDiscs = false );
const QByteArray Data(){ return data; }
private:
QByteArray data;
};
#endif // UIDMAP_H

128
nand_dump/wad.cpp Normal file
View File

@ -0,0 +1,128 @@
#include "wad.h"
#include "tools.h"
#include "tiktmd.h"
static QByteArray globalCert;
Wad::Wad( const QByteArray stuff )
{
ok = false;
if( stuff.size() < 0x80 )//less than this and there is definitely nothing there
return;
QByteArray copy = stuff;
QBuffer b( &copy );
b.open( QIODevice::ReadOnly );
quint32 tmp;
b.read( (char*)&tmp, 4 );
if( qFromBigEndian( tmp ) != 0x20 )
{
b.close();
Err( "Bad header size" );
return;
}
b.read( (char*)&tmp, 4 );
tmp = qFromBigEndian( tmp );
if( tmp != 0x49730000 && tmp != 0x69620000 && tmp != 0x426b0000 )
{
b.close();
Err( "Bad file magic word" );
return;
}
quint32 certSize;
quint32 tikSize;
quint32 tmdSize;
quint32 appSize;
quint32 footerSize;
b.read( (char*)&certSize, 4 );
certSize = qFromBigEndian( certSize );
b.seek( 0x10 );
b.read( (char*)&tikSize, 4 );
tikSize = qFromBigEndian( tikSize );
b.read( (char*)&tmdSize, 4 );
tmdSize = qFromBigEndian( tmdSize );
b.read( (char*)&appSize, 4 );
appSize = qFromBigEndian( appSize );
b.read( (char*)&footerSize, 4 );
footerSize = qFromBigEndian( footerSize );
b.close();//close the buffer, the rest of the data can be checked without it
//sanity check this thing
quint32 s = stuff.size();
if( s < ( RU( 0x40, certSize ) + RU( 0x40, tikSize ) + RU( 0x40, tmdSize ) + RU( 0x40, appSize ) + RU( 0x40, footerSize ) ) )
{
Err( "Total size is less than the combined sizes of all the parts that it is supposed to contain" );
return;
}
quint32 pos = 0x40;
certData = stuff.mid( pos, certSize );
pos += RU( 0x40, certSize );
tikData = stuff.mid( pos, tikSize );
pos += RU( 0x40, tikSize );
tmdData = stuff.mid( pos, tmdSize );
pos += RU( 0x40, tmdSize );
Ticket ticket( tikData );
Tmd t( 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";
//another quick sanity check
quint32 totalSize = 0;
for( quint32 i = 0; i < cnt; i++ )
totalSize += t.Size( i );
if( totalSize > appSize )
{
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 ) );
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() );
}
//Data = stuff.mid( pos, Size );
//pos += RU( 0x40, Size );
ok = true;
}
Wad::Wad( QList< QByteArray > stuff, bool encrypted )
{
}
void Wad::Err( QString str )
{
errStr = str;
qWarning() << "Wad::Error" << str;
}

70
nand_dump/wad.h Normal file
View File

@ -0,0 +1,70 @@
#ifndef WAD_H
#define WAD_H
#include "includes.h"
class Wad
{
public:
//create a wad instance from a bytearray containing a wad
Wad( const QByteArray stuff = QByteArray() );
//create a wad object from a list of byteArrays.
//the first one should be the tmd, the second one the ticket, and the rest are the contents, in order
//it will use the global cert unless one is given with SetCert
Wad( QList< QByteArray > stuff, bool isEncrypted = true );
//check if this wad is valid
bool IsOk(){ return ok; }
//set the cert for this wad
void SetCert( const QByteArray stuff );
//returns the tid of the tmd of this wad( which may be different than the one in the ticket if somebody did something stupid )
quint64 Tid();
//set the tid in the ticket&tmd and fakesign the wad
void SetTid( quint64 tid );
//add a new content to this wad and fakesign
//if the data is encrypted, set that arguement to true
//index is the index used for the new entry, default is after all the others
void AddContent( const QByteArray &stuff, quint16 type, bool isEncrypted = false, quint16 index = 0xffff );
//remove a content from this wad
void RemoveContent( quint16 index );
//set the global cert that will be used for all created
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 );
//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
//it will use the global cert
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();
//get the decrypted data from a content
const QByteArray Content( quint16 i );
//get the last error encountered while trying to do something
const QString LastError(){ return errStr; }
private:
bool ok;
QString errStr;
//keep encrypted parts here
QList< QByteArray > partsEnc;
QByteArray tmdData;
QByteArray tikData;
QByteArray certData;
void Err( QString str );
};
#endif // WAD_H

30
saveToy/includes.h Normal file
View File

@ -0,0 +1,30 @@
#include <QAction>
#include <QDialog>
#include <QDir>
#include <QFile>
#include <QFileDialog>
#include <QGraphicsScene>
#include <QGraphicsPixmapItem>
#include <QLayout>
#include <QList>
#include <QMainWindow>
#include <QMenu>
#include <QMessageBox>
//#include <QNetworkAccessManager>
//#include <QNetworkReply>
#include <QObject>
#include <QProcess>
#include <QQueue>
#include <QRegExp>
#include <QSettings>
#include <QtDebug>
#include <QTimer>
#include <QtGui>
#include <QtGui/QMainWindow>
#include <QTreeWidget>
#include <QTreeWidgetItem>
#include <qpushbutton.h>
#include <QWidget>
//#include <QDomDocument>
//#include <QDomElement>

12
saveToy/main.cpp Normal file
View File

@ -0,0 +1,12 @@
#include <QtGui/QApplication>
#include "mainwindow.h"
int main(int argc, char *argv[])
{
Q_INIT_RESOURCE( rc );
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

179
saveToy/mainwindow.cpp Normal file
View File

@ -0,0 +1,179 @@
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include "tools.h"
MainWindow::MainWindow(QWidget *parent) : QMainWindow(parent), ui(new Ui::MainWindow), bannerthread( this )
{
ui->setupUi(this);
progressBar.setVisible( false );
ui->statusBar->addPermanentWidget( &progressBar, 0 );
//sneekIconTimer.setInterval( 150 );//delay of icon image animation
connect( &bannerthread, SIGNAL( SendProgress( int ) ), this, SLOT( GetProgressUpdate( int ) ) );
connect( &bannerthread, SIGNAL( SendDone( int ) ), this, SLOT( LoadThreadIsDone( int ) ) );
connect( &bannerthread, SIGNAL( SendItem( QByteArray, const QString&, int, int ) ), this, SLOT( ReceiveSaveItem( QByteArray, const QString&, int, int ) ) );
connect( &sneekIconTimer, SIGNAL(timeout() ), this, SLOT( ShowNextSneekIcon() ) );
//GetSavesFromSneek( "/media/WiiFat500" );
GetSavesFromSneek( sneekPath );
}
MainWindow::~MainWindow()
{
delete ui;
}
//get the saves from a nand directory
void MainWindow::GetSavesFromSneek( const QString &path )
{
sneekPath = path;
ui->listWidget_sneekSaves->clear();
progressBar.setValue( 0 );
progressBar.setVisible( true );
bannerthread.GetBanners( path, LOAD_SNEEK );
}
/*
//sneek save clicked
void MainWindow::on_listWidget_sneekSaves_itemClicked(QListWidgetItem* item)
{
if( !item )
return;
SaveListItem *i = static_cast< SaveListItem * >( item );
qDebug() << "item clicked" << i->Tid();
}
//sneek save double clicked
void MainWindow::on_listWidget_sneekSaves_itemActivated( QListWidgetItem* item )
{
if( !item )
return;
SaveListItem *i = static_cast< SaveListItem * >( item );
qDebug() << "item activated" << i->Tid();
}*/
//sneek save item changed
void MainWindow::on_listWidget_sneekSaves_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous)
{
Q_UNUSED( previous );
if( !current )
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::ReceiveSaveItem( QByteArray stuff, const QString& tid, int type, int size )
{
switch( type )
{
case LOAD_SNEEK:
{
QByteArray copy = stuff;
SaveBanner sb( copy );
SaveListItem *item = new SaveListItem( sb, tid, size, ui->listWidget_sneekSaves );
ui->listWidget_sneekSaves->addItem( item );
}
break;
default:
break;
}
}
//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 )
{
Q_UNUSED( type );
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;
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( 0x20000, size) / 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();
}
}
void MainWindow::ShowNextSneekIcon()
{
if( ++currentSneekIcon >= sneekIcon.size() )
currentSneekIcon = 0;
ui->label_sneek_icon->setPixmap( sneekIcon.at( currentSneekIcon ) );
}

49
saveToy/mainwindow.h Normal file
View File

@ -0,0 +1,49 @@
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include "saveloadthread.h"
#include "savelistitem.h"
#include "savebanner.h"
#include "includes.h"
namespace Ui {
class MainWindow;
}
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
explicit MainWindow(QWidget *parent = 0);
~MainWindow();
private:
Ui::MainWindow *ui;
QProgressBar progressBar;
void GetSavesFromSneek( const QString &path );
SaveLoadThread bannerthread;
//void ShowSneekSaveDetails( SaveListItem *item );
int currentSneekIcon;
QTimer sneekIconTimer;
QList<QPixmap>sneekIcon;
private slots:
void on_actionSet_Sneek_Path_triggered();
void on_listWidget_sneekSaves_currentItemChanged(QListWidgetItem* current, QListWidgetItem* previous);
//void on_listWidget_sneekSaves_itemClicked(QListWidgetItem* item);
//void on_listWidget_sneekSaves_itemActivated( QListWidgetItem* item );
void ReceiveSaveItem( QByteArray, const QString&, int, int );
void GetProgressUpdate( int );
void LoadThreadIsDone( int );
void ShowSneekSaveDetails( SaveListItem *item );
void ShowNextSneekIcon();
};
#endif // MAINWINDOW_H

376
saveToy/mainwindow.ui Normal file
View File

@ -0,0 +1,376 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>949</width>
<height>535</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralWidget">
<layout class="QGridLayout" name="gridLayout">
<property name="margin">
<number>2</number>
</property>
<item row="0" column="0">
<widget class="QTabWidget" name="tabWidget">
<property name="currentIndex">
<number>1</number>
</property>
<widget class="QWidget" name="sneekTab">
<attribute name="title">
<string>Sneek</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<widget class="QListWidget" name="listWidget_sneekSaves">
<property name="minimumSize">
<size>
<width>620</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>620</width>
<height>16777215</height>
</size>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="iconSize">
<size>
<width>200</width>
<height>80</height>
</size>
</property>
<property name="movement">
<enum>QListView::Static</enum>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_2">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QLabel" name="label_sneek_icon">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<widget class="QLabel" name="label_sneek_id">
<property name="text">
<string>id</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_sneek_size">
<property name="minimumSize">
<size>
<width>193</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>size</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_sneek_title">
<property name="text">
<string>title</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_sneek_title2">
<property name="text">
<string>title2</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_sneek_path">
<property name="text">
<string>path</string>
</property>
</widget>
</item>
<item>
<spacer name="verticalSpacer">
<property name="orientation">
<enum>Qt::Vertical</enum>
</property>
<property name="sizeHint" stdset="0">
<size>
<width>20</width>
<height>40</height>
</size>
</property>
</spacer>
</item>
<item>
<widget class="QPushButton" name="pushButton_sneekDelete">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pushButton_sneekExtract">
<property name="text">
<string>Extract</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QWidget" name="tab_2">
<attribute name="title">
<string>Tab 2</string>
</attribute>
<layout class="QGridLayout" name="gridLayout_4">
<item row="0" column="0">
<widget class="QListWidget" name="listWidget_sneekSaves_2">
<property name="minimumSize">
<size>
<width>620</width>
<height>0</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>620</width>
<height>16777215</height>
</size>
</property>
<property name="selectionMode">
<enum>QAbstractItemView::ExtendedSelection</enum>
</property>
<property name="iconSize">
<size>
<width>200</width>
<height>80</height>
</size>
</property>
<property name="movement">
<enum>QListView::Static</enum>
</property>
<property name="viewMode">
<enum>QListView::IconMode</enum>
</property>
<property name="uniformItemSizes">
<bool>true</bool>
</property>
<property name="wordWrap">
<bool>true</bool>
</property>
<property name="sortingEnabled">
<bool>true</bool>
</property>
</widget>
</item>
<item row="0" column="1">
<layout class="QVBoxLayout" name="verticalLayout_3">
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QLabel" name="label_PC_icon">
<property name="minimumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="maximumSize">
<size>
<width>50</width>
<height>50</height>
</size>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
<item>
<layout class="QVBoxLayout" name="verticalLayout_4">
<item>
<widget class="QLabel" name="label_pc_id">
<property name="text">
<string>id</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_pc_size">
<property name="minimumSize">
<size>
<width>193</width>
<height>0</height>
</size>
</property>
<property name="text">
<string>size</string>
</property>
<property name="alignment">
<set>Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter</set>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
<item>
<widget class="QLabel" name="label_pc_title">
<property name="text">
<string>title</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_pc_title2">
<property name="text">
<string>title2</string>
</property>
</widget>
</item>
<item>
<widget class="QLabel" name="label_pc_path">
<property name="text">
<string>path</string>
</property>
</widget>
</item>
<item>
<widget class="QComboBox" name="comboBox_pc_date"/>
</item>
<item>
<widget class="QPlainTextEdit" name="plainTextEdit"/>
</item>
<item>
<layout class="QGridLayout" name="gridLayout_3">
<item row="0" column="0">
<widget class="QPushButton" name="pushButton_pcDelete">
<property name="text">
<string>Delete</string>
</property>
</widget>
</item>
<item row="0" column="1">
<widget class="QPushButton" name="pushButton_pc_saveDesc">
<property name="text">
<string>Save Desc.</string>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QPushButton" name="pushButton_pcInstall">
<property name="text">
<string>Install</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</item>
</layout>
</widget>
</widget>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menuBar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>949</width>
<height>27</height>
</rect>
</property>
<widget class="QMenu" name="menuTools">
<property name="title">
<string>Tools</string>
</property>
<addaction name="actionSet_Sneek_Path"/>
</widget>
<addaction name="menuTools"/>
</widget>
<widget class="QToolBar" name="mainToolBar">
<attribute name="toolBarArea">
<enum>TopToolBarArea</enum>
</attribute>
<attribute name="toolBarBreak">
<bool>false</bool>
</attribute>
</widget>
<widget class="QStatusBar" name="statusBar"/>
<action name="actionSet_Sneek_Path">
<property name="text">
<string>Set Sneek Path...</string>
</property>
<property name="shortcut">
<string>Ctrl+L</string>
</property>
</action>
</widget>
<layoutdefault spacing="6" margin="11"/>
<resources/>
<connections/>
</ui>

BIN
saveToy/noBanner.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 48 KiB

BIN
saveToy/noIcon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

6
saveToy/rc.qrc Normal file
View File

@ -0,0 +1,6 @@
<RCC>
<qresource prefix="/">
<file>noBanner.png</file>
<file>noIcon.png</file>
</qresource>
</RCC>

30
saveToy/saveToy.pro Normal file
View File

@ -0,0 +1,30 @@
#-------------------------------------------------
#
# Project created by QtCreator 2010-12-02T02:54:54
#
#-------------------------------------------------
QT += core gui
TARGET = saveToy
TEMPLATE = app
SOURCES += main.cpp\
mainwindow.cpp \
savebanner.cpp \
savelistitem.cpp \
saveloadthread.cpp \
tools.cpp
HEADERS += mainwindow.h \
includes.h \
savebanner.h \
savelistitem.h \
saveloadthread.h \
tools.h
FORMS += mainwindow.ui
RESOURCES += \
rc.qrc

258
saveToy/savebanner.cpp Normal file
View File

@ -0,0 +1,258 @@
#include "savebanner.h"
#include "tools.h"
int TPL_ConvertRGB5A3ToBitMap(quint8* tplbuf, quint8** bitmapdata, quint32 width, quint32 height);
SaveBanner::SaveBanner()
{
ok = false;
}
SaveBanner::SaveBanner( QByteArray stuff )
{
ok = false;
QBuffer f( &stuff );
f.open( QIODevice::ReadOnly );
quint32 size = f.size();
if( size < 0x72a0 || ( ( size - 0x60a0 ) % 0x1200 ) )//sanity check the size. must have enough data for the header, names, banner, and 1 icon image
{
qDebug() << "SaveBanner::SaveBanner -> bad filesize" << hex << size;
f.close();
return;
}
quint32 magic;
f.read( (char*)&magic, 4 );
if( qFromBigEndian( magic ) != 0x5749424e )//WIBN
{
hexdump( stuff, 0, 0x30 );
f.close();
qWarning() << "SaveBanner::SaveBanner -> bad file magic" << hex << qFromBigEndian( magic );
return;
}
//no clue what this stuff is, dont really need it though
//i suspect instructions for animation ? ( VC icons display forwards and backwards in the system menu )
//also speed is not always the same
//quint32 tmp;
//f.read( (char*)&tmp, 4 );
//quint32 tmp2;
//f.read( (char*)&tmp2, 4 );
f.seek( 0x20 );
quint16 name[ 0x20 ];
quint16 name2[ 0x20 ];
f.read( (char*)&name, 0x40 );
f.read( (char*)&name2, 0x40 );
//QString title;
for( int i = 0; i < 0x20 && name[ i ] != 0; i++ )
saveTitle += QChar( qFromBigEndian( name[ i ] ) );
saveTitle = saveTitle.trimmed();
//qDebug() << hex << qFromBigEndian( tmp ) << qFromBigEndian( tmp2 ) << saveTitle;
//QString title2;
for( int i = 0; i < 0x20 && name2[ i ] != 0; i++ )
saveTitle2 += QChar( qFromBigEndian( name2[ i ] ) );
saveTitle2 = saveTitle2.trimmed();
//get the banner
f.seek( 0xa0 );
QByteArray ban = f.read( 0x6000 );
//convert to an image we can display
bannerImg = ConvertTextureToImage( ban, 0xc0, 0x40 );
if( bannerImg.isNull() )
{
f.close();
qWarning() << "SaveBanner::SaveBanner -> error converting banner image";
return;
}
//get the images that make up the icon
while( f.pos() != size )
{
QByteArray icn = f.read( 0x1200 );
//check that there is actually data. some banners use all 0x00 for some images
bool null = true;
for( int i = 0; i < 0x1200; i++ )
{
if( icn.at( i ) )//this buffer contains at least 1 byte of data, try to turn it into an image
{
null = false;
break;
}
}
if( null )
{
//qDebug() << "skipping empty image";
break;
}
//make this texture int an image
QImage iconImg = ConvertTextureToImage( icn, 0x30, 0x30 );
if( iconImg.isNull() )
break;
//add the image to the list
iconImgs << iconImg;
}
f.close();
ok = true;
//qDebug() << hex << QString( "%1 %2").arg( qFromBigEndian( tmp ), 9, 16).arg( qFromBigEndian( tmp2 ), 9, 16)
//<< saveTitle.leftJustified( 0x20 ) << QString( "icons: %1").arg( iconImgs.size(), 1, 16 ) << QString( "banner size: %1" ).arg( size, 4, 16 );
}
SaveBanner::SaveBanner( const QString &bannerPath )
{
ok = false;
QFile f( bannerPath );
if( !f.exists() || !f.open( QIODevice::ReadOnly ) )
{
qWarning() << "SaveBanner::SaveBanner -> error opening" << bannerPath;
return;
}
quint32 size = f.size();
if( size < 0x72a0 || ( ( size - 0x60a0 ) % 0x1200 ) )//sanity check the size. must have enough data for the header, names, banner, and 1 icon image
{
qDebug() << "SaveBanner::SaveBanner -> bad filesize" << hex << size;
f.close();
return;
}
quint32 magic;
f.read( (char*)&magic, 4 );
if( qFromBigEndian( magic ) != 0x5749424e )//WIBN
{
f.close();
qWarning() << "SaveBanner::SaveBanner -> bad file magic" << qFromBigEndian( magic );
return;
}
//get the title of the save
f.seek( 0x20 );
quint16 name[ 0x20 ];
quint16 name2[ 0x20 ];
f.read( (char*)&name, 0x40 );
f.read( (char*)&name2, 0x40 );
//QString title;
for( int i = 0; i < 0x20 && name[ i ] != 0; i++ )
saveTitle += QChar( qFromBigEndian( name[ i ] ) );
saveTitle = saveTitle.trimmed();
//QString title2;
for( int i = 0; i < 0x20 && name2[ i ] != 0; i++ )
saveTitle2 += QChar( qFromBigEndian( name2[ i ] ) );
saveTitle2 = saveTitle2.trimmed();
//get the banner
f.seek( 0xa0 );
QByteArray ban = f.read( 0x6000 );
//convert to an image we can display
bannerImg = ConvertTextureToImage( ban, 0xc0, 0x40 );
if( bannerImg.isNull() )
{
f.close();
qWarning() << "SaveBanner::SaveBanner -> error converting banner image";
return;
}
//get the images that make up the icon
while( f.pos() != size )
{
QByteArray icn = f.read( 0x1200 );
//check that there is actually data. some banners use all 0x00 for some images
bool null = true;
for( int i = 0; i < 0x1200; i++ )
{
if( icn.at( i ) )//this buffer contains at least 1 byte of data, try to turn it into an image
{
null = false;
break;
}
}
if( null )
{
//qDebug() << "skipping empty image";
break;
}
//make this texture int an image
QImage iconImg = ConvertTextureToImage( icn, 0x30, 0x30 );
if( iconImg.isNull() )
break;
//add the image to the list
iconImgs << iconImg;
}
f.close();
ok = true;
}
QImage SaveBanner::ConvertTextureToImage( const QByteArray &ba, quint32 w, quint32 h )
{
//qDebug() << "SaveBanner::ConvertTextureToImage" << ba.size() << hex << w << h;
quint8* bitmapdata = NULL;//this will hold the converted image
int ret = TPL_ConvertRGB5A3ToBitMap( (quint8*)ba.constData(), &bitmapdata, w, h );
if( !ret )
{
qWarning() << "SaveBanner::ConvertTextureToImage -> error converting image";
return QImage();
}
QImage im = QImage( (const uchar*)bitmapdata, w, h, QImage::Format_ARGB32 );
QImage im2 = im.copy( im.rect() );//make a copy of the image so the "free" wont delete any data we still want
free( bitmapdata );
return im2;
}
int TPL_ConvertRGB5A3ToBitMap(quint8* tplbuf, quint8** bitmapdata, quint32 width, quint32 height)
{
quint32 x, y;
quint32 x1, y1;
quint32 iv;
//tplpoint -= width;
*bitmapdata = (quint8*)calloc(width * height, 4);
if(*bitmapdata == NULL)
return -1;
quint32 outsz = width * height * 4;
for(iv = 0, y1 = 0; y1 < height; y1 += 4) {
for(x1 = 0; x1 < width; x1 += 4) {
for(y = y1; y < (y1 + 4); y++) {
for(x = x1; x < (x1 + 4); x++) {
quint16 oldpixel = *(quint16*)(tplbuf + ((iv++) * 2));
if((x >= width) || (y >= height))
continue;
oldpixel = qFromBigEndian(oldpixel);
if(oldpixel & (1 << 15)) {
// RGB5
quint8 b = (((oldpixel >> 10) & 0x1F) * 255) / 31;
quint8 g = (((oldpixel >> 5) & 0x1F) * 255) / 31;
quint8 r = (((oldpixel >> 0) & 0x1F) * 255) / 31;
quint8 a = 255;
quint32 rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
(*(quint32**)bitmapdata)[x + (y * width)] = rgba;
}else{
// RGB4A3
quint8 a = (((oldpixel >> 12) & 0x7) * 255) / 7;
quint8 b = (((oldpixel >> 8) & 0xF) * 255) / 15;
quint8 g = (((oldpixel >> 4) & 0xF) * 255) / 15;
quint8 r = (((oldpixel >> 0) & 0xF) * 255) / 15;
quint32 rgba = (r << 0) | (g << 8) | (b << 16) | (a << 24);
(*(quint32**)bitmapdata)[x + (y * width)] = rgba;
}
}
}
}
}
return outsz;
}

29
saveToy/savebanner.h Normal file
View File

@ -0,0 +1,29 @@
#ifndef SAVEBANNER_H
#define SAVEBANNER_H
#include "includes.h"
class SaveBanner
{
public:
SaveBanner();
SaveBanner( const QString &bannerpath );
SaveBanner( QByteArray stuff );
QImage BannerImg(){ return bannerImg; }
QList< QImage > IconImgs() { return iconImgs; }
QString Title(){ return saveTitle; }
QString SubTitle(){ return saveTitle2; }
private:
bool ok;
QImage bannerImg;
QList< QImage > iconImgs;
QString saveTitle;
QString saveTitle2;
QImage ConvertTextureToImage( const QByteArray &ba, quint32 w, quint32 h );//expects tpl texture data in rgb5a3 format
};
#endif // SAVEBANNER_H

23
saveToy/savelistitem.cpp Normal file
View File

@ -0,0 +1,23 @@
#include "savelistitem.h"
SaveListItem::SaveListItem( SaveBanner banner, const QString &tid, quint32 s, QListWidget * parent ) : QListWidgetItem( parent ), size( s )
{
sb = banner;
QImage im = banner.BannerImg();
if( !im.isNull() )
{
im.load( ";/noBanner.png" );
}
QString tex = banner.Title();
if( tex.isEmpty() )
tex = "???";
this->setText( tex );
this->setIcon( QIcon( QPixmap::fromImage( im ) ) );
id = tid;
}
SaveListItem::~SaveListItem()
{
//qDebug() << "SaveListItem destroyed";
}

27
saveToy/savelistitem.h Normal file
View File

@ -0,0 +1,27 @@
#ifndef SAVELISTITEM_H
#define SAVELISTITEM_H
#include "savebanner.h"
#include "includes.h"
class SaveListItem : public QListWidgetItem
{
public:
SaveListItem( SaveBanner banner, const QString &tid, quint32 s, QListWidget * parent = 0 );
~SaveListItem();
SaveBanner *Banner(){ return &sb; }
QString Tid(){ return id; }
quint32 Size(){ return size; }
//quint32 Blocks(){ return size / 0x20000; }
//quint32 MiB(){ return size / 0x100000; }
private:
SaveBanner sb;
QString id;
quint32 size;
};
#endif // SAVELISTITEM_H

124
saveToy/saveloadthread.cpp Normal file
View File

@ -0,0 +1,124 @@
#include "saveloadthread.h"
SaveLoadThread::SaveLoadThread( QObject *parent ) : QThread( parent )
{
abort = false;
//qRegisterMetaType< SaveListItem* >();
}
void SaveLoadThread::ForceQuit()
{
mutex.lock();
abort = true;
mutex.unlock();
}
SaveLoadThread::~SaveLoadThread()
{
mutex.lock();
abort = true;
condition.wakeOne();
mutex.unlock();
wait();
}
void SaveLoadThread::GetBanners( const QString &bPath, int mode )
{
basePath = bPath;
type = mode;
start( NormalPriority );
}
void SaveLoadThread::run()
{
if ( abort )
{
qDebug( "SaveLoadThread::run -> Thread abort" );
return;
}
mutex.lock();
if( basePath.isEmpty() )
{
qDebug() << "SaveLoadThread::run -> its empty";
return;
}
mutex.unlock();
QFileInfo fi( basePath );
if( !fi.exists() || !fi.isDir() )
{
qWarning() << "SaveLoadThread::run ->" << basePath << "is not a directory";
return;
}
fi.setFile(basePath + "/title/00010000" );
if( !fi.exists() || !fi.isDir() )
{
qWarning() << "SaveLoadThread::run ->" << QString( basePath + "/title/00010000" ) << "is not a directory";
return;
}
QDir subDir( basePath + "/title/00010000" );
subDir.setFilter( QDir::Dirs | QDir::NoDotAndDotDot );
QFileInfoList fiL = subDir.entryInfoList();
quint32 cnt = fiL.size();
int i = 0;
subDir.setPath( basePath + "/title/00010001" );
QFileInfoList fiL2 = subDir.entryInfoList();
cnt += fiL2.size();
foreach( QFileInfo f, fiL )
{
i++;
emit SendProgress( (int)( ( (float)( i ) / (float)cnt ) * (float)100 ) );
QFile ff( f.absoluteFilePath() + "/data/banner.bin" );
if( !ff.exists() || !ff.open( QIODevice::ReadOnly ) )
continue;
QByteArray stuff = ff.readAll();
ff.close();
quint32 size = GetFolderSize( f.absoluteFilePath() + "/data" );
emit SendItem( stuff, QString( "00010000" + f.fileName() ), type, size );
}
foreach( QFileInfo f, fiL2 )
{
i++;
emit SendProgress( (int)( ( (float)( i ) / (float)cnt ) * (float)100 ) );
QFile ff( f.absoluteFilePath() + "/data/banner.bin" );
if( !ff.exists() || !ff.open( QIODevice::ReadOnly ) )
continue;
QByteArray stuff = ff.readAll();
ff.close();
quint32 size = GetFolderSize( f.absoluteFilePath() + "/data" );
emit SendItem( stuff, QString( "00010002" + f.fileName() ), type, size );
}
emit SendProgress( 100 );
emit SendDone( type );
}
int SaveLoadThread::GetFolderSize( const QString& path )
{
//qDebug() << "SaveLoadThread::GetFolderSize" << path;
quint32 ret = 0;
QDir dir( path );
dir.setFilter( QDir::Files | QDir::Dirs | QDir::NoDotAndDotDot );
QFileInfoList fiL = dir.entryInfoList();
foreach( QFileInfo fi, fiL )
{
if( fi.isDir() )
ret += GetFolderSize( fi.absoluteFilePath() );
else
ret += fi.size();
}
return ret;
}

44
saveToy/saveloadthread.h Normal file
View File

@ -0,0 +1,44 @@
#ifndef SAVELOADTHREAD_H
#define SAVELOADTHREAD_H
#include "savelistitem.h"
#include "includes.h"
enum
{
LOAD_SNEEK = 0x111,
LOAD_PC
};
class SaveLoadThread : public QThread
{
Q_OBJECT
public:
SaveLoadThread( QObject *parent = 0 );
~SaveLoadThread();
void GetBanners( const QString &bPath, int mode );
void ForceQuit();
protected:
void run();
signals:
void SendProgress( int );
void SendDone( int );
void SendItem( QByteArray, const QString&, int, int );
private:
QMutex mutex;
QWaitCondition condition;
QString basePath;
int type;
bool abort;
int GetFolderSize( const QString& path );
};
//Q_DECLARE_METATYPE( SaveListItem* )
#endif // SAVELOADTHREAD_H

46
saveToy/tools.cpp Normal file
View File

@ -0,0 +1,46 @@
#include "tools.h"
#include "includes.h"
QString currentDir;
QString pcPath = "./saveBackups";
QString sneekPath = "/media/SDHC_4GB";
char ascii( char s ) {
if ( s < 0x20 ) return '.';
if ( s > 0x7E ) return '.';
return s;
}
void hexdump( const void *d, int len ) {
unsigned char *data;
int i, off;
data = (unsigned char*)d;
fprintf( stderr, "\n");
for ( off = 0; off < len; off += 16 ) {
fprintf( stderr, "%08x ", off );
for ( i=0; i<16; i++ )
{
if( ( i + 1 ) % 4 )
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x",data[ off + i ]);
}
else
{
if ( ( i + off ) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%02x ",data[ off + i ]);
}
}
fprintf( stderr, " " );
for ( i = 0; i < 16; i++ )
if ( ( i + off) >= len ) fprintf( stderr," ");
else fprintf( stderr,"%c", ascii( data[ off + i ]));
fprintf( stderr,"\n");
}
fflush( stderr );
}
void hexdump( const QByteArray &d, int from, int len )
{
hexdump( d.data() + from, len == -1 ? d.size() : len );
}

20
saveToy/tools.h Normal file
View File

@ -0,0 +1,20 @@
#ifndef TOOLS_H
#define TOOLS_H
#include "includes.h"
#define RU(x,n) (-(-(x) & -(n))) //round up
#define MIN( x, y ) ( ( x ) < ( y ) ? ( x ) : ( y ) )
#define MAX( x, y ) ( ( x ) > ( y ) ? ( x ) : ( y ) )
char ascii( char s );
void hexdump( const void *d, int len );
void hexdump( const QByteArray &d, int from = 0, int len = -1 );
//keep track of the last folder browsed to when looking for files
extern QString currentDir;
extern QString pcPath;
extern QString sneekPath;
#endif // TOOLS_H