diff --git a/.classpath b/.classpath deleted file mode 100644 index fceb480..0000000 --- a/.classpath +++ /dev/null @@ -1,6 +0,0 @@ - - - - - - diff --git a/.gitattributes b/.gitattributes deleted file mode 100644 index bdb0cab..0000000 --- a/.gitattributes +++ /dev/null @@ -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 diff --git a/.project b/.project deleted file mode 100644 index 51d1814..0000000 --- a/.project +++ /dev/null @@ -1,17 +0,0 @@ - - - JNusTool - - - - - - org.eclipse.jdt.core.javabuilder - - - - - - org.eclipse.jdt.core.javanature - - diff --git a/.settings/org.eclipse.core.resources.prefs b/.settings/org.eclipse.core.resources.prefs deleted file mode 100644 index 82bae0f..0000000 --- a/.settings/org.eclipse.core.resources.prefs +++ /dev/null @@ -1,2 +0,0 @@ -eclipse.preferences.version=1 -encoding//src/Util.java=UTF-8 diff --git a/.settings/org.eclipse.jdt.core.prefs b/.settings/org.eclipse.jdt.core.prefs deleted file mode 100644 index 3a21537..0000000 --- a/.settings/org.eclipse.jdt.core.prefs +++ /dev/null @@ -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 diff --git a/jar/JNUSTool.jar b/jar/JNUSTool.jar deleted file mode 100644 index 2aeef33..0000000 Binary files a/jar/JNUSTool.jar and /dev/null differ diff --git a/jar/JNUSTool_Java7.jar b/jar/JNUSTool_Java7.jar deleted file mode 100644 index 4b7917a..0000000 Binary files a/jar/JNUSTool_Java7.jar and /dev/null differ diff --git a/jar/config b/jar/config deleted file mode 100644 index 3814033..0000000 --- a/jar/config +++ /dev/null @@ -1,2 +0,0 @@ -http://ccs.cdn.wup.shop.nintendo.net/ccs/download -[COMMONKEY] \ No newline at end of file diff --git a/src/de/mas/jnustool/Content.java b/src/de/mas/jnustool/Content.java index 8fd28ca..741130a 100644 --- a/src/de/mas/jnustool/Content.java +++ b/src/de/mas/jnustool/Content.java @@ -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); } } diff --git a/src/de/mas/jnustool/ContentInfo.java b/src/de/mas/jnustool/ContentInfo.java index add9032..4b6c19c 100644 --- a/src/de/mas/jnustool/ContentInfo.java +++ b/src/de/mas/jnustool/ContentInfo.java @@ -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); } -} +} \ No newline at end of file diff --git a/src/de/mas/jnustool/Directory.java b/src/de/mas/jnustool/Directory.java index 75bc4e1..cc8f4d5 100644 --- a/src/de/mas/jnustool/Directory.java +++ b/src/de/mas/jnustool/Directory.java @@ -5,89 +5,85 @@ import java.util.TreeMap; import javax.swing.tree.DefaultMutableTreeNode; -public class Directory { +public class Directory +{ String name = ""; - TreeMap folder = new TreeMap<>(); - TreeMap files = new TreeMap<>(); - - public Directory get(String s){ + TreeMap folder = new TreeMap<>(); + TreeMap 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 getFolder() { - return folder.values(); + + public Collection getFolder() + { + return folder.values(); } - - - public Collection getFiles() { - return files.values(); - } - - public void setFiles(TreeMap files) { - this.files = files; + public Collection 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; } - - -} +} \ No newline at end of file diff --git a/src/de/mas/jnustool/FEntry.java b/src/de/mas/jnustool/FEntry.java index b19b9f5..57b8815 100644 --- a/src/de/mas/jnustool/FEntry.java +++ b/src/de/mas/jnustool/FEntry.java @@ -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 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 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 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 getPathList() { + public List getPathList() + { return pathList; } - public void setPathList(List pathList) { + public void setPathList(List 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(); } - - - - - -} +} \ No newline at end of file diff --git a/src/de/mas/jnustool/FEntryDownloader.java b/src/de/mas/jnustool/FEntryDownloader.java index 755927b..886f8c9 100644 --- a/src/de/mas/jnustool/FEntryDownloader.java +++ b/src/de/mas/jnustool/FEntryDownloader.java @@ -4,19 +4,22 @@ import java.util.concurrent.Callable; public class FEntryDownloader implements Callable { - 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; + } +} \ No newline at end of file diff --git a/src/de/mas/jnustool/FST.java b/src/de/mas/jnustool/FST.java index b4887cf..a555ff9 100644 --- a/src/de/mas/jnustool/FST.java +++ b/src/de/mas/jnustool/FST.java @@ -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 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 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 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 getFileEntries() { + public List getFileEntries() + { return fileEntries; } - - public void setFileEntries(List 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; } - - - -} +} \ No newline at end of file diff --git a/src/de/mas/jnustool/NUSTitle.java b/src/de/mas/jnustool/NUSTitle.java index a4b97e0..1c443af 100644 --- a/src/de/mas/jnustool/NUSTitle.java +++ b/src/de/mas/jnustool/NUSTitle.java @@ -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; } - - - -} +} \ No newline at end of file diff --git a/src/de/mas/jnustool/Starter.java b/src/de/mas/jnustool/Starter.java deleted file mode 100644 index b3f4a54..0000000 --- a/src/de/mas/jnustool/Starter.java +++ /dev/null @@ -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(); - - } - -} diff --git a/src/de/mas/jnustool/TIK.java b/src/de/mas/jnustool/TIK.java index 76c0aed..c54a18e 100644 --- a/src/de/mas/jnustool/TIK.java +++ b/src/de/mas/jnustool/TIK.java @@ -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); + } +} \ No newline at end of file diff --git a/src/de/mas/jnustool/TitleMetaData.java b/src/de/mas/jnustool/TitleMetaData.java index 908348c..90993c8 100644 --- a/src/de/mas/jnustool/TitleMetaData.java +++ b/src/de/mas/jnustool/TitleMetaData.java @@ -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 nodesCheckingState; + HashSet checkedPaths = new HashSet(); + + // 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 nodesCheckingState; - HashSet checkedPaths = new HashSet(); + // 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(); - checkedPaths = new HashSet(); - 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); + } + } } \ No newline at end of file diff --git a/src/de/mas/jnustool/gui/NUSGUI.java b/src/de/mas/jnustool/gui/NUSGUI.java deleted file mode 100644 index 5c061f7..0000000 --- a/src/de/mas/jnustool/gui/NUSGUI.java +++ /dev/null @@ -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 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); - } -} \ No newline at end of file diff --git a/src/de/mas/jnustool/util/Decryption.java b/src/de/mas/jnustool/util/Decryption.java index dfd59b4..5e92d0e 100644 --- a/src/de/mas/jnustool/util/Decryption.java +++ b/src/de/mas/jnustool/util/Decryption.java @@ -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); + } + } +} \ No newline at end of file diff --git a/src/de/mas/jnustool/util/Downloader.java b/src/de/mas/jnustool/util/Downloader.java index 8c2e503..6ab0594 100644 --- a/src/de/mas/jnustool/util/Downloader.java +++ b/src/de/mas/jnustool/util/Downloader.java @@ -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); } - - -} +} \ No newline at end of file diff --git a/src/de/mas/jnustool/util/ExitException.java b/src/de/mas/jnustool/util/ExitException.java index 54eae42..e66db24 100644 --- a/src/de/mas/jnustool/util/ExitException.java +++ b/src/de/mas/jnustool/util/ExitException.java @@ -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; - -} +} \ No newline at end of file diff --git a/src/de/mas/jnustool/util/Settings.java b/src/de/mas/jnustool/util/Settings.java index c2f1fdf..a2c6e71 100644 --- a/src/de/mas/jnustool/util/Settings.java +++ b/src/de/mas/jnustool/util/Settings.java @@ -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; - -} +} \ No newline at end of file diff --git a/src/de/mas/jnustool/util/Util.java b/src/de/mas/jnustool/util/Util.java deleted file mode 100644 index f4a366f..0000000 --- a/src/de/mas/jnustool/util/Util.java +++ /dev/null @@ -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; - } - } -}