Refactor code

This commit is contained in:
BullyWiiPlaza 2016-02-04 01:18:52 +01:00
parent 0e03adf10f
commit d999e2fedb
25 changed files with 1528 additions and 1625 deletions

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" path="src"/>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/JavaSE-1.8"/>
<classpathentry kind="output" path="bin"/>
</classpath>

17
.gitattributes vendored
View File

@ -1,17 +0,0 @@
# Auto detect text files and perform LF normalization
* text=auto
# Custom for Visual Studio
*.cs diff=csharp
# Standard to msysgit
*.doc diff=astextplain
*.DOC diff=astextplain
*.docx diff=astextplain
*.DOCX diff=astextplain
*.dot diff=astextplain
*.DOT diff=astextplain
*.pdf diff=astextplain
*.PDF diff=astextplain
*.rtf diff=astextplain
*.RTF diff=astextplain

View File

@ -1,17 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>JNusTool</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
</natures>
</projectDescription>

View File

@ -1,2 +0,0 @@
eclipse.preferences.version=1
encoding//src/Util.java=UTF-8

View File

@ -1,11 +0,0 @@
eclipse.preferences.version=1
org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.8
org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
org.eclipse.jdt.core.compiler.compliance=1.8
org.eclipse.jdt.core.compiler.debug.lineNumber=generate
org.eclipse.jdt.core.compiler.debug.localVariable=generate
org.eclipse.jdt.core.compiler.debug.sourceFile=generate
org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
org.eclipse.jdt.core.compiler.problem.enumIdentifier=error
org.eclipse.jdt.core.compiler.source=1.8

Binary file not shown.

Binary file not shown.

View File

@ -1,2 +0,0 @@
http://ccs.cdn.wup.shop.nintendo.net/ccs/download
[COMMONKEY]

View File

@ -1,25 +1,27 @@
package de.mas.jnustool; package de.mas.jnustool;
import de.mas.jnustool.util.Util; import de.mas.jnustool.util.ConversionUtils;
public class Content { public class Content
{
int ID; // 0 0xB04
short index; // 4 0xB08
short type; // 6 0xB0A
long size; // 8 0xB0C
byte[] SHA2 = new byte[32]; // 16 0xB14
int ID; // 0 0xB04 public Content(int ID, short index, short type, long size, byte[] SHA2)
short index; // 4 0xB08 {
short type; // 6 0xB0A
long size; // 8 0xB0C
byte[] SHA2 = new byte[32]; // 16 0xB14
public Content(int ID, short index, short type, long size, byte[] SHA2) {
this.ID = ID; this.ID = ID;
this.index = index; this.index = index;
this.type = type; this.type = type;
this.size = size; this.size = size;
this.SHA2 = SHA2; this.SHA2 = SHA2;
} }
@Override @Override
public String toString(){ public String toString()
return "ID: " + ID +" index: " + index + " type: " + type + " size: " + size + " SHA2: " + Util.ByteArrayToString(SHA2); {
return "ID: " + ID + " index: " + index + " type: " + type + " size: " + size + " SHA2: " + ConversionUtils.ByteArrayToString(SHA2);
} }
} }

View File

@ -1,32 +1,34 @@
package de.mas.jnustool; package de.mas.jnustool;
import de.mas.jnustool.util.Util; import de.mas.jnustool.util.ConversionUtils;
public class ContentInfo { public class ContentInfo
public short indexOffset; // 0 0x204 {
public short commandCount; // 2 0x206 public short indexOffset; // 0 0x204
public byte[] SHA2 = new byte[32]; // 12 0x208 public short commandCount; // 2 0x206
public byte[] SHA2 = new byte[32]; // 12 0x208
//TODO: Test, size checking // TODO: Test, size checking
/* public ContentInfo(byte[] info)
* untested {
*/ this.indexOffset = (short) (((info[0] & 0xFF) << 8) | (info[1] & 0xFF));
this.commandCount = (short) (((info[2] & 0xFF) << 8) | (info[3] & 0xFF));
public ContentInfo(byte[] info){ for (int i = 0; i < 32; i++)
this.indexOffset=(short)( ((info[0]&0xFF)<<8) | (info[1]&0xFF) ); {
this.commandCount=(short)( ((info[2]&0xFF)<<8) | (info[3]&0xFF) ); this.SHA2[i] = info[4 + i];
for(int i = 0;i<32;i++){
this.SHA2[i] = info[4+i];
} }
} }
public ContentInfo(short indexOffset, short commandCount, byte[] SHA2) { public ContentInfo(short indexOffset, short commandCount, byte[] SHA2)
{
this.indexOffset = indexOffset; this.indexOffset = indexOffset;
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: " + ConversionUtils.ByteArrayToString(SHA2);
} }
} }

View File

@ -5,89 +5,85 @@ import java.util.TreeMap;
import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.DefaultMutableTreeNode;
public class Directory { public class Directory
{
String name = ""; String name = "";
TreeMap<String,Directory> folder = new TreeMap<>(); TreeMap<String, Directory> folder = new TreeMap<>();
TreeMap<String,FEntry> files = new TreeMap<>(); TreeMap<String, FEntry> files = new TreeMap<>();
public Directory get(String s){ public Directory get(String s)
{
return folder.get(s); return folder.get(s);
} }
public Directory(String name){ public Directory(String name)
{
setName(name); setName(name);
} }
public boolean containsFolder(String s){ public boolean containsFolder(String s)
{
return folder.containsKey(s); return folder.containsKey(s);
} }
public Directory getFolder(String s){ public Directory getFolder(String s)
{
return folder.get(s); return folder.get(s);
} }
public Directory addFolder(Directory s){ public Directory addFolder(Directory s)
return folder.put(s.getName(),s); {
} return folder.put(s.getName(), s);
public boolean containsFile(String s){
return files.containsKey(s);
} }
public FEntry getFile(String s){ public FEntry addFile(FEntry s)
return files.get(s); {
return files.put(s.getFileName(), s);
} }
public FEntry addFile(FEntry s){ public String getName()
return files.put(s.getFileName(),s); {
}
public String getName() {
return name; return name;
} }
public void setName(String name) { public void setName(String name)
{
this.name = name; this.name = name;
} }
public Collection<Directory> getFolder()
public Collection<Directory> getFolder() { {
return folder.values(); return folder.values();
} }
public Collection<FEntry> getFiles()
{
public Collection<FEntry> getFiles() { return files.values();
return files.values();
}
public void setFiles(TreeMap<String, FEntry> files) {
this.files = files;
} }
@Override @Override
public String toString(){ public String toString()
{
System.out.println(name + ":"); System.out.println(name + ":");
for(Directory d : folder.values()){ folder.values().forEach(System.out::println);
System.out.println(d); files.keySet().forEach(System.out::println);
}
for(String s : files.keySet()){
System.out.println(s);
}
return ""; return "";
} }
public DefaultMutableTreeNode getNodes(){ public DefaultMutableTreeNode getNodes()
{
DefaultMutableTreeNode node = new DefaultMutableTreeNode(getName()); DefaultMutableTreeNode node = new DefaultMutableTreeNode(getName());
for(Directory f: getFolder()){ for (Directory directory : getFolder())
node.add(f.getNodes()); {
node.add(directory.getNodes());
} }
for(FEntry f: getFiles()){ for (FEntry f : getFiles())
{
node.add(new DefaultMutableTreeNode(f)); node.add(new DefaultMutableTreeNode(f));
} }
return node; return node;
} }
} }

View File

