diff --git a/.gitignore b/.gitignore index af0b415..820ffc1 100644 --- a/.gitignore +++ b/.gitignore @@ -50,4 +50,5 @@ bin /config /tmp* UpdateGrabber.java -updatetitles.csv \ No newline at end of file +updatetitles.csv +src/de/mas/jnustool/StarterPRIATE.java \ No newline at end of file diff --git a/jar/JNUSTool.jar b/jar/JNUSTool.jar index e58ef4d..718d764 100644 Binary files a/jar/JNUSTool.jar and b/jar/JNUSTool.jar differ diff --git a/src/de/mas/jnustool/FEntry.java b/src/de/mas/jnustool/FEntry.java index 83fd6d9..3c8fe49 100644 --- a/src/de/mas/jnustool/FEntry.java +++ b/src/de/mas/jnustool/FEntry.java @@ -155,13 +155,16 @@ public class FEntry { return folder; } - public void downloadAndDecrypt() { + public void downloadAndDecrypt(Progress progress) { createFolder(); long titleID = getTitleID(); File f = new File(String.format("%016X", titleID) +"/" +getFullPath().substring(1, getFullPath().length())); if(f.exists()){ if(f.length() == getFileLength()){ Logger.log("Skipping: " + String.format("%8.2f MB ",getFileLength()/1024.0/1024.0) + getFullPath()); + if(progress != null){ + progress.finish(); + } return; } } @@ -172,6 +175,7 @@ public class FEntry { if(f.length() == fst.getTmd().contents[this.getContentID()].size){ Logger.log("Decrypting: " + String.format("%8.2f MB ", getFileLength()/1024.0/1024.0) + getFullPath()); Decryption decrypt = new Decryption(fst.getTmd().getNUSTitle().getTicket()); + decrypt.setProgressListener(progress); decrypt.decrypt(this,getDownloadPath()); return; }else{ @@ -203,7 +207,7 @@ public class FEntry { } } Logger.log("Downloading: " + String.format("%8.2f MB ", getFileLength()/1024.0/1024.0) + getFullPath()); - Downloader.getInstance().downloadAndDecrypt(this); + Downloader.getInstance().downloadAndDecrypt(this,progress); } catch (IOException e) { diff --git a/src/de/mas/jnustool/FEntryDownloader.java b/src/de/mas/jnustool/FEntryDownloader.java index 755927b..4dac965 100644 --- a/src/de/mas/jnustool/FEntryDownloader.java +++ b/src/de/mas/jnustool/FEntryDownloader.java @@ -5,17 +5,26 @@ import java.util.concurrent.Callable; public class FEntryDownloader implements Callable { FEntry f; + Progress progress = null; public void setTitle(FEntry f){ this.f = f; } - public FEntryDownloader(FEntry f){ + public FEntryDownloader(FEntry f,Progress fatherProgress){ setTitle(f); + createProgressListener(fatherProgress); } + private void createProgressListener(Progress fatherProgress) { + if(fatherProgress != null){ + progress = new Progress(); + progress.setTotal(f.getFileLength()); + fatherProgress.add(progress); + } + } @Override public Integer call() throws Exception { - f.downloadAndDecrypt(); + f.downloadAndDecrypt(progress); return null; } diff --git a/src/de/mas/jnustool/FST.java b/src/de/mas/jnustool/FST.java index 0d82986..5fb7b89 100644 --- a/src/de/mas/jnustool/FST.java +++ b/src/de/mas/jnustool/FST.java @@ -19,6 +19,7 @@ public class FST { int totalEntries = 0; int dirEntries = 0; public FEntry metaFENtry; + public List metaFolder = new ArrayList<>(); private Directory FSTDirectory = new Directory("root"); private Directory contentDirectory = new Directory("root"); @@ -165,6 +166,8 @@ public class FST { this.totalContentSize += fileLength; if(in_nus_title)this.totalContentSizeInNUS += fileLength; + boolean metafolder = false; + List pathList = new ArrayList<>(); //getting the full path of entry if(dir) @@ -180,7 +183,7 @@ public class FST { StringBuilder sb = new StringBuilder(); int k = 0; int nameoffoff,nameoff_entrypath; - + for( j=0; j getMetaFolder() { + return metaFolder; + } + public List getFileEntries() { return fileEntries; diff --git a/src/de/mas/jnustool/Logger.java b/src/de/mas/jnustool/Logger.java index 23fdfa5..d0046f9 100644 --- a/src/de/mas/jnustool/Logger.java +++ b/src/de/mas/jnustool/Logger.java @@ -1,5 +1,8 @@ package de.mas.jnustool; +import javax.swing.JFrame; +import javax.swing.JOptionPane; + import de.mas.jnustool.gui.NUSGUI; public class Logger { @@ -7,7 +10,12 @@ public class Logger { public static void log(String string) { NUSGUI.output.append(string + "\n"); NUSGUI.output.setCaretPosition(NUSGUI.output.getDocument().getLength()); + //System.out.println(string); } + + public static void messageBox(String string) { + JOptionPane.showMessageDialog(new JFrame(), string); + } } diff --git a/src/de/mas/jnustool/NUSTitle.java b/src/de/mas/jnustool/NUSTitle.java index 0a7a784..f66e8a7 100644 --- a/src/de/mas/jnustool/NUSTitle.java +++ b/src/de/mas/jnustool/NUSTitle.java @@ -214,15 +214,14 @@ public class NUSTitle { this.titleID = titleId; } - public void decryptFEntries(List list) { + public void decryptFEntries(List list,Progress progress) { ForkJoinPool pool = ForkJoinPool.commonPool(); List dlList = new ArrayList<>(); for(FEntry f : list){ if(!f.isDir() && f.isInNUSTitle()){ - dlList.add(new FEntryDownloader(f)); + dlList.add(new FEntryDownloader(f,progress)); } } - pool.invokeAll(dlList); Logger.log("Done!"); } diff --git a/src/de/mas/jnustool/Progress.java b/src/de/mas/jnustool/Progress.java new file mode 100644 index 0000000..36c2870 --- /dev/null +++ b/src/de/mas/jnustool/Progress.java @@ -0,0 +1,92 @@ +package de.mas.jnustool; + +import java.util.ArrayList; +import java.util.List; + +public class Progress { + private long total; + private long current; + private ProgressUpdateListener progressUpdateListener = null; + List children = new ArrayList<>(); + Progress father = null; + + + public long getTotalOfSingle() { + return total; + } + + public void setTotal(long total) { + this.total = total; + } + + public long getCurrentOfSingle() { + return current; + } + + public void setCurrent(long current) { + this.current = current; + update(); + } + + public void addCurrent(int i) { + if(this.current + i > getTotalOfSingle()){ + setCurrent(getTotalOfSingle()); + }else{ + setCurrent(getCurrent() + i); + } + + + } + + private void update() { + if(father != null) father.update(); + + if(progressUpdateListener != null){ + progressUpdateListener.updatePerformed(this); + } + } + + public void add(Progress progress) { + progress.setFather(this); + children.add(progress); + } + + private void setFather(Progress progressListener) { + this.father = progressListener; + } + + public long getCurrent() { + long tmp = getCurrentOfSingle(); + for(Progress p : children){ + tmp +=p.getCurrent(); + } + return tmp; + } + + public long getTotal() { + long tmp = getTotalOfSingle(); + for(Progress p : children){ + tmp +=p.getTotal(); + } + return tmp; + } + + public void setProgressUpdateListener(ProgressUpdateListener progressUpdateListener) { + this.progressUpdateListener = progressUpdateListener; + } + + public void clear() { + setCurrent(0); + setTotal(0); + children = new ArrayList<>(); + } + + public int statusInPercent() { + return (int) ((getCurrent()*1.0)/(getTotal()*1.0)*100); + } + + public void finish() { + setCurrent(getTotalOfSingle()); + } + +} diff --git a/src/de/mas/jnustool/ProgressUpdateListener.java b/src/de/mas/jnustool/ProgressUpdateListener.java new file mode 100644 index 0000000..6c921ee --- /dev/null +++ b/src/de/mas/jnustool/ProgressUpdateListener.java @@ -0,0 +1,9 @@ +package de.mas.jnustool; + +public interface ProgressUpdateListener { + /** + * Invoked when an action occurs. + */ + public void updatePerformed(Progress p); + +} diff --git a/src/de/mas/jnustool/Starter.java b/src/de/mas/jnustool/Starter.java index 6947fc3..2516f7a 100644 --- a/src/de/mas/jnustool/Starter.java +++ b/src/de/mas/jnustool/Starter.java @@ -6,6 +6,10 @@ import java.io.FileReader; import java.io.IOException; import java.util.ArrayList; import java.util.List; +import java.util.concurrent.Callable; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.ForkJoinPool; +import java.util.concurrent.ForkJoinTask; import de.mas.jnustool.gui.NUSGUI; import de.mas.jnustool.gui.UpdateChooser; @@ -37,23 +41,37 @@ public class Starter { if( args.length > 1 && args[1].length() == 32){ key = args[1].substring(0, 32); } + if(titleID != 0){ + NUSGUI m = new NUSGUI(new NUSTitle(titleID, key)); + m.setVisible(true); + } }else{ - titleID = getTitleID().getTitleID(); - } - if(titleID != 0){ - NUSGUI m = new NUSGUI(new NUSTitle(titleID, key), null); - m.setVisible(true); + for(NUSTitleInformation nus : getTitleID()){ + + final long tID = nus.getTitleID(); + new Thread(new Runnable() { + + @Override + public void run() { + NUSGUI m = new NUSGUI(new NUSTitle(tID, null)); + m.setVisible(true); + + } + }).start();; + } + } + } - private static NUSTitleInformation getTitleID() { + private static List getTitleID() { List updatelist = readUpdateCSV(); - NUSTitleInformation result = null; + List result = null; if(updatelist != null){ - result = new NUSTitleInformation(); + result = new ArrayList<>(); UpdateChooser.createAndShowGUI(updatelist,result); synchronized (result) { try { @@ -63,6 +81,9 @@ public class Starter { e.printStackTrace(); } } + }else{ + Logger.messageBox("Updatefile is missing or not in config?"); + System.exit(2); } return result; } @@ -80,6 +101,7 @@ public class Starter { while((line = in.readLine()) != null){ String[] infos = line.split(";"); if(infos.length != 7) { + Logger.messageBox("Updatelist is broken!"); System.out.println("Updatelist is broken!"); return null; } @@ -96,9 +118,10 @@ public class Starter { in.close(); } catch (IOException | NumberFormatException e) { try { - in.close(); + if(in != null)in.close(); } catch (IOException e1) { } + Logger.messageBox("Updatelist is broken or missing"); System.out.println("Updatelist is broken!"); return null; } @@ -112,6 +135,7 @@ public class Starter { Downloader.URL_BASE = in.readLine(); String commonkey = in.readLine(); if(commonkey.length() != 32){ + Logger.messageBox("CommonKey length is wrong"); System.out.println("Commonkey length is wrong"); System.exit(1); } @@ -121,4 +145,35 @@ public class Starter { } + + + public static void downloadMeta(List output_, Progress totalProgress) { + ForkJoinPool pool = ForkJoinPool.commonPool(); + List> list = new ArrayList<>(); + for(NUSTitleInformation nus : output_){ + final long tID = nus.getTitleID(); + list.add(pool.submit(new Callable(){ + @Override + public Boolean call() throws Exception { + NUSTitle nus = new NUSTitle(tID, null); + Progress childProgress = new Progress(); + totalProgress.add(childProgress); + nus.decryptFEntries(nus.getFst().getMetaFolder(),childProgress); + return true; + } + })); + } + for(ForkJoinTask task : list){ + try { + task.get(); + } catch (InterruptedException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } catch (ExecutionException e) { + // TODO Auto-generated catch block + e.printStackTrace(); + } + } + } + } diff --git a/src/de/mas/jnustool/gui/NUSGUI.java b/src/de/mas/jnustool/gui/NUSGUI.java index af1d2fb..803174a 100644 --- a/src/de/mas/jnustool/gui/NUSGUI.java +++ b/src/de/mas/jnustool/gui/NUSGUI.java @@ -18,13 +18,12 @@ import javax.swing.tree.TreePath; import de.mas.jnustool.FEntry; import de.mas.jnustool.NUSTitle; -import de.mas.jnustool.util.Settings; public class NUSGUI extends JFrame { private static final long serialVersionUID = 4648172894076113183L; public static JTextArea output = new JTextArea(1,10); - public NUSGUI(NUSTitle nus,Settings mode) { + public NUSGUI(NUSTitle nus) { super(); this.setResizable(false); setSize(600, 768); @@ -64,7 +63,7 @@ public class NUSGUI extends JFrame { } } - nus.decryptFEntries(list); + nus.decryptFEntries(list, null); }}).start(); } diff --git a/src/de/mas/jnustool/gui/UpdateChooser.java b/src/de/mas/jnustool/gui/UpdateChooser.java index 7bda788..9ecb2df 100644 --- a/src/de/mas/jnustool/gui/UpdateChooser.java +++ b/src/de/mas/jnustool/gui/UpdateChooser.java @@ -15,7 +15,9 @@ import javax.swing.BoxLayout; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JList; +import javax.swing.JOptionPane; import javax.swing.JPanel; +import javax.swing.JProgressBar; import javax.swing.JScrollPane; import javax.swing.JSplitPane; import javax.swing.JTable; @@ -25,6 +27,9 @@ import javax.swing.ScrollPaneConstants; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; +import de.mas.jnustool.Progress; +import de.mas.jnustool.ProgressUpdateListener; +import de.mas.jnustool.Starter; import de.mas.jnustool.util.NUSTitleInformation; public class UpdateChooser extends JPanel { @@ -42,7 +47,8 @@ public class UpdateChooser extends JPanel { setSize(800, 600); Collections.sort(list_); - output_.init(list_.get(0)); + + output_.add(list_.get(0)); String[] columnNames = { "TitleID", "Region", "Name" }; String[][] tableData = new String[list_.size()][]; int i = 0; @@ -95,7 +101,7 @@ public class UpdateChooser extends JPanel { listSelectionModel.setSelectionMode( - ListSelectionModel.SINGLE_SELECTION); + ListSelectionModel.MULTIPLE_INTERVAL_SELECTION); //Build output area. output = new JTextArea(1, 10); @@ -123,38 +129,75 @@ public class UpdateChooser extends JPanel { splitPane.add(topHalf); JPanel listContainer = new JPanel(new GridLayout(1,1)); add(listContainer, BorderLayout.NORTH); - JButton btnNewButton = new JButton("Okay"); + + JPanel panel = new JPanel(); + add(panel, BorderLayout.SOUTH); + JButton btnNewButton = new JButton("Open FST"); + panel.add(btnNewButton); + JProgressBar progressBar; + progressBar = new JProgressBar(0, 100); + progressBar.setValue(0); + progressBar.setStringPainted(true); + + JButton btnDownloadMeta = new JButton("Download META"); + JProgressBar progressBar_1 = new JProgressBar(); + panel.add(progressBar_1); + progressBar_1.setValue(0); + Progress progress = new Progress(); + progress.setProgressUpdateListener(new ProgressUpdateListener() { + + @Override + public void updatePerformed(Progress p) { + progressBar_1.setValue((int)p.statusInPercent()); + } + }); + + btnDownloadMeta.addActionListener(new ActionListener() { + public void actionPerformed(ActionEvent e) { + if(progressBar_1.getValue() == 0 || progressBar_1.getValue() == 100){ + progressBar_1.setValue(1); + progress.clear(); + new Thread(new Runnable(){ + @Override + public void run() { + Starter.downloadMeta(output_,progress); + JOptionPane.showMessageDialog(window, "Finished"); + } + + }).start(); + + }else{ + JOptionPane.showMessageDialog(window, "Operation still in progress, please wait"); + } + } + }); + panel.add(btnDownloadMeta); + + btnNewButton.addActionListener(new ActionListener() { - public void actionPerformed(ActionEvent e) { - System.out.println("lol"); + public void actionPerformed(ActionEvent e) { synchronized (output_) { window.setVisible(false); output_.notifyAll(); } - } }); - - - add(btnNewButton, BorderLayout.SOUTH); JPanel bottomHalf = new JPanel(new BorderLayout()); bottomHalf.add(controlPane, BorderLayout.PAGE_START); bottomHalf.add(outputPane, BorderLayout.CENTER); - //XXX: next line needed if bottomHalf is a scroll pane: - //bottomHalf.setMinimumSize(new Dimension(400, 50)); } - private static NUSTitleInformation output_; + private static List output_; static List list_; - public static void createAndShowGUI(List list,NUSTitleInformation output) { + public static void createAndShowGUI(List list,List result) { //Create and set up the window. JFrame frame = new JFrame("Select the title"); //Create and set up the content pane. list_ = list; - output_ =output; + output_ =result; UpdateChooser demo = new UpdateChooser(frame); demo.setOpaque(true); frame.setContentPane(demo); @@ -178,9 +221,12 @@ public class UpdateChooser extends JPanel { // Find out which indexes are selected. int minIndex = lsm.getMinSelectionIndex(); int maxIndex = lsm.getMaxSelectionIndex(); + output_.clear(); for (int i = minIndex; i <= maxIndex; i++) { if (lsm.isSelectedIndex(i)) { - output_.init(list_.get(i)); + if(!output_.contains(list_.get(i))){ + output_.add(list_.get(i)); + } } } } diff --git a/src/de/mas/jnustool/util/Decryption.java b/src/de/mas/jnustool/util/Decryption.java index f2ebc43..cfe0467 100644 --- a/src/de/mas/jnustool/util/Decryption.java +++ b/src/de/mas/jnustool/util/Decryption.java @@ -20,6 +20,7 @@ import javax.crypto.spec.IvParameterSpec; import javax.crypto.spec.SecretKeySpec; import de.mas.jnustool.FEntry; +import de.mas.jnustool.Progress; import de.mas.jnustool.TIK; public class Decryption { @@ -171,6 +172,10 @@ public class Decryption { boolean first = true; ByteArrayBuffer overflow = new ByteArrayBuffer(BLOCKSIZE); + if(progressListener != null){ + progressListener.setTotal(toDownload.getFileLength()); + progressListener.setCurrent(0); + } do{ inBlockBuffer = getChunkFromStream(inputStream,blockBuffer,overflow,BLOCKSIZE); if(first){ @@ -184,7 +189,9 @@ public class Decryption { if((wrote + inBlockBuffer) > toDownload.getFileLength()){ inBlockBuffer = (int) (toDownload.getFileLength()- wrote); } - + if(progressListener != null){ + progressListener.addCurrent(inBlockBuffer); + } wrote += inBlockBuffer; outputStream.write(output, 0, inBlockBuffer); }while(inBlockBuffer == BLOCKSIZE); @@ -209,9 +216,16 @@ public class Decryption { long wrote = 0; int inBlockBuffer; + + if(progressListener != null){ + progressListener.setTotal(toDownload.getFileLength()/HASHBLOCKSIZE*BLOCKSIZE); + progressListener.setCurrent(0); + } do{ inBlockBuffer = getChunkFromStream(inputStream,encryptedBlockBuffer,overflow,BLOCKSIZE); - + if(progressListener != null){ + progressListener.addCurrent(inBlockBuffer); + } if( writeSize > size ) writeSize = size; @@ -295,5 +309,12 @@ public class Decryption { return inBlockBuffer; } + private Progress progressListener = null; + + public void setProgressListener(Progress progressOfFile) { + this.progressListener = progressOfFile; + + } + } diff --git a/src/de/mas/jnustool/util/Downloader.java b/src/de/mas/jnustool/util/Downloader.java index 6989f6b..41a26fc 100644 --- a/src/de/mas/jnustool/util/Downloader.java +++ b/src/de/mas/jnustool/util/Downloader.java @@ -7,6 +7,7 @@ import java.net.HttpURLConnection; import java.net.URL; import de.mas.jnustool.FEntry; +import de.mas.jnustool.Progress; public class Downloader { private static Downloader instance; @@ -22,7 +23,7 @@ public class Downloader { } - public void downloadAndDecrypt(FEntry toDownload) throws IOException{ + public void downloadAndDecrypt(FEntry toDownload, Progress progressOfFile) 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("/"); @@ -45,7 +46,7 @@ public class Downloader { connection.connect(); Decryption decryption = new Decryption(toDownload.getTicket()); - + decryption.setProgressListener(progressOfFile); InputStream input = connection.getInputStream(); FileOutputStream outputStream = new FileOutputStream(String.format("%016X", toDownload.getTitleID()) +"/" + toDownload.getFullPath().substring(1, toDownload.getFullPath().length())); if(!decryptWithHash){ diff --git a/src/de/mas/jnustool/util/NUSTitleInformation.java b/src/de/mas/jnustool/util/NUSTitleInformation.java index b73b418..2083050 100644 --- a/src/de/mas/jnustool/util/NUSTitleInformation.java +++ b/src/de/mas/jnustool/util/NUSTitleInformation.java @@ -12,6 +12,7 @@ public class NUSTitleInformation implements Comparable, Ser private String content_platform; private String company_code; private int region; + private byte[] key; public enum Region{ @@ -123,6 +124,21 @@ public class NUSTitleInformation implements Comparable, Ser setID6(n.ID6); setLongnameEN(n.longnameEN); setProduct_code(n.product_code); + setKey(n.key); } + public byte[] getKey() { + return key; + } + + public void setKey(byte[] key) { + this.key = key; + } + + @Override + public boolean equals(Object o){ + return titleID == ((NUSTitleInformation)o).titleID; + } + + } diff --git a/src/de/mas/jnustool/util/Util.java b/src/de/mas/jnustool/util/Util.java index f4a366f..c7eff97 100644 --- a/src/de/mas/jnustool/util/Util.java +++ b/src/de/mas/jnustool/util/Util.java @@ -23,7 +23,7 @@ public class Util { { StringBuilder hex = new StringBuilder(ba.length * 2); for(byte b : ba){ - hex.append(String.format("%X", b)); + hex.append(String.format("%02X", b)); } return hex.toString(); }