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

View File

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

View File

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

View File

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

View File

@ -4,19 +4,22 @@ import java.util.concurrent.Callable;
public class FEntryDownloader implements Callable<Integer>
{
FEntry f;
public void setTitle(FEntry f){
this.f = f;
}
public FEntryDownloader(FEntry f){
setTitle(f);
}
@Override
public Integer call() throws Exception {
f.downloadAndDecrypt();
return null;
private FEntry fEntry;
public void setTitle(FEntry fEntry)
{
this.fEntry = fEntry;
}
}
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.List;
import de.mas.jnustool.util.Util;
import de.mas.jnustool.util.ConversionUtils;
public class FST {
public class FST
{
private TitleMetaData tmd;
long totalContentSize = 0L;
long totalContentSizeInNUS = 0L;
List<FEntry> fileEntries = new ArrayList<>();
int totalContentCount = 0;
int totalEntries = 0;
int dirEntries = 0;
private Directory FSTDirectory = new Directory("root");
private Directory contentDirectory = new Directory("root");
public FST(byte[] decrypteddata, TitleMetaData tmd) throws IOException {
parse(decrypteddata,tmd);
public FST(byte[] decryptedData, TitleMetaData tmd) throws IOException
{
parse(decryptedData, tmd);
setTmd(tmd);
buildDirectories();
}
}
private void buildDirectories() {
String contentfolder = "";
Directory curContent = contentDirectory;
for(FEntry f : getFileEntries()){
if(!f.isDir() && f.isInNUSTitle()){
contentfolder = String.format("%08X",tmd.contents[f.getContentID()].ID);
if(!contentDirectory.containsFolder(contentfolder)){
Directory newDir = new Directory(contentfolder);
contentDirectory.addFolder(newDir);
private void buildDirectories()
{
String contentFolder;
Directory curContent;
for (FEntry f : getFileEntries())
{
if (!f.isDir() && f.isInNUSTitle())
{
contentFolder = String.format("%08X", tmd.contents[f.getContentID()].ID);
if (!contentDirectory.containsFolder(contentFolder))
{
Directory newDir = new Directory(contentFolder);
contentDirectory.addFolder(newDir);
}
curContent = contentDirectory.getFolder(contentfolder);
Directory current = FSTDirectory;
int i = 0;
for(String s :f.getPathList()){
i++;
curContent = contentDirectory.getFolder(contentFolder);
Directory current = FSTDirectory;
int i = 0;
for (String s : f.getPathList())
{
i++;
//Content
if(curContent.containsFolder(s)){
if (curContent.containsFolder(s))
{
curContent = curContent.get(s);
}else{
} else
{
Directory newDir = new Directory(s);
curContent.addFolder(newDir);
curContent = newDir;
}
if(i==f.getPathList().size()){
}
if (i == f.getPathList().size())
{
curContent.addFile(f);
}
//FST
if(current.containsFolder(s)){
if (current.containsFolder(s))
{
current = current.get(s);
}else{
} else
{
Directory newDir = new Directory(s);
current.addFolder(newDir);
current = newDir;
}
if(i==f.getPathList().size()){
if (i == f.getPathList().size())
{
current.addFile(f);
}
}
}
}
}
}
}
}
private void parse(byte[] decrypteddata, TitleMetaData tmd) throws IOException {
if(!Arrays.equals(Arrays.copyOfRange(decrypteddata, 0, 3), new byte[]{0x46,0x53,0x54})){
private void parse(byte[] decryptedData, TitleMetaData tmd) throws IOException
{
if (!Arrays.equals(Arrays.copyOfRange(decryptedData, 0, 3), new byte[]{0x46, 0x53, 0x54}))
{
System.err.println("Not a FST. Maybe a wrong key?");
throw new IllegalArgumentException("File not a FST");
}
this.totalContentCount = Util.getIntFromBytes(decrypteddata, 8);
int base_offset = 0x20+totalContentCount*0x20;
this.totalEntries = Util.getIntFromBytes(decrypteddata, base_offset+8);
this.totalContentCount = ConversionUtils.getIntFromBytes(decryptedData, 8);
int base_offset = 0x20 + totalContentCount * 0x20;
this.totalEntries = ConversionUtils.getIntFromBytes(decryptedData, base_offset + 8);
int nameOff = base_offset + totalEntries * 0x10;
int level=0;
int level = 0;
int[] LEntry = 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 in_nus_title = true;
boolean extract_withHash = false;
long fileOffset;
long fileLength;
int type;
int contentID;
String filename = "";
String filename;
String path = "";
if( level > 0)
if (level > 0)
{
while( LEntry[level-1] == i )
{
while (LEntry[level - 1] == i)
{
level--;
}
}
int offset = base_offset + i*0x10;
int offset = base_offset + i * 0x10;
//getting the type
type = (int) decrypteddata[offset]+128;
if((type & FEntry.DIR_FLAG) == 1) dir = true;
if((type & FEntry.NOT_IN_NUSTITLE_FLAG) == 0 ) in_nus_title = false;
type = (int) decryptedData[offset] + 128;
if ((type & FEntry.DIR_FLAG) == 1)
{
dir = true;
}
if ((type & FEntry.NOT_IN_NUS_TITLE_FLAG) == 0)
{
in_nus_title = false;
}
//getting Name
decrypteddata[offset] = 0;
int nameoff_entry_offset = Util.getIntFromBytes(decrypteddata, offset);
decryptedData[offset] = 0;
int nameOff_entry_offset = ConversionUtils.getIntFromBytes(decryptedData, offset);
int j = 0;
int nameoff_entry = nameOff + nameoff_entry_offset;
while(decrypteddata[nameoff_entry + j] != 0){j++;}
filename = new String(Arrays.copyOfRange(decrypteddata,nameoff_entry, nameoff_entry + j));
int nameOff_entry = nameOff + nameOff_entry_offset;
while (decryptedData[nameOff_entry + j] != 0)
{
j++;
}
filename = new String(Arrays.copyOfRange(decryptedData, nameOff_entry, nameOff_entry + j));
//getting offsets. save in two ways
offset+=4;
fileOffset = (long) Util.getIntFromBytes(decrypteddata, offset);
offset+=4;
fileLength = Util.getIntAsLongFromBytes(decrypteddata, offset);
@SuppressWarnings("unused")
int parentOffset = (int) fileOffset;
offset += 4;
fileOffset = (long) ConversionUtils.getIntFromBytes(decryptedData, offset);
offset += 4;
fileLength = ConversionUtils.getIntAsLongFromBytes(decryptedData, offset);
int nextOffset = (int) fileLength;
//grabbing flags
offset+=4;
int flags = Util.getShortFromBytes(decrypteddata, offset);
if((flags & FEntry.EXTRACT_WITH_HASH_FLAG) > 0) extract_withHash = true;
if((flags & FEntry.CHANGE_OFFSET_FLAG) == 0) fileOffset <<=5;
//grabbing contentid
offset+=2;
contentID = Util.getShortFromBytes(decrypteddata, offset) ;
offset += 4;
int flags = ConversionUtils.getShortFromBytes(decryptedData, offset);
if ((flags & FEntry.EXTRACT_WITH_HASH_FLAG) > 0)
{
extract_withHash = true;
}
if ((flags & FEntry.CHANGE_OFFSET_FLAG) == 0)
{
fileOffset <<= 5;
}
//grabbing content id
offset += 2;
contentID = ConversionUtils.getShortFromBytes(decryptedData, offset);
//remember total size
this.totalContentSize += fileLength;
if(in_nus_title)this.totalContentSizeInNUS += fileLength;
if (in_nus_title)
{
this.totalContentSizeInNUS += fileLength;
}
List<String> pathList = new ArrayList<>();
//getting the full path of entry
if(dir)
if (dir)
{
dirEntries++;
Entry[level] = i;
LEntry[level++] = nextOffset ;
if( level > 15 ) // something is wrong!
LEntry[level++] = nextOffset;
if (level > 15) // something is wrong!
{
break;
}
}else{
StringBuilder sb = new StringBuilder();
int k = 0;
int nameoffoff,nameoff_entrypath;
} else
{
StringBuilder stringBuilder = new StringBuilder();
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);
k=0;
nameoff_entrypath = nameOff + nameoffoff;
while(decrypteddata[nameoff_entrypath + k] != 0){k++;}
String tmpname = new String(Arrays.copyOfRange(decrypteddata,nameoff_entrypath, nameoff_entrypath + k));
if(!tmpname.equals("")){
pathList.add(tmpname);
nameOffOff = ConversionUtils.getIntFromBytes(decryptedData, base_offset + Entry[j] * 0x10);
k = 0;
nameOff_entryPath = nameOff + nameOffOff;
while (decryptedData[nameOff_entryPath + k] != 0)
{
k++;
}
sb.append(tmpname);
sb.append("/");
String temporaryName = new String(Arrays.copyOfRange(decryptedData, nameOff_entryPath, nameOff_entryPath + k));
if (!temporaryName.equals(""))
{
pathList.add(temporaryName);
}
stringBuilder.append(temporaryName);
stringBuilder.append("/");
}
path = sb.toString();
path = stringBuilder.toString();
}
//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));
}
}
public long getTotalContentSize() {
return totalContentSize;
}
public void setTotalContentSize(long totalContentSize) {
this.totalContentSize = totalContentSize;
}
public long getTotalContentSizeInNUS() {
public long getTotalContentSizeInNUS()
{
return totalContentSizeInNUS;
}
public void setTotalContentSizeInNUS(long totalContentSizeInNUS) {
this.totalContentSizeInNUS = totalContentSizeInNUS;
}
public List<FEntry> getFileEntries() {
public List<FEntry> getFileEntries()
{
return fileEntries;
}
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() {
public int getTotalEntries()
{
return totalEntries;
}
public void setTotalEntries(int totalEntries) {
this.totalEntries = totalEntries;
}
public int getDirEntries() {
return dirEntries;
}
public void setDirEntries(int dirEntries) {
this.dirEntries = dirEntries;
}
@Override
public String toString(){
return "entryCount: " + totalContentCount+ " entries: " + totalEntries;
public String toString()
{
return "entryCount: " + totalContentCount + " entries: " + totalEntries;
}
public int getFileCount() {
public int getFileCount()
{
int i = 0;
for(FEntry f: getFileEntries()){
if(!f.isDir())
for (FEntry f : getFileEntries())
{
if (!f.isDir())
{
i++;
}
return i;
}
public int getFileCountInNUS() {
int i = 0;
for(FEntry f: getFileEntries()){
if(!f.isDir() && f.isInNUSTitle())
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;
}
public Directory getContentDirectory() {
return contentDirectory;
}
public TitleMetaData getTmd() {
public TitleMetaData getTmd()
{
return tmd;
}
public void setTmd(TitleMetaData tmd) {
public void setTmd(TitleMetaData 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.Settings;
public class NUSTitle {
public class NUSTitle
{
private TitleMetaData tmd;
private TIK ticket;
private FST fst;
private long titleID;
public NUSTitle(long titleId,String key) throws ExitException{
public NUSTitle(long titleId, String key) throws ExitException
{
setTitleID(titleId);
try {
if(Settings.downloadContent){
File f = new File(getContentPath());
if(!f.exists())f.mkdir();
try
{
if (Settings.downloadContent)
{
File f = new File(getContentPath());
if (!f.exists())
{
Files.createDirectory(f.toPath());
}
}
if(Settings.downloadContent){
if (Settings.downloadContent)
{
File f = new File(getContentPath() + "/" + "tmd");
if(!(f.exists() && Settings.skipExistingTMDTICKET)){
if (!(f.exists() && Settings.skipExistingTMDTICKET))
{
System.out.println("Downloading TMD");
Downloader.getInstance().downloadTMD(titleId,getContentPath());
}else{
Downloader.getInstance().downloadTMD(titleId, getContentPath());
} else
{
System.out.println("Skipped download of TMD. Already existing");
}
f = new File(getContentPath() + "/" + "cetk");
if(!(f.exists() && Settings.skipExistingTMDTICKET)){
if(key == null){
if (!(f.exists() && Settings.skipExistingTMDTICKET))
{
if (key == null)
{
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");
}
}
if(Settings.useCachedFiles){
if (Settings.useCachedFiles)
{
File f = new File(getContentPath() + "/" + "tmd");
if(f.exists()){
if (f.exists())
{
System.out.println("Using cached TMD.");
tmd = new TitleMetaData(f);
}else{
} else
{
System.out.println("No cached TMD found.");
}
}
if(tmd == null){
if(Settings.downloadWhenCachedFilesMissingOrBroken){
if(Settings.useCachedFiles) System.out.println("Getting missing tmd from Server!");
if (tmd == null)
{
if (Settings.downloadWhenCachedFilesMissingOrBroken)
{
if (Settings.useCachedFiles)
{
System.out.println("Getting missing tmd from Server!");
}
tmd = new TitleMetaData(Downloader.getInstance().downloadTMDToByteArray(titleId));
}else{
} else
{
System.out.println("Downloading of missing files is not enabled. Exiting");
throw new ExitException("TMD missing.");
}
}
if(key != null){
}
if (key != null)
{
System.out.println("Using ticket from parameter.");
ticket = new TIK(key,titleId);
}else{
if(Settings.useCachedFiles){
ticket = new TIK(key, titleId);
} else
{
if (Settings.useCachedFiles)
{
File f = new File(getContentPath() + "/" + "cetk");
if(f.exists()){
if (f.exists())
{
System.out.println("Using cached cetk.");
ticket = new TIK(f,titleId);
}else{
ticket = new TIK(f, titleId);
} else
{
System.out.println("No cached ticket found.");
}
}
if(ticket == null){
if(Settings.downloadWhenCachedFilesMissingOrBroken){
if(Settings.useCachedFiles) System.out.println("getting missing ticket");
ticket = new TIK(Downloader.getInstance().downloadTicketToByteArray(titleId),tmd.titleID);
}else{
if (ticket == null)
{
if (Settings.downloadWhenCachedFilesMissingOrBroken)
{
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");
throw new ExitException("Ticket missing.");
}
}
}
if(Settings.downloadContent){
if (Settings.downloadContent)
{
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) + ")");
Downloader.getInstance().downloadContent(titleId,tmd.contents[0].ID,getContentPath());
}else{
if(f.length() != tmd.contents[0].size){
if(Settings.downloadWhenCachedFilesMissingOrBroken){
Downloader.getInstance().downloadContent(titleId, tmd.contents[0].ID, getContentPath());
} else
{
if (f.length() != tmd.contents[0].size)
{
if (Settings.downloadWhenCachedFilesMissingOrBroken)
{
System.out.println("FST already existing, but broken. Downloading it again.");
Downloader.getInstance().downloadContent(titleId,tmd.contents[0].ID,getContentPath());
}else{
Downloader.getInstance().downloadContent(titleId, tmd.contents[0].ID, getContentPath());
} else
{
System.out.println("FST already existing, but broken. No download allowed.");
throw new ExitException("FST missing.");
}
}else{
}
} else
{
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;
if(Settings.useCachedFiles){
if (Settings.useCachedFiles)
{
String path = getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app";
File f = new File(path);
if(f.exists()){
File f = new File(path);
if (f.exists())
{
System.out.println("Using cached FST");
Path file = Paths.get(path);
encryptedFST = Files.readAllBytes(file);
}else{
System.out.println("No cached FST (" + String.format("%08x", tmd.contents[0].ID) + ") found.");
} else
{
System.out.println("No cached FST (" + String.format("%08x", tmd.contents[0].ID) + ") found.");
}
}
if(encryptedFST == null){
if(Settings.downloadWhenCachedFilesMissingOrBroken){
if(Settings.useCachedFiles)System.out.println("Getting FST from server.");
encryptedFST = Downloader.getInstance().downloadContentToByteArray(titleId,tmd.contents[0].ID);
}else{
if (encryptedFST == null)
{
if (Settings.downloadWhenCachedFilesMissingOrBroken)
{
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");
throw new ExitException("");
}
}
}
byte[] decryptedFST = decryption.decrypt(encryptedFST);
fst = new FST(decryptedFST,tmd);
fst = new FST(decryptedFST, tmd);
tmd.setNUSTitle(this);
if(Settings.downloadContent){
if (Settings.downloadContent)
{
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 Decrypted Files: " + ((int)((fst.getTotalContentSizeInNUS()/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("Entries: " + fst.getTotalEntries());
System.out.println("Entries: " + fst.getFileCount());
System.out.println("Files in NUSTitle: " + fst.getFileCountInNUS());
} catch (IOException e) {
// TODO Auto-generated catch block
} catch (IOException e)
{
e.printStackTrace();
}
}
public FST getFst() {
public FST getFst()
{
return fst;
}
public void setFst(FST fst) {
this.fst = fst;
}
public TitleMetaData getTmd() {
return tmd;
}
public void setTmd(TitleMetaData tmd) {
this.tmd = tmd;
}
public TIK getTicket() {
public TIK getTicket()
{
return ticket;
}
public void setTicket(TIK ticket) {
this.ticket = ticket;
}
public long getTotalContentSize() {
public long getTotalContentSize()
{
return tmd.getTotalContentSize();
}
public String getContentPath() {
public String getContentPath()
{
return getContentPathPrefix() + String.format("%016X", getTitleID());
}
public String getContentPathPrefix() {
public String getContentPathPrefix()
{
return "tmp_";
}
private long getTitleID() {
private long getTitleID()
{
return titleID;
}
private void setTitleID(long titleId) {
this.titleID = titleId;
private void setTitleID(long 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 de.mas.jnustool.util.Decryption;
import de.mas.jnustool.util.Util;
import de.mas.jnustool.util.ConversionUtils;
public class TIK {
public static int KEY_LENGTH = 16;
private byte[] encryptedKey = new byte[16];
private byte[] decryptedKey = new byte[16];
public TIK(File cetk,long titleid) throws IOException{
parse(cetk);
calculateDecryptedKey(titleid);
}
public class TIK
{
private byte[] encryptedKey = new byte[16];
private byte[] decryptedKey = new byte[16];
public TIK(String ticketKey,long titleid) {
setEncryptedKey(ticketKey);
calculateDecryptedKey(titleid);
}
public TIK(File commonETicket, long titleID) throws IOException
{
parse(commonETicket);
calculateDecryptedKey(titleID);
}
public TIK(byte[] file, long titleID) throws IOException {
parse(file);
calculateDecryptedKey(titleID);
}
public TIK(String ticketKey, long titleID)
{
setEncryptedKey(ticketKey);
calculateDecryptedKey(titleID);
}
private void calculateDecryptedKey(long titleid) {
Decryption decryption = new Decryption(Util.commonKey,titleid);
decryptedKey = decryption.decrypt(encryptedKey);
}
public TIK(byte[] file, long titleID) throws IOException
{
parse(file);
calculateDecryptedKey(titleID);
}
private void parse(byte[] cetk) throws IOException {
System.arraycopy(cetk, 0x1bf, this.encryptedKey, 0,16);
}
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;
}
private void calculateDecryptedKey(long titleID)
{
Decryption decryption = new Decryption(ConversionUtils.commonKey, titleID);
decryptedKey = decryption.decrypt(encryptedKey);
}
public void setEncryptedKey(byte[] encryptedKey) {
this.encryptedKey = encryptedKey;
}
private void parse(byte[] commonETicketBytes) throws IOException
{
System.arraycopy(commonETicketBytes, 0x1bf, this.encryptedKey, 0, 16);
}
public byte[] getDecryptedKey() {
return decryptedKey;
}
private void parse(File commonETicket) throws IOException
{
RandomAccessFile f = new RandomAccessFile(commonETicket, "r");
f.seek(0x1bf);
f.read(this.encryptedKey, 0, 16);
f.close();
}
public void setDecryptedKey(byte[] decryptedKey) {
this.decryptedKey = decryptedKey;
}
public void setEncryptedKey(String key)
{
this.encryptedKey = ConversionUtils.hexStringToByteArray(key);
}
@Override
public String toString(){
return "encrypted key: " + Util.ByteArrayToString(encryptedKey)+ " decrypted key: " + Util.ByteArrayToString(decryptedKey);
}
}
public byte[] getDecryptedKey()
{
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.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.RandomAccessFile;
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.ExitException;
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 long totalContentSize;
public TitleMetaData(File tmd) throws IOException {
public TitleMetaData(File tmd) throws IOException
{
parse(tmd);
setTotalContentSize();
}
public TitleMetaData(byte[] downloadTMDToByteArray) throws IOException {
if(downloadTMDToByteArray != null){
File tempFile = File.createTempFile("bla","blubb");
public TitleMetaData(byte[] downloadTMDToByteArray) throws IOException
{
if (downloadTMDToByteArray != null)
{
File tempFile = File.createTempFile("bla", "blubb");
FileOutputStream fos = new FileOutputStream(tempFile);
fos.write(downloadTMDToByteArray);
fos.close();
parse(tempFile);
setTotalContentSize();
}else{
} else
{
System.err.println("Invalid TMD");
throw new IllegalArgumentException("Invalid TMD");
}
}
private void parse(File tmd) throws IOException {
RandomAccessFile f = new RandomAccessFile(tmd, "r");
private void parse(File tmd) throws IOException
{
RandomAccessFile f = new RandomAccessFile(tmd, "r");
f.seek(0);
this.signatureType = f.readInt();
f.read(signature, 0, 0x100);
f.seek(0x140);
f.read(issuer, 0, 0x40);
f.seek(0x180);
this.version = f.readByte();
this.CACRLVersion = f.readByte();
@ -89,123 +89,148 @@ public class TitleMetaData {
f.seek(0x1E4);
f.read(SHA2, 0, 32);
f.seek(0x204);
short indexOffset;
short commandCount;
for(int i =0;i<64;i++){
f.seek(0x204+(0x24*i));
indexOffset =f.readShort();
commandCount =f.readShort();
byte[] buffer = new byte[0x20]; // 16 0xB14
for (int i = 0; i < 64; i++)
{
f.seek(0x204 + (0x24 * i));
indexOffset = f.readShort();
commandCount = f.readShort();
byte[] buffer = new byte[0x20]; // 16 0xB14
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];
int ID; // 0 0xB04
short index; // 4 0xB08
short type; // 6 0xB0A
long size; // 8 0xB0C
for(int i =0;i<contentCount;i++){
f.seek(0xB04+(0x30*i));
int ID; // 0 0xB04
short index; // 4 0xB08
short type; // 6 0xB0A
long size; // 8 0xB0C
for (int i = 0; i < contentCount; i++)
{
f.seek(0xB04 + (0x30 * i));
ID = f.readInt();
index = f.readShort();
type = f.readShort();
size = f.readLong();
byte[] buffer = new byte[0x20]; // 16 0xB14
f.read(buffer,0, 0x20);
this.contents[i] = new Content(ID,index,type,size,buffer);
}
byte[] buffer = new byte[0x20]; // 16 0xB14
f.read(buffer, 0, 0x20);
this.contents[i] = new Content(ID, index, type, size, buffer);
}
f.close();
}
@Override
public String toString(){
public String toString()
{
StringBuilder sb = new StringBuilder();
sb.append("signatureType: " + signatureType +"\n");
sb.append("signature: " + Util.ByteArrayToString(signature) +"\n");
sb.append("issuer: " + Util.ByteArrayToString(issuer) +"\n");
sb.append("version: " + version +"\n");
sb.append("CACRLVersion: " + CACRLVersion +"\n");
sb.append("signerCRLVersion: " + signerCRLVersion +"\n");
sb.append("systemVersion: " + String.format("%8X",systemVersion) +"\n");
sb.append("titleID: " + String.format("%8X",titleID) +"\n");
sb.append("titleType: " + titleType +"\n");
sb.append("groupID: " + groupID +"\n");
sb.append("reserved: " + Util.ByteArrayToString(reserved) +"\n");
sb.append("accessRights: " + accessRights +"\n");
sb.append("titleVersion: " + titleVersion +"\n");
sb.append("contentCount: " + contentCount +"\n");
sb.append("bootIndex: " + bootIndex +"\n");
sb.append("SHA2: " + Util.ByteArrayToString(SHA2) +"\n");
sb.append("signatureType: ").append(signatureType).append("\n");
sb.append("signature: ").append(ConversionUtils.ByteArrayToString(signature)).append("\n");
sb.append("issuer: ").append(ConversionUtils.ByteArrayToString(issuer)).append("\n");
sb.append("version: ").append(version).append("\n");
sb.append("CACRLVersion: ").append(CACRLVersion).append("\n");
sb.append("signerCRLVersion: ").append(signerCRLVersion).append("\n");
sb.append("systemVersion: ").append(String.format("%8X", systemVersion)).append("\n");
sb.append("titleID: ").append(String.format("%8X", titleID)).append("\n");
sb.append("titleType: ").append(titleType).append("\n");
sb.append("groupID: ").append(groupID).append("\n");
sb.append("reserved: ").append(ConversionUtils.ByteArrayToString(reserved)).append("\n");
sb.append("accessRights: ").append(accessRights).append("\n");
sb.append("titleVersion: ").append(titleVersion).append("\n");
sb.append("contentCount: ").append(contentCount).append("\n");
sb.append("bootIndex: ").append(bootIndex).append("\n");
sb.append("SHA2: ").append(ConversionUtils.ByteArrayToString(SHA2)).append("\n");
sb.append("contentInfos: \n");
for(int i = 0; i<contents.length-1;i++){
sb.append(" " + contentInfos[i] +"\n");
for (int i = 0; i < contents.length - 1; i++)
{
sb.append(" ").append(contentInfoArray[i]).append("\n");
}
sb.append("contents: \n");
for(int i = 0; i<contents.length-1;i++){
sb.append(" " + contents[i] +"\n");
for (int i = 0; i < contents.length - 1; i++)
{
sb.append(" ").append(contents[i]).append("\n");
}
return sb.toString();
return sb.toString();
}
public void setTotalContentSize(){
public void setTotalContentSize()
{
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;
}
}
public long getTotalContentSize() {
}
public long getTotalContentSize()
{
return totalContentSize;
}
public void downloadContents() throws IOException, ExitException{
public void downloadContents() throws IOException, ExitException
{
String tmpPath = getContentPath();
File f = new File(tmpPath);
if(!f.exists())f.mkdir();
for(Content c : contents){
if(c != contents[0]){
f = new File(tmpPath + "/" + String.format("%08X", c.ID ) + ".app");
if(f.exists()){
if(f.length() == c.size){
System.out.println("Skipping Content: " + String.format("%08X", c.ID));
}else{
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.");
}
if (!f.exists())
{
Files.createDirectory(f.toPath());
}
for (Content c : contents)
{
if (c != contents[0])
{
f = new File(tmpPath + "/" + String.format("%08X", c.ID) + ".app");
if (f.exists())
{
if (f.length() == c.size)
{
System.out.println("Skipping Content: " + String.format("%08X", c.ID));
} else
{
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));
Downloader.getInstance().downloadContent(titleID,c.ID,tmpPath);
Downloader.getInstance().downloadContent(titleID, c.ID, tmpPath);
}
}
}
}
public String getContentPath() {
public String getContentPath()
{
return nus.getContentPath();
}
public NUSTitle getNUSTitle() {
public NUSTitle getNUSTitle()
{
return nus;
}
public void setNUSTitle(NUSTitle nus) {
public void setNUSTitle(NUSTitle nus)
{
this.nus = nus;
}
}
}

View File

@ -1,8 +1,4 @@
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.Component;
@ -28,240 +24,279 @@ import javax.swing.tree.TreePath;
import de.mas.jnustool.FEntry;
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
// It totally replaces the "selection" mechanism of the JTree
private class CheckedNode {
boolean isSelected;
boolean hasChildren;
boolean allChildrenSelected;
// Disabling toggling by double-click
this.setToggleClickCount(0);
// Overriding cell renderer by new one defined above
CheckBoxCellRenderer cellRenderer = new CheckBoxCellRenderer();
this.setCellRenderer(cellRenderer);
public CheckedNode(boolean isSelected_, boolean hasChildren_, boolean allChildrenSelected_) {
isSelected = isSelected_;
hasChildren = hasChildren_;
allChildrenSelected = allChildrenSelected_;
}
}
HashMap<TreePath, CheckedNode> nodesCheckingState;
HashSet<TreePath> checkedPaths = new HashSet<TreePath>();
// Overriding selection model by an empty one
DefaultTreeSelectionModel defaultTreeSelectionModel = new DefaultTreeSelectionModel()
{
private static final long serialVersionUID = -8190634240451667286L;
// Defining a new event type for the checking mechanism and preparing event-handling mechanism
protected EventListenerList listenerList = new EventListenerList();
// Totally disabling the selection mechanism
public void setSelectionPath(TreePath path)
{
}
public class CheckChangeEvent extends EventObject {
private static final long serialVersionUID = -8100230309044193368L;
public void addSelectionPath(TreePath path)
{
}
public CheckChangeEvent(Object source) {
super(source);
}
}
public void removeSelectionPath(TreePath path)
{
}
public interface CheckChangeEventListener extends EventListener {
public void checkStateChanged(CheckChangeEvent event);
}
public void setSelectionPaths(TreePath[] pPaths)
{
}
};
public void addCheckChangeEventListener(CheckChangeEventListener listener) {
listenerList.add(CheckChangeEventListener.class, listener);
}
public void removeCheckChangeEventListener(CheckChangeEventListener listener) {
listenerList.remove(CheckChangeEventListener.class, listener);
}
// 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();
}
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);
}
}
}
public void mouseEntered(MouseEvent arg0)
{
}
// Override
public void setModel(TreeModel newModel) {
super.setModel(newModel);
resetCheckingState();
}
public void mouseExited(MouseEvent arg0)
{
}
// New method that returns only the checked paths (totally ignores original "selection" mechanism)
public TreePath[] getCheckedPaths() {
return checkedPaths.toArray(new TreePath[checkedPaths.size()]);
}
public void mousePressed(MouseEvent arg0)
{
}
// Returns true in case that the node is selected, has children but not all of them are selected
public boolean isSelectedPartially(TreePath path) {
CheckedNode cn = nodesCheckingState.get(path);
return cn.isSelected && cn.hasChildren && !cn.allChildrenSelected;
}
public void mouseReleased(MouseEvent arg0)
{
}
});
private void resetCheckingState() {
nodesCheckingState = new HashMap<TreePath, CheckedNode>();
checkedPaths = new HashSet<TreePath>();
DefaultMutableTreeNode node = (DefaultMutableTreeNode)getModel().getRoot();
if (node == null) {
return;
}
addSubtreeToCheckingStateTracking(node);
}
this.setSelectionModel(defaultTreeSelectionModel);
}
// 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()));
// 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);
}
}
// 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/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;
import java.io.File;
import java.io.FileInputStream;
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 de.mas.jnustool.FEntry;
import de.mas.jnustool.TIK;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@ -17,354 +9,399 @@ import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.spec.IvParameterSpec;
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;
import de.mas.jnustool.TIK;
public class Decryption
{
private Cipher cipher2;
private byte[] decryptedKey;
public class Decryption {
Cipher cipher2;
public Decryption(TIK ticket){
public Decryption(TIK ticket)
{
this(ticket.getDecryptedKey());
}
public Decryption(byte[] decryptedKey){
this(decryptedKey,0);
public Decryption(byte[] decryptedKey)
{
this(decryptedKey, 0);
}
public Decryption(byte[] decryptedKey, long titleId) {
try {
public Decryption(byte[] decryptedKey, long titleId)
{
try
{
cipher2 = Cipher.getInstance("AES/CBC/NoPadding");
this.decryptedKey = decryptedKey;
init(titleId);
} catch (NoSuchAlgorithmException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} catch (NoSuchPaddingException e) {
// TODO Auto-generated catch block
} catch (NoSuchAlgorithmException | NoSuchPaddingException e)
{
e.printStackTrace();
}
}
private void init(byte[] IV)
{
init(decryptedKey, IV);
}
byte[] decryptedKey;
private void init(byte[] IV) {
init(decryptedKey,IV);
private void init(long titleID)
{
init(ByteBuffer.allocate(16).putLong(titleID).array());
}
private void init(long titleid) {
init(ByteBuffer.allocate(16).putLong(titleid).array());
}
public void init(byte[] decryptedKey,long titleid){
init(decryptedKey,ByteBuffer.allocate(16).putLong(titleid).array());
}
public void init(byte[] decryptedKey,byte[] iv){
try {
this.decryptedKey = decryptedKey;
public void init(byte[] decryptedKey, byte[] iv)
{
try
{
this.decryptedKey = decryptedKey;
SecretKeySpec secretKeySpec = new SecretKeySpec(decryptedKey, "AES");
cipher2.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(iv));
} catch (Exception e) {
// TODO Auto-generated catch block
cipher2.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(iv));
} catch (Exception e)
{
e.printStackTrace();
}
}
public byte[] decrypt(byte[] input){
try {
return cipher2.doFinal(input);
} catch (IllegalBlockSizeException | BadPaddingException e) {
// TODO Auto-generated catch block
public byte[] decrypt(byte[] input)
{
try
{
return cipher2.doFinal(input);
} catch (IllegalBlockSizeException | BadPaddingException e)
{
e.printStackTrace();
}
return input;
return input;
}
public byte[] decrypt(byte[] input,int len){
return decrypt(input,0,len);
}
public byte[] decrypt(byte[] input,int offset,int len){
try {
return cipher2.doFinal(input, offset, len);
} catch (IllegalBlockSizeException | BadPaddingException e) {
// TODO Auto-generated catch block
public byte[] decrypt(byte[] input, int offset, int len)
{
try
{
return cipher2.doFinal(input, offset, len);
} catch (IllegalBlockSizeException | BadPaddingException e)
{
e.printStackTrace();
}
return input;
return input;
}
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) {
if(IV != null) this.IV = IV;
public byte[] decryptFileChunk(byte[] blockBuffer, int offset, int blockSize, byte[] IV)
{
if (IV != null)
{
this.IV = IV;
}
init(this.IV);
byte[] output = decrypt(blockBuffer,offset,BLOCKSIZE);
this.IV = Arrays.copyOfRange(blockBuffer,BLOCKSIZE-16, BLOCKSIZE);
byte[] output = decrypt(blockBuffer, offset, blockSize);
this.IV = Arrays.copyOfRange(blockBuffer, blockSize - 16, blockSize);
return output;
}
byte[] hash = new byte[20];
byte[] h0 = new byte[20];
public byte[] decryptFileChunkHash(byte[] blockBuffer, int BLOCKSIZE, int block, int contentID){
if(BLOCKSIZE != 0x10000) throw new IllegalArgumentException("Blocksize not supported");
byte[] hash = new byte[20];
byte[] h0 = new byte[20];
public byte[] decryptFileChunkHash(byte[] blockBuffer, int blockSize, int block, int contentID)
{
if (blockSize != 0x10000)
{
throw new IllegalArgumentException("Block size not supported");
}
IV = new byte[16];
IV[1] = (byte)contentID;
byte[] hashes = decryptFileChunk(blockBuffer,0x0400,IV);
System.arraycopy(hashes, (int) (0x14*block), IV, 0, 16);
System.arraycopy(hashes, (int) (0x14*block), h0, 0, 20);
if( block == 0 )
IV[1] ^= (byte)contentID;
byte[] output = decryptFileChunk(blockBuffer,0x400,0xFC00,IV);
IV[1] = (byte) contentID;
byte[] hashes = decryptFileChunk(blockBuffer, 0x0400, IV);
System.arraycopy(hashes, 0x14 * block, IV, 0, 16);
System.arraycopy(hashes, 0x14 * block, h0, 0, 20);
if (block == 0)
{
IV[1] ^= (byte) contentID;
}
byte[] output = decryptFileChunk(blockBuffer, 0x400, 0xFC00, IV);
hash = hash(output);
if(block == 0){
if (block == 0)
{
assert hash != null;
hash[1] ^= contentID;
}
if(Arrays.equals(hash, h0)){
if (Arrays.equals(hash, h0))
{
//System.out.println("checksum right");
}
else{
} else
{
System.out.println("checksum failed");
System.out.println(Util.ByteArrayToString(hash));
System.out.println(Util.ByteArrayToString(h0));
System.out.println(ConversionUtils.ByteArrayToString(hash));
System.out.println(ConversionUtils.ByteArrayToString(h0));
throw new IllegalArgumentException("checksumfail");
}
return output;
}
public static byte[] hash(byte[] hashThis) {
try {
byte[] hash = new byte[20];
MessageDigest md = MessageDigest.getInstance("SHA-1");
hash = md.digest(hashThis);
return hash;
} catch (NoSuchAlgorithmException nsae) {
System.err.println("SHA-1 algorithm is not available...");
System.exit(2);
}
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);
public static byte[] hash(byte[] hashThis)
{
try
{
byte[] hash;
MessageDigest md = MessageDigest.getInstance("SHA-1");
outputStream.close();
inputSteam.close();
hash = md.digest(hashThis);
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{
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 = -1;
byte[] downloadBuffer;
byte[] encryptedBlockBuffer = new byte[BLOCKSIZE];
byte[] buffer = new byte[BLOCKSIZE];
int encryptedBytesInBuffer = 0;
int bufferPostion = 0;
byte[] tmp = new byte[BLOCKSIZE];
boolean lastPart = false;
long wrote = 0;
do{
downloadBuffer = new byte[BLOCKSIZE-bufferPostion];
bytesRead = inputSteam.read(downloadBuffer);
int bytesInBuffer = bytesRead + bufferPostion;
if(bytesRead ==-1){
lastPart = true;
}else{
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;
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;
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 end = false;
long totalDownloadSize = 0;
long wrote = 0;
boolean first = true;
do
{
downloadBuffer = new byte[blockSize - overflowSize];
bytesRead = inputSteam.read(downloadBuffer);
totalDownloadSize += bytesRead;
if (bytesRead == -1)
{
end = true;
}
//If downloaded BLOCKSIZE, 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)(0+soffset), (int)writeSize);
wrote +=writeSize;
encryptedBytesInBuffer = 0;
block++;
if( block >= 16 )
block = 0;
if( soffset > 0)
if (!end)
{
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 (!end)
{
writeSize = HASHBLOCKSIZE;
soffset = 0;
System.arraycopy(overflowBuffer, 0, blockBuffer, inBlockBuffer, bytesRead);
}
}
}while(wrote < toDownload.getFileLength() || lastPart);
inBlockBuffer += bytesRead;
}
if (inBlockBuffer == blockSize || end)
{
if (first)
{
first = false;
} else
{
IV = null;
}
byte[] output = decryptFileChunk(blockBuffer, blockSize, IV);
if ((wrote + inBlockBuffer) > toDownload.getFileLength())
{
inBlockBuffer = (int) (toDownload.getFileLength() - wrote);
}
wrote += inBlockBuffer;
outputStream.write(output, 0, inBlockBuffer);
inBlockBuffer = 0;
}
} while (totalDownloadSize < dlFileLength && !end);
outputStream.close();
inputSteam.close();
}
public void 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;
import de.mas.jnustool.FEntry;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import de.mas.jnustool.FEntry;
public class Downloader {
public class Downloader
{
private static Downloader instance;
public static Downloader getInstance(){
if(instance == null){
public static Downloader getInstance()
{
if (instance == null)
{
instance = new Downloader();
}
return instance;
return instance;
}
private Downloader(){
private Downloader()
{
}
public void downloadAndDecrypt(FEntry toDownload) throws IOException{
String URL = URL_BASE + "/" + String.format("%016X", toDownload.getTitleID()) + "/" + String.format("%08X", toDownload.getNUScontentID());
public void downloadAndDecrypt(FEntry toDownload) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", toDownload.getTitleID()) + "/" + String.format("%08X", toDownload.getNUSContentID());
URL url = new URL(URL);
String [] path = toDownload.getFullPath().split("/");
String[] path = toDownload.getFullPath().split("/");
boolean decryptWithHash = false;
if(!path[1].equals("code") && toDownload.isExtractWithHash()){
if (!path[1].equals("code") && toDownload.isExtractWithHash())
{
decryptWithHash = true;
}
HttpURLConnection connection =(HttpURLConnection) url.openConnection();
HttpURLConnection connection = (HttpURLConnection) url.openConnection();
long fileOffset = toDownload.getFileOffset();
if(decryptWithHash){
int BLOCKSIZE = 0x10000;
int HASHBLOCKSIZE = 0xFC00;
fileOffset = ((toDownload.getFileOffset() / HASHBLOCKSIZE) * BLOCKSIZE);
if (decryptWithHash)
{
int blockSize = 0x10000;
int hashBlockSize = 0xFC00;
fileOffset = ((toDownload.getFileOffset() / hashBlockSize) * blockSize);
}
connection.setRequestProperty("Range", "bytes=" + fileOffset +"-");
connection.connect();
Decryption decryption = new Decryption(toDownload.getTicket());
InputStream input = connection.getInputStream();
FileOutputStream outputStream = new FileOutputStream(String.format("%016X", toDownload.getTitleID()) +"/" + toDownload.getFullPath().substring(1, toDownload.getFullPath().length()));
if(!decryptWithHash){
decryption.decryptFile(input, outputStream, toDownload);
}else{
decryption.decryptFileHash(input, outputStream, toDownload);
}
connection.setRequestProperty("Range", "bytes=" + fileOffset + "-");
connection.connect();
Decryption decryption = new Decryption(toDownload.getTicket());
InputStream input = connection.getInputStream();
FileOutputStream outputStream = new FileOutputStream(String.format("%016X", toDownload.getTitleID()) + "/" + toDownload.getFullPath().substring(1, toDownload.getFullPath().length()));
if (!decryptWithHash)
{
decryption.decryptFile(input, outputStream, toDownload);
} else
{
decryption.decryptFileHash(input, outputStream, toDownload);
}
connection.disconnect();
}
connection.disconnect();
}
public static String URL_BASE = "";
public void downloadTMD(long titleID,int version,String path) throws IOException {
downloadTMD(titleID,path);
public void downloadTMD(long titleID, String path) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd";
downloadFile(URL, "tmd", path);
}
public void downloadTMD(long titleID,String path) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd";
downloadFile(URL, "tmd",path);
}
public void downloadFile(String fileURL,String filename,String tmpPath) throws IOException{
public void downloadFile(String fileURL, String filename, String tmpPath) throws IOException
{
int BUFFER_SIZE = 0x800;
URL url = new URL(fileURL);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
InputStream inputStream = httpConn.getInputStream();
if(tmpPath != null){
filename = tmpPath + "/" + filename;
}
FileOutputStream outputStream = new FileOutputStream(filename);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
int bytesRead = -1;
byte[] buffer = new byte[BUFFER_SIZE];
while ((bytesRead = inputStream.read(buffer)) != -1) {
outputStream.write(buffer, 0, bytesRead);
}
InputStream inputStream = httpConn.getInputStream();
if (tmpPath != null)
{
filename = tmpPath + "/" + filename;
}
outputStream.close();
inputStream.close();
httpConn.disconnect();
FileOutputStream outputStream = new FileOutputStream(filename);
int bytesRead;
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{
downloadFile(fileURL, filename,null);
public void downloadTicket(long titleID, String path) throws IOException
{
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";
downloadFile(URL, "cetk",path);
}
public void downloadContent(long titleID,int contentID) throws IOException {
downloadContent(titleID,contentID, null);
}
public byte[] downloadContentToByteArray(long titleID,int contentID) throws IOException {
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";
public byte[] downloadContentToByteArray(long titleID, int contentID) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
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;
URL url = new URL(fileURL);
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
int responseCode = httpConn.getResponseCode();
// always check HTTP response code first
byte[] file = null;
if (responseCode == HttpURLConnection.HTTP_OK) {
int contentLength = httpConn.getContentLength();
file = new byte[contentLength];
// always check HTTP response code first
InputStream inputStream = httpConn.getInputStream();
int bytesRead = -1;
byte[] buffer = new byte[BUFFER_SIZE];
int filePostion = 0;
while ((bytesRead = inputStream.read(buffer)) != -1) {
System.arraycopy(buffer, 0, file, filePostion,bytesRead);
filePostion+=bytesRead;
}
inputStream.close();
}else{
System.err.println("File not found: " + fileURL);
}
httpConn.disconnect();
return file;
HttpURLConnection httpConn = (HttpURLConnection) url.openConnection();
int responseCode = httpConn.getResponseCode();
// always check HTTP response code first
byte[] file = null;
if (responseCode == HttpURLConnection.HTTP_OK)
{
int contentLength = httpConn.getContentLength();
file = new byte[contentLength];
// always check HTTP response code first
InputStream inputStream = httpConn.getInputStream();
int bytesRead;
byte[] buffer = new byte[BUFFER_SIZE];
int filePosition = 0;
while ((bytesRead = inputStream.read(buffer)) != -1)
{
System.arraycopy(buffer, 0, file, filePosition, bytesRead);
filePosition += bytesRead;
}
inputStream.close();
} else
{
System.err.println("File not found: " + fileURL);
}
httpConn.disconnect();
return file;
}
public byte[] downloadTicketToByteArray(long titleID) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk";
public byte[] downloadTicketToByteArray(long titleID) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk";
return downloadFileToByteArray(URL);
}
public void downloadContent(long titleID,int contentID, String tmpPath) throws IOException {
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
downloadFile(URL, String.format("%08X", contentID) +".app",tmpPath);
public void downloadContent(long titleID, int contentID, String tmpPath) throws IOException
{
String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID);
downloadFile(URL, String.format("%08X", contentID) + ".app", tmpPath);
}
}
}

View File

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

View File

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