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 commandCount; // 2 0x206 public short indexOffset; // 0 0x204
public byte[] SHA2 = new byte[32]; // 12 0x208 public short commandCount; // 2 0x206
public byte[] SHA2 = new byte[32]; // 12 0x208
//TODO: Test, size checking
/* // TODO: Test, size checking
* untested public ContentInfo(byte[] info)
*/ {
this.indexOffset = (short) (((info[0] & 0xFF) << 8) | (info[1] & 0xFF));
public ContentInfo(byte[] info){ this.commandCount = (short) (((info[2] & 0xFF) << 8) | (info[3] & 0xFF));
this.indexOffset=(short)( ((info[0]&0xFF)<<8) | (info[1]&0xFF) ); for (int i = 0; i < 32; i++)
this.commandCount=(short)( ((info[2]&0xFF)<<8) | (info[3]&0xFF) ); {
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 addFile(FEntry s)
{
return files.put(s.getFileName(), s);
} }
public FEntry getFile(String s){ public String getName()
return files.get(s); {
}
public FEntry addFile(FEntry s){
return files.put(s.getFileName(),s);
}
public String getName() {
return name; return name;
} }
public void setName(String name) { public void setName(String name)
{
this.name = name; this.name = name;
} }
public Collection<Directory> getFolder()
public Collection<Directory> getFolder() { {
return folder.values(); return folder.values();
} }
public Collection<FEntry> getFiles()
{
public Collection<FEntry> getFiles() { return files.values();
return files.values();
}
public void setFiles(TreeMap<String, FEntry> files) {
this.files = files;
} }
@Override @Override
public String toString(){ public String toString()
{
System.out.println(name + ":"); System.out.println(name + ":");
for(Directory d : folder.values()){ folder.values().forEach(System.out::println);
System.out.println(d); files.keySet().forEach(System.out::println);
}
for(String s : files.keySet()){
System.out.println(s);
}
return ""; return "";
} }
public DefaultMutableTreeNode getNodes(){ public DefaultMutableTreeNode getNodes()
{
DefaultMutableTreeNode node = new DefaultMutableTreeNode(getName()); DefaultMutableTreeNode node = new DefaultMutableTreeNode(getName());
for(Directory f: getFolder()){ for (Directory directory : getFolder())
node.add(f.getNodes()); {
} node.add(directory.getNodes());
}
for(FEntry f: getFiles()){
for (FEntry f : getFiles())
{
node.add(new DefaultMutableTreeNode(f)); node.add(new DefaultMutableTreeNode(f));
} }
return node; return node;
} }
}
}

View File