@ -2,6 +2,7 @@ package de.mas.jnustool;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.util.List; import java.util.List;
import de.mas.jnustool.util.Decryption; import de.mas.jnustool.util.Decryption;
@ -9,11 +10,12 @@ import de.mas.jnustool.util.Downloader;
import de.mas.jnustool.util.ExitException; import de.mas.jnustool.util.ExitException;
import de.mas.jnustool.util.Settings; import de.mas.jnustool.util.Settings;
public class FEntry { public class FEntry
{
private FST fst; private FST fst;
public static int DIR_FLAG = 1; public static int DIR_FLAG = 1;
public static int NOT_IN_NUSTITLE_FLAG = 0x80; public static int NOT_IN_NUS_TITLE_FLAG = 0x80;
public static int EXTRACT_WITH_HASH_FLAG = 0x440; public static int EXTRACT_WITH_HASH_FLAG = 0x440;
public static int CHANGE_OFFSET_FLAG = 0x04; public static int CHANGE_OFFSET_FLAG = 0x04;
@ -26,12 +28,12 @@ public class FEntry {
private long fileOffset = 0L; private long fileOffset = 0L;
private long fileLength = 0; private long fileLength = 0;
private int contentID = 0; private int contentID = 0;
private int NUScontentID = 0; private int NUSContentID = 0;
private List<String> pathList; private List<String> pathList;
public FEntry(String path, String filename, int contentID, int NUSContentID, long fileOffset, long fileLength, boolean dir,
public FEntry(String path, String filename, int contentID,int NUScontentID, long fileOffset, long fileLength, boolean dir, boolean in_nus_title, boolean extract_withHash, List<String> pathList, FST fst)
boolean in_nus_title, boolean extract_withHash, List<String> pathList,FST fst) { {
setPath(path); setPath(path);
setFileName(filename); setFileName(filename);
setContentID(contentID); setContentID(contentID);
@ -40,202 +42,246 @@ public class FEntry {
setDir(dir); setDir(dir);
setInNusTitle(in_nus_title); setInNusTitle(in_nus_title);
setExtractWithHash(extract_withHash); setExtractWithHash(extract_withHash);
setNUScontentID(NUScontentID); setNUSContentID(NUSContentID);
setPathList(pathList); setPathList(pathList);
this.fst = fst; this.fst = fst;
} }
public boolean isDir() { public boolean isDir()
{
return dir; return dir;
} }
private void setDir(boolean dir) { private void setDir(boolean dir)
{
this.dir = dir; this.dir = dir;
} }
public boolean isInNUSTitle() { public boolean isInNUSTitle()
{
return in_nus_title; return in_nus_title;
} }
private void setInNusTitle(boolean in_nus_title) { private void setInNusTitle(boolean in_nus_title)
{
this.in_nus_title = in_nus_title; this.in_nus_title = in_nus_title;
} }
public boolean isExtractWithHash() { public boolean isExtractWithHash()
{
return extract_withHash; return extract_withHash;
} }
private void setExtractWithHash(boolean extract_withHash) { private void setExtractWithHash(boolean extract_withHash)
{
this.extract_withHash = extract_withHash; this.extract_withHash = extract_withHash;
} }
public String getFileName() { public String getFileName()
{
return fileName; return fileName;
} }
private void setFileName(String filename) { private void setFileName(String filename)
{
this.fileName = filename; this.fileName = filename;
} }
public String getPath() { public String getFullPath()
return path; {
}
public String getFullPath() {
return path + fileName; return path + fileName;
} }
private void setPath(String path) { private void setPath(String path)
{
this.path = path; this.path = path;
} }
public long getFileOffset() { public long getFileOffset()
{
return fileOffset; return fileOffset;
} }
private void setFileOffset(long fileOffset) { private void setFileOffset(long fileOffset)
{
this.fileOffset = fileOffset; this.fileOffset = fileOffset;
} }
public int getContentID() { public int getContentID()
{
return contentID; return contentID;
} }
private void setContentID(int contentID) { private void setContentID(int contentID)
{
this.contentID = contentID; this.contentID = contentID;
} }
public long getFileLength() { public long getFileLength()
{
return fileLength; return fileLength;
} }
private void setFileLength(long fileLength) { private void setFileLength(long fileLength)
{
this.fileLength = fileLength; this.fileLength = fileLength;
} }
@Override @Override
public String toString(){ public String toString()
return getFullPath() + " Content ID:" + contentID + " Size: " + fileLength +"MB Offset: " + fileOffset; {
return getFullPath() + " Content ID:" + contentID + " Size: " + fileLength + "MB Offset: " + fileOffset;
} }
public int getNUScontentID() { public int getNUSContentID()
return NUScontentID; {
return NUSContentID;
} }
private void setNUScontentID(int nUScontentID) { private void setNUSContentID(int nusContentID)
NUScontentID = nUScontentID; {
NUSContentID = nusContentID;
} }
private void createFolder() { private void createFolder()
{
long titleID = getTitleID(); long titleID = getTitleID();
String [] path = getFullPath().split("/"); String[] path = getFullPath().split("/");
File f = new File (String.format("%016X", titleID)); File f = new File(String.format("%016X", titleID));
if(!f.exists())f.mkdir(); if (!f.exists())
{
f.mkdir();
}
String folder = String.format("%016X", titleID) +"/"; String folder = String.format("%016X", titleID) + "/";
File folder_ = null; File folder_;
for(int i = 0;i<path.length-1;i++){ for (int i = 0; i < path.length - 1; i++)
if(!path[i].equals("")){ {
folder += path[i] + "/"; if (!path[i].equals(""))
folder_ = new File(folder); {
if(!folder_.exists()){ folder += path[i] + "/";
folder_.mkdir(); folder_ = new File(folder);
} if (!folder_.exists())
} {
} try
{
Files.createDirectory(folder_.toPath());
} catch (IOException e)
{
e.printStackTrace();
}
}
}
}
} }
public String getDownloadPath(){
String [] path = getFullPath().split("/"); public String getDownloadPath()
String folder = String.format("%016X", getTitleID()) +"/"; {
for(int i = 0;i<path.length-1;i++){ String[] path = getFullPath().split("/");
if(!path[i].equals("")){ String folder = String.format("%016X", getTitleID()) + "/";
folder += path[i] + "/"; for (int i = 0; i < path.length - 1; i++)
} {
} if (!path[i].equals(""))
return folder; {
folder += path[i] + "/";
}
}
return folder;
} }
public void downloadAndDecrypt() throws ExitException { public void downloadAndDecrypt() throws ExitException
{
createFolder(); createFolder();
long titleID = getTitleID(); long titleID = getTitleID();
File f = new File(String.format("%016X", titleID) +"/" +getFullPath().substring(1, getFullPath().length())); File f = new File(String.format("%016X", titleID) + "/" + getFullPath().substring(1, getFullPath().length()));
if(f.exists()){ if (f.exists())
if(f.length() == getFileLength()){ {
System.out.println("Skipping: " + String.format("%8.2f MB ",getFileLength()/1024.0/1024.0) + getFullPath()); if (f.length() == getFileLength())
{
System.out.println("Skipping: " + String.format("%8.2f MB ", getFileLength() / 1024.0 / 1024.0) + getFullPath());
return; return;
} }
} }
try { try
if(Settings.useCachedFiles){ {
if (Settings.useCachedFiles)
{
f = new File(getContentPath()); f = new File(getContentPath());
if(f.exists()){ if (f.exists())
if(f.length() == fst.getTmd().contents[this.getContentID()].size){ {
System.out.println("Decrypting: " + String.format("%8.2f MB ", getFileLength()/1024.0/1024.0) + getFullPath()); if (f.length() == fst.getTmd().contents[this.getContentID()].size)
{
System.out.println("Decrypting: " + String.format("%8.2f MB ", getFileLength() / 1024.0 / 1024.0) + getFullPath());
Decryption decrypt = new Decryption(fst.getTmd().getNUSTitle().getTicket()); Decryption decrypt = new Decryption(fst.getTmd().getNUSTitle().getTicket());
decrypt.decrypt(this,getDownloadPath()); decrypt.decrypt(this, getDownloadPath());
return; return;
}else{ } else
if(!Settings.downloadWhenCachedFilesMissingOrBroken){ {
System.out.println("Cached content has the wrong size! Please check your: "+ getContentPath() + " Downloading not allowed"); if (!Settings.downloadWhenCachedFilesMissingOrBroken)
if(!Settings.skipBrokenFiles){ {
System.out.println("Cached content has the wrong size! Please check your: " + getContentPath() + " Downloading not allowed");
if (!Settings.skipBrokenFiles)
{
throw new ExitException(""); throw new ExitException("");
}else{ } else
{
System.out.println("Ignoring the missing file: " + this.getFileName()); System.out.println("Ignoring the missing file: " + this.getFileName());
} }
}else{ } else
{
System.out.println("Content missing. Downloading the file from the server: " + this.getFileName()); System.out.println("Content missing. Downloading the file from the server: " + this.getFileName());
} }
} }
}else{ } else
if(!Settings.downloadWhenCachedFilesMissingOrBroken){ {
if (!Settings.downloadWhenCachedFilesMissingOrBroken)
{
System.out.println("Content missing. Downloading not allowed"); System.out.println("Content missing. Downloading not allowed");
if(!Settings.skipBrokenFiles){ if (!Settings.skipBrokenFiles)
{
throw new ExitException(""); throw new ExitException("");
}else{ } else
{
System.out.println("Ignoring the missing file: " + this.getFileName()); System.out.println("Ignoring the missing file: " + this.getFileName());
} }
}else{ } else
{
System.out.println("Content missing. Downloading the file from the server: " + this.getFileName()); System.out.println("Content missing. Downloading the file from the server: " + this.getFileName());
} }
} }
} }
System.out.println("Downloading: " + String.format("%8.2f MB ", getFileLength()/1024.0/1024.0) + getFullPath()); System.out.println("Downloading: " + String.format("%8.2f MB ", getFileLength() / 1024.0 / 1024.0) + getFullPath());
Downloader.getInstance().downloadAndDecrypt(this); Downloader.getInstance().downloadAndDecrypt(this);
} catch (IOException e)
{
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }
public List<String> getPathList() { public List<String> getPathList()
{
return pathList; return pathList;
} }
public void setPathList(List<String> pathList) { public void setPathList(List<String> pathList)
{
this.pathList = pathList; this.pathList = pathList;
} }
public String getContentPath() { public String getContentPath()
return fst.getTmd().getContentPath() + "/" + String.format("%08X", getNUScontentID()) + ".app"; {
return fst.getTmd().getContentPath() + "/" + String.format("%08X", getNUSContentID()) + ".app";
} }
public long getTitleID() { public long getTitleID()
{
return fst.getTmd().titleID; return fst.getTmd().titleID;
} }
public TIK getTicket() { public TIK getTicket()
{
return fst.getTmd().getNUSTitle().getTicket(); return fst.getTmd().getNUSTitle().getTicket();
} }
} }

View File

@ -4,19 +4,22 @@ import java.util.concurrent.Callable;
public class FEntryDownloader implements Callable<Integer> public class FEntryDownloader implements Callable<Integer>
{ {
FEntry f; private FEntry fEntry;
public void setTitle(FEntry f){
this.f = f; public void setTitle(FEntry fEntry)
} {
public FEntryDownloader(FEntry f){ this.fEntry = fEntry;
setTitle(f);
} }
public FEntryDownloader(FEntry fEntry)
{
setTitle(fEntry);
}
@Override @Override
public Integer call() throws Exception { public Integer call() throws Exception
f.downloadAndDecrypt(); {
fEntry.downloadAndDecrypt();
return null; return null;
} }
} }

View File

@ -5,9 +5,10 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.List; import java.util.List;
import de.mas.jnustool.util.Util; import de.mas.jnustool.util.ConversionUtils;
public class FST { public class FST
{
private TitleMetaData tmd; private TitleMetaData tmd;
long totalContentSize = 0L; long totalContentSize = 0L;
long totalContentSizeInNUS = 0L; long totalContentSizeInNUS = 0L;
@ -23,84 +24,91 @@ public class FST {
private Directory contentDirectory = new Directory("root"); private Directory contentDirectory = new Directory("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;
Directory curContent;
for (FEntry f : getFileEntries())
{
if (!f.isDir() && f.isInNUSTitle())
{
contentFolder = String.format("%08X", tmd.contents[f.getContentID()].ID);
String contentfolder = ""; if (!contentDirectory.containsFolder(contentFolder))
Directory curContent = contentDirectory; {
for(FEntry f : getFileEntries()){ Directory newDir = new Directory(contentFolder);
if(!f.isDir() && f.isInNUSTitle()){ contentDirectory.addFolder(newDir);
contentfolder = String.format("%08X",tmd.contents[f.getContentID()].ID);
if(!contentDirectory.containsFolder(contentfolder)){
Directory newDir = new Directory(contentfolder);
contentDirectory.addFolder(newDir);
} }
curContent = contentDirectory.getFolder(contentfolder); curContent = contentDirectory.getFolder(contentFolder);
Directory current = FSTDirectory; Directory current = FSTDirectory;
int i = 0; int i = 0;
for(String s :f.getPathList()){ for (String s : f.getPathList())
i++; {
i++;
//Content //Content
if(curContent.containsFolder(s)){ if (curContent.containsFolder(s))
{
curContent = curContent.get(s); curContent = curContent.get(s);
}else{ } else
{
Directory newDir = new Directory(s); Directory newDir = new Directory(s);
curContent.addFolder(newDir); curContent.addFolder(newDir);
curContent = newDir; curContent = newDir;
} }
if(i==f.getPathList().size()){ if (i == f.getPathList().size())
{
curContent.addFile(f); curContent.addFile(f);
} }
//FST //FST
if(current.containsFolder(s)){ if (current.containsFolder(s))
{
current = current.get(s); current = current.get(s);
}else{ } else
{
Directory newDir = new Directory(s); Directory newDir = new Directory(s);
current.addFolder(newDir); current.addFolder(newDir);
current = newDir; current = newDir;
} }
if(i==f.getPathList().size()){ if (i == f.getPathList().size())
{
current.addFile(f); current.addFile(f);
} }
} }
} }
} }
} }
private void parse(byte[] decryptedData, TitleMetaData tmd) throws IOException
{
private void parse(byte[] decrypteddata, TitleMetaData tmd) throws IOException { 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})){
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 = ConversionUtils.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 = ConversionUtils.getIntFromBytes(decryptedData, base_offset + 8);
int nameOff = base_offset + totalEntries * 0x10; int nameOff = base_offset + totalEntries * 0x10;
int level=0; int level = 0;
int[] LEntry = new int[16]; int[] LEntry = new int[16];
int[] Entry = new int[16]; int[] Entry = new int[16];
for(int i = 0;i<this.totalEntries;i++){ for (int i = 0; i < this.totalEntries; i++)
{
boolean dir = false; boolean dir = false;
boolean in_nus_title = true; boolean in_nus_title = true;
boolean extract_withHash = false; boolean extract_withHash = false;
@ -110,205 +118,175 @@ public class FST {
int type; int type;
int contentID; int contentID;
String filename = ""; String filename;
String path = ""; String path = "";
if( level > 0) if (level > 0)
{ {
while( LEntry[level-1] == i ) while (LEntry[level - 1] == i)
{ {
level--; level--;
} }
} }
int offset = base_offset + i * 0x10;
int offset = base_offset + i*0x10;
//getting the type //getting the type
type = (int) decrypteddata[offset]+128; type = (int) decryptedData[offset] + 128;
if((type & FEntry.DIR_FLAG) == 1) dir = true; if ((type & FEntry.DIR_FLAG) == 1)
if((type & FEntry.NOT_IN_NUSTITLE_FLAG) == 0 ) in_nus_title = false; {
dir = true;
}
if ((type & FEntry.NOT_IN_NUS_TITLE_FLAG) == 0)
{
in_nus_title = false;
}
//getting Name //getting Name
decrypteddata[offset] = 0; decryptedData[offset] = 0;
int nameoff_entry_offset = Util.getIntFromBytes(decrypteddata, offset); int nameOff_entry_offset = ConversionUtils.getIntFromBytes(decryptedData, offset);
int j = 0; int j = 0;
int nameoff_entry = nameOff + nameoff_entry_offset; int nameOff_entry = nameOff + nameOff_entry_offset;
while(decrypteddata[nameoff_entry + j] != 0){j++;} while (decryptedData[nameOff_entry + j] != 0)
filename = new String(Arrays.copyOfRange(decrypteddata,nameoff_entry, nameoff_entry + j)); {
j++;
}
filename = new String(Arrays.copyOfRange(decryptedData, nameOff_entry, nameOff_entry + j));
//getting offsets. save in two ways //getting offsets. save in two ways
offset+=4; offset += 4;
fileOffset = (long) Util.getIntFromBytes(decrypteddata, offset); fileOffset = (long) ConversionUtils.getIntFromBytes(decryptedData, offset);
offset+=4; offset += 4;
fileLength = Util.getIntAsLongFromBytes(decrypteddata, offset); fileLength = ConversionUtils.getIntAsLongFromBytes(decryptedData, offset);
@SuppressWarnings("unused")
int parentOffset = (int) fileOffset;
int nextOffset = (int) fileLength; int nextOffset = (int) fileLength;
//grabbing flags //grabbing flags
offset+=4; offset += 4;
int flags = Util.getShortFromBytes(decrypteddata, offset); int flags = ConversionUtils.getShortFromBytes(decryptedData, offset);
if((flags & FEntry.EXTRACT_WITH_HASH_FLAG) > 0) extract_withHash = true; if ((flags & FEntry.EXTRACT_WITH_HASH_FLAG) > 0)
if((flags & FEntry.CHANGE_OFFSET_FLAG) == 0) fileOffset <<=5; {
extract_withHash = true;
//grabbing contentid }
offset+=2; if ((flags & FEntry.CHANGE_OFFSET_FLAG) == 0)
contentID = Util.getShortFromBytes(decrypteddata, offset) ; {
fileOffset <<= 5;
}
//grabbing content id
offset += 2;
contentID = ConversionUtils.getShortFromBytes(decryptedData, offset);
//remember total size //remember total size
this.totalContentSize += fileLength; this.totalContentSize += fileLength;
if(in_nus_title)this.totalContentSizeInNUS += fileLength; if (in_nus_title)
{
this.totalContentSizeInNUS += fileLength;
}
List<String> pathList = new ArrayList<>(); List<String> pathList = new ArrayList<>();
//getting the full path of entry //getting the full path of entry
if(dir) if (dir)
{ {
dirEntries++; dirEntries++;
Entry[level] = i; Entry[level] = i;
LEntry[level++] = nextOffset ; LEntry[level++] = nextOffset;
if( level > 15 ) // something is wrong! if (level > 15) // something is wrong!
{ {
break; break;
} }
}else{ } else
StringBuilder sb = new StringBuilder(); {
int k = 0; StringBuilder stringBuilder = new StringBuilder();
int nameoffoff,nameoff_entrypath; int k;
int nameOffOff, nameOff_entryPath;
for( j=0; j<level; ++j ) for (j = 0; j < level; ++j)
{ {
nameoffoff = Util.getIntFromBytes(decrypteddata,base_offset+Entry[j]*0x10); nameOffOff = ConversionUtils.getIntFromBytes(decryptedData, base_offset + Entry[j] * 0x10);
k=0; k = 0;
nameoff_entrypath = nameOff + nameoffoff; nameOff_entryPath = nameOff + nameOffOff;
while(decrypteddata[nameoff_entrypath + k] != 0){k++;} while (decryptedData[nameOff_entryPath + k] != 0)
String tmpname = new String(Arrays.copyOfRange(decrypteddata,nameoff_entrypath, nameoff_entrypath + k)); {
if(!tmpname.equals("")){ k++;
pathList.add(tmpname); }
String temporaryName = new String(Arrays.copyOfRange(decryptedData, nameOff_entryPath, nameOff_entryPath + k));
if (!temporaryName.equals(""))
{
pathList.add(temporaryName);
} }
stringBuilder.append(temporaryName);
stringBuilder.append("/");
sb.append(tmpname);
sb.append("/");
} }
path = sb.toString(); path = stringBuilder.toString();
} }
//add this to the List! //add this to the List!
fileEntries.add(new FEntry(path,filename,contentID,tmd.contents[contentID].ID,fileOffset,fileLength,dir,in_nus_title,extract_withHash,pathList,this)); fileEntries.add(new FEntry(path, filename, contentID, tmd.contents[contentID].ID, fileOffset, fileLength, dir, in_nus_title, extract_withHash, pathList, this));
//System.out.println(fileEntries.get(i)); //System.out.println(fileEntries.get(i));
} }
} }
public long getTotalContentSizeInNUS()
{
public long getTotalContentSize() {
return totalContentSize;
}
public void setTotalContentSize(long totalContentSize) {
this.totalContentSize = totalContentSize;
}
public long getTotalContentSizeInNUS() {
return totalContentSizeInNUS; return totalContentSizeInNUS;
} }
public List<FEntry> getFileEntries()
public void setTotalContentSizeInNUS(long totalContentSizeInNUS) { {
this.totalContentSizeInNUS = totalContentSizeInNUS;
}
public List<FEntry> getFileEntries() {
return fileEntries; return fileEntries;
} }
public int getTotalEntries()
public void setFileEntries(List<FEntry> fileEntries) { {
this.fileEntries = fileEntries;
}
public int getTotalContentCount() {
return totalContentCount;
}
public void setTotalContentCount(int totalContentCount) {
this.totalContentCount = totalContentCount;
}
public int getTotalEntries() {
return totalEntries; return totalEntries;
} }
public void setTotalEntries(int totalEntries) {
this.totalEntries = totalEntries;
}
public int getDirEntries() {
return dirEntries;
}
public void setDirEntries(int dirEntries) {
this.dirEntries = dirEntries;
}
@Override @Override
public String toString(){ public String toString()
return "entryCount: " + totalContentCount+ " entries: " + totalEntries; {
return "entryCount: " + totalContentCount + " entries: " + totalEntries;
} }
public int getFileCount()
public int getFileCount() { {
int i = 0; int i = 0;
for(FEntry f: getFileEntries()){ for (FEntry f : getFileEntries())
if(!f.isDir()) {
if (!f.isDir())
{
i++; i++;
}
} }
return i; return i;
} }
public int getFileCountInNUS() { public int getFileCountInNUS()
{
int i = 0; int i = 0;
for(FEntry f: getFileEntries()){ for (FEntry f : getFileEntries())
if(!f.isDir() && f.isInNUSTitle()) {
if (!f.isDir() && f.isInNUSTitle())
{
i++; i++;
}
} }
return i; return i;
} }
public Directory getFSTDirectory() { public Directory getFSTDirectory()
{
return FSTDirectory; return FSTDirectory;
} }
public Directory getContentDirectory() { public TitleMetaData getTmd()
return contentDirectory; {
}
public TitleMetaData getTmd() {
return tmd; return tmd;
} }
public void setTmd(TitleMetaData tmd) { public void setTmd(TitleMetaData tmd)
{
this.tmd = tmd; this.tmd = tmd;
} }
} }

View File

@ -11,97 +11,138 @@ import de.mas.jnustool.util.Downloader;
import de.mas.jnustool.util.ExitException; import de.mas.jnustool.util.ExitException;
import de.mas.jnustool.util.Settings; import de.mas.jnustool.util.Settings;
public class NUSTitle { public class NUSTitle
{
private TitleMetaData tmd; private TitleMetaData tmd;
private TIK ticket; private TIK ticket;
private FST fst; private FST fst;
private long titleID; private long titleID;
public NUSTitle(long titleId,String key) throws ExitException{
public NUSTitle(long titleId, String key) throws ExitException
{
setTitleID(titleId); setTitleID(titleId);
try { try
if(Settings.downloadContent){ {
File f = new File(getContentPath()); if (Settings.downloadContent)
if(!f.exists())f.mkdir(); {
File f = new File(getContentPath());
if (!f.exists())
{
Files.createDirectory(f.toPath());
}
} }
if(Settings.downloadContent){ if (Settings.downloadContent)
{
File f = new File(getContentPath() + "/" + "tmd"); File f = new File(getContentPath() + "/" + "tmd");
if(!(f.exists() && Settings.skipExistingTMDTICKET)){ if (!(f.exists() && Settings.skipExistingTMDTICKET))
{
System.out.println("Downloading TMD"); System.out.println("Downloading TMD");
Downloader.getInstance().downloadTMD(titleId,getContentPath()); Downloader.getInstance().downloadTMD(titleId, getContentPath());
}else{ } else
{
System.out.println("Skipped download of TMD. Already existing"); System.out.println("Skipped download of TMD. Already existing");
} }
f = new File(getContentPath() + "/" + "cetk"); f = new File(getContentPath() + "/" + "cetk");
if(!(f.exists() && Settings.skipExistingTMDTICKET)){ if (!(f.exists() && Settings.skipExistingTMDTICKET))
if(key == null){ {
if (key == null)
{
System.out.print("Downloading Ticket"); System.out.print("Downloading Ticket");
Downloader.getInstance().downloadTicket(titleId,getContentPath()); Downloader.getInstance().downloadTicket(titleId, getContentPath());
} }
}else{ } else
{
System.out.println("Skipped download of ticket. Already existing"); System.out.println("Skipped download of ticket. Already existing");
} }
} }
if(Settings.useCachedFiles){ if (Settings.useCachedFiles)
{
File f = new File(getContentPath() + "/" + "tmd"); File f = new File(getContentPath() + "/" + "tmd");
if(f.exists()){ if (f.exists())
{
System.out.println("Using cached TMD."); System.out.println("Using cached TMD.");
tmd = new TitleMetaData(f); tmd = new TitleMetaData(f);
}else{ } else
{
System.out.println("No cached TMD found."); System.out.println("No cached TMD found.");
} }
} }
if(tmd == null){ if (tmd == null)
if(Settings.downloadWhenCachedFilesMissingOrBroken){ {
if(Settings.useCachedFiles) System.out.println("Getting missing tmd from Server!"); if (Settings.downloadWhenCachedFilesMissingOrBroken)
{
if (Settings.useCachedFiles)
{
System.out.println("Getting missing tmd from Server!");
}
tmd = new TitleMetaData(Downloader.getInstance().downloadTMDToByteArray(titleId)); tmd = new TitleMetaData(Downloader.getInstance().downloadTMDToByteArray(titleId));
}else{ } else
{
System.out.println("Downloading of missing files is not enabled. Exiting"); System.out.println("Downloading of missing files is not enabled. Exiting");
throw new ExitException("TMD missing."); throw new ExitException("TMD missing.");
} }
} }
if(key != null){ if (key != null)
{
System.out.println("Using ticket from parameter."); System.out.println("Using ticket from parameter.");
ticket = new TIK(key,titleId); ticket = new TIK(key, titleId);
}else{ } else
if(Settings.useCachedFiles){ {
if (Settings.useCachedFiles)
{
File f = new File(getContentPath() + "/" + "cetk"); File f = new File(getContentPath() + "/" + "cetk");
if(f.exists()){ if (f.exists())
{
System.out.println("Using cached cetk."); System.out.println("Using cached cetk.");
ticket = new TIK(f,titleId); ticket = new TIK(f, titleId);
}else{ } else
{
System.out.println("No cached ticket found."); System.out.println("No cached ticket found.");
} }
} }
if(ticket == null){ if (ticket == null)
if(Settings.downloadWhenCachedFilesMissingOrBroken){ {
if(Settings.useCachedFiles) System.out.println("getting missing ticket"); if (Settings.downloadWhenCachedFilesMissingOrBroken)
ticket = new TIK(Downloader.getInstance().downloadTicketToByteArray(titleId),tmd.titleID); {
}else{ if (Settings.useCachedFiles)
{
System.out.println("getting missing ticket");
}
ticket = new TIK(Downloader.getInstance().downloadTicketToByteArray(titleId), tmd.titleID);
} else
{
System.out.println("Downloading of missing files is not enabled. Exiting"); System.out.println("Downloading of missing files is not enabled. Exiting");
throw new ExitException("Ticket missing."); throw new ExitException("Ticket missing.");
} }
} }
} }
if(Settings.downloadContent){ if (Settings.downloadContent)
{
File f = new File(getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app"); File f = new File(getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app");
if(!(f.exists() && Settings.skipExistingFiles)){ if (!(f.exists() && Settings.skipExistingFiles))
{
System.out.println("Downloading FST (" + String.format("%08x", tmd.contents[0].ID) + ")"); System.out.println("Downloading FST (" + String.format("%08x", tmd.contents[0].ID) + ")");
Downloader.getInstance().downloadContent(titleId,tmd.contents[0].ID,getContentPath()); Downloader.getInstance().downloadContent(titleId, tmd.contents[0].ID, getContentPath());
}else{ } else
if(f.length() != tmd.contents[0].size){ {
if(Settings.downloadWhenCachedFilesMissingOrBroken){ if (f.length() != tmd.contents[0].size)
{
if (Settings.downloadWhenCachedFilesMissingOrBroken)
{
System.out.println("FST already existing, but broken. Downloading it again."); System.out.println("FST already existing, but broken. Downloading it again.");
Downloader.getInstance().downloadContent(titleId,tmd.contents[0].ID,getContentPath()); Downloader.getInstance().downloadContent(titleId, tmd.contents[0].ID, getContentPath());
}else{ } else
{
System.out.println("FST already existing, but broken. No download allowed."); System.out.println("FST already existing, but broken. No download allowed.");
throw new ExitException("FST missing."); throw new ExitException("FST missing.");
} }
}else{ } else
{
System.out.println("Skipped download of FST. Already existing"); System.out.println("Skipped download of FST. Already existing");
} }
@ -109,105 +150,91 @@ public class NUSTitle {
} }
Decryption decryption = new Decryption(ticket.getDecryptedKey(),0); Decryption decryption = new Decryption(ticket.getDecryptedKey(), 0);
byte[] encryptedFST = null; byte[] encryptedFST = null;
if(Settings.useCachedFiles){ if (Settings.useCachedFiles)
{
String path = getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app"; String path = getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app";
File f = new File(path); File f = new File(path);
if(f.exists()){ if (f.exists())
{
System.out.println("Using cached FST"); System.out.println("Using cached FST");
Path file = Paths.get(path); Path file = Paths.get(path);
encryptedFST = Files.readAllBytes(file); encryptedFST = Files.readAllBytes(file);
}else{ } else
System.out.println("No cached FST (" + String.format("%08x", tmd.contents[0].ID) + ") found."); {
System.out.println("No cached FST (" + String.format("%08x", tmd.contents[0].ID) + ") found.");
} }
} }
if(encryptedFST == null){ if (encryptedFST == null)
if(Settings.downloadWhenCachedFilesMissingOrBroken){ {
if(Settings.useCachedFiles)System.out.println("Getting FST from server."); if (Settings.downloadWhenCachedFilesMissingOrBroken)
encryptedFST = Downloader.getInstance().downloadContentToByteArray(titleId,tmd.contents[0].ID); {
}else{ if (Settings.useCachedFiles)
{
System.out.println("Getting FST from server.");
}
encryptedFST = Downloader.getInstance().downloadContentToByteArray(titleId, tmd.contents[0].ID);
} else
{
System.out.println("Downloading of missing files is not enabled. Exiting"); System.out.println("Downloading of missing files is not enabled. Exiting");
throw new ExitException(""); throw new ExitException("");
} }
} }
byte[] decryptedFST = decryption.decrypt(encryptedFST); byte[] decryptedFST = decryption.decrypt(encryptedFST);
fst = new FST(decryptedFST,tmd); fst = new FST(decryptedFST, tmd);
tmd.setNUSTitle(this); tmd.setNUSTitle(this);
if(Settings.downloadContent){ if (Settings.downloadContent)
{
tmd.downloadContents(); tmd.downloadContents();
} }
System.out.println("Total Size of Content Files: " + ((int)((getTotalContentSize()/1024.0/1024.0)*100))/100.0 +" MB"); System.out.println("Total Size of Content Files: " + ((int) ((getTotalContentSize() / 1024.0 / 1024.0) * 100)) / 100.0 + " MB");
System.out.println("Total Size of Decrypted Files: " + ((int)((fst.getTotalContentSizeInNUS()/1024.0/1024.0)*100))/100.0 +" MB"); System.out.println("Total Size of Decrypted Files: " + ((int) ((fst.getTotalContentSizeInNUS() / 1024.0 / 1024.0) * 100)) / 100.0 + " MB");
System.out.println("Entries: " + fst.getTotalEntries()); System.out.println("Entries: " + fst.getTotalEntries());
System.out.println("Entries: " + fst.getFileCount()); System.out.println("Entries: " + fst.getFileCount());
System.out.println("Files in NUSTitle: " + fst.getFileCountInNUS()); System.out.println("Files in NUSTitle: " + fst.getFileCountInNUS());
} catch (IOException e) { } catch (IOException e)
// TODO Auto-generated catch block {
e.printStackTrace(); e.printStackTrace();
} }
} }
public FST getFst()
{
public FST getFst() {
return fst; return fst;
} }
public void setFst(FST fst) { public TIK getTicket()
this.fst = fst; {
}
public TitleMetaData getTmd() {
return tmd;
}
public void setTmd(TitleMetaData tmd) {
this.tmd = tmd;
}
public TIK getTicket() {
return ticket; return ticket;
} }
public long getTotalContentSize()
{
public void setTicket(TIK ticket) {
this.ticket = ticket;
}
public long getTotalContentSize() {
return tmd.getTotalContentSize(); return tmd.getTotalContentSize();
} }
public String getContentPath()
{
public String getContentPath() {
return getContentPathPrefix() + String.format("%016X", getTitleID()); return getContentPathPrefix() + String.format("%016X", getTitleID());
} }
public String getContentPathPrefix() { public String getContentPathPrefix()
{
return "tmp_"; return "tmp_";
} }
private long getTitleID()
{
private long getTitleID() {
return titleID; return titleID;
} }
private void setTitleID(long titleId) { private void setTitleID(long titleId)
{
this.titleID = titleId; this.titleID = titleId;
} }
} }

View File

@ -1,54 +0,0 @@
package de.mas.jnustool;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import de.mas.jnustool.gui.NUSGUI;
import de.mas.jnustool.util.Downloader;
import de.mas.jnustool.util.ExitException;
import de.mas.jnustool.util.Util;
public class Starter {
public static void main(String[] args) {
System.out.println("JNUSTool 0.0.2 - pre alpha - by Maschell");
System.out.println();
try {
readConfig();
} catch (IOException e) {
System.err.println("Error while reading config! Needs to be:");
System.err.println("DOWNLOAD URL BASE");
System.err.println("COMMONKEY");
return;
}
if(args.length != 0){
long titleID = Util.StringToLong(args[0]);
String key = null;
if( args.length > 1 && args[1].length() == 32){
key = args[1].substring(0, 32);
}
NUSGUI m;
try {
m = new NUSGUI(new NUSTitle(titleID, key), null);
} catch (ExitException e) {
System.out.println("Error: " + e.getMessage());
return;
}
m.setVisible(true);
}else{
System.out.println("Need parameters: TITLEID [KEY]");
}
}
public static void readConfig() throws IOException {
BufferedReader in = new BufferedReader(new FileReader(new File("config")));
Downloader.URL_BASE = in.readLine();
Util.commonKey = Util.hexStringToByteArray(in.readLine());
in.close();
}
}

View File

@ -5,67 +5,63 @@ import java.io.IOException;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import de.mas.jnustool.util.Decryption; import de.mas.jnustool.util.Decryption;
import de.mas.jnustool.util.Util; import de.mas.jnustool.util.ConversionUtils;
public class TIK { public class TIK
public static int KEY_LENGTH = 16; {
private byte[] encryptedKey = new byte[16]; private byte[] encryptedKey = new byte[16];
private byte[] decryptedKey = new byte[16]; private byte[] decryptedKey = new byte[16];
public TIK(File cetk,long titleid) throws IOException{ public TIK(File commonETicket, long titleID) throws IOException
parse(cetk); {
calculateDecryptedKey(titleid); parse(commonETicket);
} calculateDecryptedKey(titleID);
}
public TIK(String ticketKey,long titleid) { public TIK(String ticketKey, long titleID)
setEncryptedKey(ticketKey); {
calculateDecryptedKey(titleid); setEncryptedKey(ticketKey);
} calculateDecryptedKey(titleID);
}
public TIK(byte[] file, long titleID) throws IOException
{
parse(file);
calculateDecryptedKey(titleID);
}
public TIK(byte[] file, long titleID) throws IOException { private void calculateDecryptedKey(long titleID)
parse(file); {
calculateDecryptedKey(titleID); Decryption decryption = new Decryption(ConversionUtils.commonKey, titleID);
} decryptedKey = decryption.decrypt(encryptedKey);
}
private void calculateDecryptedKey(long titleid) { private void parse(byte[] commonETicketBytes) throws IOException
Decryption decryption = new Decryption(Util.commonKey,titleid); {
decryptedKey = decryption.decrypt(encryptedKey); System.arraycopy(commonETicketBytes, 0x1bf, this.encryptedKey, 0, 16);
} }
private void parse(byte[] cetk) throws IOException { private void parse(File commonETicket) throws IOException
System.arraycopy(cetk, 0x1bf, this.encryptedKey, 0,16); {
} RandomAccessFile f = new RandomAccessFile(commonETicket, "r");
f.seek(0x1bf);
f.read(this.encryptedKey, 0, 16);
f.close();
}
private void parse(File cetk) throws IOException { public void setEncryptedKey(String key)
RandomAccessFile f = new RandomAccessFile(cetk, "r"); {
f.seek(0x1bf); this.encryptedKey = ConversionUtils.hexStringToByteArray(key);
f.read(this.encryptedKey, 0, 16); }
f.close();
}
public void setEncryptedKey(String key) { public byte[] getDecryptedKey()
this.encryptedKey = Util.hexStringToByteArray(key); {
} return decryptedKey;
}
public byte[] getEncryptedKey() { @Override
return encryptedKey; public String toString()
} {
return "encrypted key: " + ConversionUtils.ByteArrayToString(encryptedKey) + " decrypted key: " + ConversionUtils.ByteArrayToString(decryptedKey);
public void setEncryptedKey(byte[] encryptedKey) { }
this.encryptedKey = encryptedKey;
}
public byte[] getDecryptedKey() {
return decryptedKey;
}
public void setDecryptedKey(byte[] decryptedKey) {
this.decryptedKey = decryptedKey;
}
@Override
public String toString(){
return "encrypted key: " + Util.ByteArrayToString(encryptedKey)+ " decrypted key: " + Util.ByteArrayToString(decryptedKey);
}
} }

View File

@ -3,65 +3,65 @@ package de.mas.jnustool;
import java.io.File; import java.io.File;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.file.Files; import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.DigestInputStream;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import de.mas.jnustool.util.Downloader; import de.mas.jnustool.util.Downloader;
import de.mas.jnustool.util.ExitException; import de.mas.jnustool.util.ExitException;
import de.mas.jnustool.util.Settings; import de.mas.jnustool.util.Settings;
import de.mas.jnustool.util.Util; import de.mas.jnustool.util.ConversionUtils;
public class TitleMetaData { public class TitleMetaData
int signatureType; // 0x000 {
byte[] signature = new byte[0x100]; // 0x004 int signatureType; // 0x000
byte[] issuer = new byte[0x40]; // 0x140 byte[] signature = new byte[0x100]; // 0x004
byte version; // 0x180 byte[] issuer = new byte[0x40]; // 0x140
byte CACRLVersion; // 0x181 byte version; // 0x180
byte signerCRLVersion; // 0x182 byte CACRLVersion; // 0x181
long systemVersion; // 0x184 byte signerCRLVersion; // 0x182
long titleID; // 0x18C long systemVersion; // 0x184
int titleType; // 0x194 long titleID; // 0x18C
short groupID; // 0x198 int titleType; // 0x194
byte[] reserved = new byte[62]; // 0x19A short groupID; // 0x198
int accessRights; // 0x1D8 byte[] reserved = new byte[62]; // 0x19A
short titleVersion; // 0x1DC int accessRights; // 0x1D8
short contentCount; // 0x1DE short titleVersion; // 0x1DC
short bootIndex; // 0x1E0 short contentCount; // 0x1DE
byte[] SHA2 = new byte[32]; // 0x1E4 short bootIndex; // 0x1E0
ContentInfo[] contentInfos = new ContentInfo[64]; // 0x1E4 byte[] SHA2 = new byte[32]; // 0x1E4
Content[] contents; // 0x1E4 ContentInfo[] contentInfoArray = new ContentInfo[64]; // 0x1E4
Content[] contents; // 0x1E4
private NUSTitle nus; private NUSTitle nus;
private long totalContentSize; private long totalContentSize;
public TitleMetaData(File tmd) throws IOException { public TitleMetaData(File tmd) throws IOException
{
parse(tmd); parse(tmd);
setTotalContentSize(); setTotalContentSize();
} }
public TitleMetaData(byte[] downloadTMDToByteArray) throws IOException { public TitleMetaData(byte[] downloadTMDToByteArray) throws IOException
if(downloadTMDToByteArray != null){ {
File tempFile = File.createTempFile("bla","blubb"); if (downloadTMDToByteArray != null)
{
File tempFile = File.createTempFile("bla", "blubb");
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");
} }
} }
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");
f.seek(0); f.seek(0);
this.signatureType = f.readInt(); this.signatureType = f.readInt();
@ -93,119 +93,144 @@ public class TitleMetaData {
short indexOffset; short indexOffset;
short commandCount; short commandCount;
for(int i =0;i<64;i++){ for (int i = 0; i < 64; i++)
f.seek(0x204+(0x24*i)); {
indexOffset =f.readShort(); f.seek(0x204 + (0x24 * i));
commandCount =f.readShort(); indexOffset = f.readShort();
byte[] buffer = new byte[0x20]; // 16 0xB14 commandCount = f.readShort();
byte[] buffer = new byte[0x20]; // 16 0xB14
f.read(buffer, 0, 0x20); f.read(buffer, 0, 0x20);
this.contentInfos[i] = new ContentInfo(indexOffset,commandCount,buffer); this.contentInfoArray[i] = new ContentInfo(indexOffset, commandCount, buffer);
} }
this.contents = new Content[contentCount]; this.contents = new Content[contentCount];
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
for(int i =0;i<contentCount;i++){ for (int i = 0; i < contentCount; i++)
f.seek(0xB04+(0x30*i)); {
f.seek(0xB04 + (0x30 * i));
ID = f.readInt(); ID = f.readInt();
index = f.readShort(); index = f.readShort();
type = f.readShort(); type = f.readShort();
size = f.readLong(); size = f.readLong();
byte[] buffer = new byte[0x20]; // 16 0xB14 byte[] buffer = new byte[0x20]; // 16 0xB14
f.read(buffer,0, 0x20); f.read(buffer, 0, 0x20);
this.contents[i] = new Content(ID,index,type,size,buffer); this.contents[i] = new Content(ID, index, type, size, buffer);
} }
f.close(); f.close();
} }
@Override @Override
public String toString(){ public String toString()
{
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("signatureType: " + signatureType +"\n"); sb.append("signatureType: ").append(signatureType).append("\n");
sb.append("signature: " + Util.ByteArrayToString(signature) +"\n"); sb.append("signature: ").append(ConversionUtils.ByteArrayToString(signature)).append("\n");
sb.append("issuer: " + Util.ByteArrayToString(issuer) +"\n"); sb.append("issuer: ").append(ConversionUtils.ByteArrayToString(issuer)).append("\n");
sb.append("version: " + version +"\n"); sb.append("version: ").append(version).append("\n");
sb.append("CACRLVersion: " + CACRLVersion +"\n"); sb.append("CACRLVersion: ").append(CACRLVersion).append("\n");
sb.append("signerCRLVersion: " + signerCRLVersion +"\n"); sb.append("signerCRLVersion: ").append(signerCRLVersion).append("\n");
sb.append("systemVersion: " + String.format("%8X",systemVersion) +"\n"); sb.append("systemVersion: ").append(String.format("%8X", systemVersion)).append("\n");
sb.append("titleID: " + String.format("%8X",titleID) +"\n"); sb.append("titleID: ").append(String.format("%8X", titleID)).append("\n");
sb.append("titleType: " + titleType +"\n"); sb.append("titleType: ").append(titleType).append("\n");
sb.append("groupID: " + groupID +"\n"); sb.append("groupID: ").append(groupID).append("\n");
sb.append("reserved: " + Util.ByteArrayToString(reserved) +"\n"); sb.append("reserved: ").append(ConversionUtils.ByteArrayToString(reserved)).append("\n");
sb.append("accessRights: " + accessRights +"\n"); sb.append("accessRights: ").append(accessRights).append("\n");
sb.append("titleVersion: " + titleVersion +"\n"); sb.append("titleVersion: ").append(titleVersion).append("\n");
sb.append("contentCount: " + contentCount +"\n"); sb.append("contentCount: ").append(contentCount).append("\n");
sb.append("bootIndex: " + bootIndex +"\n"); sb.append("bootIndex: ").append(bootIndex).append("\n");
sb.append("SHA2: " + Util.ByteArrayToString(SHA2) +"\n"); sb.append("SHA2: ").append(ConversionUtils.ByteArrayToString(SHA2)).append("\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(" ").append(contentInfoArray[i]).append("\n");
} }
sb.append("contents: \n"); sb.append("contents: \n");
for(int i = 0; i<contents.length-1;i++){ for (int i = 0; i < contents.length - 1; i++)
sb.append(" " + contents[i] +"\n"); {
sb.append(" ").append(contents[i]).append("\n");
} }
return sb.toString(); return sb.toString();
} }
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++)
{
this.totalContentSize += contents[i].size; this.totalContentSize += contents[i].size;
} }
} }
public long getTotalContentSize() { public long getTotalContentSize()
{
return totalContentSize; return totalContentSize;
} }
public void downloadContents() throws IOException, ExitException{ public void downloadContents() throws IOException, ExitException
{
String tmpPath = getContentPath(); String tmpPath = getContentPath();
File f = new File(tmpPath); File f = new File(tmpPath);
if(!f.exists())f.mkdir(); if (!f.exists())
{
Files.createDirectory(f.toPath());
}
for(Content c : contents){ for (Content c : contents)
if(c != contents[0]){ {
f = new File(tmpPath + "/" + String.format("%08X", c.ID ) + ".app"); if (c != contents[0])
if(f.exists()){ {
if(f.length() == c.size){ f = new File(tmpPath + "/" + String.format("%08X", c.ID) + ".app");
if (f.exists())
{
if (f.length() == c.size)
{
System.out.println("Skipping Content: " + String.format("%08X", c.ID)); System.out.println("Skipping Content: " + String.format("%08X", c.ID));
}else{ } else
if(Settings.downloadWhenCachedFilesMissingOrBroken){ {
System.out.println("Content " +String.format("%08X", c.ID) + " is broken. Downloading it again."); if (Settings.downloadWhenCachedFilesMissingOrBroken)
Downloader.getInstance().downloadContent(titleID,c.ID,tmpPath); {
}else{ System.out.println("Content " + String.format("%08X", c.ID) + " is broken. Downloading it again.");
if(Settings.skipBrokenFiles){ Downloader.getInstance().downloadContent(titleID, c.ID, tmpPath);
System.out.println("Content " +String.format("%08X", c.ID) + " is broken. Ignoring it."); } else
}else{ {
System.out.println("Content " +String.format("%08X", c.ID) + " is broken. Downloading not allowed."); if (Settings.skipBrokenFiles)
{
System.out.println("Content " + String.format("%08X", c.ID) + " is broken. Ignoring it.");
} else
{
System.out.println("Content " + String.format("%08X", c.ID) + " is broken. Downloading not allowed.");
throw new ExitException("Content missing."); throw new ExitException("Content missing.");
} }
} }
} }
}else{ } else
{
System.out.println("Download Content: " + String.format("%08X", c.ID)); System.out.println("Download Content: " + String.format("%08X", c.ID));
Downloader.getInstance().downloadContent(titleID,c.ID,tmpPath); Downloader.getInstance().downloadContent(titleID, c.ID, tmpPath);
} }
} }
} }
} }
public String getContentPath() { public String getContentPath()
{
return nus.getContentPath(); return nus.getContentPath();
} }
public NUSTitle getNUSTitle() { public NUSTitle getNUSTitle()
{
return nus; return nus;
} }
public void setNUSTitle(NUSTitle nus) { public void setNUSTitle(NUSTitle nus)
{
this.nus = nus; this.nus = nus;
} }
} }

