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 int ID; // 0 0xB04
short index; // 4 0xB08 short index; // 4 0xB08
short type; // 6 0xB0A short type; // 6 0xB0A
long size; // 8 0xB0C long size; // 8 0xB0C
byte[] SHA2 = new byte[32]; // 16 0xB14 byte[] SHA2 = new byte[32]; // 16 0xB14
public Content(int ID, short index, short type, long size, byte[] SHA2)
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 indexOffset; // 0 0x204
public short commandCount; // 2 0x206 public short commandCount; // 2 0x206
public byte[] SHA2 = new byte[32]; // 12 0x208 public byte[] SHA2 = new byte[32]; // 12 0x208
// TODO: Test, size checking // TODO: Test, size checking
/* public ContentInfo(byte[] info)
* untested {
*/
public ContentInfo(byte[] info){
this.indexOffset = (short) (((info[0] & 0xFF) << 8) | (info[1] & 0xFF)); this.indexOffset = (short) (((info[0] & 0xFF) << 8) | (info[1] & 0xFF));
this.commandCount = (short) (((info[2] & 0xFF) << 8) | (info[3] & 0xFF)); this.commandCount = (short) (((info[2] & 0xFF) << 8) | (info[3] & 0xFF));
for(int i = 0;i<32;i++){ for (int i = 0; i < 32; i++)
{
this.SHA2[i] = info[4 + 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); {
}
public FEntry addFile(FEntry s){
return files.put(s.getFileName(), s); return files.put(s.getFileName(), s);
} }
public String getName() { 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("")){ {
if (!path[i].equals(""))
{
folder += path[i] + "/"; folder += path[i] + "/";
folder_ = new File(folder); folder_ = new File(folder);
if(!folder_.exists()){ if (!folder_.exists())
folder_.mkdir(); {
try
{
Files.createDirectory(folder_.toPath());
} catch (IOException e)
{
e.printStackTrace();
}
} }
} }
} }
} }
public String getDownloadPath(){
public String getDownloadPath()
{
String[] path = getFullPath().split("/"); String[] path = getFullPath().split("/");
String folder = String.format("%016X", getTitleID()) + "/"; String folder = String.format("%016X", getTitleID()) + "/";
for(int i = 0;i<path.length-1;i++){ for (int i = 0; i < path.length - 1; i++)
if(!path[i].equals("")){ {
if (!path[i].equals(""))
{
folder += path[i] + "/"; folder += path[i] + "/";
} }
} }
return folder; 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()){ {
if (f.length() == getFileLength())
{
System.out.println("Skipping: " + String.format("%8.2f MB ", getFileLength() / 1024.0 / 1024.0) + getFullPath()); 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){ {
if (f.length() == fst.getTmd().contents[this.getContentID()].size)
{
System.out.println("Decrypting: " + String.format("%8.2f MB ", getFileLength() / 1024.0 / 1024.0) + getFullPath()); 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){ {
if (!Settings.downloadWhenCachedFilesMissingOrBroken)
{
System.out.println("Cached content has the wrong size! Please check your: " + getContentPath() + " Downloading not allowed"); System.out.println("Cached content has the wrong size! Please check your: " + getContentPath() + " 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());
} }
} }
}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()){
contentfolder = String.format("%08X",tmd.contents[f.getContentID()].ID);
if(!contentDirectory.containsFolder(contentfolder)){
Directory newDir = new Directory(contentfolder);
contentDirectory.addFolder(newDir); 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,7 +118,7 @@ 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)
@ -121,47 +129,59 @@ public class FST {
} }
} }
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;
}
if ((flags & FEntry.CHANGE_OFFSET_FLAG) == 0)
{
fileOffset <<= 5;
}
//grabbing content id //grabbing content id
offset += 2; offset += 2;
contentID = Util.getShortFromBytes(decrypteddata, offset) ; 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
@ -174,28 +194,31 @@ public class FST {
{ {
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!
@ -205,110 +228,65 @@ public class FST {
} }
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){ {
if (Settings.downloadContent)
{
File f = new File(getContentPath()); File f = new File(getContentPath());
if(!f.exists())f.mkdir(); 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)
{
if (Settings.useCachedFiles)
{
System.out.println("getting missing ticket");
}
ticket = new TIK(Downloader.getInstance().downloadTicketToByteArray(titleId), tmd.titleID); ticket = new TIK(Downloader.getInstance().downloadTicketToByteArray(titleId), tmd.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("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");
} }
@ -111,22 +152,31 @@ 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)
{
if (Settings.useCachedFiles)
{
System.out.println("Getting FST from server.");
}
encryptedFST = Downloader.getInstance().downloadContentToByteArray(titleId, tmd.contents[0].ID); encryptedFST = Downloader.getInstance().downloadContentToByteArray(titleId, tmd.contents[0].ID);
}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(""); throw new ExitException("");
} }
@ -136,7 +186,8 @@ public class NUSTitle {
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();
} }
@ -146,68 +197,44 @@ public class NUSTitle {
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); setEncryptedKey(ticketKey);
calculateDecryptedKey(titleid); calculateDecryptedKey(titleID);
} }
public TIK(byte[] file, long titleID) throws IOException
public TIK(byte[] file, long titleID) throws IOException { {
parse(file); parse(file);
calculateDecryptedKey(titleID); calculateDecryptedKey(titleID);
} }
private void calculateDecryptedKey(long titleid) { private void calculateDecryptedKey(long titleID)
Decryption decryption = new Decryption(Util.commonKey,titleid); {
Decryption decryption = new Decryption(ConversionUtils.commonKey, titleID);
decryptedKey = decryption.decrypt(encryptedKey); decryptedKey = decryption.decrypt(encryptedKey);
} }
private void parse(byte[] cetk) throws IOException { private void parse(byte[] commonETicketBytes) throws IOException
System.arraycopy(cetk, 0x1bf, this.encryptedKey, 0,16); {
System.arraycopy(commonETicketBytes, 0x1bf, this.encryptedKey, 0, 16);
} }
private void parse(File cetk) throws IOException { private void parse(File commonETicket) throws IOException
RandomAccessFile f = new RandomAccessFile(cetk, "r"); {
RandomAccessFile f = new RandomAccessFile(commonETicket, "r");
f.seek(0x1bf); f.seek(0x1bf);
f.read(this.encryptedKey, 0, 16); f.read(this.encryptedKey, 0, 16);
f.close(); f.close();
} }
public void setEncryptedKey(String key) { public void setEncryptedKey(String key)
this.encryptedKey = Util.hexStringToByteArray(key); {
this.encryptedKey = ConversionUtils.hexStringToByteArray(key);
} }
public byte[] getEncryptedKey() { public byte[] getDecryptedKey()
return encryptedKey; {
}
public void setEncryptedKey(byte[] encryptedKey) {
this.encryptedKey = encryptedKey;
}
public byte[] getDecryptedKey() {
return decryptedKey; return decryptedKey;
} }
public void setDecryptedKey(byte[] decryptedKey) {
this.decryptedKey = decryptedKey;
}
@Override @Override
public String toString(){ public String toString()
return "encrypted key: " + Util.ByteArrayToString(encryptedKey)+ " decrypted key: " + Util.ByteArrayToString(decryptedKey); {
return "encrypted key: " + ConversionUtils.ByteArrayToString(encryptedKey) + " decrypted key: " + ConversionUtils.ByteArrayToString(decryptedKey);
} }
} }

