mirror of
https://github.com/martravi/wiiqt6.git
synced 2024-11-21 21:19:15 +01:00
* adding a few different projects. some of them have hardcoded paths and whatnot that may need to be changed or removed
git-svn-id: http://wiiqt.googlecode.com/svn/trunk@2 389f4c8b-5dfe-645f-db0e-df882bc27289
This commit is contained in:
parent
a6cc2a78e8
commit
da59f4ec49
400
nandExtract/aes.c
Normal file
400
nandExtract/aes.c
Normal 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
18
nandExtract/aes.h
Normal 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
35
nandExtract/includes.h
Executable 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
10
nandExtract/main.cpp
Executable 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
16
nandExtract/nandExtract.pro
Executable 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
|
113
nandExtract/nandExtract.pro.user
Normal file
113
nandExtract/nandExtract.pro.user
Normal 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
536
nandExtract/nandbin.cpp
Executable 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*)¤t, 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
85
nandExtract/nandbin.h
Executable 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
119
nandExtract/nandwindow.cpp
Executable 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
40
nandExtract/nandwindow.h
Executable 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
105
nandExtract/nandwindow.ui
Executable 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
131
nandExtract/tools.cpp
Normal 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
36
nandExtract/tools.h
Normal 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
400
nand_dump/aes.c
Normal 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
18
nand_dump/aes.h
Normal 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
35
nand_dump/includes.h
Normal 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
11
nand_dump/main.cpp
Normal 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
242
nand_dump/mainwindow.cpp
Normal 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
45
nand_dump/mainwindow.h
Normal 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
229
nand_dump/mainwindow.ui
Normal 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
41
nand_dump/nand.pro
Normal 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
393
nand_dump/nanddump.cpp
Normal 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
67
nand_dump/nanddump.h
Normal 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
1476
nand_dump/nusdownloader.cpp
Normal file
File diff suppressed because it is too large
Load Diff
194
nand_dump/nusdownloader.h
Normal file
194
nand_dump/nusdownloader.h
Normal 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
|
116
nand_dump/settingtxtdialog.cpp
Normal file
116
nand_dump/settingtxtdialog.cpp
Normal 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();
|
||||
}
|
31
nand_dump/settingtxtdialog.h
Normal file
31
nand_dump/settingtxtdialog.h
Normal 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
|
183
nand_dump/settingtxtdialog.ui
Normal file
183
nand_dump/settingtxtdialog.ui
Normal 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
371
nand_dump/sha1.c
Executable 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
40
nand_dump/sha1.h
Executable 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
|
122
nand_dump/sharedcontentmap.cpp
Normal file
122
nand_dump/sharedcontentmap.cpp
Normal 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();
|
||||
}
|
27
nand_dump/sharedcontentmap.h
Normal file
27
nand_dump/sharedcontentmap.h
Normal 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
129
nand_dump/tiktmd.cpp
Normal 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
231
nand_dump/tiktmd.h
Normal 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
131
nand_dump/tools.cpp
Normal 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
34
nand_dump/tools.h
Normal 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
194
nand_dump/uidmap.cpp
Normal 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
30
nand_dump/uidmap.h
Normal 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
128
nand_dump/wad.cpp
Normal 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( © );
|
||||
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
70
nand_dump/wad.h
Normal 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
30
saveToy/includes.h
Normal 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
12
saveToy/main.cpp
Normal 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
179
saveToy/mainwindow.cpp
Normal 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
49
saveToy/mainwindow.h
Normal 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
376
saveToy/mainwindow.ui
Normal 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
BIN
saveToy/noBanner.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 48 KiB |
BIN
saveToy/noIcon.png
Normal file
BIN
saveToy/noIcon.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 9.2 KiB |
6
saveToy/rc.qrc
Normal file
6
saveToy/rc.qrc
Normal file
@ -0,0 +1,6 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>noBanner.png</file>
|
||||
<file>noIcon.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
30
saveToy/saveToy.pro
Normal file
30
saveToy/saveToy.pro
Normal 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
258
saveToy/savebanner.cpp
Normal 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
29
saveToy/savebanner.h
Normal 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
23
saveToy/savelistitem.cpp
Normal 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
27
saveToy/savelistitem.h
Normal 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
124
saveToy/saveloadthread.cpp
Normal 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
44
saveToy/saveloadthread.h
Normal 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
46
saveToy/tools.cpp
Normal 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
20
saveToy/tools.h
Normal 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
|
Loading…
Reference in New Issue
Block a user