View File

@ -1,8 +1,4 @@
package de.mas.jnustool.gui; package de.mas.jnustool.gui;
/**
* Based on
* http://stackoverflow.com/questions/21847411/java-swing-need-a-good-quality-developed-jtree-with-checkboxes/21851201#21851201
*/
import java.awt.BorderLayout; import java.awt.BorderLayout;
import java.awt.Component; import java.awt.Component;
@ -28,240 +24,279 @@ import javax.swing.tree.TreePath;
import de.mas.jnustool.FEntry; import de.mas.jnustool.FEntry;
import de.mas.jnustool.NUSTitle; import de.mas.jnustool.NUSTitle;
public class JCheckBoxTree extends JTree { /**
* Based on
* http://stackoverflow.com/questions/21847411/java-swing-need-a-good-quality-developed-jtree-with-checkboxes/21851201#21851201
*/
public class JCheckBoxTree extends JTree
{
private static final long serialVersionUID = -4194122328392241790L;
private static final long serialVersionUID = -4194122328392241790L; JCheckBoxTree selfPointer = this;
JCheckBoxTree selfPointer = this; // Defining data structure that will enable to fast check-indicate the state of each node
// It totally replaces the "selection" mechanism of the JTree
private class CheckedNode
{
boolean isSelected;
boolean hasChildren;
boolean allChildrenSelected;
public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_)
{
isSelected = isSelected_;
hasChildren = hasChildren_;
allChildrenSelected = allChildrenSelected_;
}
}
HashMap<TreePath, CheckedNode> nodesCheckingState;
HashSet<TreePath> checkedPaths = new HashSet<TreePath>();
// Defining a new event type for the checking mechanism and preparing event-handling mechanism
protected EventListenerList listenerList = new EventListenerList();
public class CheckChangeEvent extends EventObject
{
private static final long serialVersionUID = -8100230309044193368L;
public CheckChangeEvent(Object source)
{
super(source);
}
}
public interface CheckChangeEventListener extends EventListener
{
void checkStateChanged(CheckChangeEvent event);
}
void fireCheckChangeEvent(CheckChangeEvent evt)
{
Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i++)
{
if (listeners[i] == CheckChangeEventListener.class)
{
((CheckChangeEventListener) listeners[i + 1]).checkStateChanged(evt);
}
}
}
// Override
public void setModel(TreeModel newModel)
{
super.setModel(newModel);
resetCheckingState();
}
// New method that returns only the checked paths (totally ignores original "selection" mechanism)
public TreePath[] getCheckedPaths()
{
return checkedPaths.toArray(new TreePath[checkedPaths.size()]);
}
private void resetCheckingState()
{
nodesCheckingState = new HashMap<>();
checkedPaths = new HashSet<>();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) getModel().getRoot();
if (node == null)
{
return;
}
addSubtreeToCheckingStateTracking(node);
}
// Creating data structure of the current model for the checking mechanism
private void addSubtreeToCheckingStateTracking(DefaultMutableTreeNode node)
{
TreeNode[] path = node.getPath();
TreePath tp = new TreePath(path);
CheckedNode cn = new CheckedNode(false, node.getChildCount() > 0, false);
nodesCheckingState.put(tp, cn);
for (int i = 0; i < node.getChildCount(); i++)
{
addSubtreeToCheckingStateTracking((DefaultMutableTreeNode) tp.pathByAddingChild(node.getChildAt(i)).getLastPathComponent());
}
}
// Overriding cell renderer by a class that ignores the original "selection" mechanism
// It decides how to show the nodes due to the checking-mechanism
private class CheckBoxCellRenderer extends JPanel implements TreeCellRenderer
{
private static final long serialVersionUID = -7341833835878991719L;
JCheckBox checkBox;
public CheckBoxCellRenderer()
{
super();
this.setLayout(new BorderLayout());
checkBox = new JCheckBox();
add(checkBox, BorderLayout.CENTER);
setOpaque(false);
}
@Override
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object obj = node.getUserObject();
TreePath tp = new TreePath(node.getPath());
CheckedNode cn = nodesCheckingState.get(tp);
if (cn == null)
{
return this;
}
checkBox.setSelected(cn.isSelected);
if (obj instanceof FEntry)
{
FEntry f = (FEntry) obj;
checkBox.setText(f.getFileName());
} else
{
checkBox.setText(obj.toString());
}
checkBox.setOpaque(cn.isSelected && cn.hasChildren && !cn.allChildrenSelected);
return this;
}
}
public JCheckBoxTree(NUSTitle nus)
{
super();
setModel(new DefaultTreeModel(nus.getFst().getFSTDirectory().getNodes()));
// Defining data structure that will enable to fast check-indicate the state of each node // Disabling toggling by double-click
// It totally replaces the "selection" mechanism of the JTree this.setToggleClickCount(0);
private class CheckedNode { // Overriding cell renderer by new one defined above
boolean isSelected; CheckBoxCellRenderer cellRenderer = new CheckBoxCellRenderer();
boolean hasChildren; this.setCellRenderer(cellRenderer);
boolean allChildrenSelected;
public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_) { // Overriding selection model by an empty one
isSelected = isSelected_; DefaultTreeSelectionModel defaultTreeSelectionModel = new DefaultTreeSelectionModel()
hasChildren = hasChildren_; {
allChildrenSelected = allChildrenSelected_; private static final long serialVersionUID = -8190634240451667286L;
}
}
HashMap<TreePath, CheckedNode> nodesCheckingState;
HashSet<TreePath> checkedPaths = new HashSet<TreePath>();
// Defining a new event type for the checking mechanism and preparing event-handling mechanism // Totally disabling the selection mechanism
protected EventListenerList listenerList = new EventListenerList(); public void setSelectionPath(TreePath path)
{
}
public class CheckChangeEvent extends EventObject { public void addSelectionPath(TreePath path)
private static final long serialVersionUID = -8100230309044193368L; {
}
public CheckChangeEvent(Object source) { public void removeSelectionPath(TreePath path)
super(source); {
} }
}
public interface CheckChangeEventListener extends EventListener { public void setSelectionPaths(TreePath[] pPaths)
public void checkStateChanged(CheckChangeEvent event); {
} }
};
public void addCheckChangeEventListener(CheckChangeEventListener listener) { // Calling checking mechanism on mouse click
listenerList.add(CheckChangeEventListener.class, listener); this.addMouseListener(new MouseListener()
} {
public void removeCheckChangeEventListener(CheckChangeEventListener listener) { public void mouseClicked(MouseEvent arg0)
listenerList.remove(CheckChangeEventListener.class, listener); {
} TreePath tp = selfPointer.getPathForLocation(arg0.getX(), arg0.getY());
if (tp == null)
{
return;
}
boolean checkMode = !nodesCheckingState.get(tp).isSelected;
checkSubTree(tp, checkMode);
updatePredecessorsWithCheckMode(tp, checkMode);
// Firing the check change event
fireCheckChangeEvent(new CheckChangeEvent(new Object()));
// Repainting tree after the data structures were updated
selfPointer.repaint();
}
void fireCheckChangeEvent(CheckChangeEvent evt) { public void mouseEntered(MouseEvent arg0)
Object[] listeners = listenerList.getListenerList(); {
for (int i = 0; i < listeners.length; i++) { }
if (listeners[i] == CheckChangeEventListener.class) {
((CheckChangeEventListener) listeners[i + 1]).checkStateChanged(evt);
}
}
}
// Override public void mouseExited(MouseEvent arg0)
public void setModel(TreeModel newModel) { {
super.setModel(newModel); }
resetCheckingState();
}
// New method that returns only the checked paths (totally ignores original "selection" mechanism) public void mousePressed(MouseEvent arg0)
public TreePath[] getCheckedPaths() { {
return checkedPaths.toArray(new TreePath[checkedPaths.size()]); }
}
// Returns true in case that the node is selected, has children but not all of them are selected public void mouseReleased(MouseEvent arg0)
public boolean isSelectedPartially(TreePath path) { {
CheckedNode cn = nodesCheckingState.get(path); }
return cn.isSelected && cn.hasChildren && !cn.allChildrenSelected; });
}
private void resetCheckingState() { this.setSelectionModel(defaultTreeSelectionModel);
nodesCheckingState = new HashMap<TreePath, CheckedNode>(); }
checkedPaths = new HashSet<TreePath>();
DefaultMutableTreeNode node = (DefaultMutableTreeNode)getModel().getRoot();
if (node == null) {
return;
}
addSubtreeToCheckingStateTracking(node);
}
// Creating data structure of the current model for the checking mechanism // When a node is checked/unchecked, updating the states of the predecessors
private void addSubtreeToCheckingStateTracking(DefaultMutableTreeNode node) { protected void updatePredecessorsWithCheckMode(TreePath tp, boolean check)
TreeNode[] path = node.getPath(); {
TreePath tp = new TreePath(path); TreePath parentPath = tp.getParentPath();
CheckedNode cn = new CheckedNode(false, node.getChildCount() > 0, false); // If it is the root, stop the recursive calls and return
nodesCheckingState.put(tp, cn); if (parentPath == null)
for (int i = 0 ; i < node.getChildCount() ; i++) { {
addSubtreeToCheckingStateTracking((DefaultMutableTreeNode) tp.pathByAddingChild(node.getChildAt(i)).getLastPathComponent()); return;
} }
} CheckedNode parentCheckedNode = nodesCheckingState.get(parentPath);
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parentPath.getLastPathComponent();
// Overriding cell renderer by a class that ignores the original "selection" mechanism parentCheckedNode.allChildrenSelected = true;
// It decides how to show the nodes due to the checking-mechanism parentCheckedNode.isSelected = false;
private class CheckBoxCellRenderer extends JPanel implements TreeCellRenderer { for (int i = 0; i < parentNode.getChildCount(); i++)
private static final long serialVersionUID = -7341833835878991719L; {
JCheckBox checkBox; TreePath childPath = parentPath.pathByAddingChild(parentNode.getChildAt(i));
public CheckBoxCellRenderer() { CheckedNode childCheckedNode = nodesCheckingState.get(childPath);
super(); // It is enough that even one subtree is not fully selected
this.setLayout(new BorderLayout()); // to determine that the parent is not fully selected
checkBox = new JCheckBox(); if (!childCheckedNode.allChildrenSelected)
add(checkBox, BorderLayout.CENTER); {
setOpaque(false); parentCheckedNode.allChildrenSelected = false;
} }
// If at least one child is selected, selecting also the parent
@Override if (childCheckedNode.isSelected)
public Component getTreeCellRendererComponent(JTree tree, Object value, {
boolean selected, boolean expanded, boolean leaf, int row, parentCheckedNode.isSelected = true;
boolean hasFocus) { }
DefaultMutableTreeNode node = (DefaultMutableTreeNode)value; }
Object obj = node.getUserObject(); if (parentCheckedNode.isSelected)
TreePath tp = new TreePath(node.getPath()); {
CheckedNode cn = nodesCheckingState.get(tp); checkedPaths.add(parentPath);
if (cn == null) { } else
return this; {
} checkedPaths.remove(parentPath);
checkBox.setSelected(cn.isSelected); }
if(obj instanceof FEntry){ // Go to upper predecessor
FEntry f = (FEntry) obj; updatePredecessorsWithCheckMode(parentPath, check);
checkBox.setText(f.getFileName()); }
}else{
checkBox.setText(obj.toString());
}
checkBox.setOpaque(cn.isSelected && cn.hasChildren && ! cn.allChildrenSelected);
return this;
}
}
public JCheckBoxTree(NUSTitle nus) {
super();
setModel(new DefaultTreeModel(nus.getFst().getFSTDirectory().getNodes()));
// Disabling toggling by double-click
this.setToggleClickCount(0);
// Overriding cell renderer by new one defined above
CheckBoxCellRenderer cellRenderer = new CheckBoxCellRenderer();
this.setCellRenderer(cellRenderer);
// Overriding selection model by an empty one
DefaultTreeSelectionModel dtsm = new DefaultTreeSelectionModel() {
private static final long serialVersionUID = -8190634240451667286L;
// Totally disabling the selection mechanism
public void setSelectionPath(TreePath path) {
}
public void addSelectionPath(TreePath path) {
}
public void removeSelectionPath(TreePath path) {
}
public void setSelectionPaths(TreePath[] pPaths) {
}
};
// Calling checking mechanism on mouse click
this.addMouseListener(new MouseListener() {
public void mouseClicked(MouseEvent arg0) {
TreePath tp = selfPointer.getPathForLocation(arg0.getX(), arg0.getY());
if (tp == null) {
return;
}
boolean checkMode = ! nodesCheckingState.get(tp).isSelected;
checkSubTree(tp, checkMode);
updatePredecessorsWithCheckMode(tp, checkMode);
// Firing the check change event
fireCheckChangeEvent(new CheckChangeEvent(new Object()));
// Repainting tree after the data structures were updated
selfPointer.repaint();
}
public void mouseEntered(MouseEvent arg0) {
}
public void mouseExited(MouseEvent arg0) {
}
public void mousePressed(MouseEvent arg0) {
}
public void mouseReleased(MouseEvent arg0) {
}
});
this.setSelectionModel(dtsm);
}
// When a node is checked/unchecked, updating the states of the predecessors
protected void updatePredecessorsWithCheckMode(TreePath tp, boolean check) {
TreePath parentPath = tp.getParentPath();
// If it is the root, stop the recursive calls and return
if (parentPath == null) {
return;
}
CheckedNode parentCheckedNode = nodesCheckingState.get(parentPath);
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parentPath.getLastPathComponent();
parentCheckedNode.allChildrenSelected = true;
parentCheckedNode.isSelected = false;
for (int i = 0 ; i < parentNode.getChildCount() ; i++) {
TreePath childPath = parentPath.pathByAddingChild(parentNode.getChildAt(i));
CheckedNode childCheckedNode = nodesCheckingState.get(childPath);
// It is enough that even one subtree is not fully selected
// to determine that the parent is not fully selected
if (! childCheckedNode.allChildrenSelected) {
parentCheckedNode.allChildrenSelected = false;
}
// If at least one child is selected, selecting also the parent
if (childCheckedNode.isSelected) {
parentCheckedNode.isSelected = true;
}
}
if (parentCheckedNode.isSelected) {
checkedPaths.add(parentPath);
} else {
checkedPaths.remove(parentPath);
}
// Go to upper predecessor
updatePredecessorsWithCheckMode(parentPath, check);
}
// Recursively checks/unchecks a subtree
protected void checkSubTree(TreePath tp, boolean check) {
CheckedNode cn = nodesCheckingState.get(tp);
cn.isSelected = check;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
for (int i = 0 ; i < node.getChildCount() ; i++) {
checkSubTree(tp.pathByAddingChild(node.getChildAt(i)), check);
}
cn.allChildrenSelected = check;
if (check) {
checkedPaths.add(tp);
} else {
checkedPaths.remove(tp);
}
}
// Recursively checks/un-checks a subtree
protected void checkSubTree(TreePath tp, boolean check)
{
CheckedNode cn = nodesCheckingState.get(tp);
cn.isSelected = check;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
for (int i = 0; i < node.getChildCount(); i++)
{
checkSubTree(tp.pathByAddingChild(node.getChildAt(i)), check);
}
cn.allChildrenSelected = check;
if (check)
{
checkedPaths.add(tp);
} else
{
checkedPaths.remove(tp);
}
}
} }