View File

@ -3,21 +3,16 @@ 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 int signatureType; // 0x000
byte[] signature = new byte[0x100]; // 0x004 byte[] signature = new byte[0x100]; // 0x004
byte[] issuer = new byte[0x40]; // 0x140 byte[] issuer = new byte[0x40]; // 0x140
@ -34,7 +29,7 @@ public class TitleMetaData {
short contentCount; // 0x1DE short contentCount; // 0x1DE
short bootIndex; // 0x1E0 short bootIndex; // 0x1E0
byte[] SHA2 = new byte[32]; // 0x1E4 byte[] SHA2 = new byte[32]; // 0x1E4
ContentInfo[] contentInfos = new ContentInfo[64]; // 0x1E4 ContentInfo[] contentInfoArray = new ContentInfo[64]; // 0x1E4
Content[] contents; // 0x1E4 Content[] contents; // 0x1E4
@ -42,26 +37,31 @@ public class TitleMetaData {
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){ {
if (downloadTMDToByteArray != null)
{
File tempFile = File.createTempFile("bla", "blubb"); 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,13 +93,14 @@ 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)); f.seek(0x204 + (0x24 * i));
indexOffset = f.readShort(); indexOffset = f.readShort();
commandCount = f.readShort(); commandCount = f.readShort();
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.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];
@ -109,7 +110,8 @@ public class TitleMetaData {
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();
@ -124,70 +126,91 @@ public class TitleMetaData {
} }
@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]){ {
if (c != contents[0])
{
f = new File(tmpPath + "/" + String.format("%08X", c.ID) + ".app"); f = new File(tmpPath + "/" + String.format("%08X", c.ID) + ".app");
if(f.exists()){ if (f.exists())
if(f.length() == c.size){ {
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){ {
if (Settings.downloadWhenCachedFilesMissingOrBroken)
{
System.out.println("Content " + String.format("%08X", c.ID) + " is broken. Downloading it again."); System.out.println("Content " + String.format("%08X", c.ID) + " is broken. Downloading it again.");
Downloader.getInstance().downloadContent(titleID, c.ID, tmpPath); Downloader.getInstance().downloadContent(titleID, c.ID, tmpPath);
}else{ } else
if(Settings.skipBrokenFiles){ {
if (Settings.skipBrokenFiles)
{
System.out.println("Content " + String.format("%08X", c.ID) + " is broken. Ignoring it."); 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."); 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);
} }
@ -196,16 +219,18 @@ public class TitleMetaData {
} }
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,105 +24,112 @@ 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 // Defining data structure that will enable to fast check-indicate the state of each node
// It totally replaces the "selection" mechanism of the JTree // It totally replaces the "selection" mechanism of the JTree
private class CheckedNode { private class CheckedNode
{
boolean isSelected; boolean isSelected;
boolean hasChildren; boolean hasChildren;
boolean allChildrenSelected; boolean allChildrenSelected;
public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_) { public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_)
{
isSelected = isSelected_; isSelected = isSelected_;
hasChildren = hasChildren_; hasChildren = hasChildren_;
allChildrenSelected = allChildrenSelected_; allChildrenSelected = allChildrenSelected_;
} }
} }
HashMap<TreePath, CheckedNode> nodesCheckingState; HashMap<TreePath, CheckedNode> nodesCheckingState;
HashSet<TreePath> checkedPaths = new HashSet<TreePath>(); HashSet<TreePath> checkedPaths = new HashSet<TreePath>();
// Defining a new event type for the checking mechanism and preparing event-handling mechanism // Defining a new event type for the checking mechanism and preparing event-handling mechanism
protected EventListenerList listenerList = new EventListenerList(); protected EventListenerList listenerList = new EventListenerList();
public class CheckChangeEvent extends EventObject { public class CheckChangeEvent extends EventObject
{
private static final long serialVersionUID = -8100230309044193368L; private static final long serialVersionUID = -8100230309044193368L;
public CheckChangeEvent(Object source) { public CheckChangeEvent(Object source)
{
super(source); super(source);
} }
} }
public interface CheckChangeEventListener extends EventListener { public interface CheckChangeEventListener extends EventListener
public void checkStateChanged(CheckChangeEvent event); {
void checkStateChanged(CheckChangeEvent event);
} }
public void addCheckChangeEventListener(CheckChangeEventListener listener) { void fireCheckChangeEvent(CheckChangeEvent evt)
listenerList.add(CheckChangeEventListener.class, listener); {
}
public void removeCheckChangeEventListener(CheckChangeEventListener listener) {
listenerList.remove(CheckChangeEventListener.class, listener);
}
void fireCheckChangeEvent(CheckChangeEvent evt) {
Object[] listeners = listenerList.getListenerList(); Object[] listeners = listenerList.getListenerList();
for (int i = 0; i < listeners.length; i++) { for (int i = 0; i < listeners.length; i++)
if (listeners[i] == CheckChangeEventListener.class) { {
if (listeners[i] == CheckChangeEventListener.class)
{
((CheckChangeEventListener) listeners[i + 1]).checkStateChanged(evt); ((CheckChangeEventListener) listeners[i + 1]).checkStateChanged(evt);
} }
} }
} }
// Override // Override
public void setModel(TreeModel newModel) { public void setModel(TreeModel newModel)
{
super.setModel(newModel); super.setModel(newModel);
resetCheckingState(); resetCheckingState();
} }
// New method that returns only the checked paths (totally ignores original "selection" mechanism) // New method that returns only the checked paths (totally ignores original "selection" mechanism)
public TreePath[] getCheckedPaths() { public TreePath[] getCheckedPaths()
{
return checkedPaths.toArray(new TreePath[checkedPaths.size()]); 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 private void resetCheckingState()
public boolean isSelectedPartially(TreePath path) { {
CheckedNode cn = nodesCheckingState.get(path); nodesCheckingState = new HashMap<>();
return cn.isSelected && cn.hasChildren && !cn.allChildrenSelected; checkedPaths = new HashSet<>();
}
private void resetCheckingState() {
nodesCheckingState = new HashMap<TreePath, CheckedNode>();
checkedPaths = new HashSet<TreePath>();
DefaultMutableTreeNode node = (DefaultMutableTreeNode) getModel().getRoot(); DefaultMutableTreeNode node = (DefaultMutableTreeNode) getModel().getRoot();
if (node == null) { if (node == null)
{
return; return;
} }
addSubtreeToCheckingStateTracking(node); addSubtreeToCheckingStateTracking(node);
} }
// Creating data structure of the current model for the checking mechanism // Creating data structure of the current model for the checking mechanism
private void addSubtreeToCheckingStateTracking(DefaultMutableTreeNode node) { private void addSubtreeToCheckingStateTracking(DefaultMutableTreeNode node)
{
TreeNode[] path = node.getPath(); TreeNode[] path = node.getPath();
TreePath tp = new TreePath(path); TreePath tp = new TreePath(path);
CheckedNode cn = new CheckedNode(false, node.getChildCount() > 0, false); CheckedNode cn = new CheckedNode(false, node.getChildCount() > 0, false);
nodesCheckingState.put(tp, cn); nodesCheckingState.put(tp, cn);
for (int i = 0 ; i < node.getChildCount() ; i++) { for (int i = 0; i < node.getChildCount(); i++)
{
addSubtreeToCheckingStateTracking((DefaultMutableTreeNode) tp.pathByAddingChild(node.getChildAt(i)).getLastPathComponent()); addSubtreeToCheckingStateTracking((DefaultMutableTreeNode) tp.pathByAddingChild(node.getChildAt(i)).getLastPathComponent());
} }
} }
// Overriding cell renderer by a class that ignores the original "selection" mechanism // Overriding cell renderer by a class that ignores the original "selection" mechanism
// It decides how to show the nodes due to the checking-mechanism // It decides how to show the nodes due to the checking-mechanism
private class CheckBoxCellRenderer extends JPanel implements TreeCellRenderer { private class CheckBoxCellRenderer extends JPanel implements TreeCellRenderer
{
private static final long serialVersionUID = -7341833835878991719L; private static final long serialVersionUID = -7341833835878991719L;
JCheckBox checkBox; JCheckBox checkBox;
public CheckBoxCellRenderer() {
public CheckBoxCellRenderer()
{
super(); super();
this.setLayout(new BorderLayout()); this.setLayout(new BorderLayout());
checkBox = new JCheckBox(); checkBox = new JCheckBox();
@ -137,19 +140,23 @@ public class JCheckBoxTree extends JTree {
@Override @Override
public Component getTreeCellRendererComponent(JTree tree, Object value, public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded, boolean leaf, int row, boolean selected, boolean expanded, boolean leaf, int row,
boolean hasFocus) { boolean hasFocus)
{
DefaultMutableTreeNode node = (DefaultMutableTreeNode) value; DefaultMutableTreeNode node = (DefaultMutableTreeNode) value;
Object obj = node.getUserObject(); Object obj = node.getUserObject();
TreePath tp = new TreePath(node.getPath()); TreePath tp = new TreePath(node.getPath());
CheckedNode cn = nodesCheckingState.get(tp); CheckedNode cn = nodesCheckingState.get(tp);
if (cn == null) { if (cn == null)
{
return this; return this;
} }
checkBox.setSelected(cn.isSelected); checkBox.setSelected(cn.isSelected);
if(obj instanceof FEntry){ if (obj instanceof FEntry)
{
FEntry f = (FEntry) obj; FEntry f = (FEntry) obj;
checkBox.setText(f.getFileName()); checkBox.setText(f.getFileName());
}else{ } else
{
checkBox.setText(obj.toString()); checkBox.setText(obj.toString());
} }
@ -158,11 +165,11 @@ public class JCheckBoxTree extends JTree {
} }
} }
public JCheckBoxTree(NUSTitle nus) { public JCheckBoxTree(NUSTitle nus)
{
super(); super();
setModel(new DefaultTreeModel(nus.getFst().getFSTDirectory().getNodes())); setModel(new DefaultTreeModel(nus.getFst().getFSTDirectory().getNodes()));
// Disabling toggling by double-click // Disabling toggling by double-click
@ -172,26 +179,36 @@ public class JCheckBoxTree extends JTree {
this.setCellRenderer(cellRenderer); this.setCellRenderer(cellRenderer);
// Overriding selection model by an empty one // Overriding selection model by an empty one
DefaultTreeSelectionModel dtsm = new DefaultTreeSelectionModel() { DefaultTreeSelectionModel defaultTreeSelectionModel = new DefaultTreeSelectionModel()
{
private static final long serialVersionUID = -8190634240451667286L; private static final long serialVersionUID = -8190634240451667286L;
// Totally disabling the selection mechanism // Totally disabling the selection mechanism
public void setSelectionPath(TreePath path) { public void setSelectionPath(TreePath path)
{
} }
public void addSelectionPath(TreePath path) {
public void addSelectionPath(TreePath path)
{
} }
public void removeSelectionPath(TreePath path) {
public void removeSelectionPath(TreePath path)
{
} }
public void setSelectionPaths(TreePath[] pPaths) {
public void setSelectionPaths(TreePath[] pPaths)
{
} }
}; };
// Calling checking mechanism on mouse click // Calling checking mechanism on mouse click
this.addMouseListener(new MouseListener() { this.addMouseListener(new MouseListener()
public void mouseClicked(MouseEvent arg0) { {
public void mouseClicked(MouseEvent arg0)
{
TreePath tp = selfPointer.getPathForLocation(arg0.getX(), arg0.getY()); TreePath tp = selfPointer.getPathForLocation(arg0.getX(), arg0.getY());
if (tp == null) { if (tp == null)
{
return; return;
} }
boolean checkMode = !nodesCheckingState.get(tp).isSelected; boolean checkMode = !nodesCheckingState.get(tp).isSelected;
@ -202,66 +219,84 @@ public class JCheckBoxTree extends JTree {
// Repainting tree after the data structures were updated // Repainting tree after the data structures were updated
selfPointer.repaint(); selfPointer.repaint();
} }
public void mouseEntered(MouseEvent arg0) {
public void mouseEntered(MouseEvent arg0)
{
} }
public void mouseExited(MouseEvent arg0) {
public void mouseExited(MouseEvent arg0)
{
} }
public void mousePressed(MouseEvent arg0) {
public void mousePressed(MouseEvent arg0)
{
} }
public void mouseReleased(MouseEvent arg0) {
public void mouseReleased(MouseEvent arg0)
{
} }
}); });
this.setSelectionModel(dtsm); this.setSelectionModel(defaultTreeSelectionModel);
} }
// When a node is checked/unchecked, updating the states of the predecessors // When a node is checked/unchecked, updating the states of the predecessors
protected void updatePredecessorsWithCheckMode(TreePath tp, boolean check) { protected void updatePredecessorsWithCheckMode(TreePath tp, boolean check)
{
TreePath parentPath = tp.getParentPath(); TreePath parentPath = tp.getParentPath();
// If it is the root, stop the recursive calls and return // If it is the root, stop the recursive calls and return
if (parentPath == null) { if (parentPath == null)
{
return; return;
} }
CheckedNode parentCheckedNode = nodesCheckingState.get(parentPath); CheckedNode parentCheckedNode = nodesCheckingState.get(parentPath);
DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parentPath.getLastPathComponent(); DefaultMutableTreeNode parentNode = (DefaultMutableTreeNode) parentPath.getLastPathComponent();
parentCheckedNode.allChildrenSelected = true; parentCheckedNode.allChildrenSelected = true;
parentCheckedNode.isSelected = false; parentCheckedNode.isSelected = false;
for (int i = 0 ; i < parentNode.getChildCount() ; i++) { for (int i = 0; i < parentNode.getChildCount(); i++)
{
TreePath childPath = parentPath.pathByAddingChild(parentNode.getChildAt(i)); TreePath childPath = parentPath.pathByAddingChild(parentNode.getChildAt(i));
CheckedNode childCheckedNode = nodesCheckingState.get(childPath); CheckedNode childCheckedNode = nodesCheckingState.get(childPath);
// It is enough that even one subtree is not fully selected // It is enough that even one subtree is not fully selected
// to determine that the parent is not fully selected // to determine that the parent is not fully selected
if (! childCheckedNode.allChildrenSelected) { if (!childCheckedNode.allChildrenSelected)
{
parentCheckedNode.allChildrenSelected = false; parentCheckedNode.allChildrenSelected = false;
} }
// If at least one child is selected, selecting also the parent // If at least one child is selected, selecting also the parent
if (childCheckedNode.isSelected) { if (childCheckedNode.isSelected)
{
parentCheckedNode.isSelected = true; parentCheckedNode.isSelected = true;
} }
} }
if (parentCheckedNode.isSelected) { if (parentCheckedNode.isSelected)
{
checkedPaths.add(parentPath); checkedPaths.add(parentPath);
} else { } else
{
checkedPaths.remove(parentPath); checkedPaths.remove(parentPath);
} }
// Go to upper predecessor // Go to upper predecessor
updatePredecessorsWithCheckMode(parentPath, check); updatePredecessorsWithCheckMode(parentPath, check);
} }
// Recursively checks/unchecks a subtree // Recursively checks/un-checks a subtree
protected void checkSubTree(TreePath tp, boolean check) { protected void checkSubTree(TreePath tp, boolean check)
{
CheckedNode cn = nodesCheckingState.get(tp); CheckedNode cn = nodesCheckingState.get(tp);
cn.isSelected = check; cn.isSelected = check;
DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent(); DefaultMutableTreeNode node = (DefaultMutableTreeNode) tp.getLastPathComponent();
for (int i = 0 ; i < node.getChildCount() ; i++) { for (int i = 0; i < node.getChildCount(); i++)
{
checkSubTree(tp.pathByAddingChild(node.getChildAt(i)), check); checkSubTree(tp.pathByAddingChild(node.getChildAt(i)), check);
} }
cn.allChildrenSelected = check; cn.allChildrenSelected = check;
if (check) { if (check)
{
checkedPaths.add(tp); checkedPaths.add(tp);
} else { } else
{
checkedPaths.remove(tp); 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,221 +9,248 @@ 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; {
private void init(byte[] IV) {
init(decryptedKey, 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
{
byte[] hash;
MessageDigest md = MessageDigest.getInstance("SHA-1"); 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.err.println("SHA-1 algorithm is not available...");
System.exit(2); System.exit(2);
} }
return null; return null;
} }
public void decryptFile(InputStream inputSteam, OutputStream outputStream, FEntry toDownload) throws IOException
public void decryptFile(InputStream inputSteam, OutputStream outputStream,FEntry toDownload) throws IOException{ {
int BLOCKSIZE = 0x8000; int blockSize = 0x8000;
long dlFileLength = toDownload.getFileLength(); long dlFileLength = toDownload.getFileLength();
if(dlFileLength > (dlFileLength/BLOCKSIZE)*BLOCKSIZE){ if (dlFileLength > (dlFileLength / blockSize) * blockSize)
dlFileLength = ((dlFileLength/BLOCKSIZE)*BLOCKSIZE) +BLOCKSIZE; {
dlFileLength = ((dlFileLength / blockSize) * blockSize) + blockSize;
} }
int bytesRead = -1; int bytesRead;
byte[] IV = new byte[16]; byte[] IV = new byte[16];
IV[1] = (byte) toDownload.getContentID(); IV[1] = (byte) toDownload.getContentID();
byte[] downloadBuffer; byte[] downloadBuffer;
byte[] blockBuffer = new byte[BLOCKSIZE]; byte[] blockBuffer = new byte[blockSize];
byte[] overflowBuffer = new byte[BLOCKSIZE]; byte[] overflowBuffer = new byte[blockSize];
int overflowsize = 0; int overflowSize = 0;
int inBlockBuffer = 0; int inBlockBuffer = 0;
byte[] tmp = new byte[BLOCKSIZE]; byte[] tmp = new byte[blockSize];
boolean endd = false; boolean end = false;
long downloadTotalsize = 0; long totalDownloadSize = 0;
long wrote = 0; long wrote = 0;
boolean first = true; boolean first = true;
do{ do
downloadBuffer = new byte[BLOCKSIZE-overflowsize]; {
downloadBuffer = new byte[blockSize - overflowSize];
bytesRead = inputSteam.read(downloadBuffer); bytesRead = inputSteam.read(downloadBuffer);
downloadTotalsize += bytesRead; totalDownloadSize += bytesRead;
if(bytesRead ==-1){ if (bytesRead == -1)
endd = true; {
end = true;
} }
if(!endd)System.arraycopy(downloadBuffer, 0, overflowBuffer, overflowsize,bytesRead); if (!end)
{
System.arraycopy(downloadBuffer, 0, overflowBuffer, overflowSize, bytesRead);
}
bytesRead += overflowsize; bytesRead += overflowSize;
overflowsize = 0; overflowSize = 0;
int oldInThisBlock = inBlockBuffer; int oldInThisBlock = inBlockBuffer;
if(oldInThisBlock + bytesRead > BLOCKSIZE){ if (oldInThisBlock + bytesRead > blockSize)
{
int tooMuch = (oldInThisBlock + bytesRead) - BLOCKSIZE; int tooMuch = (oldInThisBlock + bytesRead) - blockSize;
int toRead = BLOCKSIZE - oldInThisBlock; int toRead = blockSize - oldInThisBlock;
System.arraycopy(overflowBuffer, 0, blockBuffer, oldInThisBlock, toRead); System.arraycopy(overflowBuffer, 0, blockBuffer, oldInThisBlock, toRead);
inBlockBuffer += toRead; inBlockBuffer += toRead;
overflowsize = tooMuch; overflowSize = tooMuch;
System.arraycopy(overflowBuffer, toRead, tmp, 0, tooMuch); System.arraycopy(overflowBuffer, toRead, tmp, 0, tooMuch);
System.arraycopy(tmp, 0, overflowBuffer, 0, tooMuch); System.arraycopy(tmp, 0, overflowBuffer, 0, tooMuch);
} else
{
}else{ if (!end)
if(!endd)System.arraycopy(overflowBuffer, 0, blockBuffer, inBlockBuffer, bytesRead); {
System.arraycopy(overflowBuffer, 0, blockBuffer, inBlockBuffer, bytesRead);
}
inBlockBuffer += bytesRead; inBlockBuffer += bytesRead;
} }
if(inBlockBuffer == BLOCKSIZE || endd){ if (inBlockBuffer == blockSize || end)
if(first){ {
if (first)
{
first = false; first = false;
}else{ } else
{
IV = null; IV = null;
} }
byte[] output = decryptFileChunk(blockBuffer,BLOCKSIZE,IV); byte[] output = decryptFileChunk(blockBuffer, blockSize, IV);
if((wrote + inBlockBuffer) > toDownload.getFileLength()){ if ((wrote + inBlockBuffer) > toDownload.getFileLength())
{
inBlockBuffer = (int) (toDownload.getFileLength() - wrote); inBlockBuffer = (int) (toDownload.getFileLength() - wrote);
} }
@ -241,130 +260,148 @@ public class Decryption {
inBlockBuffer = 0; inBlockBuffer = 0;
} }
}while(downloadTotalsize < dlFileLength && !endd); } while (totalDownloadSize < dlFileLength && !end);
outputStream.close(); outputStream.close();
inputSteam.close(); inputSteam.close();
} }
public void decryptFileHash(InputStream inputSteam, OutputStream outputStream,FEntry toDownload) throws IOException{ public void decryptFileHash(InputStream inputSteam, OutputStream outputStream, FEntry toDownload) throws IOException
int BLOCKSIZE = 0x10000; {
int HASHBLOCKSIZE = 0xFC00; int blockSize = 0x10000;
long writeSize = HASHBLOCKSIZE; // Hash block size int hashBlockSize = 0xFC00;
long writeSize = hashBlockSize; // Hash block size
long block = (toDownload.getFileOffset() / HASHBLOCKSIZE) & 0xF; long block = (toDownload.getFileOffset() / hashBlockSize) & 0xF;
long soffset = toDownload.getFileOffset() - (toDownload.getFileOffset() / HASHBLOCKSIZE * HASHBLOCKSIZE); long sOffset = toDownload.getFileOffset() - (toDownload.getFileOffset() / hashBlockSize * hashBlockSize);
long size = toDownload.getFileLength(); long size = toDownload.getFileLength();
if( soffset+size > writeSize ) if (sOffset + size > writeSize)
writeSize = writeSize - soffset; {
writeSize = writeSize - sOffset;
}
int bytesRead = -1; int bytesRead;
byte[] downloadBuffer; byte[] downloadBuffer;
byte[] encryptedBlockBuffer = new byte[BLOCKSIZE]; byte[] encryptedBlockBuffer = new byte[blockSize];
byte[] buffer = new byte[BLOCKSIZE]; byte[] buffer = new byte[blockSize];
int encryptedBytesInBuffer = 0; int encryptedBytesInBuffer = 0;
int bufferPostion = 0; int bufferPosition = 0;
byte[] tmp = new byte[blockSize];
byte[] tmp = new byte[BLOCKSIZE];
boolean lastPart = false; boolean lastPart = false;
long wrote = 0; long wrote = 0;
do{ do
downloadBuffer = new byte[BLOCKSIZE-bufferPostion]; {
downloadBuffer = new byte[blockSize - bufferPosition];
bytesRead = inputSteam.read(downloadBuffer); bytesRead = inputSteam.read(downloadBuffer);
int bytesInBuffer = bytesRead + bufferPostion; int bytesInBuffer = bytesRead + bufferPosition;
if(bytesRead ==-1){ if (bytesRead == -1)
{
lastPart = true; lastPart = true;
}else{ } else
System.arraycopy(downloadBuffer, 0, buffer, bufferPostion,bytesRead); //copy downloaded stuff in buffer {
bufferPostion = 0; System.arraycopy(downloadBuffer, 0, buffer, bufferPosition, bytesRead); //copy downloaded stuff in buffer
bufferPosition = 0;
} }
if(encryptedBytesInBuffer + bytesInBuffer > BLOCKSIZE){ if (encryptedBytesInBuffer + bytesInBuffer > blockSize)
int tooMuch = (encryptedBytesInBuffer + bytesInBuffer) - BLOCKSIZE; {
int toRead = BLOCKSIZE - encryptedBytesInBuffer; 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 {
if (!lastPart)
{
System.arraycopy(buffer, 0, encryptedBlockBuffer, encryptedBytesInBuffer, bytesInBuffer); //When File if at the end, no more need to copy
}
encryptedBytesInBuffer += bytesInBuffer; 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()); InputStream input = new FileInputStream(fileEntry.getContentPath());
FileOutputStream outputStream = new FileOutputStream(outputPath + "/" + fileEntry.getFileName()); FileOutputStream outputStream = new FileOutputStream(outputPath + "/" + fileEntry.getFileName());
input.skip(fileOffset); long actualBytesSkipped = 0;
long bytesToSkip = fileOffset;
if(!decryptWithHash){ while (actualBytesSkipped != bytesToSkip)
{
actualBytesSkipped += input.skip(bytesToSkip - actualBytesSkipped);
}
if (!decryptWithHash)
{
decryptFile(input, outputStream, fileEntry); decryptFile(input, outputStream, fileEntry);
}else{ } else
{
decryptFileHash(input, outputStream, fileEntry); decryptFileHash(input, outputStream, fileEntry);
} }
} }
} }

View File

@ -1,43 +1,50 @@
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 + "-");
@ -48,9 +55,11 @@ public class Downloader {
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); decryption.decryptFile(input, outputStream, toDownload);
}else{ } else
{
decryption.decryptFileHash(input, outputStream, toDownload); decryption.decryptFileHash(input, outputStream, toDownload);
} }
@ -59,28 +68,30 @@ public class Downloader {
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); {
}
public void downloadTMD(long titleID,String path) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd"; String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd";
downloadFile(URL, "tmd", path); downloadFile(URL, "tmd", path);
} }
public void downloadFile(String fileURL,String filename,String tmpPath) throws IOException{
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);
} }
@ -90,26 +101,26 @@ public class Downloader {
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); {
}
public void downloadTicket(long titleID,String path) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk"; String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk";
downloadFile(URL, "cetk", path); downloadFile(URL, "cetk", path);
} }
public void downloadContent(long titleID,int contentID) throws IOException {
downloadContent(titleID,contentID, null); public byte[] downloadContentToByteArray(long titleID, int contentID) throws IOException
} {
public byte[] downloadContentToByteArray(long titleID,int contentID) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID); String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
return downloadFileToByteArray(URL); return downloadFileToByteArray(URL);
} }
public byte[] downloadTMDToByteArray(long titleID) throws IOException {
public byte[] downloadTMDToByteArray(long titleID) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd"; 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();
@ -118,7 +129,8 @@ public class Downloader {
// 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];
@ -126,31 +138,32 @@ public class Downloader {
InputStream inputStream = httpConn.getInputStream(); InputStream inputStream = httpConn.getInputStream();
int bytesRead = -1; int bytesRead;
byte[] buffer = new byte[BUFFER_SIZE]; byte[] buffer = new byte[BUFFER_SIZE];
int filePostion = 0; int filePosition = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) { while ((bytesRead = inputStream.read(buffer)) != -1)
System.arraycopy(buffer, 0, file, filePostion,bytesRead); {
filePostion+=bytesRead; System.arraycopy(buffer, 0, file, filePosition, bytesRead);
filePosition += bytesRead;
} }
inputStream.close(); inputStream.close();
}else{ } else
{
System.err.println("File not found: " + fileURL); System.err.println("File not found: " + fileURL);
} }
httpConn.disconnect(); httpConn.disconnect();
return file; return file;
} }
public byte[] downloadTicketToByteArray(long titleID) throws IOException {
public byte[] downloadTicketToByteArray(long titleID) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk"; 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 {
public void downloadContent(long titleID, int contentID, String tmpPath) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID); String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
downloadFile(URL, String.format("%08X", contentID) + ".app", tmpPath); 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;
}
}
}