2016-02-02 19:38:53 +01:00
|
|
|
package de.mas.jnustool;
|
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
import java.io.File;
|
2016-02-01 20:54:01 +01:00
|
|
|
import java.io.IOException;
|
2016-02-02 22:55:33 +01:00
|
|
|
import java.nio.file.Files;
|
|
|
|
import java.nio.file.Path;
|
|
|
|
import java.nio.file.Paths;
|
2016-02-05 16:40:26 +01:00
|
|
|
import java.util.ArrayList;
|
|
|
|
import java.util.List;
|
|
|
|
import java.util.concurrent.ForkJoinPool;
|
2016-02-01 20:54:01 +01:00
|
|
|
|
2016-02-02 19:38:53 +01:00
|
|
|
import de.mas.jnustool.util.Decryption;
|
|
|
|
import de.mas.jnustool.util.Downloader;
|
2016-02-02 22:55:33 +01:00
|
|
|
import de.mas.jnustool.util.Settings;
|
2016-02-02 19:38:53 +01:00
|
|
|
|
2016-02-01 20:54:01 +01:00
|
|
|
public class NUSTitle {
|
|
|
|
private TitleMetaData tmd;
|
|
|
|
private TIK ticket;
|
2016-02-01 23:57:01 +01:00
|
|
|
private FST fst;
|
2016-02-02 22:55:33 +01:00
|
|
|
private long titleID;
|
2016-02-05 16:40:26 +01:00
|
|
|
public NUSTitle(long titleId,String key) {
|
2016-02-02 22:55:33 +01:00
|
|
|
setTitleID(titleId);
|
2016-02-05 16:40:26 +01:00
|
|
|
try {
|
2016-02-02 22:55:33 +01:00
|
|
|
if(Settings.downloadContent){
|
|
|
|
File f = new File(getContentPath());
|
|
|
|
if(!f.exists())f.mkdir();
|
|
|
|
}
|
2016-02-05 16:40:26 +01:00
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
if(Settings.downloadContent){
|
|
|
|
|
|
|
|
File f = new File(getContentPath() + "/" + "tmd");
|
|
|
|
if(!(f.exists() && Settings.skipExistingTMDTICKET)){
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Downloading TMD");
|
2016-02-02 22:55:33 +01:00
|
|
|
Downloader.getInstance().downloadTMD(titleId,getContentPath());
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Skipped download of TMD. Already existing");
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
|
|
|
f = new File(getContentPath() + "/" + "cetk");
|
|
|
|
if(!(f.exists() && Settings.skipExistingTMDTICKET)){
|
|
|
|
if(key == null){
|
|
|
|
System.out.print("Downloading Ticket");
|
|
|
|
Downloader.getInstance().downloadTicket(titleId,getContentPath());
|
|
|
|
}
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Skipped download of ticket. Already existing");
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
|
|
|
}
|
2016-02-01 20:54:01 +01:00
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
if(Settings.useCachedFiles){
|
|
|
|
File f = new File(getContentPath() + "/" + "tmd");
|
|
|
|
if(f.exists()){
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Using cached TMD.");
|
2016-02-02 22:55:33 +01:00
|
|
|
tmd = new TitleMetaData(f);
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("No cached TMD found.");
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if(tmd == null){
|
|
|
|
if(Settings.downloadWhenCachedFilesMissingOrBroken){
|
2016-02-05 16:40:26 +01:00
|
|
|
if(Settings.useCachedFiles) Logger.log("Getting missing tmd from Server!");
|
2016-02-02 22:55:33 +01:00
|
|
|
tmd = new TitleMetaData(Downloader.getInstance().downloadTMDToByteArray(titleId));
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Downloading of missing files is not enabled. Exiting");
|
|
|
|
System.exit(2);
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if(key != null){
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Using ticket from parameter.");
|
2016-02-02 22:55:33 +01:00
|
|
|
ticket = new TIK(key,titleId);
|
2016-02-01 20:54:01 +01:00
|
|
|
}else{
|
2016-02-02 22:55:33 +01:00
|
|
|
if(Settings.useCachedFiles){
|
|
|
|
File f = new File(getContentPath() + "/" + "cetk");
|
|
|
|
if(f.exists()){
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Using cached cetk.");
|
2016-02-02 22:55:33 +01:00
|
|
|
ticket = new TIK(f,titleId);
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("No cached ticket found.");
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if(ticket == null){
|
|
|
|
if(Settings.downloadWhenCachedFilesMissingOrBroken){
|
2016-02-05 16:40:26 +01:00
|
|
|
if(Settings.useCachedFiles) Logger.log("getting missing ticket");
|
2016-02-02 22:55:33 +01:00
|
|
|
ticket = new TIK(Downloader.getInstance().downloadTicketToByteArray(titleId),tmd.titleID);
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Downloading of missing files is not enabled. Exiting");
|
|
|
|
System.exit(2);
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
|
|
|
}
|
2016-02-01 20:54:01 +01:00
|
|
|
}
|
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
if(Settings.downloadContent){
|
|
|
|
File f = new File(getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app");
|
|
|
|
if(!(f.exists() && Settings.skipExistingFiles)){
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Downloading FST (" + String.format("%08x", tmd.contents[0].ID) + ")");
|
2016-02-02 22:55:33 +01:00
|
|
|
Downloader.getInstance().downloadContent(titleId,tmd.contents[0].ID,getContentPath());
|
|
|
|
}else{
|
|
|
|
if(f.length() != tmd.contents[0].size){
|
|
|
|
if(Settings.downloadWhenCachedFilesMissingOrBroken){
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("FST already existing, but broken. Downloading it again.");
|
2016-02-02 22:55:33 +01:00
|
|
|
Downloader.getInstance().downloadContent(titleId,tmd.contents[0].ID,getContentPath());
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("FST already existing, but broken. No download allowed.");
|
|
|
|
System.exit(2);
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Skipped download of FST. Already existing");
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
}
|
2016-02-01 20:54:01 +01:00
|
|
|
|
2016-02-05 16:40:26 +01:00
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
Decryption decryption = new Decryption(ticket.getDecryptedKey(),0);
|
|
|
|
byte[] encryptedFST = null;
|
|
|
|
if(Settings.useCachedFiles){
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log(getContentPath());
|
2016-02-02 22:55:33 +01:00
|
|
|
String path = getContentPath() + "/" + String.format("%08x", tmd.contents[0].ID) + ".app";
|
|
|
|
File f = new File(path);
|
|
|
|
if(f.exists()){
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Using cached FST");
|
2016-02-02 22:55:33 +01:00
|
|
|
Path file = Paths.get(path);
|
|
|
|
encryptedFST = Files.readAllBytes(file);
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("No cached FST (" + String.format("%08x", tmd.contents[0].ID) + ") found.");
|
|
|
|
}
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
|
|
|
if(encryptedFST == null){
|
|
|
|
if(Settings.downloadWhenCachedFilesMissingOrBroken){
|
2016-02-05 16:40:26 +01:00
|
|
|
if(Settings.useCachedFiles)Logger.log("Getting FST from server.");
|
2016-02-02 22:55:33 +01:00
|
|
|
encryptedFST = Downloader.getInstance().downloadContentToByteArray(titleId,tmd.contents[0].ID);
|
|
|
|
}else{
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Downloading of missing files is not enabled. Exiting");
|
|
|
|
System.exit(2);
|
2016-02-02 22:55:33 +01:00
|
|
|
}
|
2016-02-05 16:40:26 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
decryption.init(ticket.getDecryptedKey(), 0);
|
2016-02-01 20:54:01 +01:00
|
|
|
byte[] decryptedFST = decryption.decrypt(encryptedFST);
|
|
|
|
|
2016-02-02 19:38:53 +01:00
|
|
|
fst = new FST(decryptedFST,tmd);
|
2016-02-02 22:55:33 +01:00
|
|
|
tmd.setNUSTitle(this);
|
|
|
|
|
|
|
|
if(Settings.downloadContent){
|
|
|
|
tmd.downloadContents();
|
|
|
|
}
|
2016-02-01 20:54:01 +01:00
|
|
|
|
2016-02-05 16:40:26 +01:00
|
|
|
Logger.log("Total Size of Content Files: " + ((int)((getTotalContentSize()/1024.0/1024.0)*100))/100.0 +" MB");
|
|
|
|
Logger.log("Total Size of Decrypted Files: " + ((int)((fst.getTotalContentSizeInNUS()/1024.0/1024.0)*100))/100.0 +" MB");
|
|
|
|
Logger.log("Entries: " + fst.getTotalEntries());
|
|
|
|
Logger.log("Entries: " + fst.getFileCount());
|
|
|
|
Logger.log("Files in NUSTitle: " + fst.getFileCountInNUS());
|
2016-02-01 20:54:01 +01:00
|
|
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
// TODO Auto-generated catch block
|
|
|
|
e.printStackTrace();
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2016-02-01 23:57:01 +01:00
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-02-01 23:57:01 +01:00
|
|
|
public FST getFst() {
|
|
|
|
return fst;
|
|
|
|
}
|
2016-02-02 22:55:33 +01:00
|
|
|
|
2016-02-01 23:57:01 +01:00
|
|
|
public void setFst(FST fst) {
|
|
|
|
this.fst = fst;
|
|
|
|
}
|
2016-02-02 22:55:33 +01:00
|
|
|
|
2016-02-01 23:57:01 +01:00
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
public TitleMetaData getTmd() {
|
|
|
|
return tmd;
|
|
|
|
}
|
2016-02-01 23:57:01 +01:00
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
public void setTmd(TitleMetaData tmd) {
|
|
|
|
this.tmd = tmd;
|
|
|
|
}
|
|
|
|
|
|
|
|
public TIK getTicket() {
|
|
|
|
return ticket;
|
2016-02-01 20:54:01 +01:00
|
|
|
}
|
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
public void setTicket(TIK ticket) {
|
|
|
|
this.ticket = ticket;
|
2016-02-01 20:54:01 +01:00
|
|
|
}
|
|
|
|
|
|
|
|
public long getTotalContentSize() {
|
|
|
|
return tmd.getTotalContentSize();
|
|
|
|
}
|
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
|
|
|
|
|
|
|
|
public String getContentPath() {
|
|
|
|
return getContentPathPrefix() + String.format("%016X", getTitleID());
|
|
|
|
}
|
|
|
|
|
|
|
|
public String getContentPathPrefix() {
|
|
|
|
return "tmp_";
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
2016-02-05 16:40:26 +01:00
|
|
|
public long getTitleID() {
|
2016-02-02 22:55:33 +01:00
|
|
|
return titleID;
|
|
|
|
}
|
|
|
|
|
|
|
|
private void setTitleID(long titleId) {
|
|
|
|
this.titleID = titleId;
|
|
|
|
}
|
|
|
|
|
2016-02-06 18:25:08 +01:00
|
|
|
public void decryptFEntries(List<FEntry> list,Progress progress) {
|
2016-02-05 16:40:26 +01:00
|
|
|
ForkJoinPool pool = ForkJoinPool.commonPool();
|
|
|
|
List<FEntryDownloader> dlList = new ArrayList<>();
|
|
|
|
for(FEntry f : list){
|
|
|
|
if(!f.isDir() && f.isInNUSTitle()){
|
2016-02-06 18:25:08 +01:00
|
|
|
dlList.add(new FEntryDownloader(f,progress));
|
2016-02-05 16:40:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
pool.invokeAll(dlList);
|
|
|
|
Logger.log("Done!");
|
|
|
|
}
|
|
|
|
|
2016-02-02 22:55:33 +01:00
|
|
|
|
|
|
|
|
2016-02-01 20:54:01 +01:00
|
|
|
}
|