@ -2,6 +2,7 @@ package de.mas.jnustool;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.file.Files;
import java.util.List; import java.util.List;
import de.mas.jnustool.util.Decryption; import de.mas.jnustool.util.Decryption;
@ -9,29 +10,30 @@ 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;
private boolean dir = false; private boolean dir = false;
private boolean in_nus_title = false; private boolean in_nus_title = false;
private boolean extract_withHash = false; private boolean extract_withHash = false;
private String fileName = ""; private String fileName = "";
private String path = ""; private String path = "";
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())
{
String folder = String.format("%016X", titleID) +"/"; f.mkdir();
File folder_ = null; }
for(int i = 0;i<path.length-1;i++){
if(!path[i].equals("")){ String folder = String.format("%016X", titleID) + "/";
folder += path[i] + "/"; File folder_;
folder_ = new File(folder); for (int i = 0; i < path.length - 1; i++)
if(!folder_.exists()){ {
folder_.mkdir(); if (!path[i].equals(""))
} {
} folder += path[i] + "/";
} folder_ = new File(folder);
if (!folder_.exists())
} {
public String getDownloadPath(){ try
String [] path = getFullPath().split("/"); {
String folder = String.format("%016X", getTitleID()) +"/"; Files.createDirectory(folder_.toPath());
for(int i = 0;i<path.length-1;i++){ } catch (IOException e)
if(!path[i].equals("")){ {
folder += path[i] + "/"; e.printStackTrace();
} }
} }
return folder; }
}
} }
public void downloadAndDecrypt() throws ExitException { public String getDownloadPath()
{
String[] path = getFullPath().split("/");
String folder = String.format("%016X", getTitleID()) + "/";
for (int i = 0; i < path.length - 1; i++)
{
if (!path[i].equals(""))
{
folder += path[i] + "/";
}
}
return folder;
}
public void downloadAndDecrypt() throws ExitException
{
createFolder(); createFolder();
long titleID = getTitleID(); long titleID = getTitleID();
File f = new File(String.format("%016X", titleID) +"/" +getFullPath().substring(1, getFullPath().length())); File f = new File(String.format("%016X", titleID) + "/" + getFullPath().substring(1, getFullPath().length()));
if(f.exists()){ if (f.exists())
if(f.length() == getFileLength()){ {
System.out.println("Skipping: " + String.format("%8.2f MB ",getFileLength()/1024.0/1024.0) + getFullPath()); if (f.length() == getFileLength())
{
System.out.println("Skipping: " + String.format("%8.2f MB ", getFileLength() / 1024.0 / 1024.0) + getFullPath());
return; return;
} }
} }
try { try
if(Settings.useCachedFiles){ {
if (Settings.useCachedFiles)
{
f = new File(getContentPath()); f = new File(getContentPath());
if(f.exists()){ if (f.exists())
if(f.length() == fst.getTmd().contents[this.getContentID()].size){ {
System.out.println("Decrypting: " + String.format("%8.2f MB ", getFileLength()/1024.0/1024.0) + getFullPath()); if (f.length() == fst.getTmd().contents[this.getContentID()].size)
{
System.out.println("Decrypting: " + String.format("%8.2f MB ", getFileLength() / 1024.0 / 1024.0) + getFullPath());
Decryption decrypt = new Decryption(fst.getTmd().getNUSTitle().getTicket()); Decryption decrypt = new Decryption(fst.getTmd().getNUSTitle().getTicket());
decrypt.decrypt(this,getDownloadPath()); decrypt.decrypt(this, getDownloadPath());
return; return;
}else{ } else
if(!Settings.downloadWhenCachedFilesMissingOrBroken){ {
System.out.println("Cached content has the wrong size! Please check your: "+ getContentPath() + " Downloading not allowed"); if (!Settings.downloadWhenCachedFilesMissingOrBroken)
if(!Settings.skipBrokenFiles){ {
System.out.println("Cached content has the wrong size! Please check your: " + getContentPath() + " Downloading not allowed");
if (!Settings.skipBrokenFiles)
{
throw new ExitException(""); throw new ExitException("");
}else{ } else
{
System.out.println("Ignoring the missing file: " + this.getFileName()); System.out.println("Ignoring the missing file: " + this.getFileName());
} }
}else{ } else
{
System.out.println("Content missing. Downloading the file from the server: " + this.getFileName()); System.out.println("Content missing. Downloading the file from the server: " + this.getFileName());
} }
} }
}else{ } else
if(!Settings.downloadWhenCachedFilesMissingOrBroken){ {
if (!Settings.downloadWhenCachedFilesMissingOrBroken)
{
System.out.println("Content missing. Downloading not allowed"); System.out.println("Content missing. Downloading not allowed");
if(!Settings.skipBrokenFiles){ if (!Settings.skipBrokenFiles)
{
throw new ExitException(""); throw new ExitException("");
}else{ } else
{
System.out.println("Ignoring the missing file: " + this.getFileName()); System.out.println("Ignoring the missing file: " + this.getFileName());
} }
}else{ } else
{
System.out.println("Content missing. Downloading the file from the server: " + this.getFileName()); System.out.println("Content missing. Downloading the file from the server: " + this.getFileName());
} }
} }
} }
System.out.println("Downloading: " + String.format("%8.2f MB ", getFileLength()/1024.0/1024.0) + getFullPath()); System.out.println("Downloading: " + String.format("%8.2f MB ", getFileLength() / 1024.0 / 1024.0) + getFullPath());
Downloader.getInstance().downloadAndDecrypt(this); Downloader.getInstance().downloadAndDecrypt(this);
} catch (IOException e)
{
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace(); e.printStackTrace();
} }
} }
public List<String> getPathList() { public List<String> getPathList()
{
return pathList; return pathList;
} }
public void setPathList(List<String> pathList) { public void setPathList(List<String> pathList)
{
this.pathList = pathList; this.pathList = pathList;
} }
public String getContentPath() { public String getContentPath()
return fst.getTmd().getContentPath() + "/" + String.format("%08X", getNUScontentID()) + ".app"; {
return fst.getTmd().getContentPath() + "/" + String.format("%08X", getNUSContentID()) + ".app";
} }
public long getTitleID() { public long getTitleID()
{
return fst.getTmd().titleID; return fst.getTmd().titleID;
} }
public TIK getTicket() { public TIK getTicket()
{
return fst.getTmd().getNUSTitle().getTicket(); return fst.getTmd().getNUSTitle().getTicket();
} }
}
}