View File

@ -1,80 +0,0 @@
package de.mas.jnustool.gui;
import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ForkJoinPool;
import javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreePath;
import de.mas.jnustool.FEntry;
import de.mas.jnustool.NUSTitle;
import de.mas.jnustool.util.Settings;
import de.mas.jnustool.FEntryDownloader;
public class NUSGUI extends JFrame {
private static final long serialVersionUID = 4648172894076113183L;
public NUSGUI(NUSTitle nus,Settings mode) {
super();
setSize(800, 600);
getContentPane().setLayout(new BorderLayout(0, 0));
final JCheckBoxTree cbt = new JCheckBoxTree(nus);
JScrollPane qPane = new JScrollPane(cbt,
JScrollPane.VERTICAL_SCROLLBAR_AS_NEEDED,
JScrollPane.HORIZONTAL_SCROLLBAR_NEVER);
this.getContentPane().add(qPane);
JButton btnNewButton = new JButton("Download");
btnNewButton.addActionListener(new ActionListener() {
public void actionPerformed(ActionEvent e) {
new Thread(new Runnable() { public void run() {
ForkJoinPool pool = ForkJoinPool.commonPool();
List<FEntryDownloader> list = new ArrayList<>();
TreePath[] paths = cbt.getCheckedPaths();
for (TreePath tp : paths) {
Object obj = tp.getPath()[tp.getPath().length-1];
if(((DefaultMutableTreeNode)obj).getUserObject() instanceof FEntry){
FEntry f = (FEntry) ((DefaultMutableTreeNode)obj).getUserObject();
if(!f.isDir() && f.isInNUSTitle()){
list.add(new FEntryDownloader(f));
}
}
}
pool.invokeAll(list);
System.out.println("Done!");
}}).start();
}
});
getContentPane().add(btnNewButton, BorderLayout.SOUTH);
/*cbt.addCheckChangeEventListener(new JCheckBoxTree.CheckChangeEventListener() {
public void checkStateChanged(JCheckBoxTree.CheckChangeEvent event) {
System.out.println("event");
TreePath[] paths = cbt.getCheckedPaths();
for (TreePath tp : paths) {
for (Object pathPart : tp.getPath()) {
System.out.print(pathPart + ",");
}
System.out.println();
}
}
});*/
this.setDefaultCloseOperation(EXIT_ON_CLOSE);
}
}

