Several bugfixes, updated the CSV, and added the filelist.txt creation when downloading updates

Also started to add some comments (finally) :D
This commit is contained in:
Maschell 2016-04-11 12:48:21 +02:00
parent 92c6de805e
commit d9dd8371b4
21 changed files with 399 additions and 161 deletions

BIN
JNUSTool.jar Normal file

Binary file not shown.

Binary file not shown.

View File

@ -247,16 +247,16 @@
0005000E-10189200;2;WUP;011C;WUP-P-BSPE;BSPE1C;Sportsball;16
0005000E-1018C400;1;WUP;00AF;WUP-P-BSFJ;BSFJAF;???F??????????? ????SF???????????;16
0005000E-1018D900;1;WUP;00AF;WUP-P-APHJ;APHJAF;LOST REAVERS;17
0005000E-1018DB00;1;WUP;0001;WUP-U-AMAJ;AMAJ01;Super Mario Maker;16,32,48,64,80,96,113,128,144,160
0005000E-1018DC00;2;WUP;0001;WUP-U-AMAE;AMAE01;Super Mario Maker;16,32,48,64,80,96,113,128,144,160
0005000E-1018DD00;4;WUP;0001;WUP-U-AMAP;AMAP01;Super Mario Maker;32,48,64,80,96,113,128,144,160
0005000E-1018DB00;1;WUP;0001;WUP-U-AMAJ;AMAJ01;Super Mario Maker;16,32,48,64,80,96,113,128,144,160,176
0005000E-1018DC00;2;WUP;0001;WUP-U-AMAE;AMAE01;Super Mario Maker;16,32,48,64,80,96,113,128,144,160,176
0005000E-1018DD00;4;WUP;0001;WUP-U-AMAP;AMAP01;Super Mario Maker;32,48,64,80,96,113,128,144,160,176
0005000E-1018DE00;4;WUP;00NK;WUP-U-ATCP;ATCPNK;Tetrobot and Co.;16
0005000E-1018ED00;2;WUP;00NK;WUP-U-ATCE;ATCENK;Tetrobot and Co.;16
0005000E-10191700;4;WUP;00UP;WUP-U-BMVP;BMVPUP;99Moves;16,32
0005000E-10191800;4;WUP;00UP;WUP-U-WAEP;WAEPUP;Rock 'N Racing Off Road;16
0005000E-10192200;1;WUP;00AF;WUP-P-BRSJ;BRSJAF;KAMEN RIDER SUMMON RIDE!;16,33,48,64,80
0005000E-10193300;4;WUP;006V;WUP-P-BLKP;BLKP6V;Legend of Kay Anniversary;16
0005000E-10194200;2;WUP;00WR;WUP-P-APZE;APZEWR;LEGO® DIMENSIONS™;16,32,48,64,80,96
0005000E-10194200;2;WUP;00WR;WUP-P-APZE;APZEWR;LEGO® DIMENSIONS™;16,32,48,64,80,96,112
0005000E-10195D00;4;WUP;00WR;WUP-P-APZP;APZPWR;LEGO® DIMENSIONS™;32,48,64,80,96,112,128
0005000E-10197800;4;WUP;011S;WUP-P-ACQP;ACQP1S;Costume Quest 2;16
0005000E-10199000;1;WUP;0001;WUP-P-AVXJ;AVXJ01;Mario Tennis: Ultra Smash;16,32
@ -324,14 +324,15 @@
0005000E-101D6100;4;WUP;00FV;WUP-U-AGWP;AGWPFV;Gunman Clive HD Collection;16
0005000E-101D6D00;4;WUP;010P;WUP-P-ARNP;ARNP0P;Runbow;17,33
0005000E-101D7400;4;WUP;010C;WUP-U-ATTP;ATTP0C;Totem Topple;16
0005000E-101D7500;4;WUP;00DU;WUP-P-AUMP;AUMPDU;Minecraft: Wii U Edition;16,32,48
0005000E-101D7500;4;WUP;00DU;WUP-P-AUMP;AUMPDU;Minecraft: Wii U Edition;16,32,48,64
0005000E-101D8300;4;WUP;00TJ;WUP-U-AEJP;AEJPTJ;Electronic Super Joy;16
0005000E-101D9600;4;WUP;00UP;WUP-U-ARXP;ARXPUP;Rock 'N Racing Off Road DX;16
0005000E-101D9D00;2;WUP;00DU;WUP-P-AUME;AUMEDU;Minecraft: Wii U Edition;16,32,48
0005000E-101DBE00;1;WUP;0188;WUP-P-AUMJ;AUMJ88;Minecraft: Wii U Edition;16,32,48
0005000E-101D9D00;2;WUP;00DU;WUP-P-AUME;AUMEDU;Minecraft: Wii U Edition;16,32,48,64
0005000E-101DBE00;1;WUP;0188;WUP-P-AUMJ;AUMJ88;Minecraft: Wii U Edition;16,32,48,64
0005000E-101DC100;4;WUP;016E;WUP-P-AD5P;AD5P6E;Octodad: Dadliest Catch;16
0005000E-101DCA00;4;WUP;017D;WUP-P-AVMP;AVMP7D;VoxelMaker;16
0005000E-101DD700;1;WUP;013P;WUP-P-ARNJ;ARNJ3P;Runbow;16,17
0005000E-101DDC00;1;WUP;00AF;WUP-P-BD3J;BD3JAF;????????????3.0;17,33,48
0005000E-101DF400;2;WUP;0001;WUP-P-APKE;APKE01;POKKÉN TOURNAMENT;16
0005000E-101DF400;2;WUP;0001;WUP-P-APKE;APKE01;POKKÉN TOURNAMENT;16,32
0005000E-101DF500;4;WUP;0001;WUP-P-APKP;APKP01;POKKÉN TOURNAMENT;16
0005000E-101E4300;1;WUP;00TF;WUP-P-AMPJ;AMPJTF;???? ????? ???????;16

