diff --git a/src/Decryption.java b/src/Decryption.java deleted file mode 100644 index f5da766..0000000 --- a/src/Decryption.java +++ /dev/null @@ -1,158 +0,0 @@ -import java.nio.ByteBuffer; -import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; -import java.util.Arrays; - -import javax.crypto.BadPaddingException; -import javax.crypto.Cipher; -import javax.crypto.IllegalBlockSizeException; -import javax.crypto.NoSuchPaddingException; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; - -public class Decryption { - Cipher cipher2; - - public Decryption(TIK ticket){ - this(ticket.getDecryptedKey()); - } - - public Decryption(byte[] decryptedKey){ - this(decryptedKey,0); - } - - 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 - e.printStackTrace(); - } - } - - - byte[] decryptedKey; - - - private void init(byte[] IV) { - init(decryptedKey,IV); - } - - 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; - SecretKeySpec secretKeySpec = new SecretKeySpec(decryptedKey, "AES"); - cipher2.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(iv)); - } catch (Exception e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - } - - public byte[] decrypt(byte[] input){ - try { - return cipher2.doFinal(input); - } catch (IllegalBlockSizeException | BadPaddingException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } - 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 - e.printStackTrace(); - } - 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 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); - 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"); - 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); - - hash = hash(output); - if(block == 0){ - - hash[1] ^= contentID; - - } - if(Arrays.equals(hash, h0)){ - //System.out.println("checksum right"); - } - else{ - System.out.println("checksum failed"); - System.out.println(Util.ByteArrayToString(hash)); - System.out.println(Util.ByteArrayToString(h0)); - } - 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; - } - - - - - - -} diff --git a/src/Downloader.java b/src/Downloader.java deleted file mode 100644 index ecded7a..0000000 --- a/src/Downloader.java +++ /dev/null @@ -1,352 +0,0 @@ -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; -import java.net.HttpURLConnection; -import java.net.URL; - -public class Downloader { - private static Downloader instance; - - public static Downloader getInstance(){ - if(instance == null){ - instance = new Downloader(); - } - return instance; - } - private Downloader(){ - - } - - public void downloadAndDecrypt(String URL,long fileOffset,long fileLength,FEntry toDownload,TIK ticket) throws IOException{ - URL url = new URL(URL); - - HttpURLConnection connection = - (HttpURLConnection) url.openConnection(); - - int BLOCKSIZE = 0x8000; - long dlFileLength = fileLength; - if(dlFileLength > (dlFileLength/BLOCKSIZE)*BLOCKSIZE){ - dlFileLength = ((dlFileLength/BLOCKSIZE)*BLOCKSIZE) +BLOCKSIZE; - } - - connection.setRequestProperty("Range", "bytes=" + fileOffset+"-"); - connection.connect(); - - - FileOutputStream outputStream = new FileOutputStream(String.format("%016X", titleID) +"/" + toDownload.getFullPath().substring(1, toDownload.getFullPath().length())); - InputStream input = connection.getInputStream(); - - 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; - Decryption decryption = new Decryption(ticket); - boolean first = true; - do{ - - downloadBuffer = new byte[BLOCKSIZE-overflowsize]; - - bytesRead = input.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 =decryption.decryptFileChunk(blockBuffer,BLOCKSIZE,IV); - - if((wrote + inBlockBuffer) > fileLength){ - inBlockBuffer = (int) (fileLength- wrote); - } - - wrote += inBlockBuffer; - outputStream.write(output, 0, inBlockBuffer); - - inBlockBuffer = 0; - } - - }while(downloadTotalsize < dlFileLength && !endd); - - outputStream.close(); - input.close(); - - connection.disconnect(); - } - - public void downloadAndDecryptHash(String URL,long fileOffset,long fileLength,FEntry toDownload,TIK ticket) throws IOException{ - int BLOCKSIZE = 0x10000; - int HASHBLOCKSIZE = 0xFC00; - long writeSize = HASHBLOCKSIZE; // Hash block size - - long block = (fileOffset / HASHBLOCKSIZE) & 0xF; - - long soffset = fileOffset - (fileOffset / HASHBLOCKSIZE * HASHBLOCKSIZE); - fileOffset = ((fileOffset / HASHBLOCKSIZE) * BLOCKSIZE); - - long size = fileLength; - - if( soffset+size > writeSize ) - writeSize = writeSize - soffset; - - URL url = new URL(URL); - - HttpURLConnection connection = (HttpURLConnection) url.openConnection(); - - connection.setRequestProperty("Range", "bytes=" + fileOffset+"-"); - connection.connect(); - - FileOutputStream outputStream = new FileOutputStream(String.format("%016X", titleID) +"/" +toDownload.getFullPath().substring(1, toDownload.getFullPath().length())); - InputStream input = connection.getInputStream(); - - 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; - Decryption decryption = new Decryption(ticket); - do{ - downloadBuffer = new byte[BLOCKSIZE-bufferPostion]; - bytesRead = input.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; - } - - //If downloaded BLOCKSIZE, or file at the end: Decrypt! - if(encryptedBytesInBuffer == BLOCKSIZE || lastPart){ - - if( writeSize > size ) - writeSize = size; - - byte[] output = decryption.decryptFileChunkHash(encryptedBlockBuffer, BLOCKSIZE, (int) block,toDownload.getContentID()); - - if((wrote + writeSize) > fileLength){ - writeSize = (int) (fileLength- wrote); - } - - outputStream.write(output, (int)(0+soffset), (int)writeSize); - wrote +=writeSize; - encryptedBytesInBuffer = 0; - - block++; - if( block >= 16 ) - block = 0; - - if( soffset > 0) - { - writeSize = HASHBLOCKSIZE; - soffset = 0; - } - } - }while(wrote < fileLength || lastPart); - - outputStream.close(); - input.close(); - connection.disconnect(); - } - - public long titleID =0; - public TIK ticket = null; - public void download( FEntry toDownload) { - File f = new File (String.format("%016X", titleID)); - if(!f.exists())f.mkdir(); - - f = new File(String.format("%016X", titleID) +"/" +toDownload.getFullPath().substring(1, toDownload.getFullPath().length())); - if(f.exists()){ - if(f.length() == toDownload.getFileLength()){ - System.out.println("Skipping: " + String.format("%8.2f MB ", toDownload.getFileLength()/1024.0/1024.0) + toDownload.getFullPath()); - return; - } - } - - - System.out.println("Downloading: " + String.format("%8.2f MB ", toDownload.getFileLength()/1024.0/1024.0) + toDownload.getFullPath()); - - String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", toDownload.getNUScontentID()); - String [] path = toDownload.getFullPath().split("/"); - - String folder = String.format("%016X", titleID) +"/"; - File folder_ = null; - for(int i = 0;i pathList) { + boolean in_nus_title, boolean extract_withHash, List pathList,FST fst) { setPath(path); setFileName(filename); setContentID(contentID); @@ -32,6 +39,7 @@ public class FEntry { setExtractWithHash(extract_withHash); setNUScontentID(NUScontentID); setPathList(pathList); + this.fst = fst; } public boolean isDir() { @@ -114,9 +122,41 @@ public class FEntry { private void setNUScontentID(int nUScontentID) { NUScontentID = nUScontentID; } + + private void createFolder() { + long titleID = fst.getTmd().titleID; + 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 1 && args[1].length() == 32){ key = args[1].substring(0, 32); } - NUSGUI m = new NUSGUI(new NUSTitle(titleID, key)); + NUSGUI m = new NUSGUI(new NUSTitle(titleID, key), null); m.setVisible(true); }else{ System.out.println("Need parameters: TITLEID [KEY]"); diff --git a/src/TIK.java b/src/de/mas/jnustool/TIK.java similarity index 94% rename from src/TIK.java rename to src/de/mas/jnustool/TIK.java index 1f47ba9..76c0aed 100644 --- a/src/TIK.java +++ b/src/de/mas/jnustool/TIK.java @@ -1,7 +1,12 @@ +package de.mas.jnustool; + import java.io.File; import java.io.IOException; import java.io.RandomAccessFile; +import de.mas.jnustool.util.Decryption; +import de.mas.jnustool.util.Util; + public class TIK { public static int KEY_LENGTH = 16; private byte[] encryptedKey = new byte[16]; diff --git a/src/TitleDownloader.java b/src/de/mas/jnustool/TitleDownloader.java similarity index 64% rename from src/TitleDownloader.java rename to src/de/mas/jnustool/TitleDownloader.java index 1ba1b19..3b59de5 100644 --- a/src/TitleDownloader.java +++ b/src/de/mas/jnustool/TitleDownloader.java @@ -1,3 +1,5 @@ +package de.mas.jnustool; + import java.util.concurrent.Callable; public class TitleDownloader implements Callable @@ -12,10 +14,10 @@ public class TitleDownloader implements Callable } -@Override -public Integer call() throws Exception { - f.download(); - return null; -} + @Override + public Integer call() throws Exception { + f.downloadAndDecrypt(); + return null; + } } diff --git a/src/TitleMetaData.java b/src/de/mas/jnustool/TitleMetaData.java similarity index 96% rename from src/TitleMetaData.java rename to src/de/mas/jnustool/TitleMetaData.java index 9c15f49..b9f0d9f 100644 --- a/src/TitleMetaData.java +++ b/src/de/mas/jnustool/TitleMetaData.java @@ -1,8 +1,12 @@ +package de.mas.jnustool; + import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.RandomAccessFile; +import de.mas.jnustool.util.Util; + public class TitleMetaData { int signatureType; // 0x000 byte[] signature = new byte[0x100]; // 0x004 @@ -23,6 +27,7 @@ public class TitleMetaData { ContentInfo[] contentInfos = new ContentInfo[64]; // 0x1E4 Content[] contents; // 0x1E4 + private FST fst; private long totalContentSize; @@ -148,5 +153,12 @@ public class TitleMetaData { return totalContentSize; } + public FST getFst() { + return fst; + } + + public void setFst(FST fst) { + this.fst = fst; + } } diff --git a/src/JCheckBoxTree.java b/src/de/mas/jnustool/gui/JCheckBoxTree.java similarity index 96% rename from src/JCheckBoxTree.java rename to src/de/mas/jnustool/gui/JCheckBoxTree.java index d27e144..ac93305 100644 --- a/src/JCheckBoxTree.java +++ b/src/de/mas/jnustool/gui/JCheckBoxTree.java @@ -1,3 +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 @@ -24,6 +25,9 @@ import javax.swing.tree.TreeModel; import javax.swing.tree.TreeNode; import javax.swing.tree.TreePath; +import de.mas.jnustool.FEntry; +import de.mas.jnustool.NUSTitle; + public class JCheckBoxTree extends JTree { private static final long serialVersionUID = -4194122328392241790L; @@ -158,19 +162,8 @@ public class JCheckBoxTree extends JTree { super(); - - - - - - /* - parent.add(new DefaultMutableTreeNode("hot dogs")); - parent.add(new DefaultMutableTreeNode("pizza")); - parent.add(new DefaultMutableTreeNode("ravioli")); - parent.add(new DefaultMutableTreeNode("bananas"));*/ - //return ; - setModel(new DefaultTreeModel(nus.getFst().getDirectory().getNodes())); + setModel(new DefaultTreeModel(nus.getFst().getFSTDirectory().getNodes())); // Disabling toggling by double-click this.setToggleClickCount(0); diff --git a/src/NUSGUI.java b/src/de/mas/jnustool/gui/NUSGUI.java similarity index 92% rename from src/NUSGUI.java rename to src/de/mas/jnustool/gui/NUSGUI.java index d579561..c33db63 100644 --- a/src/NUSGUI.java +++ b/src/de/mas/jnustool/gui/NUSGUI.java @@ -1,3 +1,5 @@ +package de.mas.jnustool.gui; + import java.awt.BorderLayout; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; @@ -11,11 +13,16 @@ import javax.swing.JScrollPane; import javax.swing.tree.DefaultMutableTreeNode; import javax.swing.tree.TreePath; +import de.mas.jnustool.Settings; +import de.mas.jnustool.FEntry; +import de.mas.jnustool.NUSTitle; +import de.mas.jnustool.TitleDownloader; + public class NUSGUI extends JFrame { private static final long serialVersionUID = 4648172894076113183L; - public NUSGUI(NUSTitle nus) { + public NUSGUI(NUSTitle nus,Settings mode) { super(); setSize(800, 600); getContentPane().setLayout(new BorderLayout(0, 0)); diff --git a/src/de/mas/jnustool/util/Decryption.java b/src/de/mas/jnustool/util/Decryption.java new file mode 100644 index 0000000..bcf26c6 --- /dev/null +++ b/src/de/mas/jnustool/util/Decryption.java @@ -0,0 +1,341 @@ +package de.mas.jnustool.util; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.nio.ByteBuffer; +import java.security.MessageDigest; +import java.security.NoSuchAlgorithmException; +import java.util.Arrays; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +import de.mas.jnustool.FEntry; +import de.mas.jnustool.TIK; + +public class Decryption { + Cipher cipher2; + + public Decryption(TIK ticket){ + this(ticket.getDecryptedKey()); + } + + public Decryption(byte[] decryptedKey){ + this(decryptedKey,0); + } + + 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 + e.printStackTrace(); + } + } + + + byte[] decryptedKey; + + + private void init(byte[] IV) { + init(decryptedKey,IV); + } + + 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; + SecretKeySpec secretKeySpec = new SecretKeySpec(decryptedKey, "AES"); + cipher2.init(Cipher.DECRYPT_MODE, secretKeySpec, new IvParameterSpec(iv)); + } catch (Exception e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + + public byte[] decrypt(byte[] input){ + try { + return cipher2.doFinal(input); + } catch (IllegalBlockSizeException | BadPaddingException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + 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 + e.printStackTrace(); + } + 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 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); + 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"); + 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); + + hash = hash(output); + if(block == 0){ + + hash[1] ^= contentID; + + } + if(Arrays.equals(hash, h0)){ + //System.out.println("checksum right"); + } + else{ + System.out.println("checksum failed"); + System.out.println(Util.ByteArrayToString(hash)); + System.out.println(Util.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); + + outputStream.close(); + inputSteam.close(); + } + + public void decryptFileHash(InputStream inputSteam, OutputStream outputStream,FEntry toDownload) throws IOException{ + int BLOCKSIZE = 0x10000; + int HASHBLOCKSIZE = 0xFC00; + long writeSize = HASHBLOCKSIZE; // Hash block size + + long block = (toDownload.getFileOffset() / HASHBLOCKSIZE) & 0xF; + + long soffset = toDownload.getFileOffset() - (toDownload.getFileOffset() / HASHBLOCKSIZE * HASHBLOCKSIZE); + + long size = toDownload.getFileLength(); + + if( soffset+size > writeSize ) + writeSize = writeSize - soffset; + + int bytesRead = -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; + } + + //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) + { + writeSize = HASHBLOCKSIZE; + soffset = 0; + } + } + }while(wrote < toDownload.getFileLength() || lastPart); + + outputStream.close(); + inputSteam.close(); + + } + + + + + +} diff --git a/src/de/mas/jnustool/util/Downloader.java b/src/de/mas/jnustool/util/Downloader.java new file mode 100644 index 0000000..214426c --- /dev/null +++ b/src/de/mas/jnustool/util/Downloader.java @@ -0,0 +1,149 @@ +package de.mas.jnustool.util; + +import java.io.File; +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; +import de.mas.jnustool.TIK; + +public class Downloader { + private static Downloader instance; + + public static Downloader getInstance(){ + if(instance == null){ + instance = new Downloader(); + } + return instance; + } + private Downloader(){ + + } + + public long titleID =0; + public TIK ticket = null; + + public void downloadAndDecrypt(FEntry toDownload) throws IOException{ + String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", toDownload.getNUScontentID()); + URL url = new URL(URL); + String [] path = toDownload.getFullPath().split("/"); + boolean decryptWithHash = false; + if(!path[1].equals("code") && toDownload.isExtractWithHash()){ + decryptWithHash = true; + } + HttpURLConnection connection =(HttpURLConnection) url.openConnection(); + long fileOffset = toDownload.getFileOffset(); + + if(decryptWithHash){ + int BLOCKSIZE = 0x10000; + int HASHBLOCKSIZE = 0xFC00; + fileOffset = ((toDownload.getFileOffset() / HASHBLOCKSIZE) * BLOCKSIZE); + + } + + connection.setRequestProperty("Range", "bytes=" + fileOffset +"-"); + + connection.connect(); + + Decryption decryption = new Decryption(ticket); + + InputStream input = connection.getInputStream(); + FileOutputStream outputStream = new FileOutputStream(String.format("%016X", titleID) +"/" + toDownload.getFullPath().substring(1, toDownload.getFullPath().length())); + if(!decryptWithHash){ + decryption.decryptFile(input, outputStream, toDownload); + }else{ + decryption.decryptFileHash(input, outputStream, toDownload); + } + + connection.disconnect(); + } + + public static String URL_BASE = ""; + + public void downloadTMD(int version) throws IOException { + downloadTMD(); + } + public void downloadTMD() throws IOException { + String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/tmd"; + downloadFile(URL, "tmd"); + } + public void downloadFile(String fileURL,String filename) throws IOException{ + int BUFFER_SIZE = 0x800; + URL url = new URL(fileURL); + HttpURLConnection httpConn = (HttpURLConnection) url.openConnection(); + + InputStream inputStream = httpConn.getInputStream(); + + FileOutputStream outputStream = new FileOutputStream(filename); + + int bytesRead = -1; + 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 downloadTicket() throws IOException { + String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk"; + downloadFile(URL, "cetk"); + } + public void downloadContent(int contentID) throws IOException { + String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID); + downloadFile(URL, String.format("%08X", contentID)); + + } + public byte[] downloadContentToByteArray(int contentID) throws IOException { + String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/" + String.format("%08X", contentID); + return downloadFileToByteArray(URL); + } + public byte[] downloadTMDToByteArray() 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; + + } + public byte[] downloadTicketToByteArray() throws IOException { + String URL = URL_BASE + "/" + String.format("%016X", titleID) + "/cetk"; + return downloadFileToByteArray(URL); + } + +} diff --git a/src/Util.java b/src/de/mas/jnustool/util/Util.java similarity index 96% rename from src/Util.java rename to src/de/mas/jnustool/util/Util.java index 683bac6..f4a366f 100644 --- a/src/Util.java +++ b/src/de/mas/jnustool/util/Util.java @@ -1,9 +1,12 @@ +package de.mas.jnustool.util; + import java.math.BigInteger; import java.nio.ByteBuffer; import java.util.Arrays; public class Util { - static byte[] commonKey; + + public static byte[] commonKey; public static byte[] hexStringToByteArray(String s) { int len = s.length();