View File

@ -1,15 +1,7 @@
package de.mas.jnustool.util; package de.mas.jnustool.util;
import java.io.File; import de.mas.jnustool.FEntry;
import java.io.FileInputStream; import de.mas.jnustool.TIK;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import javax.crypto.BadPaddingException; import javax.crypto.BadPaddingException;
import javax.crypto.Cipher; import javax.crypto.Cipher;
@ -17,354 +9,399 @@ import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException; import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec; import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.ByteBuffer;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
import de.mas.jnustool.FEntry; public class Decryption
import de.mas.jnustool.TIK; {
private Cipher cipher2;
private byte[] decryptedKey;
public class Decryption { public Decryption(TIK ticket)
Cipher cipher2; {
public Decryption(TIK ticket){
this(ticket.getDecryptedKey()); this(ticket.getDecryptedKey());
} }
public Decryption(byte[] decryptedKey){ public Decryption(byte[] decryptedKey)
this(decryptedKey,0); {
this(decryptedKey, 0);
} }
public Decryption(byte[] decryptedKey, long titleId) { public Decryption(byte[] decryptedKey, long titleId)
try { {
try
{
cipher2 = Cipher.getInstance("AES/CBC/NoPadding"); cipher2 = Cipher.getInstance("AES/CBC/NoPadding");
this.decryptedKey = decryptedKey; this.decryptedKey = decryptedKey;
init(titleId); init(titleId);
} catch (NoSuchAlgorithmException | NoSuchPaddingException e)
} catch (NoSuchAlgorithmException e) { {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }
private void init(byte[] IV)
byte[] decryptedKey; {
init(decryptedKey, IV);
private void init(byte[] IV) {
init(decryptedKey,IV);
} }
private void init(long titleid) { private void init(long titleID)
init(ByteBuffer.allocate(16).putLong(titleid).array()); {
init(ByteBuffer.allocate(16).putLong(titleID).array());
} }
public void init(byte[] decryptedKey,long titleid){ public void init(byte[] decryptedKey, byte[] iv)
init(decryptedKey,ByteBuffer.allocate(16).putLong(titleid).array()); {
} try
{
public void init(byte[] decryptedKey,byte[] iv){
try {
this.decryptedKey = decryptedKey; this.decryptedKey = decryptedKey;
SecretKeySpec secretKeySpec = new SecretKeySpec(decryptedKey, "AES"); SecretKeySpec secretKeySpec = new SecretKeySpec(decryptedKey, "AES");
cipher2.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(iv)); cipher2.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(iv));
} catch (Exception e) { } catch (Exception e)
// TODO Auto-generated catch block {
e.printStackTrace(); e.printStackTrace();
} }
} }
public byte[] decrypt(byte[] input){ public byte[] decrypt(byte[] input)
try { {
try
{
return cipher2.doFinal(input); return cipher2.doFinal(input);
} catch (IllegalBlockSizeException | BadPaddingException e) { } catch (IllegalBlockSizeException | BadPaddingException e)
// TODO Auto-generated catch block {
e.printStackTrace(); e.printStackTrace();
} }
return input; return input;
} }
public byte[] decrypt(byte[] input,int len){ public byte[] decrypt(byte[] input, int offset, int len)
return decrypt(input,0,len); {
} try
{
public byte[] decrypt(byte[] input,int offset,int len){
try {
return cipher2.doFinal(input, offset, len); return cipher2.doFinal(input, offset, len);
} catch (IllegalBlockSizeException | BadPaddingException e) { } catch (IllegalBlockSizeException | BadPaddingException e)
// TODO Auto-generated catch block {
e.printStackTrace(); e.printStackTrace();
} }
return input; return input;
} }
byte[] IV; byte[] IV;
public byte[] decryptFileChunk(byte[] blockBuffer, int BLOCKSIZE, byte[] IV) {
return decryptFileChunk(blockBuffer,0,BLOCKSIZE, IV); public byte[] decryptFileChunk(byte[] blockBuffer, int blockSize, byte[] IV)
{
return decryptFileChunk(blockBuffer, 0, blockSize, IV);
} }
public byte[] decryptFileChunk(byte[] blockBuffer, int offset, int BLOCKSIZE, byte[] IV) { public byte[] decryptFileChunk(byte[] blockBuffer, int offset, int blockSize, byte[] IV)
if(IV != null) this.IV = IV; {
if (IV != null)
{
this.IV = IV;
}
init(this.IV); init(this.IV);
byte[] output = decrypt(blockBuffer,offset,BLOCKSIZE); byte[] output = decrypt(blockBuffer, offset, blockSize);
this.IV = Arrays.copyOfRange(blockBuffer,BLOCKSIZE-16, BLOCKSIZE); this.IV = Arrays.copyOfRange(blockBuffer, blockSize - 16, blockSize);
return output; return output;
} }
byte[] hash = new byte[20]; byte[] hash = new byte[20];
byte[] h0 = new byte[20]; byte[] h0 = new byte[20];
public byte[] decryptFileChunkHash(byte[] blockBuffer, int BLOCKSIZE, int block, int contentID){ public byte[] decryptFileChunkHash(byte[] blockBuffer, int blockSize, int block, int contentID)
if(BLOCKSIZE != 0x10000) throw new IllegalArgumentException("Blocksize not supported"); {
if (blockSize != 0x10000)
{
throw new IllegalArgumentException("Block size not supported");
}
IV = new byte[16]; IV = new byte[16];
IV[1] = (byte)contentID; IV[1] = (byte) contentID;
byte[] hashes = decryptFileChunk(blockBuffer,0x0400,IV); byte[] hashes = decryptFileChunk(blockBuffer, 0x0400, IV);
System.arraycopy(hashes, (int) (0x14*block), IV, 0, 16); System.arraycopy(hashes, 0x14 * block, IV, 0, 16);
System.arraycopy(hashes, (int) (0x14*block), h0, 0, 20); System.arraycopy(hashes, 0x14 * block, h0, 0, 20);
if( block == 0 ) if (block == 0)
IV[1] ^= (byte)contentID; {
IV[1] ^= (byte) contentID;
}
byte[] output = decryptFileChunk(blockBuffer,0x400,0xFC00,IV); byte[] output = decryptFileChunk(blockBuffer, 0x400, 0xFC00, IV);
hash = hash(output); hash = hash(output);
if(block == 0){ if (block == 0)
{
assert hash != null;
hash[1] ^= contentID; hash[1] ^= contentID;
} }
if(Arrays.equals(hash, h0)){ if (Arrays.equals(hash, h0))
{
//System.out.println("checksum right"); //System.out.println("checksum right");
} } else
else{ {
System.out.println("checksum failed"); System.out.println("checksum failed");
System.out.println(Util.ByteArrayToString(hash)); System.out.println(ConversionUtils.ByteArrayToString(hash));
System.out.println(Util.ByteArrayToString(h0)); System.out.println(ConversionUtils.ByteArrayToString(h0));
throw new IllegalArgumentException("checksumfail"); throw new IllegalArgumentException("checksumfail");
} }
return output; return output;
} }
public static byte[] hash(byte[] hashThis) { public static byte[] hash(byte[] hashThis)
try { {
byte[] hash = new byte[20]; try
MessageDigest md = MessageDigest.getInstance("SHA-1"); {
byte[] hash;
MessageDigest md = MessageDigest.getInstance("SHA-1");
hash = md.digest(hashThis); hash = md.digest(hashThis);
return hash; return hash;
} catch (NoSuchAlgorithmException nsae) { } catch (NoSuchAlgorithmException noSuchAlgorithm)
System.err.println("SHA-1 algorithm is not available..."); {
System.exit(2); System.err.println("SHA-1 algorithm is not available...");
} System.exit(2);
return null; }
} return null;
public void decryptFile(InputStream inputSteam, OutputStream outputStream,FEntry toDownload) throws IOException{
int BLOCKSIZE = 0x8000;
long dlFileLength = toDownload.getFileLength();
if(dlFileLength > (dlFileLength/BLOCKSIZE)*BLOCKSIZE){
dlFileLength = ((dlFileLength/BLOCKSIZE)*BLOCKSIZE) +BLOCKSIZE;
}
int bytesRead = -1;
byte[] IV = new byte[16];
IV[1] = (byte)toDownload.getContentID();
byte[] downloadBuffer;
byte[] blockBuffer = new byte[BLOCKSIZE];
byte[] overflowBuffer = new byte[BLOCKSIZE];
int overflowsize = 0;
int inBlockBuffer = 0;
byte[] tmp = new byte[BLOCKSIZE];
boolean endd = false;
long downloadTotalsize = 0;
long wrote = 0;
boolean first = true;
do{
downloadBuffer = new byte[BLOCKSIZE-overflowsize];
bytesRead = inputSteam.read(downloadBuffer);
downloadTotalsize += bytesRead;
if(bytesRead ==-1){
endd = true;
}
if(!endd)System.arraycopy(downloadBuffer, 0, overflowBuffer, overflowsize,bytesRead);
bytesRead += overflowsize;
overflowsize = 0;
int oldInThisBlock = inBlockBuffer;
if(oldInThisBlock + bytesRead > BLOCKSIZE){
int tooMuch = (oldInThisBlock + bytesRead) - BLOCKSIZE;
int toRead = BLOCKSIZE - oldInThisBlock;
System.arraycopy(overflowBuffer, 0, blockBuffer, oldInThisBlock, toRead);
inBlockBuffer += toRead;
overflowsize = tooMuch;
System.arraycopy(overflowBuffer, toRead, tmp, 0, tooMuch);
System.arraycopy(tmp, 0, overflowBuffer, 0, tooMuch);
}else{
if(!endd)System.arraycopy(overflowBuffer, 0, blockBuffer, inBlockBuffer, bytesRead);
inBlockBuffer +=bytesRead;
}
if(inBlockBuffer == BLOCKSIZE || endd){
if(first){
first = false;
}else{
IV = null;
}
byte[] output = decryptFileChunk(blockBuffer,BLOCKSIZE,IV);
if((wrote + inBlockBuffer) > toDownload.getFileLength()){
inBlockBuffer = (int) (toDownload.getFileLength()- wrote);
}
wrote += inBlockBuffer;
outputStream.write(output, 0, inBlockBuffer);
inBlockBuffer = 0;
}
}while(downloadTotalsize < dlFileLength && !endd);
outputStream.close();
inputSteam.close();
} }
public void decryptFileHash(InputStream inputSteam, OutputStream outputStream,FEntry toDownload) throws IOException{ public void decryptFile(InputStream inputSteam, OutputStream outputStream, FEntry toDownload) throws IOException
int BLOCKSIZE = 0x10000; {
int HASHBLOCKSIZE = 0xFC00; int blockSize = 0x8000;
long writeSize = HASHBLOCKSIZE; // Hash block size long dlFileLength = toDownload.getFileLength();
if (dlFileLength > (dlFileLength / blockSize) * blockSize)
{
dlFileLength = ((dlFileLength / blockSize) * blockSize) + blockSize;
}
long block = (toDownload.getFileOffset() / HASHBLOCKSIZE) & 0xF; int bytesRead;
long soffset = toDownload.getFileOffset() - (toDownload.getFileOffset() / HASHBLOCKSIZE * HASHBLOCKSIZE); byte[] IV = new byte[16];
IV[1] = (byte) toDownload.getContentID();
long size = toDownload.getFileLength(); byte[] downloadBuffer;
if( soffset+size > writeSize ) byte[] blockBuffer = new byte[blockSize];
writeSize = writeSize - soffset; byte[] overflowBuffer = new byte[blockSize];
int overflowSize = 0;
int bytesRead = -1; int inBlockBuffer = 0;
byte[] downloadBuffer; byte[] tmp = new byte[blockSize];
boolean end = false;
long totalDownloadSize = 0;
long wrote = 0;
byte[] encryptedBlockBuffer = new byte[BLOCKSIZE]; boolean first = true;
byte[] buffer = new byte[BLOCKSIZE]; do
{
downloadBuffer = new byte[blockSize - overflowSize];
bytesRead = inputSteam.read(downloadBuffer);
totalDownloadSize += bytesRead;
if (bytesRead == -1)
{
end = true;
}
int encryptedBytesInBuffer = 0; if (!end)
int bufferPostion = 0; {
System.arraycopy(downloadBuffer, 0, overflowBuffer, overflowSize, bytesRead);
}
bytesRead += overflowSize;
byte[] tmp = new byte[BLOCKSIZE]; overflowSize = 0;
boolean lastPart = false; int oldInThisBlock = inBlockBuffer;
long wrote = 0;
do{ if (oldInThisBlock + bytesRead > blockSize)
downloadBuffer = new byte[BLOCKSIZE-bufferPostion]; {
bytesRead = inputSteam.read(downloadBuffer); int tooMuch = (oldInThisBlock + bytesRead) - blockSize;
int bytesInBuffer = bytesRead + bufferPostion; int toRead = blockSize - oldInThisBlock;
if(bytesRead ==-1){
lastPart = true;
}else{
System.arraycopy(downloadBuffer, 0, buffer, bufferPostion,bytesRead); //copy downloaded stuff in buffer
bufferPostion = 0;
}
if(encryptedBytesInBuffer + bytesInBuffer > BLOCKSIZE){ System.arraycopy(overflowBuffer, 0, blockBuffer, oldInThisBlock, toRead);
int tooMuch = (encryptedBytesInBuffer + bytesInBuffer) - BLOCKSIZE; inBlockBuffer += toRead;
int toRead = BLOCKSIZE - encryptedBytesInBuffer;
overflowSize = tooMuch;
System.arraycopy(overflowBuffer, toRead, tmp, 0, tooMuch);
System.arraycopy(tmp, 0, overflowBuffer, 0, tooMuch);
} else
{
if (!end)
{
System.arraycopy(overflowBuffer, 0, blockBuffer, inBlockBuffer, bytesRead);
}
inBlockBuffer += bytesRead;
}
if (inBlockBuffer == blockSize || end)
{
if (first)
{
first = false;
} else
{
IV = null;
}
byte[] output = decryptFileChunk(blockBuffer, blockSize, IV);
if ((wrote + inBlockBuffer) > toDownload.getFileLength())
{
inBlockBuffer = (int) (toDownload.getFileLength() - wrote);
}
wrote += inBlockBuffer;
outputStream.write(output, 0, inBlockBuffer);
inBlockBuffer = 0;
}
} while (totalDownloadSize < dlFileLength && !end);
outputStream.close();
inputSteam.close();
}
public void decryptFileHash(InputStream inputSteam, OutputStream outputStream, FEntry toDownload) throws IOException
{
int blockSize = 0x10000;
int hashBlockSize = 0xFC00;
long writeSize = hashBlockSize; // Hash block size
long block = (toDownload.getFileOffset() / hashBlockSize) & 0xF;
long sOffset = toDownload.getFileOffset() - (toDownload.getFileOffset() / hashBlockSize * hashBlockSize);
long size = toDownload.getFileLength();
if (sOffset + size > writeSize)
{
writeSize = writeSize - sOffset;
}
int bytesRead;
byte[] downloadBuffer;
byte[] encryptedBlockBuffer = new byte[blockSize];
byte[] buffer = new byte[blockSize];
int encryptedBytesInBuffer = 0;
int bufferPosition = 0;
byte[] tmp = new byte[blockSize];
boolean lastPart = false;
long wrote = 0;
do
{
downloadBuffer = new byte[blockSize - bufferPosition];
bytesRead = inputSteam.read(downloadBuffer);
int bytesInBuffer = bytesRead + bufferPosition;
if (bytesRead == -1)
{
lastPart = true;
} else
{
System.arraycopy(downloadBuffer, 0, buffer, bufferPosition, bytesRead); //copy downloaded stuff in buffer
bufferPosition = 0;
}
if (encryptedBytesInBuffer + bytesInBuffer > blockSize)
{
int tooMuch = (encryptedBytesInBuffer + bytesInBuffer) - blockSize;
int toRead = blockSize - encryptedBytesInBuffer;
System.arraycopy(buffer, 0, encryptedBlockBuffer, encryptedBytesInBuffer, toRead); // make buffer with encrypteddata full System.arraycopy(buffer, 0, encryptedBlockBuffer, encryptedBytesInBuffer, toRead); // make buffer with encrypteddata full
encryptedBytesInBuffer += toRead; encryptedBytesInBuffer += toRead;
bufferPostion = tooMuch; //set buffer position; bufferPosition = tooMuch; //set buffer position;
System.arraycopy(buffer, toRead, tmp, 0, tooMuch); System.arraycopy(buffer, toRead, tmp, 0, tooMuch);
System.arraycopy(tmp, 0, buffer, 0, tooMuch); System.arraycopy(tmp, 0, buffer, 0, tooMuch);
}else{ } else
if(!lastPart) System.arraycopy(buffer, 0, encryptedBlockBuffer, encryptedBytesInBuffer, bytesInBuffer); //When File if at the end, no more need to copy {
encryptedBytesInBuffer +=bytesInBuffer; if (!lastPart)
{
System.arraycopy(buffer, 0, encryptedBlockBuffer, encryptedBytesInBuffer, bytesInBuffer); //When File if at the end, no more need to copy
}
encryptedBytesInBuffer += bytesInBuffer;
} }
//If downloaded BLOCKSIZE, or file at the end: Decrypt! //If downloaded block size, or file at the end: Decrypt!
if(encryptedBytesInBuffer == BLOCKSIZE || lastPart){ if (encryptedBytesInBuffer == blockSize || lastPart)
{
if( writeSize > size ) if (writeSize > size)
{
writeSize = size; writeSize = size;
}
byte[] output = decryptFileChunkHash(encryptedBlockBuffer, BLOCKSIZE, (int) block,toDownload.getContentID()); byte[] output = decryptFileChunkHash(encryptedBlockBuffer, blockSize, (int) block, toDownload.getContentID());
if((wrote + writeSize) > toDownload.getFileLength()){ if ((wrote + writeSize) > toDownload.getFileLength())
writeSize = (int) (toDownload.getFileLength()- wrote); {
} writeSize = (int) (toDownload.getFileLength() - wrote);
}
outputStream.write(output, (int)(0+soffset), (int)writeSize); outputStream.write(output, (int) (sOffset), (int) writeSize);
wrote +=writeSize; wrote += writeSize;
encryptedBytesInBuffer = 0; encryptedBytesInBuffer = 0;
block++; block++;
if( block >= 16 ) if (block >= 16)
block = 0;
if( soffset > 0)
{ {
writeSize = HASHBLOCKSIZE; block = 0;
soffset = 0; }
if (sOffset > 0)
{
writeSize = hashBlockSize;
sOffset = 0;
} }
} }
}while(wrote < toDownload.getFileLength() || lastPart); } while (wrote < toDownload.getFileLength() || lastPart);
outputStream.close(); outputStream.close();
inputSteam.close(); inputSteam.close();
} }
public void decrypt(FEntry fileEntry,String outputPath) throws IOException { public void decrypt(FEntry fileEntry, String outputPath) throws IOException
String [] path = fileEntry.getFullPath().split("/"); {
String[] path = fileEntry.getFullPath().split("/");
boolean decryptWithHash = false; boolean decryptWithHash = false;
if(!path[1].equals("code") && fileEntry.isExtractWithHash()){ if (!path[1].equals("code") && fileEntry.isExtractWithHash())
{
decryptWithHash = true; decryptWithHash = true;
} }
long fileOffset = fileEntry.getFileOffset(); long fileOffset = fileEntry.getFileOffset();
if(decryptWithHash){ if (decryptWithHash)
int BLOCKSIZE = 0x10000; {
int HASHBLOCKSIZE = 0xFC00; int blockSize = 0x10000;
fileOffset = ((fileEntry.getFileOffset() / HASHBLOCKSIZE) * BLOCKSIZE); int hashBlockSize = 0xFC00;
fileOffset = ((fileEntry.getFileOffset() / hashBlockSize) * blockSize);
} }
InputStream input = new FileInputStream(fileEntry.getContentPath());
FileOutputStream outputStream = new FileOutputStream(outputPath + "/" + fileEntry.getFileName());
InputStream input = new FileInputStream(fileEntry.getContentPath()); long actualBytesSkipped = 0;
FileOutputStream outputStream = new FileOutputStream(outputPath + "/" + fileEntry.getFileName()); long bytesToSkip = fileOffset;
input.skip(fileOffset); while (actualBytesSkipped != bytesToSkip)
{
if(!decryptWithHash){ actualBytesSkipped += input.skip(bytesToSkip - actualBytesSkipped);
decryptFile(input, outputStream, fileEntry); }
}else{
decryptFileHash(input, outputStream, fileEntry);
}
if (!decryptWithHash)
{
decryptFile(input, outputStream, fileEntry);
} else
{
decryptFileHash(input, outputStream, fileEntry);
}
} }
} }