1 0005000E-10100600 1 WUP 00AF WUP-U-AKNJ AKNJAF TEKKEN TAG TOURNAMENT 2 Wii U EDITION 16
247 0005000E-10189200 2 WUP 011C WUP-P-BSPE BSPE1C Sportsball 16
248 0005000E-1018C400 1 WUP 00AF WUP-P-BSFJ BSFJAF ???F??????????? ????SF??????????? 16
249 0005000E-1018D900 1 WUP 00AF WUP-P-APHJ APHJAF LOST REAVERS 17
250 0005000E-1018DB00 1 WUP 0001 WUP-U-AMAJ AMAJ01 Super Mario Maker 16,32,48,64,80,96,113,128,144,160 16,32,48,64,80,96,113,128,144,160,176
251 0005000E-1018DC00 2 WUP 0001 WUP-U-AMAE AMAE01 Super Mario Maker 16,32,48,64,80,96,113,128,144,160 16,32,48,64,80,96,113,128,144,160,176
252 0005000E-1018DD00 4 WUP 0001 WUP-U-AMAP AMAP01 Super Mario Maker 32,48,64,80,96,113,128,144,160 32,48,64,80,96,113,128,144,160,176
253 0005000E-1018DE00 4 WUP 00NK WUP-U-ATCP ATCPNK Tetrobot and Co. 16
254 0005000E-1018ED00 2 WUP 00NK WUP-U-ATCE ATCENK Tetrobot and Co. 16
255 0005000E-10191700 4 WUP 00UP WUP-U-BMVP BMVPUP 99Moves 16,32
256 0005000E-10191800 4 WUP 00UP WUP-U-WAEP WAEPUP Rock 'N Racing Off Road 16
257 0005000E-10192200 1 WUP 00AF WUP-P-BRSJ BRSJAF KAMEN RIDER SUMMON RIDE! 16,33,48,64,80
258 0005000E-10193300 4 WUP 006V WUP-P-BLKP BLKP6V Legend of Kay Anniversary 16
259 0005000E-10194200 2 WUP 00WR WUP-P-APZE APZEWR LEGO® DIMENSIONS™ 16,32,48,64,80,96 16,32,48,64,80,96,112
260 0005000E-10195D00 4 WUP 00WR WUP-P-APZP APZPWR LEGO® DIMENSIONS™ 32,48,64,80,96,112,128
261 0005000E-10197800 4 WUP 011S WUP-P-ACQP ACQP1S Costume Quest 2 16
262 0005000E-10199000 1 WUP 0001 WUP-P-AVXJ AVXJ01 Mario Tennis: Ultra Smash 16,32
324 0005000E-101D6100 4 WUP 00FV WUP-U-AGWP AGWPFV Gunman Clive HD Collection 16
325 0005000E-101D6D00 4 WUP 010P WUP-P-ARNP ARNP0P Runbow 17,33
326 0005000E-101D7400 4 WUP 010C WUP-U-ATTP ATTP0C Totem Topple 16
327 0005000E-101D7500 4 WUP 00DU WUP-P-AUMP AUMPDU Minecraft: Wii U Edition 16,32,48 16,32,48,64
328 0005000E-101D8300 4 WUP 00TJ WUP-U-AEJP AEJPTJ Electronic Super Joy 16
329 0005000E-101D9600 4 WUP 00UP WUP-U-ARXP ARXPUP Rock 'N Racing Off Road DX 16
330 0005000E-101D9D00 2 WUP 00DU WUP-P-AUME AUMEDU Minecraft: Wii U Edition 16,32,48 16,32,48,64
331 0005000E-101DBE00 1 WUP 0188 WUP-P-AUMJ AUMJ88 Minecraft: Wii U Edition 16,32,48 16,32,48,64
332 0005000E-101DC100 4 WUP 016E WUP-P-AD5P AD5P6E Octodad: Dadliest Catch 16
333 0005000E-101DCA00 4 WUP 017D WUP-P-AVMP AVMP7D VoxelMaker 16
334 0005000E-101DD700 1 WUP 013P WUP-P-ARNJ ARNJ3P Runbow 16,17
335 0005000E-101DDC00 1 WUP 00AF WUP-P-BD3J BD3JAF ????????????3.0 17,33,48
336 0005000E-101DF400 2 WUP 0001 WUP-P-APKE APKE01 POKKÉN TOURNAMENT 16 16,32
337 0005000E-101DF500 4 WUP 0001 WUP-P-APKP APKP01 POKKÉN TOURNAMENT 16
338 0005000E-101E4300 1 WUP 00TF WUP-P-AMPJ AMPJTF ???? ????? ??????? 16