View File

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

View File

@ -5,310 +5,288 @@ 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;
List<FEntry> fileEntries = new ArrayList<>(); List<FEntry> fileEntries = new ArrayList<>();
int totalContentCount = 0; int totalContentCount = 0;
int totalEntries = 0; int totalEntries = 0;
int dirEntries = 0; int dirEntries = 0;
private Directory FSTDirectory = new Directory("root"); private Directory FSTDirectory = new Directory("root");
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 = ""; String contentFolder;
Directory curContent = contentDirectory; Directory curContent;
for(FEntry f : getFileEntries()){ for (FEntry f : getFileEntries())
if(!f.isDir() && f.isInNUSTitle()){ {
contentfolder = String.format("%08X",tmd.contents[f.getContentID()].ID); if (!f.isDir() && f.isInNUSTitle())
{
if(!contentDirectory.containsFolder(contentfolder)){ contentFolder = String.format("%08X", tmd.contents[f.getContentID()].ID);
Directory newDir = new Directory(contentfolder);
contentDirectory.addFolder(newDir); if (!contentDirectory.containsFolder(contentFolder))
{
Directory newDir = new Directory(contentFolder);
contentDirectory.addFolder(newDir);
} }
curContent = contentDirectory.getFolder(contentfolder); curContent = contentDirectory.getFolder(contentFolder);
Directory current = FSTDirectory; Directory current = FSTDirectory;
int i = 0; int i = 0;
for(String s :f.getPathList()){ for (String s : f.getPathList())
i++; {
i++;
//Content //Content
if(curContent.containsFolder(s)){ if (curContent.containsFolder(s))
{
curContent = curContent.get(s); curContent = curContent.get(s);
}else{ } else
{
Directory newDir = new Directory(s); Directory newDir = new Directory(s);
curContent.addFolder(newDir); curContent.addFolder(newDir);
curContent = newDir; curContent = newDir;
} }
if(i==f.getPathList().size()){ if (i == f.getPathList().size())
{
curContent.addFile(f); curContent.addFile(f);
} }
//FST //FST
if(current.containsFolder(s)){ if (current.containsFolder(s))
{
current = current.get(s); current = current.get(s);
}else{ } else
{
Directory newDir = new Directory(s); Directory newDir = new Directory(s);
current.addFolder(newDir); current.addFolder(newDir);
current = newDir; current = newDir;
} }
if(i==f.getPathList().size()){ if (i == f.getPathList().size())
{
current.addFile(f); current.addFile(f);
} }
} }
} }
} }
} }
private void parse(byte[] decryptedData, TitleMetaData tmd) throws IOException
private void parse(byte[] decrypteddata, TitleMetaData tmd) throws IOException { {
if (!Arrays.equals(Arrays.copyOfRange(decryptedData, 0, 3), new byte[]{0x46, 0x53, 0x54}))
if(!Arrays.equals(Arrays.copyOfRange(decrypteddata, 0, 3), new byte[]{0x46,0x53,0x54})){ {
System.err.println("Not a FST. Maybe a wrong key?"); System.err.println("Not a FST. Maybe a wrong key?");
throw new IllegalArgumentException("File not a FST"); throw new IllegalArgumentException("File not a FST");
} }
this.totalContentCount = Util.getIntFromBytes(decrypteddata, 8); this.totalContentCount = ConversionUtils.getIntFromBytes(decryptedData, 8);
int base_offset = 0x20+totalContentCount*0x20; int base_offset = 0x20 + totalContentCount * 0x20;
this.totalEntries = Util.getIntFromBytes(decrypteddata, base_offset+8); this.totalEntries = ConversionUtils.getIntFromBytes(decryptedData, base_offset + 8);
int nameOff = base_offset + totalEntries * 0x10; int nameOff = base_offset + totalEntries * 0x10;
int level=0; int level = 0;
int[] LEntry = new int[16]; int[] LEntry = new int[16];
int[] Entry = new int[16]; int[] Entry = new int[16];
for(int i = 0;i<this.totalEntries;i++){ for (int i = 0; i < this.totalEntries; i++)
{
boolean dir = false; boolean dir = false;
boolean in_nus_title = true; boolean in_nus_title = true;
boolean extract_withHash = false; boolean extract_withHash = false;
long fileOffset; long fileOffset;
long fileLength; long fileLength;
int type; int type;
int contentID; int contentID;
String filename = ""; String filename;
String path = ""; String path = "";
if( level > 0) if (level > 0)
{ {
while( LEntry[level-1] == i ) while (LEntry[level - 1] == i)
{ {
level--; level--;
} }
} }
int offset = base_offset + i * 0x10;
int offset = base_offset + i*0x10;
//getting the type //getting the type
type = (int) decrypteddata[offset]+128; type = (int) decryptedData[offset] + 128;
if((type & FEntry.DIR_FLAG) == 1) dir = true; if ((type & FEntry.DIR_FLAG) == 1)
if((type & FEntry.NOT_IN_NUSTITLE_FLAG) == 0 ) in_nus_title = false; {
dir = true;
}
if ((type & FEntry.NOT_IN_NUS_TITLE_FLAG) == 0)
{
in_nus_title = false;
}
//getting Name //getting Name
decrypteddata[offset] = 0; decryptedData[offset] = 0;
int nameoff_entry_offset = Util.getIntFromBytes(decrypteddata, offset); int nameOff_entry_offset = ConversionUtils.getIntFromBytes(decryptedData, offset);
int j = 0; int j = 0;
int nameoff_entry = nameOff + nameoff_entry_offset; int nameOff_entry = nameOff + nameOff_entry_offset;
while(decrypteddata[nameoff_entry + j] != 0){j++;} while (decryptedData[nameOff_entry + j] != 0)
filename = new String(Arrays.copyOfRange(decrypteddata,nameoff_entry, nameoff_entry + j)); {
j++;
}
filename = new String(Arrays.copyOfRange(decryptedData, nameOff_entry, nameOff_entry + j));
//getting offsets. save in two ways //getting offsets. save in two ways
offset+=4; offset += 4;
fileOffset = (long) Util.getIntFromBytes(decrypteddata, offset); fileOffset = (long) ConversionUtils.getIntFromBytes(decryptedData, offset);
offset+=4; offset += 4;
fileLength = Util.getIntAsLongFromBytes(decrypteddata, offset); fileLength = ConversionUtils.getIntAsLongFromBytes(decryptedData, offset);
@SuppressWarnings("unused")
int parentOffset = (int) fileOffset;
int nextOffset = (int) fileLength; int nextOffset = (int) fileLength;
//grabbing flags //grabbing flags
offset+=4; offset += 4;
int flags = Util.getShortFromBytes(decrypteddata, offset); int flags = ConversionUtils.getShortFromBytes(decryptedData, offset);
if((flags & FEntry.EXTRACT_WITH_HASH_FLAG) > 0) extract_withHash = true; if ((flags & FEntry.EXTRACT_WITH_HASH_FLAG) > 0)
if((flags & FEntry.CHANGE_OFFSET_FLAG) == 0) fileOffset <<=5; {
extract_withHash = true;
//grabbing contentid }
offset+=2; if ((flags & FEntry.CHANGE_OFFSET_FLAG) == 0)
contentID = Util.getShortFromBytes(decrypteddata, offset) ; {
fileOffset <<= 5;
}
//grabbing content id
offset += 2;
contentID = ConversionUtils.getShortFromBytes(decryptedData, offset);
//remember total size //remember total size
this.totalContentSize += fileLength; this.totalContentSize += fileLength;
if(in_nus_title)this.totalContentSizeInNUS += fileLength; if (in_nus_title)
{
this.totalContentSizeInNUS += fileLength;
}
List<String> pathList = new ArrayList<>(); List<String> pathList = new ArrayList<>();
//getting the full path of entry //getting the full path of entry
if(dir) if (dir)
{ {
dirEntries++; dirEntries++;
Entry[level] = i; Entry[level] = i;
LEntry[level++] = nextOffset ; LEntry[level++] = nextOffset;
if( level > 15 ) // something is wrong! if (level > 15) // something is wrong!
{ {
break; break;
} }
}else{ } else
StringBuilder sb = new StringBuilder(); {
int k = 0; StringBuilder stringBuilder = new StringBuilder();
int nameoffoff,nameoff_entrypath; int k;
int nameOffOff, nameOff_entryPath;
for( j=0; j<level; ++j ) for (j = 0; j < level; ++j)
{ {
nameoffoff = Util.getIntFromBytes(decrypteddata,base_offset+Entry[j]*0x10); nameOffOff = ConversionUtils.getIntFromBytes(decryptedData, base_offset + Entry[j] * 0x10);
k=0; k = 0;
nameoff_entrypath = nameOff + nameoffoff; nameOff_entryPath = nameOff + nameOffOff;
while(decrypteddata[nameoff_entrypath + k] != 0){k++;} while (decryptedData[nameOff_entryPath + k] != 0)
String tmpname = new String(Arrays.copyOfRange(decrypteddata,nameoff_entrypath, nameoff_entrypath + k)); {
if(!tmpname.equals("")){ k++;
pathList.add(tmpname);
} }
String temporaryName = new String(Arrays.copyOfRange(decryptedData, nameOff_entryPath, nameOff_entryPath + k));
if (!temporaryName.equals(""))
{
sb.append(tmpname); pathList.add(temporaryName);
sb.append("/"); }
stringBuilder.append(temporaryName);
stringBuilder.append("/");
} }
path = sb.toString(); path = stringBuilder.toString();
} }
//add this to the List! //add this to the List!
fileEntries.add(new FEntry(path,filename,contentID,tmd.contents[contentID].ID,fileOffset,fileLength,dir,in_nus_title,extract_withHash,pathList,this)); fileEntries.add(new FEntry(path, filename, contentID, tmd.contents[contentID].ID, fileOffset, fileLength, dir, in_nus_title, extract_withHash, pathList, this));
//System.out.println(fileEntries.get(i)); //System.out.println(fileEntries.get(i));
} }
}
public long getTotalContentSize() {
return totalContentSize;
} }
public long getTotalContentSizeInNUS()
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; }
}
public int getFileCountInNUS() {
int i = 0;
for(FEntry f: getFileEntries()){
if(!f.isDir() && f.isInNUSTitle())
i++;
}
return i; return i;
} }
public Directory getFSTDirectory() { public int getFileCountInNUS()
{
int i = 0;
for (FEntry f : getFileEntries())
{
if (!f.isDir() && f.isInNUSTitle())
{
i++;
}
}
return i;
}
public Directory getFSTDirectory()
{
return FSTDirectory; return FSTDirectory;
} }
public Directory getContentDirectory() {
return contentDirectory;
}
public TitleMetaData getTmd() { 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,203 +11,230 @@ import de.mas.jnustool.util.Downloader;
import de.mas.jnustool.util.ExitException; import de.mas.jnustool.util.ExitException;
import de.mas.jnustool.util.Settings; import de.mas.jnustool.util.Settings;
public class NUSTitle { public class NUSTitle
{
private TitleMetaData tmd; private TitleMetaData tmd;
private TIK ticket; private TIK ticket;
private FST fst; private FST fst;
private long titleID; private long titleID;
public NUSTitle(long titleId,String key) throws ExitException{
public NUSTitle(long titleId, String key) throws ExitException
{
setTitleID(titleId); setTitleID(titleId);
try { try
if(Settings.downloadContent){ {
File f = new File(getContentPath()); if (Settings.downloadContent)
if(!f.exists())f.mkdir(); {
File f = new File(getContentPath());
if (!f.exists())
{
Files.createDirectory(f.toPath());
}
} }
if(Settings.downloadContent){ if (Settings.downloadContent)
{
File f = new File(getContentPath() + "/" + "tmd"); File f = new File(getContentPath() + "/" + "tmd");
if(!(f.exists() && Settings.skipExistingTMDTICKET)){ if (!(f.exists() && Settings.skipExistingTMDTICKET))
{
System.out.println("Downloading TMD"); System.out.println("Downloading TMD");
Downloader.getInstance().downloadTMD(titleId,getContentPath()); Downloader.getInstance().downloadTMD(titleId, getContentPath());
}else{ } else
{
System.out.println("Skipped download of TMD. Already existing"); System.out.println("Skipped download of TMD. Already existing");
} }
f = new File(getContentPath() + "/" + "cetk"); f = new File(getContentPath() + "/" + "cetk");
if(!(f.exists() && Settings.skipExistingTMDTICKET)){ if (!(f.exists() && Settings.skipExistingTMDTICKET))
if(key == null){ {
if (key == null)
{
System.out.print("Downloading Ticket"); System.out.print("Downloading Ticket");
Downloader.getInstance().downloadTicket(titleId,getContentPath()); Downloader.getInstance().downloadTicket(titleId, getContentPath());
} }
}else{ } else
{
System.out.println("Skipped download of ticket. Already existing"); System.out.println("Skipped download of ticket. Already existing");
} }
} }
if(Settings.useCachedFiles){ if (Settings.useCachedFiles)
{
File f = new File(getContentPath() + "/" + "tmd"); File f = new File(getContentPath() + "/" + "tmd");
if(f.exists()){ if (f.exists())
{
System.out.println("Using cached TMD."); System.out.println("Using cached TMD.");
tmd = new TitleMetaData(f); tmd = new TitleMetaData(f);
}else{ } else
{
System.out.println("No cached TMD found."); System.out.println("No cached TMD found.");
} }
} }
if(tmd == null){ if (tmd == null)
if(Settings.downloadWhenCachedFilesMissingOrBroken){ {
if(Settings.useCachedFiles) System.out.println("Getting missing tmd from Server!"); if (Settings.downloadWhenCachedFilesMissingOrBroken)
{
if (Settings.useCachedFiles)
{
System.out.println("Getting missing tmd from Server!");
}
tmd = new TitleMetaData(Downloader.getInstance().downloadTMDToByteArray(titleId)); tmd = new TitleMetaData(Downloader.getInstance().downloadTMDToByteArray(titleId));
}else{ } else
{
System.out.println("Downloading of missing files is not enabled. Exiting"); System.out.println("Downloading of missing files is not enabled. Exiting");
throw new ExitException("TMD missing."); throw new ExitException("TMD missing.");
} }
} }
if(key != null){ if (key != null)
{
System.out.println("Using ticket from parameter."); System.out.println("Using ticket from parameter.");
ticket = new TIK(key,titleId); ticket = new TIK(key, titleId);
}else{ } else
if(Settings.useCachedFiles){ {
if (Settings.useCachedFiles)
{
File f = new File(getContentPath() + "/" + "cetk"); File f = new File(getContentPath() + "/" + "cetk");
if(f.exists()){ if (f.exists())
{
System.out.println("Using cached cetk."); System.out.println("Using cached cetk.");
ticket = new TIK(f,titleId); ticket = new TIK(f, titleId);
}else{ } else
{
System.out.println("No cached ticket found."); System.out.println("No cached ticket found.");
} }
} }
if(ticket == null){ if (ticket == null)
if(Settings.downloadWhenCachedFilesMissingOrBroken){ {
if(Settings.useCachedFiles) System.out.println("getting missing ticket"); if (Settings.downloadWhenCachedFilesMissingOrBroken)
ticket = new TIK(Downloader.getInstance().downloadTicketToByteArray(titleId),tmd.titleID); {
}else{ if (Settings.useCachedFiles)
{
System.out.println("getting missing ticket");
}
ticket = new TIK(Downloader.getInstance().downloadTicketToByteArray(titleId), tmd.titleID);
} else
{
System.out.println("Downloading of missing files is not enabled. Exiting"); System.out.println("Downloading of missing files is not enabled. Exiting");
throw new ExitException("Ticket missing."); throw new ExitException("Ticket missing.");
} }
} }
} }
if(Settings.downloadContent){ if (Settings.downloadContent)
{
File f = new File(getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app"); File f = new File(getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app");
if(!(f.exists() && Settings.skipExistingFiles)){ if (!(f.exists() && Settings.skipExistingFiles))
{
System.out.println("Downloading FST (" + String.format("%08x", tmd.contents[0].ID) + ")"); System.out.println("Downloading FST (" + String.format("%08x", tmd.contents[0].ID) + ")");
Downloader.getInstance().downloadContent(titleId,tmd.contents[0].ID,getContentPath()); Downloader.getInstance().downloadContent(titleId, tmd.contents[0].ID, getContentPath());
}else{ } else
if(f.length() != tmd.contents[0].size){ {
if(Settings.downloadWhenCachedFilesMissingOrBroken){ if (f.length() != tmd.contents[0].size)
{
if (Settings.downloadWhenCachedFilesMissingOrBroken)
{
System.out.println("FST already existing, but broken. Downloading it again."); System.out.println("FST already existing, but broken. Downloading it again.");
Downloader.getInstance().downloadContent(titleId,tmd.contents[0].ID,getContentPath()); Downloader.getInstance().downloadContent(titleId, tmd.contents[0].ID, getContentPath());
}else{ } else
{
System.out.println("FST already existing, but broken. No download allowed."); System.out.println("FST already existing, but broken. No download allowed.");
throw new ExitException("FST missing."); throw new ExitException("FST missing.");
} }
}else{ } else
{
System.out.println("Skipped download of FST. Already existing"); System.out.println("Skipped download of FST. Already existing");
} }
} }
} }
Decryption decryption = new Decryption(ticket.getDecryptedKey(),0); Decryption decryption = new Decryption(ticket.getDecryptedKey(), 0);
byte[] encryptedFST = null; byte[] encryptedFST = null;
if(Settings.useCachedFiles){ if (Settings.useCachedFiles)
{
String path = getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app"; String path = getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app";
File f = new File(path); File f = new File(path);
if(f.exists()){ if (f.exists())
{
System.out.println("Using cached FST"); System.out.println("Using cached FST");
Path file = Paths.get(path); Path file = Paths.get(path);
encryptedFST = Files.readAllBytes(file); encryptedFST = Files.readAllBytes(file);
}else{ } else
System.out.println("No cached FST (" + String.format("%08x", tmd.contents[0].ID) + ") found."); {
System.out.println("No cached FST (" + String.format("%08x", tmd.contents[0].ID) + ") found.");
} }
} }
if(encryptedFST == null){ if (encryptedFST == null)
if(Settings.downloadWhenCachedFilesMissingOrBroken){ {
if(Settings.useCachedFiles)System.out.println("Getting FST from server."); if (Settings.downloadWhenCachedFilesMissingOrBroken)
encryptedFST = Downloader.getInstance().downloadContentToByteArray(titleId,tmd.contents[0].ID); {
}else{ if (Settings.useCachedFiles)
{
System.out.println("Getting FST from server.");
}
encryptedFST = Downloader.getInstance().downloadContentToByteArray(titleId, tmd.contents[0].ID);
} else
{
System.out.println("Downloading of missing files is not enabled. Exiting"); System.out.println("Downloading of missing files is not enabled. Exiting");
throw new ExitException(""); throw new ExitException("");
} }
} }
byte[] decryptedFST = decryption.decrypt(encryptedFST); byte[] decryptedFST = decryption.decrypt(encryptedFST);
fst = new FST(decryptedFST,tmd); fst = new FST(decryptedFST, tmd);
tmd.setNUSTitle(this); tmd.setNUSTitle(this);
if(Settings.downloadContent){ if (Settings.downloadContent)
{
tmd.downloadContents(); tmd.downloadContents();
} }
System.out.println("Total Size of Content Files: " + ((int)((getTotalContentSize()/1024.0/1024.0)*100))/100.0 +" MB"); System.out.println("Total Size of Content Files: " + ((int) ((getTotalContentSize() / 1024.0 / 1024.0) * 100)) / 100.0 + " MB");
System.out.println("Total Size of Decrypted Files: " + ((int)((fst.getTotalContentSizeInNUS()/1024.0/1024.0)*100))/100.0 +" MB"); System.out.println("Total Size of Decrypted Files: " + ((int) ((fst.getTotalContentSizeInNUS() / 1024.0 / 1024.0) * 100)) / 100.0 + " MB");
System.out.println("Entries: " + fst.getTotalEntries()); System.out.println("Entries: " + fst.getTotalEntries());
System.out.println("Entries: " + fst.getFileCount()); System.out.println("Entries: " + fst.getFileCount());
System.out.println("Files in NUSTitle: " + fst.getFileCountInNUS()); System.out.println("Files in NUSTitle: " + fst.getFileCountInNUS());
} catch (IOException e) { } catch (IOException e)
// TODO Auto-generated catch block {
e.printStackTrace(); e.printStackTrace();
} }
} }
public FST getFst()
public FST getFst() { {
return fst; return fst;
} }
public void setFst(FST fst) {
this.fst = fst;
}
public TitleMetaData getTmd() { public TIK getTicket()
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{
parse(cetk);
calculateDecryptedKey(titleid);
}
public TIK(String ticketKey,long titleid) { public TIK(File commonETicket, long titleID) throws IOException
setEncryptedKey(ticketKey); {
calculateDecryptedKey(titleid); parse(commonETicket);
} calculateDecryptedKey(titleID);
}
public TIK(byte[] file, long titleID) throws IOException { public TIK(String ticketKey, long titleID)
parse(file); {
calculateDecryptedKey(titleID); setEncryptedKey(ticketKey);
} calculateDecryptedKey(titleID);
}
private void calculateDecryptedKey(long titleid) { public TIK(byte[] file, long titleID) throws IOException
Decryption decryption = new Decryption(Util.commonKey,titleid); {
decryptedKey = decryption.decrypt(encryptedKey); parse(file);
} calculateDecryptedKey(titleID);
}
private void parse(byte[] cetk) throws IOException { private void calculateDecryptedKey(long titleID)
System.arraycopy(cetk, 0x1bf, this.encryptedKey, 0,16); {
} Decryption decryption = new Decryption(ConversionUtils.commonKey, titleID);
decryptedKey = decryption.decrypt(encryptedKey);
private void parse(File cetk) throws IOException { }
RandomAccessFile f = new RandomAccessFile(cetk, "r");
f.seek(0x1bf);
f.read(this.encryptedKey, 0, 16);
f.close();
}
public void setEncryptedKey(String key) {
this.encryptedKey = Util.hexStringToByteArray(key);
}
public byte[] getEncryptedKey() {
return encryptedKey;
}
public void setEncryptedKey(byte[] encryptedKey) { private void parse(byte[] commonETicketBytes) throws IOException
this.encryptedKey = encryptedKey; {
} System.arraycopy(commonETicketBytes, 0x1bf, this.encryptedKey, 0, 16);
}
public byte[] getDecryptedKey() { private void parse(File commonETicket) throws IOException
return decryptedKey; {
} RandomAccessFile f = new RandomAccessFile(commonETicket, "r");
f.seek(0x1bf);
f.read(this.encryptedKey, 0, 16);
f.close();
}
public void setDecryptedKey(byte[] decryptedKey) { public void setEncryptedKey(String key)
this.decryptedKey = decryptedKey; {
} this.encryptedKey = ConversionUtils.hexStringToByteArray(key);
}
@Override public byte[] getDecryptedKey()
public String toString(){ {
return "encrypted key: " + Util.ByteArrayToString(encryptedKey)+ " decrypted key: " + Util.ByteArrayToString(decryptedKey); return decryptedKey;
} }
}
@Override
public String toString()
{
return "encrypted key: " + ConversionUtils.ByteArrayToString(encryptedKey) + " decrypted key: " + ConversionUtils.ByteArrayToString(decryptedKey);
}
}

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,12 +1,11 @@
package de.mas.jnustool.util; package de.mas.jnustool.util;
public class Settings
public class Settings { {
public static boolean downloadContent = false; public static boolean downloadContent = false;
public static boolean useCachedFiles = false; public static boolean useCachedFiles = false;
public static boolean downloadWhenCachedFilesMissingOrBroken = true; public static boolean downloadWhenCachedFilesMissingOrBroken = true;
public static boolean skipBrokenFiles = false; public static boolean skipBrokenFiles = false;
public static boolean skipExistingFiles = true; public static boolean skipExistingFiles = true;
public static boolean skipExistingTMDTICKET = true; public static boolean skipExistingTMDTICKET = true;
}
}

View File

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