View File

@ -1,156 +1,169 @@
package de.mas.jnustool.util; package de.mas.jnustool.util;
import de.mas.jnustool.FEntry;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import de.mas.jnustool.FEntry; public class Downloader
{
public class Downloader {
private static Downloader instance; private static Downloader instance;
public static Downloader getInstance(){ public static Downloader getInstance()
if(instance == null){ {
if (instance == null)
{
instance = new Downloader(); instance = new Downloader();
} }
return instance; return instance;
} }
private Downloader(){
private Downloader()
{
} }
public void downloadAndDecrypt(FEntry toDownload) throws IOException
public void downloadAndDecrypt(FEntry toDownload) throws IOException{ {
String URL = URL_BASE + "/" + String.format("%016X", toDownload.getTitleID()) + "/" + String.format("%08X", toDownload.getNUScontentID()); String URL = URL_BASE + "/" + String.format("%016X", toDownload.getTitleID()) + "/" + String.format("%08X", toDownload.getNUSContentID());
URL url = new URL(URL); URL url = new URL(URL);
String [] path = toDownload.getFullPath().split("/"); String[] path = toDownload.getFullPath().split("/");
boolean decryptWithHash = false; boolean decryptWithHash = false;
if(!path[1].equals("code") && toDownload.isExtractWithHash()){ if (!path[1].equals("code") && toDownload.isExtractWithHash())
{
decryptWithHash = true; decryptWithHash = true;
} }
HttpURLConnection connection =(HttpURLConnection) url.openConnection(); HttpURLConnection connection = (HttpURLConnection) url.openConnection();
long fileOffset = toDownload.getFileOffset(); long fileOffset = toDownload.getFileOffset();
if(decryptWithHash){ if (decryptWithHash)
int BLOCKSIZE = 0x10000; {
int HASHBLOCKSIZE = 0xFC00; int blockSize = 0x10000;
fileOffset = ((toDownload.getFileOffset() / HASHBLOCKSIZE) * BLOCKSIZE); int hashBlockSize = 0xFC00;
fileOffset = ((toDownload.getFileOffset() / hashBlockSize) * blockSize);
} }
connection.setRequestProperty("Range", "bytes=" + fileOffset +"-"); connection.setRequestProperty("Range", "bytes=" + fileOffset + "-");
connection.connect(); connection.connect();
Decryption decryption = new Decryption(toDownload.getTicket()); Decryption decryption = new Decryption(toDownload.getTicket());
InputStream input = connection.getInputStream(); InputStream input = connection.getInputStream();
FileOutputStream outputStream = new FileOutputStream(String.format("%016X", toDownload.getTitleID()) +"/" + toDownload.getFullPath().substring(1, toDownload.getFullPath().length())); FileOutputStream outputStream = new FileOutputStream(String.format("%016X", toDownload.getTitleID()) + "/" + toDownload.getFullPath().substring(1, toDownload.getFullPath().length()));
if(!decryptWithHash){ if (!decryptWithHash)
decryption.decryptFile(input, outputStream, toDownload); {
}else{ decryption.decryptFile(input, outputStream, toDownload);
decryption.decryptFileHash(input, outputStream, toDownload); } else
} {
decryption.decryptFileHash(input, outputStream, toDownload);
}
connection.disconnect(); connection.disconnect();
} }
public static String URL_BASE = ""; public static String URL_BASE = "";
public void downloadTMD(long titleID,int version,String path) throws IOException { public void downloadTMD(long titleID, String path) throws IOException
downloadTMD(titleID,path); {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd";
downloadFile(URL, "tmd", path);
} }
public void downloadTMD(long titleID,String path) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd"; public void downloadFile(String fileURL, String filename, String tmpPath) throws IOException
downloadFile(URL, "tmd",path); {
}
public void downloadFile(String fileURL,String filename,String tmpPath) throws IOException{
int BUFFER_SIZE = 0x800; int BUFFER_SIZE = 0x800;
URL url = new URL(fileURL); URL url = new URL(fileURL);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
InputStream inputStream = httpConn.getInputStream(); InputStream inputStream = httpConn.getInputStream();
if(tmpPath != null){ if (tmpPath != null)
filename = tmpPath + "/" + filename; {
} filename = tmpPath + "/" + filename;
}
FileOutputStream outputStream = new FileOutputStream(filename); FileOutputStream outputStream = new FileOutputStream(filename);
int bytesRead = -1; int bytesRead;
byte[] buffer = new byte[BUFFER_SIZE]; byte[] buffer = new byte[BUFFER_SIZE];
while ((bytesRead = inputStream.read(buffer)) != -1) { while ((bytesRead = inputStream.read(buffer)) != -1)
outputStream.write(buffer, 0, bytesRead); {
} outputStream.write(buffer, 0, bytesRead);
}
outputStream.close(); outputStream.close();
inputStream.close(); inputStream.close();
httpConn.disconnect(); httpConn.disconnect();
} }
public void downloadFile(String fileURL,String filename) throws IOException{ public void downloadTicket(long titleID, String path) throws IOException
downloadFile(fileURL, filename,null); {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk";
downloadFile(URL, "cetk", path);
} }
public void downloadTicket(long titleID,String path) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk"; public byte[] downloadContentToByteArray(long titleID, int contentID) throws IOException
downloadFile(URL, "cetk",path); {
} String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
public void downloadContent(long titleID,int contentID) throws IOException {
downloadContent(titleID,contentID, null);
}
public byte[] downloadContentToByteArray(long titleID,int contentID) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
return downloadFileToByteArray(URL); return downloadFileToByteArray(URL);
} }
public byte[] downloadTMDToByteArray(long titleID) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd"; public byte[] downloadTMDToByteArray(long titleID) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd";
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;
URL url = new URL(fileURL); URL url = new URL(fileURL);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
int responseCode = httpConn.getResponseCode(); int responseCode = httpConn.getResponseCode();
// always check HTTP response code first // always check HTTP response code first
byte[] file = null; byte[] file = null;
if (responseCode == HttpURLConnection.HTTP_OK) { if (responseCode == HttpURLConnection.HTTP_OK)
int contentLength = httpConn.getContentLength(); {
int contentLength = httpConn.getContentLength();
file = new byte[contentLength]; file = new byte[contentLength];
// always check HTTP response code first // always check HTTP response code first
InputStream inputStream = httpConn.getInputStream(); InputStream inputStream = httpConn.getInputStream();
int bytesRead = -1;
byte[] buffer = new byte[BUFFER_SIZE];
int filePostion = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
System.arraycopy(buffer, 0, file, filePostion,bytesRead);
filePostion+=bytesRead;
}
inputStream.close();
}else{
System.err.println("File not found: " + fileURL);
}
httpConn.disconnect();
return file;
int bytesRead;
byte[] buffer = new byte[BUFFER_SIZE];
int filePosition = 0;
while ((bytesRead = inputStream.read(buffer)) != -1)
{
System.arraycopy(buffer, 0, file, filePosition, bytesRead);
filePosition += bytesRead;
}
inputStream.close();
} else
{
System.err.println("File not found: " + fileURL);
}
httpConn.disconnect();
return file;
} }
public byte[] downloadTicketToByteArray(long titleID) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk"; public byte[] downloadTicketToByteArray(long titleID) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk";
return downloadFileToByteArray(URL); return downloadFileToByteArray(URL);
} }
public void downloadContent(long titleID,int contentID, String tmpPath) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
downloadFile(URL, String.format("%08X", contentID) +".app",tmpPath);
public void downloadContent(long titleID, int contentID, String tmpPath) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
downloadFile(URL, String.format("%08X", contentID) + ".app", tmpPath);
} }
} }