Binary file not shown.

View File

@ -7,17 +7,24 @@ import java.util.concurrent.atomic.AtomicInteger;
import de.mas.jnustool.util.Downloader;
import de.mas.jnustool.util.Settings;
import de.mas.jnustool.util.Util;
/**
* Content file of the NUSTitle. Holds the encrpyted files
*
* Thanks to crediar for the offsets in CDecrypt
* @author Maschell
*
*/
public class Content {
/**
* TODO: make it simpler
*/
int ID; // 0 0xB04
short index; // 4 0xB08
short type; // 6 0xB0A
long size; // 8 0xB0C
byte[] SHA2 = new byte[32]; // 16 0xB14
TitleMetaData tmd;
AtomicInteger error_output_done = new AtomicInteger(0);// 16 0xB14
AtomicInteger error_output_done = new AtomicInteger(0);
public Content(int ID, short index, short type, long size, byte[] SHA2,TitleMetaData tmd) {
this.ID = ID;
@ -27,23 +34,23 @@ public class Content {
this.SHA2 = SHA2;
this.tmd = tmd;
}
@Override
public String toString(){
return "ID: " + ID +" index: " + index + " type: " + type + " size: " + size + " SHA2: " + Util.ByteArrayToString(SHA2);
}
public void download(Progress progress) throws IOException{
/**
* Downloads the content files (encrypted)
* @param progress: A progress object can be used to get informations of the progress. Will be ignored when null is used.
* @throws IOException
*/
public void download(Progress progress) throws IOException{
String tmpPath = tmd.getContentPath();
if ((type & 0x02) == 0x02){
Downloader.getInstance().downloadContentH3(tmd.titleID,ID,tmpPath,null);
}
File f = new File(tmpPath + "/" + String.format("%08X", ID ) + ".app");
if(f.exists()){
if(f.length() == size){
Logger.log("Skipping Content: " + String.format("%08X", ID));
progress.addCurrent((int) size);
return;
}else{
if(Settings.downloadWhenCachedFilesMissingOrBroken){
if(Settings.downloadWhenCachedFilesMissingOrBroken){
Logger.log("Content " +String.format("%08X", ID) + " is broken. Downloading it again.");
Downloader.getInstance().downloadContent(tmd.titleID,ID,tmpPath,progress);
}else{
@ -59,6 +66,13 @@ public class Content {
Logger.log("Download Content: " + String.format("%08X", ID));
Downloader.getInstance().downloadContent(tmd.titleID,ID,tmpPath,progress);
}
if ((type & 0x02) == 0x02){
Downloader.getInstance().downloadContentH3(tmd.titleID,ID,tmpPath,null);
}
}
@Override
public String toString(){
return "ID: " + ID +" index: " + index + " type: " + type + " size: " + size + " SHA2: " + Util.ByteArrayToString(SHA2);
}
}

View File

@ -2,30 +2,50 @@ package de.mas.jnustool;
import java.util.concurrent.Callable;
/**
* Callable Class to download Contents
* Has no return value
* @author Maschell
*
*/
public class ContentDownloader implements Callable<Integer>
{
Content c;
Content content;
Progress progress = null;
public void setContent(Content c){
this.c = c;
}
public ContentDownloader(Content f,Progress fatherProgress){
setContent(f);
/**
*
* @param content: the content that need to be downloaded
* @param fatherProgress: father progress that need be informed about the progress. (Can be NULL)
*/
public ContentDownloader(Content content,Progress fatherProgress){
setContent(content);
createProgress(fatherProgress);
}
/**
* Sets the content that need to be downloaded
* @param content
*/
public void setContent(Content content){
this.content = content;
}
/**
* Creates a new sub process for this content.
* @param fatherProgress: the father progress
*/
private void createProgress(Progress fatherProgress) {
if(fatherProgress != null){
progress = new Progress();
fatherProgress.add(progress);
progress.addTotal(c.size);
progress.addTotal(this.content.size);
}
}
}
@Override
public Integer call() throws Exception {
c.download(progress);
this.content.download(progress);
return null;
}

View File

@ -1,17 +1,21 @@
package de.mas.jnustool;
import de.mas.jnustool.util.Util;
/**
* Stores informations of the content [...]
*
*
* Thanks to crediar for the offsets in CDecrypt
* @author Maschell
*
*/
public class ContentInfo {
public short indexOffset; // 0 0x204
public short indexOffset; // 0 0x204
public short commandCount; // 2 0x206
public byte[] SHA2 = new byte[32]; // 12 0x208
//TODO: Test, size checking
/*
* untested
*/
//TODO: Test, size checking. Untested right now
public ContentInfo(byte[] info){
this.indexOffset=(short)( ((info[0]&0xFF)<<8) | (info[1]&0xFF) );
this.commandCount=(short)( ((info[2]&0xFF)<<8) | (info[3]&0xFF) );
@ -25,6 +29,7 @@ public class ContentInfo {
this.commandCount = commandCount;
this.SHA2 = SHA2;
}
@Override
public String toString(){
return "indexOffset: " + indexOffset +" commandCount: " + commandCount + " SHA2: " + Util.ByteArrayToString(SHA2);

View File

@ -4,41 +4,68 @@ import java.util.Collection;
import java.util.TreeMap;
import javax.swing.tree.DefaultMutableTreeNode;
public class Directory {
/**
* A Class that represents a Directory of a NUSTitle file table.
* Every directory can hold other directories and Objects that implements the IHasName Interface. Build in a tree structure.
* @author Maschell
*
*/
public class Directory<T extends IHasName> {
String name = "";
TreeMap<String,Directory> folder = new TreeMap<>();
TreeMap<String,FEntry> files = new TreeMap<>();
public Directory get(String s){
return folder.get(s);
}
TreeMap<String,Directory<T>> folder = new TreeMap<>();
TreeMap<String,T> files = new TreeMap<>();
/**
*
* @param name Name of this Directory
*/
public Directory(String name){
setName(name);
}
public boolean containsFolder(String s){
return folder.containsKey(s);
/**
* Checks if a sub directory with the given name exits
* @param name Name of the sub directory
* @return true is the sub directory exists
*/
public boolean containsFolder(String name){
return folder.containsKey(name);
}
/**
* Returns a sub directory with the given name
* or {@code null} if this directory contains no mapping for the name.
* @param name Name of the sub directory
* @return The sub directory or null
*/
public Directory<T> getFolder(String name){
return folder.get(name);
}
public Directory getFolder(String s){
return folder.get(s);
/**
* Adds a sub directory
* @param subDirectory the sub directory.
* @return the previous directory associated with the same name, or
* {@code null} if there was no directoy with this name.
*/
public Directory<T> addFolder(Directory<T> subDirectory){
return folder.put(subDirectory.getName(),subDirectory);
}
public Directory addFolder(Directory s){
return folder.put(s.getName(),s);
}
public boolean containsFile(String s){
return files.containsKey(s);
/**
* Checks if a object with the given name exits. This will NOT check the sub directories
* @param name name of the object
* @return true is a object with the same name exists.
*/
public boolean containsFile(String name){
return files.containsKey(name);
}
public FEntry getFile(String s){
public T getFile(String s){
return files.get(s);
}
public FEntry addFile(FEntry s){
return files.put(s.getFileName(),s);
public T addFile(T s){
return files.put(s.getName(),s);
}
public String getName() {
@ -47,28 +74,38 @@ public class Directory {
public void setName(String name) {
this.name = name;
}
}
public Collection<Directory> getFolder() {
public Collection<Directory<T>> getFolder() {
return folder.values();
}
public Collection<FEntry> getFiles() {
public Collection<T> getFiles() {
return files.values();
}
public void setFiles(TreeMap<String, FEntry> files) {
public void setFiles(TreeMap<String, T> files) {
this.files = files;
}
public DefaultMutableTreeNode getNodes(){
DefaultMutableTreeNode node = new DefaultMutableTreeNode(getName());
for(Directory<T> f: getFolder()){
node.add(f.getNodes());
}
for(T f: getFiles()){
node.add(new DefaultMutableTreeNode(f));
}
return node;
}
@Override
public String toString(){
StringBuilder sb = new StringBuilder();
sb.append(name + ":" + "\n");
for(Directory d : folder.values()){
for(Directory<T> d : folder.values()){
sb.append(d + "\n");
}
for(String s : files.keySet()){
@ -77,18 +114,5 @@ public class Directory {
return sb.toString();
}
public DefaultMutableTreeNode getNodes(){
DefaultMutableTreeNode node = new DefaultMutableTreeNode(getName());
for(Directory f: getFolder()){
node.add(f.getNodes());
}
for(FEntry f: getFiles()){
node.add(new DefaultMutableTreeNode(f));
}
return node;
}
}

View File

@ -9,7 +9,7 @@ import de.mas.jnustool.util.Downloader;
import de.mas.jnustool.util.Settings;
import de.mas.jnustool.util.Util;
public class FEntry {
public class FEntry implements IHasName{
private FST fst;
public static int DIR_FLAG = 1;
@ -233,6 +233,11 @@ public class FEntry {
return Downloader.getInstance().downloadAndDecrypt(this,null,true);
}
@Override
public String getName() {
return getFileName();
}

View File

@ -20,30 +20,31 @@ public class FST {
int dirEntries = 0;
public FEntry metaFENtry;
public List<FEntry> metaFolder = new ArrayList<>();
private Directory FSTDirectory = new Directory("root");
private Directory<FEntry> FSTDirectory = new Directory<FEntry>("root");
private Directory contentDirectory = new Directory("root");
private Directory<FEntry> contentDirectory = new Directory<FEntry>("root");
public FST(byte[] decrypteddata, TitleMetaData tmd) throws IOException {
parse(decrypteddata,tmd);
setTmd(tmd);
buildDirectories();
}
private void buildDirectories() {
String contentfolder = "";
Directory curContent = contentDirectory;
Directory<FEntry> curContent = contentDirectory;
for(FEntry f : getFileEntries()){
if(!f.isDir() && f.isInNUSTitle()){
contentfolder = String.format("%08X",tmd.contents[f.getContentID()].ID);
if(!contentDirectory.containsFolder(contentfolder)){
Directory newDir = new Directory(contentfolder);
Directory<FEntry> newDir = new Directory<FEntry>(contentfolder);
contentDirectory.addFolder(newDir);
}
curContent = contentDirectory.getFolder(contentfolder);
Directory current = FSTDirectory;
Directory<FEntry> current = FSTDirectory;
int i = 0;
for(String s :f.getPathList()){
@ -51,9 +52,9 @@ public class FST {
//Content
if(curContent.containsFolder(s)){
curContent = curContent.get(s);
curContent = curContent.getFolder(s);
}else{
Directory newDir = new Directory(s);
Directory<FEntry> newDir = new Directory<FEntry>(s);
curContent.addFolder(newDir);
curContent = newDir;
}
@ -63,9 +64,9 @@ public class FST {
//FST
if(current.containsFolder(s)){
current = current.get(s);
current = current.getFolder(s);
}else{
Directory newDir = new Directory(s);
Directory<FEntry> newDir = new Directory<FEntry>(s);
current.addFolder(newDir);
current = newDir;
}
@ -81,10 +82,11 @@ public class FST {
if(!Arrays.equals(Arrays.copyOfRange(decrypteddata, 0, 3), new byte[]{0x46,0x53,0x54})){
Logger.log(Util.ByteArrayToString(Arrays.copyOfRange(decrypteddata, 0, 3)));
System.err.println("Not a FST. Maybe a wrong key?");
throw new IllegalArgumentException("File not a FST");
}
}
this.totalContentCount = Util.getIntFromBytes(decrypteddata, 8);
int base_offset = 0x20+totalContentCount*0x20;
this.totalEntries = Util.getIntFromBytes(decrypteddata, base_offset+8);
@ -296,11 +298,11 @@ public class FST {
return sb.toString();
}
public Directory getFSTDirectory() {
public Directory<FEntry> getFSTDirectory() {
return FSTDirectory;
}
public Directory getContentDirectory() {
public Directory<FEntry> getContentDirectory() {
return contentDirectory;
}

View File

@ -0,0 +1,5 @@
package de.mas.jnustool;
public interface IHasName {
public String getName();
}

View File

@ -10,8 +10,7 @@ public class Logger {
public static void log(String string) {
NUSGUI.output.append(string + "\n");
NUSGUI.output.setCaretPosition(NUSGUI.output.getDocument().getLength());
System.out.println(string);
//System.out.println(string);
}
public static void messageBox(String string) {

View File

@ -25,7 +25,7 @@ import de.mas.jnustool.util.NUSTitleInformation;
import de.mas.jnustool.util.Settings;
import de.mas.jnustool.util.Util;
public class NUSTitle {
public class NUSTitle {
private TitleMetaData tmd;
private TIK ticket;
private FST fst;
@ -34,9 +34,12 @@ public class NUSTitle {
private String longNameFolder = new String();
private int version = -1;
private String getTMDName(){
String result = "title.tmd";
if(version > 0 && Settings.DL_ALL_VERSIONS){
result += "." + version;
}
return result;
}
@ -155,35 +158,34 @@ public class NUSTitle {
Logger.log("Downloading of missing files is not enabled. Exiting");
System.exit(2);
}
}
}
decryption.init(ticket.getDecryptedKey(), 0);
decryption.init(ticket.getDecryptedKey(),0);
byte[] decryptedFST = decryption.decrypt(encryptedFST);
fst = new FST(decryptedFST,tmd);
tmd.setNUSTitle(this);
setTargetPath(String.format("%016X", getTitleID()));
setTargetPath(String.format("%016X", getTitleID()));
setLongNameFolder(String.format("%016X", getTitleID()));
setLongNameFolder(String.format("%016X", getTitleID()));
byte[] metaxml = fst.metaFENtry.downloadAsByteArray();
if(metaxml != null){
InputStream bis = new ByteArrayInputStream(metaxml);
NUSTitleInformation nusinfo = readMeta(bis);
String folder = nusinfo.getLongnameEN() + " [" + nusinfo.getID6() + "]";
String subfolder = "";
if(tmd.isUpdate()) subfolder = "/" + "updates" + "/" + "v" + tmd.titleVersion;
setTargetPath(folder + subfolder);
setLongNameFolder(folder);
if(fst.metaFENtry != null){
byte[] metaxml = fst.metaFENtry.downloadAsByteArray();
if(metaxml != null){
InputStream bis = new ByteArrayInputStream(metaxml);
NUSTitleInformation nusinfo = readMeta(bis);
String folder = nusinfo.getLongnameEN() + " [" + nusinfo.getID6() + "]";
String subfolder = "";
if(tmd.isUpdate()) subfolder = "/" + "updates" + "/" + "v" + tmd.titleVersion;
setTargetPath(folder + subfolder);
setLongNameFolder(folder);
}
}
if(Settings.downloadContent){
downloadEncryptedFiles(null);
}
}
Logger.log("Total Size of Content Files: " + ((int)((getTotalContentSize()/1024.0/1024.0)*100))/100.0 +" MB");
Logger.log("Total Size of Decrypted Files: " + ((int)((fst.getTotalContentSizeInNUS()/1024.0/1024.0)*100))/100.0 +" MB");
@ -198,6 +200,7 @@ public class NUSTitle {
}
public void downloadEncryptedFiles(Progress progress) throws IOException {
Util.createSubfolder(getContentPath());
Downloader.getInstance().downloadTMD(titleID,version,getContentPath());
@ -209,12 +212,16 @@ public class NUSTitle {
fos.write(tmd.cert);
fos.write(ticket.cert1);
fos.close();
if(version > 0 && Settings.DL_ALL_VERSIONS){
fos = new FileOutputStream(getContentPath() + "/title.cert." + version);
fos.write(ticket.cert0);
fos.write(tmd.cert);
fos.write(ticket.cert1);
fos.close();
}
}catch(Exception e){
Logger.log("Random error.");
}
}
NUSTitleInformation readMeta(InputStream bis) {
@ -278,7 +285,7 @@ public class NUSTitle {
public String getContentPath() {
String result = getContentPathPrefix() + String.format("%016X", getTitleID());
if(version > 0){
if(version > 0 && !Settings.DL_ALL_VERSIONS){ //Only add the prefix when we don't download all version of that title
result += "_v" + version;
}
return result;
@ -307,11 +314,12 @@ public class NUSTitle {
}
}
pool.invokeAll(dlList);
if(tmd.isUpdate()) Util.buildFileList(getTargetPath(),"content",Settings.FILELIST_NAME);
Logger.log("Done!");
}
public void setTargetPath(String path){
path = path.replaceAll("[:\\\\*?|<>]", "");
path = Util.replaceCharsInString(path);
this.targetPath = path;
}
@ -324,7 +332,7 @@ public class NUSTitle {
}
public void setLongNameFolder(String path) {
path = path.replaceAll("[:\\\\*?|<>]", "");
path = Util.replaceCharsInString(path);
longNameFolder = path;
}
@ -335,7 +343,4 @@ public class NUSTitle {
public void setVersion(int version) {
this.version = version;
}
}

View File

@ -27,8 +27,8 @@ public class Progress {
}
private void setCurrent(long current) {
this.current.set(current);
update();
this.current.set(current);
update();
}
public void addTotal(long i) {
@ -62,7 +62,7 @@ public class Progress {
private void update() {
if(father != null) father.update();
if(progressUpdateListener != null){
if(progressUpdateListener != null){
progressUpdateListener.updatePerformed(this);
}
}
@ -114,7 +114,7 @@ public class Progress {
}
public void finish() {
setCurrent(getTotalOfSingle());
addCurrent((int) getTotalOfSingle());
}
private boolean inprogress = false;

View File

@ -10,6 +10,7 @@ import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ForkJoinPool;
import java.util.concurrent.ForkJoinTask;
import java.util.concurrent.atomic.AtomicInteger;
import de.mas.jnustool.gui.NUSGUI;
import de.mas.jnustool.gui.UpdateChooser;
@ -23,7 +24,7 @@ public class Starter {
public static void main(String[] args) {
Logger.log("JNUSTool 0.0.5 - alpha - by Maschell");
Logger.log("JNUSTool 0.0.6 - alpha - by Maschell");
Logger.log("");
try {
readConfig();
@ -31,6 +32,7 @@ public class Starter {
System.err.println("Error while reading config! Needs to be:");
System.err.println("DOWNLOAD URL BASE");
System.err.println("COMMONKEY");
System.err.println("updateinfos.csv");
return;
}
@ -108,7 +110,7 @@ public class Starter {
String product_code = infos[4];
String ID6 = infos[5];
String longnameEN = infos[6];
String[] versions = infos[7].split(",");
String[] versions = infos[7].split(",");
NUSTitleInformation info = new NUSTitleInformation(titleID, longnameEN, ID6, product_code, content_platform, company_code, region,versions);
list.add(info);
@ -131,7 +133,7 @@ public class Starter {
public static void readConfig() throws IOException {
BufferedReader in = new BufferedReader(new FileReader(new File("config")));
Downloader.URL_BASE = in.readLine();
String commonkey = in.readLine();
String commonkey = in.readLine();
if(commonkey.length() != 32){
Logger.messageBox("CommonKey length is wrong");
Logger.log("Commonkey length is wrong");
@ -223,4 +225,45 @@ public class Starter {
}
public static AtomicInteger finished = new AtomicInteger();
public static void downloadEncryptedAllVersions(List<NUSTitleInformation> output_, Progress progress) {
ForkJoinPool pool = new ForkJoinPool(25);
List<ForkJoinTask<Boolean>> list = new ArrayList<>();
for(NUSTitleInformation nus : output_){
final long tID = nus.getTitleID();
list.add(pool.submit(new Callable<Boolean>(){
@Override
public Boolean call() throws Exception {
int count = 1;
for(Integer i : nus.getAllVersions()){
NUSTitle nusa = new NUSTitle(tID,i, Util.ByteArrayToString(nus.getKey()));
Progress childProgress = new Progress();
progress.add(childProgress);
nusa.downloadEncryptedFiles(progress);
System.out.println("Update download progress " + "(" + nus.getLongnameEN() + ") version "+ i + " complete! This was " + count + " of " + nus.getAllVersions().size() + "!");
count++;
}
System.out.println("Update download complete " + "(" + nus.getLongnameEN() +")" +"! Loaded updates for " + nus.getAllVersions().size() + " version. Now are " + finished.incrementAndGet() + " of " + output_.size() + " done! ");
return true;
}
}));
}
for(ForkJoinTask<Boolean> task : list){
try {
task.get();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (ExecutionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}

View File

@ -9,8 +9,16 @@ import java.util.List;
import java.util.concurrent.ForkJoinPool;
import de.mas.jnustool.util.Util;
/**
* The TitleMetaData (TMD) stores information of the NUSTitle. [...]
*
*
* Thanks to crediar for the offsets in CDecrypt
* @author Maschell
*
*/
public class TitleMetaData {
//TODO: cleanup
int signatureType; // 0x000
byte[] signature = new byte[0x100]; // 0x004
byte[] issuer = new byte[0x40]; // 0x140
@ -43,18 +51,24 @@ public class TitleMetaData {
public TitleMetaData(byte[] downloadTMDToByteArray) throws IOException {
if(downloadTMDToByteArray != null){
File tempFile;
tempFile = File.createTempFile("bla","blubb");
tempFile = File.createTempFile("jnustool",".tmp");
FileOutputStream fos = new FileOutputStream(tempFile);
fos.write(downloadTMDToByteArray);
fos.close();
parse(tempFile);
setTotalContentSize();
}else{
System.err.println("Invalid TMD");
throw new IllegalArgumentException("Invalid TMD");
}
}
/**
* Parses a TMD file
* @param tmd
* @throws IOException
*/
private void parse(File tmd) throws IOException {
RandomAccessFile f = new RandomAccessFile(tmd, "r");
@ -143,6 +157,7 @@ public class TitleMetaData {
sb.append("SHA2: " + Util.ByteArrayToString(SHA2) +"\n");
sb.append("cert: " + Util.ByteArrayToString(cert) +"\n");
sb.append("contentInfos: \n");
for(int i = 0; i<contents.length-1;i++){
sb.append(" " + contentInfos[i] +"\n");
}
@ -152,6 +167,10 @@ public class TitleMetaData {
}
return sb.toString();
}
/**
* Calculates the total size of the encrypted content file
*/
public void setTotalContentSize(){
this.totalContentSize = 0;
for(int i = 0; i <contents.length-1;i++){
@ -159,15 +178,29 @@ public class TitleMetaData {
}
}
/**
* Size of all encrypted contents (Without tmd,cetk and h3 files)
* @return Size of contents
*/
public long getTotalContentSize() {
return totalContentSize;
}
/**
* Checks of the title is an update
* @return true if its an update
*/
public boolean isUpdate() {
return (titleID & 0x5000E00000000L) == 0x5000E00000000L;
}
/**
* Downloads all content files (encrypted)
* @param progress: A progress object can be used to get informations of the progress. Will be ignored when null is used.
* @throws IOException
*/
public void downloadContents(Progress progress) throws IOException{
String tmpPath = getContentPath();
File f = new File(tmpPath);
if(!f.exists())f.mkdir();
@ -182,14 +215,26 @@ public class TitleMetaData {
}
/**
* Returns the path of the folder where the encrypted content files are stored
* @return path of contents
*/
public String getContentPath() {
return nus.getContentPath();
}
/**
* Returns the instance of the NUSTitle of this TMD
* @return NUSTitle
*/
public NUSTitle getNUSTitle() {
return nus;
}
/**
* Sets the NUSTitle of this TMD
* @param nus The NUStitle that will be set
*/
public void setNUSTitle(NUSTitle nus) {
this.nus = nus;
}

View File

@ -43,6 +43,7 @@ import de.mas.jnustool.Progress;
import de.mas.jnustool.ProgressUpdateListener;
import de.mas.jnustool.Starter;
import de.mas.jnustool.util.NUSTitleInformation;
import de.mas.jnustool.util.Settings;
public class UpdateChooser extends JPanel {
/**
@ -73,7 +74,7 @@ public class UpdateChooser extends JPanel {
tableData[i][3] = n.getLatestVersion();
JComboBox<String> comboBox = new JComboBox<>();
for(String v : n.getAllVersions()){
for(String v : n.getAllVersionsAsString()){
comboBox.addItem(v);
}
final int position = i;
@ -216,7 +217,11 @@ public class UpdateChooser extends JPanel {
public void run() {
progressBar_1.setValue(0);
progress.clear();
Starter.downloadEncrypted(output_,progress);
if(Settings.DL_ALL_VERSIONS){
Starter.downloadEncryptedAllVersions(output_,progress);
}else{
Starter.downloadEncrypted(output_,progress);
}
progress.operationFinish();
Logger.messageBox("Finished");
}
@ -229,7 +234,6 @@ public class UpdateChooser extends JPanel {
}
});
panel.add(btnDownloadEncrypted);
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
@ -305,6 +309,7 @@ public class UpdateChooser extends JPanel {
}
}
/**
* each row TableCellEditor
*
@ -312,24 +317,27 @@ public class UpdateChooser extends JPanel {
* @author Nobuo Tamemasa
*/
class EachRowEditor implements TableCellEditor {
protected Hashtable editors;
class EachRowEditor implements TableCellEditor {
@SuppressWarnings("rawtypes")
protected Hashtable editors;
protected TableCellEditor editor, defaultEditor;
JTable table;
/**
* Constructs a EachRowEditor. create default editor
*
* @see TableCellEditor
* @see DefaultCellEditor
*/
public EachRowEditor(JTable table) {
this.table = table;
editors = new Hashtable();
defaultEditor = new DefaultCellEditor(new JTextField());
}
protected TableCellEditor editor, defaultEditor;
JTable table;
/**
* Constructs a EachRowEditor. create default editor
*
* @see TableCellEditor
* @see DefaultCellEditor
*/
@SuppressWarnings("rawtypes")
public EachRowEditor(JTable table) {
this.table = table;
editors = new Hashtable();
defaultEditor = new DefaultCellEditor(new JTextField());
}
/**
* @param row
@ -337,7 +345,8 @@ public class UpdateChooser extends JPanel {
* @param editor
* table cell editor
*/
public void setEditorAt(int row, TableCellEditor editor) {
@SuppressWarnings("unchecked")
public void setEditorAt(int row, TableCellEditor editor) {
editors.put(new Integer(row), editor);
}

View File

@ -78,8 +78,9 @@ public class Downloader {
public void downloadTMD(long titleID,int version,String path) throws IOException {
String version_suf = "";
if(version > 0) version_suf = "." + version;
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd" + version_suf;
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd" + version_suf;
downloadFile(URL, "title.tmd",path,null);
if(Settings.DL_ALL_VERSIONS) downloadFile(URL, "title.tmd"+version_suf,path,null);
}
public void downloadFile(String fileURL,String filename,String tmpPath, Progress progress) throws IOException{
@ -114,23 +115,28 @@ public class Downloader {
public void downloadFile(String fileURL,String filename) throws IOException{
downloadFile(fileURL, filename,null,null);
}
public void downloadTicket(long titleID,String path) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk";
downloadFile(URL, "title.tik",path,null);
}
public void downloadContent(long titleID,int contentID,Progress progress) throws IOException {
downloadContent(titleID,contentID, null,progress);
}
public byte[] downloadContentToByteArray(long titleID,int contentID) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
return downloadFileToByteArray(URL);
}
public byte[] downloadTMDToByteArray(long titleID, int version) throws IOException {
String version_suf = "";
if(version > 0) version_suf = "." + version;
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd" +version_suf;
return downloadFileToByteArray(URL);
}
private byte[] downloadFileToByteArray(String fileURL) throws IOException {
int BUFFER_SIZE = 0x800;

View File

@ -125,7 +125,7 @@ public class NUSTitleInformation implements Comparable<NUSTitleInformation>, Ser
for(Integer i :versions){
result += ";" + i;
}
result += ";" + getSelectedVersion();
//result += ";" + getSelectedVersion();
return result;
}
@ -166,7 +166,11 @@ public class NUSTitleInformation implements Comparable<NUSTitleInformation>, Ser
return result;
}
public List<String> getAllVersions() {
public List<Integer> getAllVersions() {
return versions;
}
public List<String> getAllVersionsAsString() {
List<String> list = new ArrayList<>();
if(versions != null && !versions.isEmpty()){
for(Integer v: versions){

View File

@ -8,5 +8,6 @@ public class Settings {
public static boolean skipBrokenFiles = false;
public static boolean skipExistingFiles = true;
public static boolean skipExistingTMDTICKET = true;
public static boolean DL_ALL_VERSIONS = false;
public static String FILELIST_NAME = "filelist.txt";
}

View File

@ -1,6 +1,9 @@
package de.mas.jnustool.util;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
@ -83,4 +86,51 @@ public class Util {
}
}
}
public static String replaceCharsInString(String path){
if(path != null){
path = path.replaceAll("[:\\\\*?|<>]", "");
}
return path;
}
public static void buildFileList(String basePath, String targetPath, String filename) {
File contentFolder = new File(basePath + "\\" + targetPath);
if(contentFolder.exists()){
FileWriter fw;
BufferedWriter bw = null;
try {
fw = new FileWriter(basePath + "\\ "+ filename);
bw = new BufferedWriter(fw);
bw.write(readFileListRecursive(contentFolder));
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
bw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
//TODO make it more generic
private static String readFileListRecursive(File contentFolder) {
if(contentFolder.exists()){
StringBuilder sb = new StringBuilder();
for(File f : contentFolder.listFiles()){
if(f.isDirectory()){
sb.append("?" + f.getName() + "\n");
sb.append(readFileListRecursive(f));
sb.append("?..\n");
}else{
sb.append(f.getName() + "\n");
}
}
return sb.toString();
}else{
return "";
}
}
}