mirror of
https://github.com/Maschell/JNUSTool.git
synced 2024-11-28 02:44:19 +01:00
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:
parent
92c6de805e
commit
d9dd8371b4
BIN
JNUSTool.jar
Normal file
BIN
JNUSTool.jar
Normal file
Binary file not shown.
BIN
jar/JNUSTool.jar
BIN
jar/JNUSTool.jar
Binary file not shown.
@ -247,16 +247,16 @@
|
|||||||
0005000E-10189200;2;WUP;011C;WUP-P-BSPE;BSPE1C;Sportsball;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-1018C400;1;WUP;00AF;WUP-P-BSFJ;BSFJAF;???F??????????? ????SF???????????;16
|
||||||
0005000E-1018D900;1;WUP;00AF;WUP-P-APHJ;APHJAF;LOST REAVERS;17
|
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-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
|
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
|
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-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-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-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-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-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-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-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-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
|
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-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-101D6D00;4;WUP;010P;WUP-P-ARNP;ARNP0P;Runbow;17,33
|
||||||
0005000E-101D7400;4;WUP;010C;WUP-U-ATTP;ATTP0C;Totem Topple;16
|
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-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-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-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
|
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-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-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-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-101DF500;4;WUP;0001;WUP-P-APKP;APKP01;POKKÉN TOURNAMENT;16
|
||||||
0005000E-101E4300;1;WUP;00TF;WUP-P-AMPJ;AMPJTF;???? ????? ???????;16
|
0005000E-101E4300;1;WUP;00TF;WUP-P-AMPJ;AMPJTF;???? ????? ???????;16
|
||||||
|
|
BIN
release.zip
BIN
release.zip
Binary file not shown.
@ -7,17 +7,24 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
import de.mas.jnustool.util.Downloader;
|
import de.mas.jnustool.util.Downloader;
|
||||||
import de.mas.jnustool.util.Settings;
|
import de.mas.jnustool.util.Settings;
|
||||||
import de.mas.jnustool.util.Util;
|
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 {
|
public class Content {
|
||||||
|
/**
|
||||||
|
* TODO: make it simpler
|
||||||
|
*/
|
||||||
int ID; // 0 0xB04
|
int ID; // 0 0xB04
|
||||||
short index; // 4 0xB08
|
short index; // 4 0xB08
|
||||||
short type; // 6 0xB0A
|
short type; // 6 0xB0A
|
||||||
long size; // 8 0xB0C
|
long size; // 8 0xB0C
|
||||||
byte[] SHA2 = new byte[32]; // 16 0xB14
|
byte[] SHA2 = new byte[32]; // 16 0xB14
|
||||||
TitleMetaData tmd;
|
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) {
|
public Content(int ID, short index, short type, long size, byte[] SHA2,TitleMetaData tmd) {
|
||||||
this.ID = ID;
|
this.ID = ID;
|
||||||
@ -27,21 +34,21 @@ public class Content {
|
|||||||
this.SHA2 = SHA2;
|
this.SHA2 = SHA2;
|
||||||
this.tmd = tmd;
|
this.tmd = tmd;
|
||||||
}
|
}
|
||||||
@Override
|
|
||||||
public String toString(){
|
|
||||||
return "ID: " + ID +" index: " + index + " type: " + type + " size: " + size + " SHA2: " + Util.ByteArrayToString(SHA2);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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{
|
public void download(Progress progress) throws IOException{
|
||||||
String tmpPath = tmd.getContentPath();
|
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");
|
File f = new File(tmpPath + "/" + String.format("%08X", ID ) + ".app");
|
||||||
if(f.exists()){
|
if(f.exists()){
|
||||||
if(f.length() == size){
|
if(f.length() == size){
|
||||||
Logger.log("Skipping Content: " + String.format("%08X", ID));
|
Logger.log("Skipping Content: " + String.format("%08X", ID));
|
||||||
progress.addCurrent((int) size);
|
progress.addCurrent((int) size);
|
||||||
|
return;
|
||||||
}else{
|
}else{
|
||||||
if(Settings.downloadWhenCachedFilesMissingOrBroken){
|
if(Settings.downloadWhenCachedFilesMissingOrBroken){
|
||||||
Logger.log("Content " +String.format("%08X", ID) + " is broken. Downloading it again.");
|
Logger.log("Content " +String.format("%08X", ID) + " is broken. Downloading it again.");
|
||||||
@ -59,6 +66,13 @@ public class Content {
|
|||||||
Logger.log("Download Content: " + String.format("%08X", ID));
|
Logger.log("Download Content: " + String.format("%08X", ID));
|
||||||
Downloader.getInstance().downloadContent(tmd.titleID,ID,tmpPath,progress);
|
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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,30 +2,50 @@ package de.mas.jnustool;
|
|||||||
|
|
||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Callable Class to download Contents
|
||||||
|
* Has no return value
|
||||||
|
* @author Maschell
|
||||||
|
*
|
||||||
|
*/
|
||||||
public class ContentDownloader implements Callable<Integer>
|
public class ContentDownloader implements Callable<Integer>
|
||||||
{
|
{
|
||||||
Content c;
|
Content content;
|
||||||
Progress progress = null;
|
Progress progress = null;
|
||||||
public void setContent(Content c){
|
|
||||||
this.c = c;
|
/**
|
||||||
}
|
*
|
||||||
public ContentDownloader(Content f,Progress fatherProgress){
|
* @param content: the content that need to be downloaded
|
||||||
setContent(f);
|
* @param fatherProgress: father progress that need be informed about the progress. (Can be NULL)
|
||||||
|
*/
|
||||||
|
public ContentDownloader(Content content,Progress fatherProgress){
|
||||||
|
setContent(content);
|
||||||
createProgress(fatherProgress);
|
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) {
|
private void createProgress(Progress fatherProgress) {
|
||||||
if(fatherProgress != null){
|
if(fatherProgress != null){
|
||||||
progress = new Progress();
|
progress = new Progress();
|
||||||
fatherProgress.add(progress);
|
fatherProgress.add(progress);
|
||||||
progress.addTotal(c.size);
|
progress.addTotal(this.content.size);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Integer call() throws Exception {
|
public Integer call() throws Exception {
|
||||||
c.download(progress);
|
this.content.download(progress);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,16 +1,20 @@
|
|||||||
package de.mas.jnustool;
|
package de.mas.jnustool;
|
||||||
|
|
||||||
import de.mas.jnustool.util.Util;
|
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 class ContentInfo {
|
||||||
public short indexOffset; // 0 0x204
|
public short indexOffset; // 0 0x204
|
||||||
public short commandCount; // 2 0x206
|
public short commandCount; // 2 0x206
|
||||||
public byte[] SHA2 = new byte[32]; // 12 0x208
|
public byte[] SHA2 = new byte[32]; // 12 0x208
|
||||||
|
|
||||||
//TODO: Test, size checking
|
//TODO: Test, size checking. Untested right now
|
||||||
/*
|
|
||||||
* untested
|
|
||||||
*/
|
|
||||||
|
|
||||||
public ContentInfo(byte[] info){
|
public ContentInfo(byte[] info){
|
||||||
this.indexOffset=(short)( ((info[0]&0xFF)<<8) | (info[1]&0xFF) );
|
this.indexOffset=(short)( ((info[0]&0xFF)<<8) | (info[1]&0xFF) );
|
||||||
@ -25,6 +29,7 @@ public class ContentInfo {
|
|||||||
this.commandCount = commandCount;
|
this.commandCount = commandCount;
|
||||||
this.SHA2 = SHA2;
|
this.SHA2 = SHA2;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
return "indexOffset: " + indexOffset +" commandCount: " + commandCount + " SHA2: " + Util.ByteArrayToString(SHA2);
|
return "indexOffset: " + indexOffset +" commandCount: " + commandCount + " SHA2: " + Util.ByteArrayToString(SHA2);
|
||||||
|
@ -4,41 +4,68 @@ import java.util.Collection;
|
|||||||
import java.util.TreeMap;
|
import java.util.TreeMap;
|
||||||
|
|
||||||
import javax.swing.tree.DefaultMutableTreeNode;
|
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 = "";
|
String name = "";
|
||||||
TreeMap<String,Directory> folder = new TreeMap<>();
|
TreeMap<String,Directory<T>> folder = new TreeMap<>();
|
||||||
TreeMap<String,FEntry> files = new TreeMap<>();
|
TreeMap<String,T> files = new TreeMap<>();
|
||||||
|
|
||||||
public Directory get(String s){
|
|
||||||
return folder.get(s);
|
|
||||||
}
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
*
|
||||||
|
* @param name Name of this Directory
|
||||||
|
*/
|
||||||
public Directory(String name){
|
public Directory(String name){
|
||||||
setName(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);
|
* Checks if a object with the given name exits. This will NOT check the sub directories
|
||||||
}
|
* @param name name of the object
|
||||||
public boolean containsFile(String s){
|
* @return true is a object with the same name exists.
|
||||||
return files.containsKey(s);
|
*/
|
||||||
|
public boolean containsFile(String name){
|
||||||
|
return files.containsKey(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FEntry getFile(String s){
|
public T getFile(String s){
|
||||||
return files.get(s);
|
return files.get(s);
|
||||||
}
|
}
|
||||||
|
|
||||||
public FEntry addFile(FEntry s){
|
public T addFile(T s){
|
||||||
return files.put(s.getFileName(),s);
|
return files.put(s.getName(),s);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -49,26 +76,36 @@ public class Directory {
|
|||||||
this.name = name;
|
this.name = name;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<Directory<T>> getFolder() {
|
||||||
public Collection<Directory> getFolder() {
|
|
||||||
return folder.values();
|
return folder.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Collection<T> getFiles() {
|
||||||
|
|
||||||
public Collection<FEntry> getFiles() {
|
|
||||||
return files.values();
|
return files.values();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setFiles(TreeMap<String, FEntry> files) {
|
public void setFiles(TreeMap<String, T> files) {
|
||||||
this.files = 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
|
@Override
|
||||||
public String toString(){
|
public String toString(){
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
sb.append(name + ":" + "\n");
|
sb.append(name + ":" + "\n");
|
||||||
for(Directory d : folder.values()){
|
for(Directory<T> d : folder.values()){
|
||||||
sb.append(d + "\n");
|
sb.append(d + "\n");
|
||||||
}
|
}
|
||||||
for(String s : files.keySet()){
|
for(String s : files.keySet()){
|
||||||
@ -77,18 +114,5 @@ public class Directory {
|
|||||||
return sb.toString();
|
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;
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ import de.mas.jnustool.util.Downloader;
|
|||||||
import de.mas.jnustool.util.Settings;
|
import de.mas.jnustool.util.Settings;
|
||||||
import de.mas.jnustool.util.Util;
|
import de.mas.jnustool.util.Util;
|
||||||
|
|
||||||
public class FEntry {
|
public class FEntry implements IHasName{
|
||||||
private FST fst;
|
private FST fst;
|
||||||
|
|
||||||
public static int DIR_FLAG = 1;
|
public static int DIR_FLAG = 1;
|
||||||
@ -233,6 +233,11 @@ public class FEntry {
|
|||||||
return Downloader.getInstance().downloadAndDecrypt(this,null,true);
|
return Downloader.getInstance().downloadAndDecrypt(this,null,true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return getFileName();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
@ -20,30 +20,31 @@ public class FST {
|
|||||||
int dirEntries = 0;
|
int dirEntries = 0;
|
||||||
public FEntry metaFENtry;
|
public FEntry metaFENtry;
|
||||||
public List<FEntry> metaFolder = new ArrayList<>();
|
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 {
|
public FST(byte[] decrypteddata, TitleMetaData tmd) throws IOException {
|
||||||
parse(decrypteddata,tmd);
|
parse(decrypteddata,tmd);
|
||||||
setTmd(tmd);
|
setTmd(tmd);
|
||||||
buildDirectories();
|
buildDirectories();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void buildDirectories() {
|
private void buildDirectories() {
|
||||||
String contentfolder = "";
|
String contentfolder = "";
|
||||||
Directory curContent = contentDirectory;
|
Directory<FEntry> curContent = contentDirectory;
|
||||||
for(FEntry f : getFileEntries()){
|
for(FEntry f : getFileEntries()){
|
||||||
if(!f.isDir() && f.isInNUSTitle()){
|
if(!f.isDir() && f.isInNUSTitle()){
|
||||||
contentfolder = String.format("%08X",tmd.contents[f.getContentID()].ID);
|
contentfolder = String.format("%08X",tmd.contents[f.getContentID()].ID);
|
||||||
|
|
||||||
if(!contentDirectory.containsFolder(contentfolder)){
|
if(!contentDirectory.containsFolder(contentfolder)){
|
||||||
Directory newDir = new Directory(contentfolder);
|
Directory<FEntry> newDir = new Directory<FEntry>(contentfolder);
|
||||||
contentDirectory.addFolder(newDir);
|
contentDirectory.addFolder(newDir);
|
||||||
}
|
}
|
||||||
curContent = contentDirectory.getFolder(contentfolder);
|
curContent = contentDirectory.getFolder(contentfolder);
|
||||||
|
|
||||||
Directory current = FSTDirectory;
|
Directory<FEntry> current = FSTDirectory;
|
||||||
int i = 0;
|
int i = 0;
|
||||||
|
|
||||||
for(String s :f.getPathList()){
|
for(String s :f.getPathList()){
|
||||||
@ -51,9 +52,9 @@ public class FST {
|
|||||||
|
|
||||||
//Content
|
//Content
|
||||||
if(curContent.containsFolder(s)){
|
if(curContent.containsFolder(s)){
|
||||||
curContent = curContent.get(s);
|
curContent = curContent.getFolder(s);
|
||||||
}else{
|
}else{
|
||||||
Directory newDir = new Directory(s);
|
Directory<FEntry> newDir = new Directory<FEntry>(s);
|
||||||
curContent.addFolder(newDir);
|
curContent.addFolder(newDir);
|
||||||
curContent = newDir;
|
curContent = newDir;
|
||||||
}
|
}
|
||||||
@ -63,9 +64,9 @@ public class FST {
|
|||||||
|
|
||||||
//FST
|
//FST
|
||||||
if(current.containsFolder(s)){
|
if(current.containsFolder(s)){
|
||||||
current = current.get(s);
|
current = current.getFolder(s);
|
||||||
}else{
|
}else{
|
||||||
Directory newDir = new Directory(s);
|
Directory<FEntry> newDir = new Directory<FEntry>(s);
|
||||||
current.addFolder(newDir);
|
current.addFolder(newDir);
|
||||||
current = newDir;
|
current = newDir;
|
||||||
}
|
}
|
||||||
@ -81,10 +82,11 @@ public class FST {
|
|||||||
|
|
||||||
if(!Arrays.equals(Arrays.copyOfRange(decrypteddata, 0, 3), new byte[]{0x46,0x53,0x54})){
|
if(!Arrays.equals(Arrays.copyOfRange(decrypteddata, 0, 3), new byte[]{0x46,0x53,0x54})){
|
||||||
Logger.log(Util.ByteArrayToString(Arrays.copyOfRange(decrypteddata, 0, 3)));
|
Logger.log(Util.ByteArrayToString(Arrays.copyOfRange(decrypteddata, 0, 3)));
|
||||||
|
|
||||||
System.err.println("Not a FST. Maybe a wrong key?");
|
System.err.println("Not a FST. Maybe a wrong key?");
|
||||||
throw new IllegalArgumentException("File not a FST");
|
throw new IllegalArgumentException("File not a FST");
|
||||||
}
|
|
||||||
|
|
||||||
|
}
|
||||||
this.totalContentCount = Util.getIntFromBytes(decrypteddata, 8);
|
this.totalContentCount = Util.getIntFromBytes(decrypteddata, 8);
|
||||||
int base_offset = 0x20+totalContentCount*0x20;
|
int base_offset = 0x20+totalContentCount*0x20;
|
||||||
this.totalEntries = Util.getIntFromBytes(decrypteddata, base_offset+8);
|
this.totalEntries = Util.getIntFromBytes(decrypteddata, base_offset+8);
|
||||||
@ -296,11 +298,11 @@ public class FST {
|
|||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
public Directory getFSTDirectory() {
|
public Directory<FEntry> getFSTDirectory() {
|
||||||
return FSTDirectory;
|
return FSTDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
public Directory getContentDirectory() {
|
public Directory<FEntry> getContentDirectory() {
|
||||||
return contentDirectory;
|
return contentDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
5
src/de/mas/jnustool/IHasName.java
Normal file
5
src/de/mas/jnustool/IHasName.java
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
package de.mas.jnustool;
|
||||||
|
|
||||||
|
public interface IHasName {
|
||||||
|
public String getName();
|
||||||
|
}
|
@ -10,8 +10,7 @@ public class Logger {
|
|||||||
public static void log(String string) {
|
public static void log(String string) {
|
||||||
NUSGUI.output.append(string + "\n");
|
NUSGUI.output.append(string + "\n");
|
||||||
NUSGUI.output.setCaretPosition(NUSGUI.output.getDocument().getLength());
|
NUSGUI.output.setCaretPosition(NUSGUI.output.getDocument().getLength());
|
||||||
System.out.println(string);
|
//System.out.println(string);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void messageBox(String string) {
|
public static void messageBox(String string) {
|
||||||
|
@ -34,9 +34,12 @@ public class NUSTitle {
|
|||||||
private String longNameFolder = new String();
|
private String longNameFolder = new String();
|
||||||
private int version = -1;
|
private int version = -1;
|
||||||
|
|
||||||
|
|
||||||
private String getTMDName(){
|
private String getTMDName(){
|
||||||
String result = "title.tmd";
|
String result = "title.tmd";
|
||||||
|
if(version > 0 && Settings.DL_ALL_VERSIONS){
|
||||||
|
result += "." + version;
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -157,7 +160,6 @@ public class NUSTitle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
decryption.init(ticket.getDecryptedKey(),0);
|
decryption.init(ticket.getDecryptedKey(),0);
|
||||||
byte[] decryptedFST = decryption.decrypt(encryptedFST);
|
byte[] decryptedFST = decryption.decrypt(encryptedFST);
|
||||||
|
|
||||||
@ -166,10 +168,9 @@ public class NUSTitle {
|
|||||||
tmd.setNUSTitle(this);
|
tmd.setNUSTitle(this);
|
||||||
|
|
||||||
setTargetPath(String.format("%016X", getTitleID()));
|
setTargetPath(String.format("%016X", getTitleID()));
|
||||||
|
|
||||||
setLongNameFolder(String.format("%016X", getTitleID()));
|
setLongNameFolder(String.format("%016X", getTitleID()));
|
||||||
|
|
||||||
|
if(fst.metaFENtry != null){
|
||||||
byte[] metaxml = fst.metaFENtry.downloadAsByteArray();
|
byte[] metaxml = fst.metaFENtry.downloadAsByteArray();
|
||||||
if(metaxml != null){
|
if(metaxml != null){
|
||||||
InputStream bis = new ByteArrayInputStream(metaxml);
|
InputStream bis = new ByteArrayInputStream(metaxml);
|
||||||
@ -180,6 +181,7 @@ public class NUSTitle {
|
|||||||
setTargetPath(folder + subfolder);
|
setTargetPath(folder + subfolder);
|
||||||
setLongNameFolder(folder);
|
setLongNameFolder(folder);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if(Settings.downloadContent){
|
if(Settings.downloadContent){
|
||||||
downloadEncryptedFiles(null);
|
downloadEncryptedFiles(null);
|
||||||
@ -198,6 +200,7 @@ public class NUSTitle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void downloadEncryptedFiles(Progress progress) throws IOException {
|
public void downloadEncryptedFiles(Progress progress) throws IOException {
|
||||||
|
|
||||||
Util.createSubfolder(getContentPath());
|
Util.createSubfolder(getContentPath());
|
||||||
|
|
||||||
Downloader.getInstance().downloadTMD(titleID,version,getContentPath());
|
Downloader.getInstance().downloadTMD(titleID,version,getContentPath());
|
||||||
@ -209,12 +212,16 @@ public class NUSTitle {
|
|||||||
fos.write(tmd.cert);
|
fos.write(tmd.cert);
|
||||||
fos.write(ticket.cert1);
|
fos.write(ticket.cert1);
|
||||||
fos.close();
|
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){
|
}catch(Exception e){
|
||||||
Logger.log("Random error.");
|
Logger.log("Random error.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
NUSTitleInformation readMeta(InputStream bis) {
|
NUSTitleInformation readMeta(InputStream bis) {
|
||||||
@ -278,7 +285,7 @@ public class NUSTitle {
|
|||||||
|
|
||||||
public String getContentPath() {
|
public String getContentPath() {
|
||||||
String result = getContentPathPrefix() + String.format("%016X", getTitleID());
|
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;
|
result += "_v" + version;
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
@ -307,11 +314,12 @@ public class NUSTitle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
pool.invokeAll(dlList);
|
pool.invokeAll(dlList);
|
||||||
|
if(tmd.isUpdate()) Util.buildFileList(getTargetPath(),"content",Settings.FILELIST_NAME);
|
||||||
Logger.log("Done!");
|
Logger.log("Done!");
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTargetPath(String path){
|
public void setTargetPath(String path){
|
||||||
path = path.replaceAll("[:\\\\*?|<>]", "");
|
path = Util.replaceCharsInString(path);
|
||||||
this.targetPath = path;
|
this.targetPath = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +332,7 @@ public class NUSTitle {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setLongNameFolder(String path) {
|
public void setLongNameFolder(String path) {
|
||||||
path = path.replaceAll("[:\\\\*?|<>]", "");
|
path = Util.replaceCharsInString(path);
|
||||||
longNameFolder = path;
|
longNameFolder = path;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -335,7 +343,4 @@ public class NUSTitle {
|
|||||||
public void setVersion(int version) {
|
public void setVersion(int version) {
|
||||||
this.version = version;
|
this.version = version;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -114,7 +114,7 @@ public class Progress {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void finish() {
|
public void finish() {
|
||||||
setCurrent(getTotalOfSingle());
|
addCurrent((int) getTotalOfSingle());
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean inprogress = false;
|
private boolean inprogress = false;
|
||||||
|
@ -10,6 +10,7 @@ import java.util.concurrent.Callable;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
import java.util.concurrent.ForkJoinTask;
|
import java.util.concurrent.ForkJoinTask;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
import de.mas.jnustool.gui.NUSGUI;
|
import de.mas.jnustool.gui.NUSGUI;
|
||||||
import de.mas.jnustool.gui.UpdateChooser;
|
import de.mas.jnustool.gui.UpdateChooser;
|
||||||
@ -23,7 +24,7 @@ public class Starter {
|
|||||||
|
|
||||||
public static void main(String[] args) {
|
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("");
|
Logger.log("");
|
||||||
try {
|
try {
|
||||||
readConfig();
|
readConfig();
|
||||||
@ -31,6 +32,7 @@ public class Starter {
|
|||||||
System.err.println("Error while reading config! Needs to be:");
|
System.err.println("Error while reading config! Needs to be:");
|
||||||
System.err.println("DOWNLOAD URL BASE");
|
System.err.println("DOWNLOAD URL BASE");
|
||||||
System.err.println("COMMONKEY");
|
System.err.println("COMMONKEY");
|
||||||
|
System.err.println("updateinfos.csv");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,8 +9,16 @@ import java.util.List;
|
|||||||
import java.util.concurrent.ForkJoinPool;
|
import java.util.concurrent.ForkJoinPool;
|
||||||
|
|
||||||
import de.mas.jnustool.util.Util;
|
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 {
|
public class TitleMetaData {
|
||||||
|
//TODO: cleanup
|
||||||
int signatureType; // 0x000
|
int signatureType; // 0x000
|
||||||
byte[] signature = new byte[0x100]; // 0x004
|
byte[] signature = new byte[0x100]; // 0x004
|
||||||
byte[] issuer = new byte[0x40]; // 0x140
|
byte[] issuer = new byte[0x40]; // 0x140
|
||||||
@ -43,18 +51,24 @@ public class TitleMetaData {
|
|||||||
public TitleMetaData(byte[] downloadTMDToByteArray) throws IOException {
|
public TitleMetaData(byte[] downloadTMDToByteArray) throws IOException {
|
||||||
if(downloadTMDToByteArray != null){
|
if(downloadTMDToByteArray != null){
|
||||||
File tempFile;
|
File tempFile;
|
||||||
tempFile = File.createTempFile("bla","blubb");
|
tempFile = File.createTempFile("jnustool",".tmp");
|
||||||
FileOutputStream fos = new FileOutputStream(tempFile);
|
FileOutputStream fos = new FileOutputStream(tempFile);
|
||||||
fos.write(downloadTMDToByteArray);
|
fos.write(downloadTMDToByteArray);
|
||||||
fos.close();
|
fos.close();
|
||||||
parse(tempFile);
|
parse(tempFile);
|
||||||
setTotalContentSize();
|
setTotalContentSize();
|
||||||
|
|
||||||
}else{
|
}else{
|
||||||
System.err.println("Invalid TMD");
|
System.err.println("Invalid TMD");
|
||||||
throw new IllegalArgumentException("Invalid TMD");
|
throw new IllegalArgumentException("Invalid TMD");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses a TMD file
|
||||||
|
* @param tmd
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
private void parse(File tmd) throws IOException {
|
private void parse(File tmd) throws IOException {
|
||||||
|
|
||||||
RandomAccessFile f = new RandomAccessFile(tmd, "r");
|
RandomAccessFile f = new RandomAccessFile(tmd, "r");
|
||||||
@ -143,6 +157,7 @@ public class TitleMetaData {
|
|||||||
sb.append("SHA2: " + Util.ByteArrayToString(SHA2) +"\n");
|
sb.append("SHA2: " + Util.ByteArrayToString(SHA2) +"\n");
|
||||||
sb.append("cert: " + Util.ByteArrayToString(cert) +"\n");
|
sb.append("cert: " + Util.ByteArrayToString(cert) +"\n");
|
||||||
sb.append("contentInfos: \n");
|
sb.append("contentInfos: \n");
|
||||||
|
|
||||||
for(int i = 0; i<contents.length-1;i++){
|
for(int i = 0; i<contents.length-1;i++){
|
||||||
sb.append(" " + contentInfos[i] +"\n");
|
sb.append(" " + contentInfos[i] +"\n");
|
||||||
}
|
}
|
||||||
@ -152,6 +167,10 @@ public class TitleMetaData {
|
|||||||
}
|
}
|
||||||
return sb.toString();
|
return sb.toString();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Calculates the total size of the encrypted content file
|
||||||
|
*/
|
||||||
public void setTotalContentSize(){
|
public void setTotalContentSize(){
|
||||||
this.totalContentSize = 0;
|
this.totalContentSize = 0;
|
||||||
for(int i = 0; i <contents.length-1;i++){
|
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() {
|
public long getTotalContentSize() {
|
||||||
return totalContentSize;
|
return totalContentSize;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks of the title is an update
|
||||||
|
* @return true if its an update
|
||||||
|
*/
|
||||||
public boolean isUpdate() {
|
public boolean isUpdate() {
|
||||||
return (titleID & 0x5000E00000000L) == 0x5000E00000000L;
|
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{
|
public void downloadContents(Progress progress) throws IOException{
|
||||||
|
|
||||||
String tmpPath = getContentPath();
|
String tmpPath = getContentPath();
|
||||||
File f = new File(tmpPath);
|
File f = new File(tmpPath);
|
||||||
if(!f.exists())f.mkdir();
|
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() {
|
public String getContentPath() {
|
||||||
return nus.getContentPath();
|
return nus.getContentPath();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the instance of the NUSTitle of this TMD
|
||||||
|
* @return NUSTitle
|
||||||
|
*/
|
||||||
public NUSTitle getNUSTitle() {
|
public NUSTitle getNUSTitle() {
|
||||||
return nus;
|
return nus;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the NUSTitle of this TMD
|
||||||
|
* @param nus The NUStitle that will be set
|
||||||
|
*/
|
||||||
public void setNUSTitle(NUSTitle nus) {
|
public void setNUSTitle(NUSTitle nus) {
|
||||||
this.nus = nus;
|
this.nus = nus;
|
||||||
}
|
}
|
||||||
|
@ -43,6 +43,7 @@ import de.mas.jnustool.Progress;
|
|||||||
import de.mas.jnustool.ProgressUpdateListener;
|
import de.mas.jnustool.ProgressUpdateListener;
|
||||||
import de.mas.jnustool.Starter;
|
import de.mas.jnustool.Starter;
|
||||||
import de.mas.jnustool.util.NUSTitleInformation;
|
import de.mas.jnustool.util.NUSTitleInformation;
|
||||||
|
import de.mas.jnustool.util.Settings;
|
||||||
|
|
||||||
public class UpdateChooser extends JPanel {
|
public class UpdateChooser extends JPanel {
|
||||||
/**
|
/**
|
||||||
@ -73,7 +74,7 @@ public class UpdateChooser extends JPanel {
|
|||||||
tableData[i][3] = n.getLatestVersion();
|
tableData[i][3] = n.getLatestVersion();
|
||||||
|
|
||||||
JComboBox<String> comboBox = new JComboBox<>();
|
JComboBox<String> comboBox = new JComboBox<>();
|
||||||
for(String v : n.getAllVersions()){
|
for(String v : n.getAllVersionsAsString()){
|
||||||
comboBox.addItem(v);
|
comboBox.addItem(v);
|
||||||
}
|
}
|
||||||
final int position = i;
|
final int position = i;
|
||||||
@ -216,7 +217,11 @@ public class UpdateChooser extends JPanel {
|
|||||||
public void run() {
|
public void run() {
|
||||||
progressBar_1.setValue(0);
|
progressBar_1.setValue(0);
|
||||||
progress.clear();
|
progress.clear();
|
||||||
|
if(Settings.DL_ALL_VERSIONS){
|
||||||
|
Starter.downloadEncryptedAllVersions(output_,progress);
|
||||||
|
}else{
|
||||||
Starter.downloadEncrypted(output_,progress);
|
Starter.downloadEncrypted(output_,progress);
|
||||||
|
}
|
||||||
progress.operationFinish();
|
progress.operationFinish();
|
||||||
Logger.messageBox("Finished");
|
Logger.messageBox("Finished");
|
||||||
}
|
}
|
||||||
@ -230,7 +235,6 @@ public class UpdateChooser extends JPanel {
|
|||||||
});
|
});
|
||||||
panel.add(btnDownloadEncrypted);
|
panel.add(btnDownloadEncrypted);
|
||||||
|
|
||||||
|
|
||||||
btnNewButton.addActionListener(new ActionListener() {
|
btnNewButton.addActionListener(new ActionListener() {
|
||||||
public void actionPerformed(ActionEvent e) {
|
public void actionPerformed(ActionEvent e) {
|
||||||
if(!progress.isInProgress()){
|
if(!progress.isInProgress()){
|
||||||
@ -305,6 +309,7 @@ public class UpdateChooser extends JPanel {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* each row TableCellEditor
|
* each row TableCellEditor
|
||||||
*
|
*
|
||||||
@ -313,6 +318,7 @@ public class UpdateChooser extends JPanel {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
class EachRowEditor implements TableCellEditor {
|
class EachRowEditor implements TableCellEditor {
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
protected Hashtable editors;
|
protected Hashtable editors;
|
||||||
|
|
||||||
protected TableCellEditor editor, defaultEditor;
|
protected TableCellEditor editor, defaultEditor;
|
||||||
@ -325,6 +331,8 @@ public class UpdateChooser extends JPanel {
|
|||||||
* @see TableCellEditor
|
* @see TableCellEditor
|
||||||
* @see DefaultCellEditor
|
* @see DefaultCellEditor
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
@SuppressWarnings("rawtypes")
|
||||||
public EachRowEditor(JTable table) {
|
public EachRowEditor(JTable table) {
|
||||||
this.table = table;
|
this.table = table;
|
||||||
editors = new Hashtable();
|
editors = new Hashtable();
|
||||||
@ -337,6 +345,7 @@ public class UpdateChooser extends JPanel {
|
|||||||
* @param editor
|
* @param editor
|
||||||
* table cell editor
|
* table cell editor
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public void setEditorAt(int row, TableCellEditor editor) {
|
public void setEditorAt(int row, TableCellEditor editor) {
|
||||||
editors.put(new Integer(row), editor);
|
editors.put(new Integer(row), editor);
|
||||||
}
|
}
|
||||||
|
@ -80,6 +80,7 @@ public class Downloader {
|
|||||||
if(version > 0) version_suf = "." + version;
|
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);
|
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{
|
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{
|
public void downloadFile(String fileURL,String filename) throws IOException{
|
||||||
downloadFile(fileURL, filename,null,null);
|
downloadFile(fileURL, filename,null,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void downloadTicket(long titleID,String path) throws IOException {
|
public void downloadTicket(long titleID,String path) throws IOException {
|
||||||
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk";
|
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk";
|
||||||
downloadFile(URL, "title.tik",path,null);
|
downloadFile(URL, "title.tik",path,null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void downloadContent(long titleID,int contentID,Progress progress) throws IOException {
|
public void downloadContent(long titleID,int contentID,Progress progress) throws IOException {
|
||||||
downloadContent(titleID,contentID, null,progress);
|
downloadContent(titleID,contentID, null,progress);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] downloadContentToByteArray(long titleID,int contentID) throws IOException {
|
public byte[] downloadContentToByteArray(long titleID,int contentID) throws IOException {
|
||||||
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
|
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
|
||||||
return downloadFileToByteArray(URL);
|
return downloadFileToByteArray(URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] downloadTMDToByteArray(long titleID, int version) throws IOException {
|
public byte[] downloadTMDToByteArray(long titleID, int version) throws IOException {
|
||||||
String version_suf = "";
|
String version_suf = "";
|
||||||
if(version > 0) version_suf = "." + version;
|
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;
|
||||||
return downloadFileToByteArray(URL);
|
return downloadFileToByteArray(URL);
|
||||||
}
|
}
|
||||||
|
|
||||||
private byte[] downloadFileToByteArray(String fileURL) throws IOException {
|
private byte[] downloadFileToByteArray(String fileURL) throws IOException {
|
||||||
|
|
||||||
int BUFFER_SIZE = 0x800;
|
int BUFFER_SIZE = 0x800;
|
||||||
|
@ -125,7 +125,7 @@ public class NUSTitleInformation implements Comparable<NUSTitleInformation>, Ser
|
|||||||
for(Integer i :versions){
|
for(Integer i :versions){
|
||||||
result += ";" + i;
|
result += ";" + i;
|
||||||
}
|
}
|
||||||
result += ";" + getSelectedVersion();
|
//result += ";" + getSelectedVersion();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -166,7 +166,11 @@ public class NUSTitleInformation implements Comparable<NUSTitleInformation>, Ser
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<String> getAllVersions() {
|
public List<Integer> getAllVersions() {
|
||||||
|
return versions;
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<String> getAllVersionsAsString() {
|
||||||
List<String> list = new ArrayList<>();
|
List<String> list = new ArrayList<>();
|
||||||
if(versions != null && !versions.isEmpty()){
|
if(versions != null && !versions.isEmpty()){
|
||||||
for(Integer v: versions){
|
for(Integer v: versions){
|
||||||
|
@ -8,5 +8,6 @@ public class Settings {
|
|||||||
public static boolean skipBrokenFiles = false;
|
public static boolean skipBrokenFiles = false;
|
||||||
public static boolean skipExistingFiles = true;
|
public static boolean skipExistingFiles = true;
|
||||||
public static boolean skipExistingTMDTICKET = true;
|
public static boolean skipExistingTMDTICKET = true;
|
||||||
|
public static boolean DL_ALL_VERSIONS = false;
|
||||||
|
public static String FILELIST_NAME = "filelist.txt";
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,9 @@
|
|||||||
package de.mas.jnustool.util;
|
package de.mas.jnustool.util;
|
||||||
|
|
||||||
|
import java.io.BufferedWriter;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileWriter;
|
||||||
|
import java.io.IOException;
|
||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
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 "";
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user