View File

@ -1,14 +1,9 @@
package de.mas.jnustool.util; package de.mas.jnustool.util;
public class ExitException extends Exception { public class ExitException extends Exception
{
public ExitException(String string) { public ExitException(String string)
{
super(string); super(string);
} }
/**
*
*/
private static final long serialVersionUID = 1L;
} }

View File

@ -1,12 +1,11 @@
package de.mas.jnustool.util; package de.mas.jnustool.util;
public class Settings
public class Settings { {
public static boolean downloadContent = false; public static boolean downloadContent = false;
public static boolean useCachedFiles = false; public static boolean useCachedFiles = false;
public static boolean downloadWhenCachedFilesMissingOrBroken = true; public static boolean downloadWhenCachedFilesMissingOrBroken = true;
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;
} }

View File

@ -1,62 +0,0 @@
package de.mas.jnustool.util;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.util.Arrays;
public class Util {
public static byte[] commonKey;
public static byte[] hexStringToByteArray(String s) {
int len = s.length();
byte[] data = new byte[len / 2];
for (int i = 0; i < len; i += 2) {
data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4)
+ Character.digit(s.charAt(i+1), 16));
}
return data;
}
public static String ByteArrayToString(byte[] ba)
{
StringBuilder hex = new StringBuilder(ba.length * 2);
for(byte b : ba){
hex.append(String.format("%X", b));
}
return hex.toString();
}
public static int getIntFromBytes(byte[] input,int offset){
return ByteBuffer.wrap(Arrays.copyOfRange(input,offset, offset+4)).getInt();
}
public static long getIntAsLongFromBytes(byte[] input,int offset){
long result = 0 ;
if((int)input[offset]+128 > 0 && (int)input[offset]+128 < 128){
input[offset] += 128;
result = (long)ByteBuffer.wrap(Arrays.copyOfRange(input,offset, offset+4)).getInt();
result += 1024L*1024L*2048L;
return result;
}
return (long)ByteBuffer.wrap(Arrays.copyOfRange(input,offset, offset+4)).getInt();
}
public static short getShortFromBytes(byte[] input, int offset) {
return ByteBuffer.wrap(Arrays.copyOfRange(input,offset, offset+2)).getShort();
}
public static long StringToLong(String s) {
try{
BigInteger bi = new BigInteger(s, 16);
return bi.longValue();
}catch(NumberFormatException e){
System.err.println("Invalid Title ID");
return 0L;
}
}
}