Code clean up, made attributes/classes final, added missing private constructors

added proper skip for inputstreams and serveral more small improvements.
Not tested (yet)
This commit is contained in:
Maschell 2016-12-16 16:48:17 +01:00
parent 6b8efcaf53
commit a9b989ee0e
50 changed files with 593 additions and 503 deletions

View File

@ -25,10 +25,10 @@ import de.mas.jnus.lib.utils.StreamUtils;
import de.mas.jnus.lib.utils.Utils; import de.mas.jnus.lib.utils.Utils;
import de.mas.jnus.lib.utils.cryptography.NUSDecryption; import de.mas.jnus.lib.utils.cryptography.NUSDecryption;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
public class DecryptionService { public final class DecryptionService {
private static Map<NUSTitle,DecryptionService> instances = new HashMap<>(); private static Map<NUSTitle,DecryptionService> instances = new HashMap<>();
@Getter private final NUSTitle NUSTitle;
public static DecryptionService getInstance(NUSTitle nustitle) { public static DecryptionService getInstance(NUSTitle nustitle) {
if(!instances.containsKey(nustitle)){ if(!instances.containsKey(nustitle)){
@ -37,10 +37,8 @@ public class DecryptionService {
return instances.get(nustitle); return instances.get(nustitle);
} }
@Getter @Setter private NUSTitle NUSTitle = null;
private DecryptionService(NUSTitle nustitle){ private DecryptionService(NUSTitle nustitle){
setNUSTitle(nustitle); this.NUSTitle = nustitle;
} }
public Ticket getTicket() { public Ticket getTicket() {
@ -80,16 +78,16 @@ public class DecryptionService {
} }
if(targetFile.length() == entry.getFileSize()){ if(targetFile.length() == entry.getFileSize()){
Content c = entry.getContent(); Content c = entry.getContent();
if(!c.isHashed()){ if(c.isHashed()){
System.out.println("File already exists: " + entry.getFilename());
return;
}else{
if(Arrays.equals(HashUtil.hashSHA1(target,(int) c.getEncryptedFileSize()), c.getSHA2Hash())){ if(Arrays.equals(HashUtil.hashSHA1(target,(int) c.getEncryptedFileSize()), c.getSHA2Hash())){
System.out.println("File already exists: " + entry.getFilename()); System.out.println("File already exists: " + entry.getFilename());
return; return;
}else{ }else{
System.out.println("File already exists with the same filesize, but the hash doesn't match: " + entry.getFilename()); System.out.println("File already exists with the same filesize, but the hash doesn't match: " + entry.getFilename());
} }
}else{
System.out.println("File already exists: " + entry.getFilename());
return;
} }
}else{ }else{
@ -138,9 +136,7 @@ public class DecryptionService {
short contentIndex = (short)content.getIndex(); short contentIndex = (short)content.getIndex();
long encryptedFileSize = content.getEncryptedFileSize(); long encryptedFileSize = content.getEncryptedFileSize();
if(!content.isEncrypted()){ if(content.isEncrypted()){
StreamUtils.saveInputStreamToOutputStreamWithHash(inputStream, outputStream,size,content.getSHA2Hash(),encryptedFileSize);
}else{
if(content.isHashed()){ if(content.isHashed()){
NUSDataProvider dataProvider = getNUSTitle().getDataProvider(); NUSDataProvider dataProvider = getNUSTitle().getDataProvider();
byte[] h3 = dataProvider.getContentH3Hash(content); byte[] h3 = dataProvider.getContentH3Hash(content);
@ -148,6 +144,8 @@ public class DecryptionService {
}else{ }else{
nusdecryption.decryptFileStream(inputStream, outputStream, size, (short)contentIndex,content.getSHA2Hash(),encryptedFileSize); nusdecryption.decryptFileStream(inputStream, outputStream, size, (short)contentIndex,content.getSHA2Hash(),encryptedFileSize);
} }
}else{
StreamUtils.saveInputStreamToOutputStreamWithHash(inputStream, outputStream,size,content.getSHA2Hash(),encryptedFileSize);
} }
inputStream.close(); inputStream.close();

View File

@ -12,11 +12,12 @@ import de.mas.jnus.lib.implementations.NUSDataProvider;
import de.mas.jnus.lib.utils.FileUtils; import de.mas.jnus.lib.utils.FileUtils;
import de.mas.jnus.lib.utils.Utils; import de.mas.jnus.lib.utils.Utils;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
public class ExtractionService { public final class ExtractionService {
private static Map<NUSTitle,ExtractionService> instances = new HashMap<>(); private static Map<NUSTitle,ExtractionService> instances = new HashMap<>();
@Getter private final NUSTitle NUSTitle;
public static ExtractionService getInstance(NUSTitle nustitle) { public static ExtractionService getInstance(NUSTitle nustitle) {
if(!instances.containsKey(nustitle)){ if(!instances.containsKey(nustitle)){
instances.put(nustitle, new ExtractionService(nustitle)); instances.put(nustitle, new ExtractionService(nustitle));
@ -24,10 +25,8 @@ public class ExtractionService {
return instances.get(nustitle); return instances.get(nustitle);
} }
@Getter @Setter private NUSTitle NUSTitle = null;
private ExtractionService(NUSTitle nustitle){ private ExtractionService(NUSTitle nustitle){
setNUSTitle(nustitle); this.NUSTitle = nustitle;
} }
private NUSDataProvider getDataProvider(){ private NUSDataProvider getDataProvider(){
@ -76,7 +75,7 @@ public class ExtractionService {
byte[] rawTMD= getDataProvider().getRawTMD(); byte[] rawTMD= getDataProvider().getRawTMD();
if(rawTMD != null && rawTMD.length == 0){ if(rawTMD == null || rawTMD.length == 0){
System.out.println("Couldn't write TMD: No TMD loaded"); System.out.println("Couldn't write TMD: No TMD loaded");
return; return;
} }
@ -90,7 +89,7 @@ public class ExtractionService {
byte[] rawTicket= getDataProvider().getRawTicket(); byte[] rawTicket= getDataProvider().getRawTicket();
if(rawTicket != null && rawTicket.length == 0){ if(rawTicket == null || rawTicket.length == 0){
System.out.println("Couldn't write Ticket: No Ticket loaded"); System.out.println("Couldn't write Ticket: No Ticket loaded");
return; return;
} }
@ -104,7 +103,7 @@ public class ExtractionService {
byte[] rawCert = getDataProvider().getRawCert(); byte[] rawCert = getDataProvider().getRawCert();
if(rawCert != null && rawCert.length == 0){ if(rawCert == null || rawCert.length == 0){
System.out.println("Couldn't write Cert: No Cert loaded"); System.out.println("Couldn't write Cert: No Cert loaded");
return; return;
} }

View File

@ -17,8 +17,6 @@ import lombok.Getter;
import lombok.Setter; import lombok.Setter;
public class NUSTitle { public class NUSTitle {
@Getter @Setter private String inputPath = "";
@Getter @Setter private FST FST; @Getter @Setter private FST FST;
@Getter @Setter private TMD TMD; @Getter @Setter private TMD TMD;
@Getter @Setter private Ticket ticket; @Getter @Setter private Ticket ticket;
@ -53,10 +51,10 @@ public class NUSTitle {
} }
public FSTEntry getFSTEntryByFullPath(String givenFullPath) { public FSTEntry getFSTEntryByFullPath(String givenFullPath) {
givenFullPath = givenFullPath.replaceAll("/", "\\\\"); String fullPath = givenFullPath.replaceAll("/", "\\\\");
if(!givenFullPath.startsWith("\\")) givenFullPath = "\\" +givenFullPath; if(!fullPath.startsWith("\\")) fullPath = "\\" +fullPath;
for(FSTEntry f :getAllFSTEntriesFlat()){ for(FSTEntry f :getAllFSTEntriesFlat()){
if(f.getFullPath().equals(givenFullPath)){ if(f.getFullPath().equals(fullPath)){
return f; return f;
} }
} }

View File

@ -7,9 +7,9 @@ import lombok.Data;
@Data @Data
public class NUSTitleConfig { public class NUSTitleConfig {
private String inputPath = ""; private String inputPath;
private WUDInfo WUDInfo = null; private WUDInfo WUDInfo;
private Ticket ticket = null; private Ticket ticket;
private int version = Settings.LATEST_TMD_VERSION; private int version = Settings.LATEST_TMD_VERSION;
private long titleID = 0x0L; private long titleID = 0x0L;

View File

@ -13,16 +13,14 @@ import de.mas.jnus.lib.utils.cryptography.AESDecryption;
abstract class NUSTitleLoader { abstract class NUSTitleLoader {
protected NUSTitleLoader(){ protected NUSTitleLoader(){
//should be empty
} }
public NUSTitle loadNusTitle(NUSTitleConfig config) throws Exception{ public NUSTitle loadNusTitle(NUSTitleConfig config) throws Exception{
NUSTitle result = new NUSTitle(); NUSTitle result = new NUSTitle();
NUSDataProvider dataProvider = getDataProvider(config); NUSDataProvider dataProvider = getDataProvider(result, config);
result.setDataProvider(dataProvider); result.setDataProvider(dataProvider);
dataProvider.setNUSTitle(result);
TMD tmd = TMD.parseTMD(dataProvider.getRawTMD()); TMD tmd = TMD.parseTMD(dataProvider.getRawTMD());
result.setTMD(tmd); result.setTMD(tmd);
@ -63,5 +61,5 @@ abstract class NUSTitleLoader {
return result; return result;
} }
protected abstract NUSDataProvider getDataProvider(NUSTitleConfig config); protected abstract NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config);
} }

View File

@ -4,7 +4,7 @@ import de.mas.jnus.lib.entities.Ticket;
import de.mas.jnus.lib.implementations.NUSDataProviderLocal; import de.mas.jnus.lib.implementations.NUSDataProviderLocal;
import de.mas.jnus.lib.implementations.NUSDataProvider; import de.mas.jnus.lib.implementations.NUSDataProvider;
public class NUSTitleLoaderLocal extends NUSTitleLoader { public final class NUSTitleLoaderLocal extends NUSTitleLoader {
private NUSTitleLoaderLocal(){ private NUSTitleLoaderLocal(){
super(); super();
@ -26,10 +26,8 @@ public class NUSTitleLoaderLocal extends NUSTitleLoader {
} }
@Override @Override
protected NUSDataProvider getDataProvider(NUSTitleConfig config) { protected NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config) {
NUSDataProviderLocal result = new NUSDataProviderLocal(); return new NUSDataProviderLocal(title,config.getInputPath());
result.setLocalPath(config.getInputPath());
return result;
} }
} }

View File

@ -4,7 +4,7 @@ import de.mas.jnus.lib.entities.Ticket;
import de.mas.jnus.lib.implementations.NUSDataProviderRemote; import de.mas.jnus.lib.implementations.NUSDataProviderRemote;
import de.mas.jnus.lib.implementations.NUSDataProvider; import de.mas.jnus.lib.implementations.NUSDataProvider;
public class NUSTitleLoaderRemote extends NUSTitleLoader{ public final class NUSTitleLoaderRemote extends NUSTitleLoader{
private NUSTitleLoaderRemote(){ private NUSTitleLoaderRemote(){
super(); super();
@ -33,11 +33,8 @@ public class NUSTitleLoaderRemote extends NUSTitleLoader{
} }
@Override @Override
protected NUSDataProvider getDataProvider(NUSTitleConfig config) { protected NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config) {
NUSDataProviderRemote result = new NUSDataProviderRemote(); return new NUSDataProviderRemote(title,config.getVersion(),config.getTitleID());
result.setVersion(config.getVersion());
result.setTitleID(config.getTitleID());
return result;
} }
} }

View File

@ -8,7 +8,7 @@ import de.mas.jnus.lib.implementations.wud.WUDImage;
import de.mas.jnus.lib.implementations.wud.parser.WUDInfo; import de.mas.jnus.lib.implementations.wud.parser.WUDInfo;
import de.mas.jnus.lib.implementations.wud.parser.WUDInfoParser; import de.mas.jnus.lib.implementations.wud.parser.WUDInfoParser;
public class NUSTitleLoaderWUD extends NUSTitleLoader { public final class NUSTitleLoaderWUD extends NUSTitleLoader {
private NUSTitleLoaderWUD(){ private NUSTitleLoaderWUD(){
super(); super();
@ -20,7 +20,7 @@ public class NUSTitleLoaderWUD extends NUSTitleLoader {
public static NUSTitle loadNUSTitle(String WUDPath, byte[] titleKey) throws Exception{ public static NUSTitle loadNUSTitle(String WUDPath, byte[] titleKey) throws Exception{
NUSTitleLoader loader = new NUSTitleLoaderWUD(); NUSTitleLoader loader = new NUSTitleLoaderWUD();
NUSTitleConfig config = new NUSTitleConfig(); NUSTitleConfig config = new NUSTitleConfig();
byte[] usedTitleKey = titleKey;
File wudFile = new File(WUDPath); File wudFile = new File(WUDPath);
if(!wudFile.exists()){ if(!wudFile.exists()){
System.out.println(WUDPath + " does not exist."); System.out.println(WUDPath + " does not exist.");
@ -28,15 +28,15 @@ public class NUSTitleLoaderWUD extends NUSTitleLoader {
} }
WUDImage image = new WUDImage(wudFile); WUDImage image = new WUDImage(wudFile);
if(titleKey == null){ if(usedTitleKey == null){
File keyFile = new File(wudFile.getParentFile().getPath() + File.separator + Settings.WUD_KEY_FILENAME); File keyFile = new File(wudFile.getParentFile().getPath() + File.separator + Settings.WUD_KEY_FILENAME);
if(!keyFile.exists()){ if(!keyFile.exists()){
System.out.println(keyFile.getAbsolutePath() + " does not exist and no title key was provided."); System.out.println(keyFile.getAbsolutePath() + " does not exist and no title key was provided.");
return null; return null;
} }
titleKey = Files.readAllBytes(keyFile.toPath()); usedTitleKey = Files.readAllBytes(keyFile.toPath());
} }
WUDInfo wudInfo = WUDInfoParser.createAndLoad(image.getWUDDiscReader(), titleKey); WUDInfo wudInfo = WUDInfoParser.createAndLoad(image.getWUDDiscReader(), usedTitleKey);
if(wudInfo == null){ if(wudInfo == null){
return null; return null;
} }
@ -47,10 +47,8 @@ public class NUSTitleLoaderWUD extends NUSTitleLoader {
} }
@Override @Override
protected NUSDataProvider getDataProvider(NUSTitleConfig config) { protected NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config) {
NUSDataProviderWUD result = new NUSDataProviderWUD(); return new NUSDataProviderWUD(title,config.getWUDInfo());
result.setWUDInfo(config.getWUDInfo());
return result;
} }
} }

View File

@ -9,7 +9,7 @@ import de.mas.jnus.lib.implementations.woomy.WoomyParser;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class NUSTitleLoaderWoomy extends NUSTitleLoader { public final class NUSTitleLoaderWoomy extends NUSTitleLoader {
public static NUSTitle loadNUSTitle(String inputFile) throws Exception{ public static NUSTitle loadNUSTitle(String inputFile) throws Exception{
NUSTitleLoaderWoomy loader = new NUSTitleLoaderWoomy(); NUSTitleLoaderWoomy loader = new NUSTitleLoaderWoomy();
@ -24,10 +24,8 @@ public class NUSTitleLoaderWoomy extends NUSTitleLoader {
return loader.loadNusTitle(config); return loader.loadNusTitle(config);
} }
@Override @Override
protected NUSDataProvider getDataProvider(NUSTitleConfig config) { protected NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config) {
NUSDataProviderWoomy dataProvider = new NUSDataProviderWoomy(); return new NUSDataProviderWoomy(title,config.getWoomyInfo());
dataProvider.setWoomyInfo(config.getWoomyInfo());
return dataProvider;
} }
} }

View File

@ -23,7 +23,11 @@ import de.mas.jnus.lib.utils.Utils;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class WUDService { public final class WUDService {
private WUDService(){
//Just an utility class
}
public static File compressWUDToWUX(WUDImage image,String outputFolder) throws IOException{ public static File compressWUDToWUX(WUDImage image,String outputFolder) throws IOException{
return compressWUDToWUX(image, outputFolder, "game.wux",false); return compressWUDToWUX(image, outputFolder, "game.wux",false);
} }
@ -44,16 +48,17 @@ public class WUDService {
return null; return null;
} }
Utils.createDir(outputFolder); String usedOutputFolder = outputFolder;
if(usedOutputFolder == null) usedOutputFolder = "";
Utils.createDir(usedOutputFolder);
String filePath; String filePath;
if(outputFolder == null) outputFolder = ""; if(usedOutputFolder.isEmpty()){
if(!outputFolder.isEmpty()){
filePath = outputFolder+ File.separator + filename;
}else{
filePath = filename; filePath = filename;
}else{
filePath = usedOutputFolder + File.separator + filename;
} }
File outputFile = new File(filePath); File outputFile = new File(filePath);
if(outputFile.exists() && !overwrite){ if(outputFile.exists() && !overwrite){
@ -98,14 +103,14 @@ public class WUDService {
int read = StreamUtils.getChunkFromStream(in, blockBuffer, overflow, bufferSize); int read = StreamUtils.getChunkFromStream(in, blockBuffer, overflow, bufferSize);
ByteArrayWrapper hash = new ByteArrayWrapper(HashUtil.hashSHA1(blockBuffer)); ByteArrayWrapper hash = new ByteArrayWrapper(HashUtil.hashSHA1(blockBuffer));
if((oldOffset = sectorHashes.get(hash)) != null){ if((oldOffset = sectorHashes.get(hash)) == null){
sectorMapping.put(curSector, oldOffset);
oldOffset = null;
}else{ //its a new sector
sectorMapping.put(curSector, realSector); sectorMapping.put(curSector, realSector);
sectorHashes.put(hash, realSector); sectorHashes.put(hash, realSector);
fileOutput.write(blockBuffer); fileOutput.write(blockBuffer);
realSector++; realSector++;
}else{
sectorMapping.put(curSector, oldOffset);
oldOffset = null;
} }
written += read; written += read;

View File

@ -4,39 +4,53 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import de.mas.jnus.lib.entities.content.Content; import de.mas.jnus.lib.entities.content.Content;
import de.mas.jnus.lib.entities.content.ContentInfo; import de.mas.jnus.lib.entities.content.ContentInfo;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
public class TMD { public final class TMD {
@Getter @Setter private int signatureType; // 0x000 @Getter private final int signatureType; // 0x000
@Getter @Setter private byte[] signature = new byte[0x100]; // 0x004 @Getter private final byte[] signature; // 0x004
@Getter @Setter private byte[] issuer = new byte[0x40]; // 0x140 @Getter private final byte[] issuer; // 0x140
@Getter @Setter private byte version; // 0x180 @Getter private final byte version; // 0x180
@Getter @Setter private byte CACRLVersion; // 0x181 @Getter private final byte CACRLVersion; // 0x181
@Getter @Setter private byte signerCRLVersion; // 0x182 @Getter private final byte signerCRLVersion; // 0x182
@Getter @Setter private long systemVersion; // 0x184 @Getter private final long systemVersion; // 0x184
@Getter @Setter private long titleID; // 0x18C @Getter private final long titleID; // 0x18C
@Getter @Setter private int titleType; // 0x194 @Getter private final int titleType; // 0x194
@Getter @Setter private short groupID; // 0x198 @Getter private final short groupID; // 0x198
@Getter @Setter private byte[] reserved = new byte[62]; // 0x19A @Getter private final byte[] reserved; // 0x19A
@Getter @Setter private int accessRights; // 0x1D8 @Getter private final int accessRights; // 0x1D8
@Getter @Setter private short titleVersion; // 0x1DC @Getter private final short titleVersion; // 0x1DC
@Getter @Setter private short contentCount; // 0x1DE @Getter private final short contentCount; // 0x1DE
@Getter @Setter private short bootIndex; // 0x1E0 @Getter private final short bootIndex; // 0x1E0
@Getter @Setter private byte[] SHA2 = new byte[0x20]; // 0x1E4 @Getter private final byte[] SHA2; // 0x1E4
@Getter @Setter private ContentInfo[] contentInfos = new ContentInfo[0x40]; @Getter private final ContentInfo[] contentInfos;
Map<Integer,Content> contentToIndex = new HashMap<>(); private final Map<Integer,Content> contentToIndex = new HashMap<>();
Map<Integer,Content> contentToID = new HashMap<>(); private final Map<Integer,Content> contentToID = new HashMap<>();
@Getter @Setter private byte[] rawTMD = new byte[0];
private TMD(){
private TMD(TMDParam param) {
super();
this.signatureType = param.getSignatureType();
this.signature = param.getSignature();
this.issuer = param.getIssuer();
this.version = param.getVersion();
this.CACRLVersion = param.getCACRLVersion();
this.signerCRLVersion = param.getSignerCRLVersion();
this.systemVersion = param.getSystemVersion();
this.titleID = param.getTitleID();
this.titleType = param.getTitleType();
this.groupID = param.getGroupID();
this.reserved = param.getReserved();
this.accessRights = param.getAccessRights();
this.titleVersion = param.getTitleVersion();
this.contentCount = param.getContentCount();
this.bootIndex = param.getBootIndex();
this.SHA2 = param.getSHA2();
this.contentInfos = param.getContentInfos();
} }
public static TMD parseTMD(File tmd) throws IOException { public static TMD parseTMD(File tmd) throws IOException {
@ -48,15 +62,12 @@ public class TMD {
} }
public static TMD parseTMD(byte[] input) { public static TMD parseTMD(byte[] input) {
TMD result = new TMD();
result.setRawTMD(Arrays.copyOf(input,input.length));
byte[] signature = new byte[0x100]; byte[] signature = new byte[0x100];
byte[] issuer = new byte[0x40]; byte[] issuer = new byte[0x40];
byte[] reserved = new byte[62]; byte[] reserved = new byte[62];
byte[] SHA2 = new byte[0x20]; byte[] SHA2 = new byte[0x20];
ContentInfo[] contentInfos = result.getContentInfos(); ContentInfo[] contentInfos = new ContentInfo[0x40];
ByteBuffer buffer = ByteBuffer.allocate(input.length); ByteBuffer buffer = ByteBuffer.allocate(input.length);
buffer.put(input); buffer.put(input);
@ -106,6 +117,25 @@ public class TMD {
contentInfos[i] = ContentInfo.parseContentInfo(contentInfo); contentInfos[i] = ContentInfo.parseContentInfo(contentInfo);
} }
TMDParam param = new TMDParam();
param.setSignatureType(signatureType);
param.setSignature(signature);
param.setVersion(version);
param.setCACRLVersion(CACRLVersion);
param.setSignerCRLVersion(signerCRLVersion);
param.setSystemVersion(systemVersion);
param.setTitleID(titleID);
param.setTitleType(titleType);
param.setGroupID(groupID);
param.setAccessRights(accessRights);
param.setTitleVersion(titleVersion);
param.setContentCount(contentCount);
param.setBootIndex(bootIndex);
param.setSHA2(SHA2);
param.setContentInfos(contentInfos);
TMD result = new TMD(param);
//Get Contents //Get Contents
for(int i =0;i<contentCount;i++){ for(int i =0;i<contentCount;i++){
buffer.position(0xB04+(0x30*i)); buffer.position(0xB04+(0x30*i));
@ -116,22 +146,6 @@ public class TMD {
result.setContentToID(c.getID(), c); result.setContentToID(c.getID(), c);
} }
result.setSignatureType(signatureType);
result.setSignature(signature);
result.setVersion(version);
result.setCACRLVersion(CACRLVersion);
result.setSignerCRLVersion(signerCRLVersion);
result.setSystemVersion(systemVersion);
result.setTitleID(titleID);
result.setTitleType(titleType);
result.setGroupID(groupID);
result.setAccessRights(accessRights);
result.setTitleVersion(titleVersion);
result.setContentCount(contentCount);
result.setBootIndex(bootIndex);
result.setSHA2(SHA2);
result.setContentInfos(contentInfos);
return result; return result;
} }

View File

@ -0,0 +1,25 @@
package de.mas.jnus.lib.entities;
import de.mas.jnus.lib.entities.content.ContentInfo;
import lombok.Data;
@Data
public class TMDParam {
private int signatureType; // 0x000
private byte[] signature = new byte[0x100]; // 0x004
private byte[] issuer = new byte[0x40]; // 0x140
private byte version; // 0x180
private byte CACRLVersion; // 0x181
private byte signerCRLVersion; // 0x182
private long systemVersion; // 0x184
private long titleID; // 0x18C
private int titleType; // 0x194
private short groupID; // 0x198
private byte[] reserved = new byte[62]; // 0x19A
private int accessRights; // 0x1D8
private short titleVersion; // 0x1DC
private short contentCount; // 0x1DE
private short bootIndex; // 0x1E0
private byte[] SHA2 = new byte[0x20]; // 0x1E4
private ContentInfo[] contentInfos = new ContentInfo[0x40]; //
}

View File

@ -10,23 +10,20 @@ import de.mas.jnus.lib.Settings;
import de.mas.jnus.lib.utils.Utils; import de.mas.jnus.lib.utils.Utils;
import de.mas.jnus.lib.utils.cryptography.AESDecryption; import de.mas.jnus.lib.utils.cryptography.AESDecryption;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class Ticket { public final class Ticket {
@Getter @Setter private byte[] encryptedKey = new byte[0x10]; @Getter private final byte[] encryptedKey;
@Getter @Setter private byte[] decryptedKey = new byte[0x10]; @Getter private final byte[] decryptedKey;
@Getter @Setter private byte[] IV = new byte[0x10]; @Getter private final byte[] IV;
@Getter @Setter private byte[] cert0 = new byte[0x300];
@Getter @Setter private byte[] cert1 = new byte[0x400];
@Getter @Setter private byte[] rawTicket = new byte[0];
private Ticket(){
private Ticket(byte[] encryptedKey, byte[] decryptedKey,byte[] IV) {
super();
this.encryptedKey = encryptedKey;
this.decryptedKey = decryptedKey;
this.IV = IV;
} }
public static Ticket parseTicket(File ticket) throws IOException { public static Ticket parseTicket(File ticket) throws IOException {
@ -55,36 +52,15 @@ public class Ticket {
long titleID = buffer.getLong(); long titleID = buffer.getLong();
Ticket result = createTicket(encryptedKey,titleID); Ticket result = createTicket(encryptedKey,titleID);
result.setRawTicket(Arrays.copyOf(ticket, ticket.length));
//read certs.
byte[] cert0 = new byte[0x300];
byte[] cert1 = new byte[0x400];
if(ticket.length >= 0x650){
buffer.position(0x350);
buffer.get(cert0,0x00,0x300);
}
if(ticket.length >= 0xA50){
buffer.position(0x650);
buffer.get(cert1,0x00,0x400);
}
result.setCert0(cert0);
result.setCert1(cert1);
return result; return result;
} }
public static Ticket createTicket(byte[] encryptedKey, long titleID) { public static Ticket createTicket(byte[] encryptedKey, long titleID) {
Ticket result = new Ticket();
result.encryptedKey = encryptedKey;
byte[] IV = ByteBuffer.allocate(0x10).putLong(titleID).array(); byte[] IV = ByteBuffer.allocate(0x10).putLong(titleID).array();
result.decryptedKey = calculateDecryptedKey(result.encryptedKey,IV); byte[] decryptedKey = calculateDecryptedKey(encryptedKey,IV);
result.setIV(IV);
return result; return new Ticket(encryptedKey,decryptedKey,IV);
} }
private static byte[] calculateDecryptedKey(byte[] encryptedKey, byte[] IV) { private static byte[] calculateDecryptedKey(byte[] encryptedKey, byte[] IV) {
@ -109,9 +85,7 @@ public class Ticket {
if (getClass() != obj.getClass()) if (getClass() != obj.getClass())
return false; return false;
Ticket other = (Ticket) obj; Ticket other = (Ticket) obj;
if (!Arrays.equals(encryptedKey, other.encryptedKey)) return Arrays.equals(encryptedKey, other.encryptedKey);
return false;
return true;
} }
@Override @Override

View File

@ -20,16 +20,24 @@ public class Content{
public static final short CONTENT_HASHED = 0x0002; public static final short CONTENT_HASHED = 0x0002;
public static final short CONTENT_ENCRYPTED = 0x0001; public static final short CONTENT_ENCRYPTED = 0x0001;
@Getter @Setter private int ID = 0x00; @Getter private final int ID;
@Getter @Setter private short index = 0x00; @Getter private final short index;
@Getter @Setter private short type = 0x0000; @Getter private final short type;
@Getter @Setter private long encryptedFileSize = 0; @Getter private final long encryptedFileSize;
@Getter @Setter private byte[] SHA2Hash = new byte[0x14]; @Getter private final byte[] SHA2Hash;
@Getter private List<FSTEntry> entries = new ArrayList<>(); @Getter private final List<FSTEntry> entries = new ArrayList<>();
@Getter @Setter private ContentFSTInfo contentFSTInfo = null; @Getter @Setter private ContentFSTInfo contentFSTInfo;
private Content(ContentParam param) {
this.ID = param.getID();
this.index = param.getIndex();
this.type = param.getType();
this.encryptedFileSize = param.getEncryptedFileSize();
this.SHA2Hash = param.getSHA2Hash();
}
/** /**
* Creates a new Content object given be the raw byte data * Creates a new Content object given be the raw byte data
@ -44,6 +52,7 @@ public class Content{
ByteBuffer buffer = ByteBuffer.allocate(input.length); ByteBuffer buffer = ByteBuffer.allocate(input.length);
buffer.put(input); buffer.put(input);
buffer.position(0); buffer.position(0);
int ID = buffer.getInt(0x00); int ID = buffer.getInt(0x00);
short index = buffer.getShort(0x04); short index = buffer.getShort(0x04);
short type = buffer.getShort(0x06); short type = buffer.getShort(0x06);
@ -51,15 +60,15 @@ public class Content{
buffer.position(0x10); buffer.position(0x10);
byte[] hash = new byte[0x14]; byte[] hash = new byte[0x14];
buffer.get(hash, 0x00, 0x14); buffer.get(hash, 0x00, 0x14);
return new Content(ID, index,type,encryptedFileSize, hash);
}
public Content(int ID, short index, short type, long encryptedFileSize, byte[] hash) { ContentParam param = new ContentParam();
setID(ID); param.setID(ID);
setIndex(index); param.setIndex(index);
setType(type); param.setType(type);
setEncryptedFileSize(encryptedFileSize); param.setEncryptedFileSize(encryptedFileSize);
setSHA2Hash(hash); param.setSHA2Hash(hash);
return new Content(param);
} }
/** /**
@ -137,9 +146,7 @@ public class Content{
Content other = (Content) obj; Content other = (Content) obj;
if (ID != other.ID) if (ID != other.ID)
return false; return false;
if (!Arrays.equals(SHA2Hash, other.SHA2Hash)) return Arrays.equals(SHA2Hash, other.SHA2Hash);
return false;
return true;
} }
@Override @Override

View File

@ -4,7 +4,6 @@ import java.nio.ByteBuffer;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
@EqualsAndHashCode @EqualsAndHashCode
/** /**
@ -13,20 +12,23 @@ import lombok.Setter;
* @author Maschell * @author Maschell
* *
*/ */
public class ContentFSTInfo { public final class ContentFSTInfo {
@Getter @Setter private long offsetSector; @Getter private final long offsetSector;
@Getter @Setter private long sizeSector; @Getter private final long sizeSector;
@Getter @Setter private long ownerTitleID; @Getter private final long ownerTitleID;
@Getter @Setter private int groupID; @Getter private final int groupID;
@Getter @Setter private byte unkown; @Getter private final byte unkown;
private static int SECTOR_SIZE = 0x8000; private static int SECTOR_SIZE = 0x8000;
private ContentFSTInfo(){ private ContentFSTInfo(ContentFSTInfoParam param){
this.offsetSector = param.getOffsetSector();
this.sizeSector = param.getSizeSector();
this.ownerTitleID = param.getOwnerTitleID();
this.groupID = param.getGroupID();
this.unkown = param.getUnkown();
} }
/** /**
* Creates a new ContentFSTInfo object given be the raw byte data * Creates a new ContentFSTInfo object given be the raw byte data
* @param input 0x20 byte of data from the FST (starting at 0x20) * @param input 0x20 byte of data from the FST (starting at 0x20)
@ -37,7 +39,7 @@ public class ContentFSTInfo {
System.out.println("Error: invalid ContentFSTInfo byte[] input"); System.out.println("Error: invalid ContentFSTInfo byte[] input");
return null; return null;
} }
ContentFSTInfo cFSTInfo = new ContentFSTInfo(); ContentFSTInfoParam param = new ContentFSTInfoParam();
ByteBuffer buffer = ByteBuffer.allocate(input.length); ByteBuffer buffer = ByteBuffer.allocate(input.length);
buffer.put(input); buffer.put(input);
@ -48,13 +50,13 @@ public class ContentFSTInfo {
int groupID = buffer.getInt(); int groupID = buffer.getInt();
byte unkown = buffer.get(); byte unkown = buffer.get();
cFSTInfo.setOffsetSector(offset); param.setOffsetSector(offset);
cFSTInfo.setSizeSector(size); param.setSizeSector(size);
cFSTInfo.setOwnerTitleID(ownerTitleID); param.setOwnerTitleID(ownerTitleID);
cFSTInfo.setGroupID(groupID); param.setGroupID(groupID);
cFSTInfo.setUnkown(unkown); param.setUnkown(unkown);
return cFSTInfo; return new ContentFSTInfo(param);
} }
/** /**

View File

@ -0,0 +1,12 @@
package de.mas.jnus.lib.entities.content;
import lombok.Data;
@Data
public class ContentFSTInfoParam {
private long offsetSector = 0;
private long sizeSector = 0;
private long ownerTitleID = 0;
private int groupID = 0;
private byte unkown = 0;
}

View File

@ -5,7 +5,6 @@ import java.util.Arrays;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
@EqualsAndHashCode @EqualsAndHashCode
/** /**
@ -14,9 +13,9 @@ import lombok.Setter;
* *
*/ */
public class ContentInfo{ public class ContentInfo{
@Getter @Setter private short indexOffset = 0x00; @Getter private final short indexOffset;
@Getter @Setter private short commandCount = 0x00; @Getter private final short commandCount;
@Getter @Setter private byte[] SHA2Hash = new byte[0x20]; @Getter private final byte[] SHA2Hash;
public ContentInfo() { public ContentInfo() {
this((short) 0); this((short) 0);
@ -29,9 +28,9 @@ public class ContentInfo{
this(indexOffset,commandCount,null); this(indexOffset,commandCount,null);
} }
public ContentInfo(short indexOffset,short commandCount,byte[] SHA2Hash) { public ContentInfo(short indexOffset,short commandCount,byte[] SHA2Hash) {
setIndexOffset(indexOffset); this.indexOffset = indexOffset;
setCommandCount(commandCount); this.commandCount = commandCount;
setSHA2Hash(SHA2Hash); this.SHA2Hash = SHA2Hash;
} }
/** /**

View File

@ -0,0 +1,14 @@
package de.mas.jnus.lib.entities.content;
import lombok.Data;
@Data
public class ContentParam {
private int ID = 0x00;
private short index = 0x00;
private short type = 0x0000;
private long encryptedFileSize = 0;
private byte[] SHA2Hash = new byte[0x14];
private ContentFSTInfo contentFSTInfo = null;
}

View File

@ -8,22 +8,23 @@ import de.mas.jnus.lib.entities.content.Content;
import de.mas.jnus.lib.entities.content.ContentFSTInfo; import de.mas.jnus.lib.entities.content.ContentFSTInfo;
import de.mas.jnus.lib.utils.ByteUtils; import de.mas.jnus.lib.utils.ByteUtils;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
/** /**
* Represents the FST * Represents the FST
* @author Maschell * @author Maschell
* *
*/ */
public class FST { public final class FST {
@Getter @Setter private FSTEntry root = FSTEntry.getRootFSTEntry(); @Getter private final FSTEntry root = FSTEntry.getRootFSTEntry();
@Getter @Setter private int unknown; @Getter private final int unknown;
@Getter @Setter private int contentCount = 0; @Getter private final int contentCount;
@Getter @Setter private Map<Integer,ContentFSTInfo> contentFSTInfos = new HashMap<>(); @Getter private final Map<Integer,ContentFSTInfo> contentFSTInfos = new HashMap<>();
private FST(){
private FST(int unknown, int contentCount) {
super();
this.unknown = unknown;
this.contentCount = contentCount;
} }
/** /**
@ -36,11 +37,12 @@ public class FST {
if(!Arrays.equals(Arrays.copyOfRange(fstData, 0, 3), new byte[]{0x46,0x53,0x54})){ if(!Arrays.equals(Arrays.copyOfRange(fstData, 0, 3), new byte[]{0x46,0x53,0x54})){
throw new IllegalArgumentException("Not a FST. Maybe a wrong key?"); throw new IllegalArgumentException("Not a FST. Maybe a wrong key?");
} }
FST result = new FST();
int unknownValue = ByteUtils.getIntFromBytes(fstData, 0x04); int unknownValue = ByteUtils.getIntFromBytes(fstData, 0x04);
int contentCount = ByteUtils.getIntFromBytes(fstData, 0x08); int contentCount = ByteUtils.getIntFromBytes(fstData, 0x08);
FST result = new FST(unknownValue,contentCount);
int contentfst_offset = 0x20; int contentfst_offset = 0x20;
int contentfst_size = 0x20*contentCount; int contentfst_size = 0x20*contentCount;
@ -71,11 +73,6 @@ public class FST {
FSTService.parseFST(root,fstSection,nameSection,contentsMappedByIndex,contentFSTInfos); FSTService.parseFST(root,fstSection,nameSection,contentsMappedByIndex,contentFSTInfos);
result.setContentCount(contentCount);
result.setUnknown(unknownValue);
result.setContentFSTInfos(contentFSTInfos);
return result; return result;
} }
} }

View File

@ -5,7 +5,6 @@ import java.util.List;
import de.mas.jnus.lib.entities.content.Content; import de.mas.jnus.lib.entities.content.Content;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
@ -18,29 +17,38 @@ public class FSTEntry{
public static final byte FSTEntry_DIR = (byte)0x01; public static final byte FSTEntry_DIR = (byte)0x01;
public static final byte FSTEntry_notInNUS = (byte)0x80; public static final byte FSTEntry_notInNUS = (byte)0x80;
@Getter @Setter private String filename = ""; @Getter private final String filename;
@Getter @Setter private String path = ""; @Getter private final String path;
@Getter @Setter private FSTEntry parent = null; @Getter private final FSTEntry parent;
private List<FSTEntry> children = null; @Getter private final List<FSTEntry> children = new ArrayList<>();
@Getter @Setter private short flags; @Getter private final short flags;
@Getter @Setter private long fileSize = 0; @Getter private final long fileSize;
@Getter @Setter private long fileOffset = 0; @Getter private final long fileOffset;
@Getter private Content content = null; @Getter private final Content content;
@Getter @Setter private byte[] hash = new byte[0x14]; @Getter private final boolean isDir;
@Getter private final boolean isRoot;
@Getter private final boolean isNotInPackage;
@Getter @Setter private boolean isDir = false; @Getter private final short contentFSTID;
@Getter @Setter private boolean isRoot = false;
@Getter @Setter private boolean notInPackage = false;
@Getter @Setter private short contentFSTID = 0;
public FSTEntry(){
protected FSTEntry(FSTEntryParam fstParam){
this.filename = fstParam.getFilename();
this.path = fstParam.getPath();
this.flags = fstParam.getFlags();
this.parent = fstParam.getParent();
this.fileSize = fstParam.getFileSize();
this.fileOffset = fstParam.getFileOffset();
this.content = fstParam.getContent();
content.addEntry(this);
this.isDir = fstParam.isDir();
this.isRoot = fstParam.isRoot();
this.isNotInPackage = fstParam.isNotInPackage();
this.contentFSTID = fstParam.getContentFSTID();
} }
/** /**
@ -48,9 +56,10 @@ public class FSTEntry{
* @return * @return
*/ */
public static FSTEntry getRootFSTEntry(){ public static FSTEntry getRootFSTEntry(){
FSTEntry entry = new FSTEntry(); FSTEntryParam param = new FSTEntryParam();
entry.setRoot(true); param.setRoot(true);
return entry; param.setDir(true);
return new FSTEntry(param);
} }
public String getFullPath() { public String getFullPath() {
@ -65,18 +74,6 @@ public class FSTEntry{
return count; return count;
} }
public void addChildren(FSTEntry fstEntry) {
getChildren().add(fstEntry);
fstEntry.setParent(this);
}
public List<FSTEntry> getChildren() {
if(children == null){
children = new ArrayList<>();
}
return children;
}
public List<FSTEntry> getDirChildren(){ public List<FSTEntry> getDirChildren(){
return getDirChildren(false); return getDirChildren(false);
} }
@ -121,16 +118,6 @@ public class FSTEntry{
return entries; return entries;
} }
public void setContent(Content content) {
if(content == null){
log.warning("Can't set content for "+ getFilename() + ": Content it null");
System.out.println();
return;
}
this.content = content;
content.addEntry(this);
}
public long getFileOffsetBlock() { public long getFileOffsetBlock() {
if(getContent().isHashed()){ if(getContent().isHashed()){
return (getFileOffset()/0xFC00) * 0x10000; return (getFileOffset()/0xFC00) * 0x10000;
@ -160,6 +147,6 @@ public class FSTEntry{
public String toString() { public String toString() {
return "FSTEntry [filename=" + filename + ", path=" + path + ", flags=" + flags + ", filesize=" + fileSize return "FSTEntry [filename=" + filename + ", path=" + path + ", flags=" + flags + ", filesize=" + fileSize
+ ", fileoffset=" + fileOffset + ", content=" + content + ", isDir=" + isDir + ", isRoot=" + isRoot + ", fileoffset=" + fileOffset + ", content=" + content + ", isDir=" + isDir + ", isRoot=" + isRoot
+ ", notInPackage=" + notInPackage + "]"; + ", notInPackage=" + isNotInPackage + "]";
} }
} }

View File

@ -0,0 +1,24 @@
package de.mas.jnus.lib.entities.fst;
import de.mas.jnus.lib.entities.content.Content;
import lombok.Data;
@Data
public class FSTEntryParam {
private String filename = "";
private String path = "";
private FSTEntry parent = null;
private short flags;
private long fileSize = 0;
private long fileOffset = 0;
private Content content = null;
private boolean isDir = false;
private boolean isRoot = false;
private boolean notInPackage = false;
private short contentFSTID = 0;
}

View File

@ -11,8 +11,12 @@ import de.mas.jnus.lib.utils.ByteUtils;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class FSTService { public final class FSTService {
protected static void parseFST(FSTEntry rootEntry, byte[] fstSection, byte[] namesSection,Map<Integer,Content> contentsByIndex,Map<Integer,ContentFSTInfo> contentsFSTByIndex) { private FSTService(){
//Don't use this!!!
}
public static void parseFST(FSTEntry rootEntry, byte[] fstSection, byte[] namesSection,Map<Integer,Content> contentsByIndex,Map<Integer,ContentFSTInfo> contentsFSTByIndex) {
int totalEntries = ByteUtils.getIntFromBytes(fstSection, 0x08); int totalEntries = ByteUtils.getIntFromBytes(fstSection, 0x08);
int level = 0; int level = 0;
@ -20,8 +24,6 @@ public class FSTService {
int[] Entry = new int[16]; int[] Entry = new int[16];
HashMap<Integer,FSTEntry> fstEntryToOffsetMap = new HashMap<>(); HashMap<Integer,FSTEntry> fstEntryToOffsetMap = new HashMap<>();
rootEntry.setDir(true);
Entry[level] = 0; Entry[level] = 0;
LEntry[level++] = 0; LEntry[level++] = 0;
@ -35,7 +37,7 @@ public class FSTService {
byte[] curEntry = Arrays.copyOfRange(fstSection,i*0x10,(i+1)*0x10); byte[] curEntry = Arrays.copyOfRange(fstSection,i*0x10,(i+1)*0x10);
FSTEntry entry = new FSTEntry(); FSTEntryParam entryParam = new FSTEntryParam();
String path = getFullPath(level, fstSection, namesSection, Entry); String path = getFullPath(level, fstSection, namesSection, Entry);
String filename = getName(curEntry,namesSection); String filename = getName(curEntry,namesSection);
@ -47,19 +49,15 @@ public class FSTService {
short contentIndex = ByteUtils.getShortFromBytes(curEntry, 0x0E); short contentIndex = ByteUtils.getShortFromBytes(curEntry, 0x0E);
if((curEntry[0] & FSTEntry.FSTEntry_notInNUS) == FSTEntry.FSTEntry_notInNUS){ if((curEntry[0] & FSTEntry.FSTEntry_notInNUS) == FSTEntry.FSTEntry_notInNUS){
entry.setNotInPackage(true); entryParam.setNotInPackage(true);
} }
FSTEntry parent = null;
if((curEntry[0] & FSTEntry.FSTEntry_DIR) == FSTEntry.FSTEntry_DIR){ if((curEntry[0] & FSTEntry.FSTEntry_DIR) == FSTEntry.FSTEntry_DIR){
entry.setDir(true); entryParam.setDir(true);
int parentOffset = (int) fileOffset; int parentOffset = (int) fileOffset;
int nextOffset = (int) fileSize; int nextOffset = (int) fileSize;
FSTEntry parent = fstEntryToOffsetMap.get(parentOffset); parent = fstEntryToOffsetMap.get(parentOffset);
if(parent != null){
log.fine("no parent found for a FSTEntry");
parent.addChildren(entry);
}
Entry[level] = i; Entry[level] = i;
LEntry[level++] = nextOffset ; LEntry[level++] = nextOffset ;
@ -69,26 +67,21 @@ public class FSTService {
break; break;
} }
}else{ }else{
entry.setFileOffset(fileOffset<<5); entryParam.setFileOffset(fileOffset<<5);
entry.setFileSize(fileSize); entryParam.setFileSize(fileSize);
FSTEntry parent = fstEntryToOffsetMap.get(Entry[level-1]); parent = fstEntryToOffsetMap.get(Entry[level-1]);
if(parent != null){
parent.addChildren(entry);
}else{
log.warning(entryOffset +"couldn't find parent @ " + Entry[level-1]);
}
} }
entry.setFlags(flags); entryParam.setFlags(flags);
entry.setFilename(filename); entryParam.setFilename(filename);
entry.setPath(path); entryParam.setPath(path);
if(contentsByIndex != null){ if(contentsByIndex != null){
Content content = contentsByIndex.get((int)contentIndex); Content content = contentsByIndex.get((int)contentIndex);
if(content == null){ if(content == null){
log.warning("Content for FST Entry not found"); log.warning("Content for FST Entry not found");
}else{ }else{
entry.setContent(content); entryParam.setContent(content);
ContentFSTInfo contentFSTInfo = contentsFSTByIndex.get((int)contentIndex); ContentFSTInfo contentFSTInfo = contentsFSTByIndex.get((int)contentIndex);
if(contentFSTInfo == null){ if(contentFSTInfo == null){
@ -99,7 +92,10 @@ public class FSTService {
} }
} }
entry.setContentFSTID(contentIndex); entryParam.setContentFSTID(contentIndex);
entryParam.setParent(parent);
FSTEntry entry = new FSTEntry(entryParam);
fstEntryToOffsetMap.put(entryOffset, entry); fstEntryToOffsetMap.put(entryOffset, entry);
} }

View File

@ -3,7 +3,6 @@ package de.mas.jnus.lib.implementations;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.util.concurrent.SynchronousQueue;
import com.sun.istack.internal.NotNull; import com.sun.istack.internal.NotNull;
@ -13,7 +12,6 @@ import de.mas.jnus.lib.entities.content.Content;
import de.mas.jnus.lib.utils.FileUtils; import de.mas.jnus.lib.utils.FileUtils;
import de.mas.jnus.lib.utils.Utils; import de.mas.jnus.lib.utils.Utils;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
@ -24,10 +22,10 @@ import lombok.extern.java.Log;
* *
*/ */
public abstract class NUSDataProvider { public abstract class NUSDataProvider {
@Getter private final NUSTitle NUSTitle;
@Getter @Setter private NUSTitle NUSTitle = null; public NUSDataProvider (NUSTitle title) {
this.NUSTitle = title;
public NUSDataProvider () {
} }
/** /**
@ -56,7 +54,7 @@ public abstract class NUSDataProvider {
return; return;
} }
byte[] hash = getContentH3Hash(content); byte[] hash = getContentH3Hash(content);
if(hash == null){ if(hash == null || hash.length == 0){
return; return;
} }
String h3Filename = String.format("%08X%s", content.getID(),Settings.H3_EXTENTION); String h3Filename = String.format("%08X%s", content.getID(),Settings.H3_EXTENTION);

View File

@ -6,15 +6,18 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.nio.file.Files; import java.nio.file.Files;
import de.mas.jnus.lib.NUSTitle;
import de.mas.jnus.lib.Settings; import de.mas.jnus.lib.Settings;
import de.mas.jnus.lib.entities.content.Content; import de.mas.jnus.lib.entities.content.Content;
import de.mas.jnus.lib.utils.StreamUtils;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
public class NUSDataProviderLocal extends NUSDataProvider { public final class NUSDataProviderLocal extends NUSDataProvider {
@Getter @Setter private String localPath = ""; @Getter private final String localPath;
public NUSDataProviderLocal() { public NUSDataProviderLocal(NUSTitle nustitle, String localPath) {
super(nustitle);
this.localPath = localPath;
} }
public String getFilePathOnDisk(Content c) { public String getFilePathOnDisk(Content c) {
@ -29,7 +32,7 @@ public class NUSDataProviderLocal extends NUSDataProvider {
return null; return null;
} }
InputStream in = new FileInputStream(filepath); InputStream in = new FileInputStream(filepath);
in.skip(offset); StreamUtils.skipExactly(in,offset);
return in; return in;
} }
@ -38,7 +41,7 @@ public class NUSDataProviderLocal extends NUSDataProvider {
String h3Path = getLocalPath() + File.separator + String.format("%08X.h3", content.getID()); String h3Path = getLocalPath() + File.separator + String.format("%08X.h3", content.getID());
File h3File = new File(h3Path); File h3File = new File(h3Path);
if(!h3File.exists()){ if(!h3File.exists()){
return null; return new byte[0];
} }
return Files.readAllBytes(h3File.toPath()); return Files.readAllBytes(h3File.toPath());
} }
@ -59,10 +62,6 @@ public class NUSDataProviderLocal extends NUSDataProvider {
return Files.readAllBytes(ticketFile.toPath()); return Files.readAllBytes(ticketFile.toPath());
} }
@Override
public void cleanup() throws IOException {
}
@Override @Override
public byte[] getRawCert() throws IOException { public byte[] getRawCert() throws IOException {
String inputPath = getLocalPath(); String inputPath = getLocalPath();
@ -70,4 +69,9 @@ public class NUSDataProviderLocal extends NUSDataProvider {
File certFile = new File(certPath); File certFile = new File(certPath);
return Files.readAllBytes(certFile.toPath()); return Files.readAllBytes(certFile.toPath());
} }
@Override
public void cleanup() throws IOException {
//We don't need this
}
} }

View File

@ -3,21 +3,25 @@ package de.mas.jnus.lib.implementations;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import de.mas.jnus.lib.Settings; import de.mas.jnus.lib.NUSTitle;
import de.mas.jnus.lib.entities.content.Content; import de.mas.jnus.lib.entities.content.Content;
import de.mas.jnus.lib.utils.download.NUSDownloadService; import de.mas.jnus.lib.utils.download.NUSDownloadService;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
public class NUSDataProviderRemote extends NUSDataProvider { public class NUSDataProviderRemote extends NUSDataProvider {
@Getter @Setter private int version = Settings.LATEST_TMD_VERSION; @Getter private final int version;
@Getter @Setter private long titleID = 0L; @Getter private final long titleID;
public NUSDataProviderRemote(NUSTitle title,int version, long titleID) {
super(title);
this.version = version;
this.titleID = titleID;
}
@Override @Override
public InputStream getInputStreamFromContent(Content content, long fileOffsetBlock) throws IOException { public InputStream getInputStreamFromContent(Content content, long fileOffsetBlock) throws IOException {
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance(); NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
InputStream in = downloadService.getInputStreamForURL(getRemoteURL(content),fileOffsetBlock); return downloadService.getInputStreamForURL(getRemoteURL(content),fileOffsetBlock);
return in;
} }
private String getRemoteURL(Content content) { private String getRemoteURL(Content content) {
@ -51,12 +55,12 @@ public class NUSDataProviderRemote extends NUSDataProvider {
} }
@Override @Override
public void cleanup() throws IOException { public byte[] getRawCert() throws IOException {
// TODO Auto-generated method stub return new byte[0];
} }
@Override @Override
public byte[] getRawCert() throws IOException { public void cleanup() throws IOException {
return null; //We don't need this
} }
} }

View File

@ -3,6 +3,7 @@ package de.mas.jnus.lib.implementations;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import de.mas.jnus.lib.NUSTitle;
import de.mas.jnus.lib.Settings; import de.mas.jnus.lib.Settings;
import de.mas.jnus.lib.entities.TMD; import de.mas.jnus.lib.entities.TMD;
import de.mas.jnus.lib.entities.content.Content; import de.mas.jnus.lib.entities.content.Content;
@ -16,12 +17,13 @@ import lombok.Setter;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class NUSDataProviderWUD extends NUSDataProvider { public class NUSDataProviderWUD extends NUSDataProvider {
@Getter @Setter private WUDInfo WUDInfo = null; @Getter private final WUDInfo WUDInfo;
@Setter(AccessLevel.PRIVATE) private TMD TMD = null; @Setter(AccessLevel.PRIVATE) private TMD TMD = null;
public NUSDataProviderWUD() { public NUSDataProviderWUD(NUSTitle title,WUDInfo wudinfo) {
super(); super(title);
this.WUDInfo = wudinfo;
} }
public long getOffsetInWUD(Content content) { public long getOffsetInWUD(Content content) {
@ -97,6 +99,7 @@ public class NUSDataProviderWUD extends NUSDataProvider {
@Override @Override
public void cleanup() { public void cleanup() {
//We don't need it
} }
} }

View File

@ -5,10 +5,12 @@ import java.io.InputStream;
import java.util.zip.ZipEntry; import java.util.zip.ZipEntry;
import java.util.zip.ZipException; import java.util.zip.ZipException;
import de.mas.jnus.lib.NUSTitle;
import de.mas.jnus.lib.Settings; import de.mas.jnus.lib.Settings;
import de.mas.jnus.lib.entities.content.Content; import de.mas.jnus.lib.entities.content.Content;
import de.mas.jnus.lib.implementations.woomy.WoomyInfo; import de.mas.jnus.lib.implementations.woomy.WoomyInfo;
import de.mas.jnus.lib.implementations.woomy.WoomyZipFile; import de.mas.jnus.lib.implementations.woomy.WoomyZipFile;
import lombok.AccessLevel;
import lombok.Getter; import lombok.Getter;
import lombok.NonNull; import lombok.NonNull;
import lombok.Setter; import lombok.Setter;
@ -16,8 +18,13 @@ import lombok.extern.java.Log;
@Log @Log
public class NUSDataProviderWoomy extends NUSDataProvider{ public class NUSDataProviderWoomy extends NUSDataProvider{
@Getter @Setter private WoomyInfo woomyInfo; @Getter private final WoomyInfo woomyInfo;
@Setter private WoomyZipFile woomyZipFile; @Setter(AccessLevel.PRIVATE) private WoomyZipFile woomyZipFile;
public NUSDataProviderWoomy(NUSTitle title,WoomyInfo woomyInfo) {
super(title);
this.woomyInfo = woomyInfo;
}
@Override @Override
public InputStream getInputStreamFromContent(@NonNull Content content, long fileOffsetBlock) throws IOException { public InputStream getInputStreamFromContent(@NonNull Content content, long fileOffsetBlock) throws IOException {
@ -39,7 +46,7 @@ public class NUSDataProviderWoomy extends NUSDataProvider{
zipFile.close(); zipFile.close();
return result; return result;
} }
return null; return new byte[0];
} }
@Override @Override
@ -70,10 +77,10 @@ public class NUSDataProviderWoomy extends NUSDataProvider{
} }
public WoomyZipFile getSharedWoomyZipFile() throws ZipException, IOException { public WoomyZipFile getSharedWoomyZipFile() throws ZipException, IOException {
if(woomyZipFile == null || woomyZipFile.isClosed()){ if(this.woomyZipFile == null || this.woomyZipFile.isClosed()){
woomyZipFile = getNewWoomyZipFile(); this.woomyZipFile = getNewWoomyZipFile();
} }
return woomyZipFile; return this.woomyZipFile;
} }
private WoomyZipFile getNewWoomyZipFile() throws ZipException, IOException { private WoomyZipFile getNewWoomyZipFile() throws ZipException, IOException {
@ -82,14 +89,13 @@ public class NUSDataProviderWoomy extends NUSDataProvider{
@Override @Override
public void cleanup() throws IOException { public void cleanup() throws IOException {
if(woomyZipFile != null && woomyZipFile.isClosed()){ if(this.woomyZipFile != null && this.woomyZipFile.isClosed()){
woomyZipFile.close(); this.woomyZipFile.close();
} }
} }
@Override @Override
public byte[] getRawCert() throws IOException { public byte[] getRawCert() throws IOException {
// TODO Auto-generated method stub return new byte[0];
return null;
} }
} }

View File

@ -7,33 +7,19 @@ import lombok.Data;
@Data @Data
public class WoomyMeta { public class WoomyMeta {
private String name; private final String name;
private int icon; private final int icon;
private List<WoomyEntry> entries; private final List<WoomyEntry> entries = new ArrayList<>();
public void addEntry(String name,String folder, int entryCount){ public void addEntry(String name,String folder, int entryCount){
WoomyEntry entry = new WoomyEntry(name, folder, entryCount); WoomyEntry entry = new WoomyEntry(name, folder, entryCount);
getEntries().add(entry); getEntries().add(entry);
} }
public List<WoomyEntry> getEntries(){
if(entries == null){
setEntries(new ArrayList<>());
}
return entries;
}
@Data @Data
public class WoomyEntry { public class WoomyEntry {
private final String name;
public WoomyEntry(String name, String folder, int entryCount) { private final String folder;
setName(name); private final int entryCount;
setFolder(folder);
setEntryCount(entryCount);
}
private String name;
private String folder;
private int entryCount;
} }
} }

View File

@ -9,7 +9,7 @@ import de.mas.jnus.lib.utils.XMLParser;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class WoomyMetaParser extends XMLParser{ public final class WoomyMetaParser extends XMLParser{
private static final String WOOMY_METADATA_NAME = "name"; private static final String WOOMY_METADATA_NAME = "name";
private static final String WOOMY_METADATA_ICON = "icon"; private static final String WOOMY_METADATA_ICON = "icon";
@ -25,7 +25,8 @@ public class WoomyMetaParser extends XMLParser{
public static WoomyMeta parseMeta(InputStream data){ public static WoomyMeta parseMeta(InputStream data){
XMLParser parser = new WoomyMetaParser(); XMLParser parser = new WoomyMetaParser();
WoomyMeta result = new WoomyMeta(); String resultName = "";
int resultIcon = 0;
try { try {
parser.loadDocument(data); parser.loadDocument(data);
} catch(Exception e){ } catch(Exception e){
@ -35,14 +36,17 @@ public class WoomyMetaParser extends XMLParser{
String name = parser.getValueOfElement(WOOMY_METADATA_NAME); String name = parser.getValueOfElement(WOOMY_METADATA_NAME);
if(name != null && !name.isEmpty()){ if(name != null && !name.isEmpty()){
result.setName(name); resultName = name;
} }
String icon = parser.getValueOfElement(WOOMY_METADATA_ICON); String icon = parser.getValueOfElement(WOOMY_METADATA_ICON);
if(icon != null && !icon.isEmpty()){ if(icon != null && !icon.isEmpty()){
int icon_val = Integer.parseInt(icon); int icon_val = Integer.parseInt(icon);
result.setIcon(icon_val); resultIcon = icon_val;
} }
WoomyMeta result = new WoomyMeta(resultName,resultIcon);
Node entries_node = parser.getNodeByValue(WOOMY_METADATA_ENTRIES); Node entries_node = parser.getNodeByValue(WOOMY_METADATA_ENTRIES);
NodeList entry_list = entries_node.getChildNodes(); NodeList entry_list = entries_node.getChildNodes();

View File

@ -4,6 +4,7 @@ import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap; import java.util.HashMap;
import java.util.Locale;
import java.util.Map; import java.util.Map;
import java.util.regex.Matcher; import java.util.regex.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
@ -27,7 +28,10 @@ import lombok.extern.java.Log;
* *
*/ */
@Log @Log
public class WoomyParser { public final class WoomyParser {
private WoomyParser(){
//
}
public static WoomyInfo createWoomyInfo(File woomyFile) throws IOException, ParserConfigurationException, SAXException{ public static WoomyInfo createWoomyInfo(File woomyFile) throws IOException, ParserConfigurationException, SAXException{
WoomyInfo result = new WoomyInfo(); WoomyInfo result = new WoomyInfo();
if(!woomyFile.exists()){ if(!woomyFile.exists()){
@ -72,7 +76,7 @@ public class WoomyParser {
if(matcher.matches()){ if(matcher.matches()){
String[] tokens = entryName.split("[\\\\|/]"); //We only want the filename! String[] tokens = entryName.split("[\\\\|/]"); //We only want the filename!
String filename = tokens[tokens.length - 1]; String filename = tokens[tokens.length - 1];
result.put(filename.toLowerCase(), entry); result.put(filename.toLowerCase(Locale.ENGLISH), entry);
} }
} }
} }

View File

@ -20,13 +20,14 @@ import lombok.extern.java.Log;
public class WUDImage { public class WUDImage {
public static long WUD_FILESIZE = 0x5D3A00000L; public static long WUD_FILESIZE = 0x5D3A00000L;
@Getter @Setter private File fileHandle = null; @Getter private final File fileHandle;
@Getter @Setter private WUDImageCompressedInfo compressedInfo = null; @Getter @Setter private WUDImageCompressedInfo compressedInfo = null;
@Getter @Setter private boolean isCompressed = false; @Getter private final boolean isCompressed;
@Getter @Setter private boolean isSplitted = false; @Getter private final boolean isSplitted;
private long inputFileSize = 0L; private long inputFileSize = 0L;
@Setter private WUDDiscReader WUDDiscReader = null; @Getter private final WUDDiscReader WUDDiscReader;
public WUDImage(File file) throws IOException{ public WUDImage(File file) throws IOException{
if(file == null || !file.exists()){ if(file == null || !file.exists()){
@ -42,7 +43,8 @@ public class WUDImage {
if(compressedInfo.isWUX()){ if(compressedInfo.isWUX()){
log.info("Image is compressed"); log.info("Image is compressed");
setCompressed(true); this.isCompressed = true;
this.isSplitted = false;
Map<Integer,Long> indexTable = new HashMap<>(); Map<Integer,Long> indexTable = new HashMap<>();
long offsetIndexTable = compressedInfo.getOffsetIndexTable(); long offsetIndexTable = compressedInfo.getOffsetIndexTable();
fileStream.seek(offsetIndexTable); fileStream.seek(offsetIndexTable);
@ -56,27 +58,27 @@ public class WUDImage {
} }
compressedInfo.setIndexTable(indexTable); compressedInfo.setIndexTable(indexTable);
setCompressedInfo(compressedInfo); setCompressedInfo(compressedInfo);
}else if(file.getName().equals(String.format(WUDDiscReaderSplitted.WUD_SPLITTED_DEFAULT_FILEPATTERN, 1)) && }else{
this.isCompressed = false;
if(file.getName().equals(String.format(WUDDiscReaderSplitted.WUD_SPLITTED_DEFAULT_FILEPATTERN, 1)) &&
(file.length() == WUDDiscReaderSplitted.WUD_SPLITTED_FILE_SIZE)){ (file.length() == WUDDiscReaderSplitted.WUD_SPLITTED_FILE_SIZE)){
setSplitted(true); this.isSplitted = true;
log.info("Image is splitted"); log.info("Image is splitted");
}else{
this.isSplitted = false;
}
}
if(isCompressed()){
this.WUDDiscReader = new WUDDiscReaderCompressed(this);
}else if(isSplitted()){
this.WUDDiscReader = new WUDDiscReaderSplitted(this);
}else{
this.WUDDiscReader = new WUDDiscReaderUncompressed(this);
} }
fileStream.close(); fileStream.close();
setFileHandle(file); this.fileHandle = file;
}
public WUDDiscReader getWUDDiscReader() {
if(WUDDiscReader == null){
if(isCompressed()){
setWUDDiscReader(new WUDDiscReaderCompressed(this));
}else if(isSplitted()){
setWUDDiscReader(new WUDDiscReaderSplitted(this));
}else{
setWUDDiscReader(new WUDDiscReaderUncompressed(this));
}
}
return WUDDiscReader;
} }
public long getWUDFileSize() { public long getWUDFileSize() {

View File

@ -15,16 +15,16 @@ public class WUDImageCompressedInfo {
public static final int WUX_MAGIC_1 = 0x1099d02e; public static final int WUX_MAGIC_1 = 0x1099d02e;
public static final int SECTOR_SIZE = 0x8000; public static final int SECTOR_SIZE = 0x8000;
@Getter @Setter private int magic0; @Getter private final int sectorSize;
@Getter @Setter private int magic1; @Getter private final long uncompressedSize;
@Getter @Setter private int sectorSize; @Getter private final int flags;
@Getter @Setter private long uncompressedSize;
@Getter @Setter private int flags;
@Getter @Setter private long indexTableEntryCount = 0; @Getter @Setter private long indexTableEntryCount;
@Getter @Setter private long offsetIndexTable = 0; @Getter private final long offsetIndexTable = WUX_HEADER_SIZE;
@Getter @Setter private long offsetSectorArray = 0; @Getter @Setter private long offsetSectorArray;
@Getter @Setter private long indexTableSize = 0; @Getter @Setter private long indexTableSize;
private final boolean valid;
@Getter private Map<Integer,Long> indexTable = new HashMap<>(); @Getter private Map<Integer,Long> indexTable = new HashMap<>();
@ -33,11 +33,16 @@ public class WUDImageCompressedInfo {
System.out.println("WUX header length wrong"); System.out.println("WUX header length wrong");
System.exit(1); System.exit(1);
} }
setMagic0(ByteUtils.getIntFromBytes(headData, 0x00,ByteOrder.LITTLE_ENDIAN)); int magic0 = ByteUtils.getIntFromBytes(headData, 0x00,ByteOrder.LITTLE_ENDIAN);
setMagic1(ByteUtils.getIntFromBytes(headData, 0x04,ByteOrder.LITTLE_ENDIAN)); int magic1 = ByteUtils.getIntFromBytes(headData, 0x04,ByteOrder.LITTLE_ENDIAN);
setSectorSize(ByteUtils.getIntFromBytes(headData, 0x08,ByteOrder.LITTLE_ENDIAN)); if(magic0 == WUX_MAGIC_0 && magic1 == WUX_MAGIC_1){
setFlags(ByteUtils.getIntFromBytes(headData, 0x0C,ByteOrder.LITTLE_ENDIAN)); valid = true;
setUncompressedSize(ByteUtils.getLongFromBytes(headData, 0x10,ByteOrder.LITTLE_ENDIAN)); }else{
valid = false;
}
this.sectorSize = ByteUtils.getIntFromBytes(headData, 0x08,ByteOrder.LITTLE_ENDIAN);
this.flags = ByteUtils.getIntFromBytes(headData, 0x0C,ByteOrder.LITTLE_ENDIAN);
this.uncompressedSize = ByteUtils.getLongFromBytes(headData, 0x10,ByteOrder.LITTLE_ENDIAN);
calculateOffsets(); calculateOffsets();
} }
@ -47,17 +52,16 @@ public class WUDImageCompressedInfo {
} }
public WUDImageCompressedInfo(int sectorSize,int flags, long uncompressedSize) { public WUDImageCompressedInfo(int sectorSize,int flags, long uncompressedSize) {
setMagic0(WUX_MAGIC_0); this.sectorSize = sectorSize;
setMagic1(WUX_MAGIC_1); this.flags = flags;
setSectorSize(sectorSize); this.uncompressedSize= uncompressedSize;
setFlags(flags); valid = true;
setUncompressedSize(uncompressedSize); calculateOffsets();
} }
private void calculateOffsets() { private void calculateOffsets() {
long indexTableEntryCount = (getUncompressedSize()+ getSectorSize()-1) / getSectorSize(); long indexTableEntryCount = (getUncompressedSize()+ getSectorSize()-1) / getSectorSize();
setIndexTableEntryCount(indexTableEntryCount); setIndexTableEntryCount(indexTableEntryCount);
setOffsetIndexTable(0x20);
long offsetSectorArray = (getOffsetIndexTable() + ((long)getIndexTableEntryCount() * 0x04L)); long offsetSectorArray = (getOffsetIndexTable() + ((long)getIndexTableEntryCount() * 0x04L));
// align to SECTOR_SIZE // align to SECTOR_SIZE
offsetSectorArray = (offsetSectorArray + (long)(getSectorSize()-1)); offsetSectorArray = (offsetSectorArray + (long)(getSectorSize()-1));
@ -68,15 +72,7 @@ public class WUDImageCompressedInfo {
} }
public boolean isWUX() { public boolean isWUX() {
return (getMagic0() == WUX_MAGIC_0 && getMagic1() == WUX_MAGIC_1); return valid;
}
@Override
public String toString() {
return "WUDImageCompressedInfo [magic0=" + String.format("0x%08X", magic0) + ", magic1=" + String.format("0x%08X", magic1) + ", sectorSize=" + String.format("0x%08X", sectorSize)
+ ", uncompressedSize=" + String.format("0x%016X", uncompressedSize) + ", flags=" + String.format("0x%08X", flags) + ", indexTableEntryCount="
+ indexTableEntryCount + ", offsetIndexTable=" + offsetIndexTable + ", offsetSectorArray="
+ offsetSectorArray + ", indexTableSize=" + indexTableSize + "]";
} }
public long getSectorIndex(int sectorIndex) { public long getSectorIndex(int sectorIndex) {
@ -90,8 +86,8 @@ public class WUDImageCompressedInfo {
public byte[] getHeaderAsBytes() { public byte[] getHeaderAsBytes() {
ByteBuffer result = ByteBuffer.allocate(WUX_HEADER_SIZE); ByteBuffer result = ByteBuffer.allocate(WUX_HEADER_SIZE);
result.order(ByteOrder.LITTLE_ENDIAN); result.order(ByteOrder.LITTLE_ENDIAN);
result.putInt(getMagic0()); result.putInt(WUX_MAGIC_0);
result.putInt(getMagic1()); result.putInt(WUX_MAGIC_1);
result.putInt(getSectorSize()); result.putInt(getSectorSize());
result.putInt(getFlags()); result.putInt(getFlags());
result.putLong(getUncompressedSize()); result.putLong(getUncompressedSize());

View File

@ -6,7 +6,15 @@ import lombok.EqualsAndHashCode;
@Data @Data
@EqualsAndHashCode(callSuper=true) @EqualsAndHashCode(callSuper=true)
public class WUDGamePartition extends WUDPartition { public class WUDGamePartition extends WUDPartition {
private byte[] rawTMD; private final byte[] rawTMD;
private byte[] rawCert; private final byte[] rawCert;
private byte[] rawTicket; private final byte[] rawTicket;
public WUDGamePartition(String partitionName, long partitionOffset, byte[] rawTMD, byte[] rawCert,
byte[] rawTicket) {
super(partitionName, partitionOffset);
this.rawTMD = rawTMD;
this.rawCert = rawCert;
this.rawTicket = rawTicket;
}
} }

View File

@ -1,5 +1,6 @@
package de.mas.jnus.lib.implementations.wud.parser; package de.mas.jnus.lib.implementations.wud.parser;
import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
@ -11,19 +12,20 @@ import lombok.Setter;
@Data @Data
public class WUDInfo { public class WUDInfo {
private byte[] titleKey = null; private final byte[] titleKey;
private WUDDiscReader WUDDiscReader = null; private final WUDDiscReader WUDDiscReader;
private Map<String,WUDPartition> partitions = null; private final Map<String,WUDPartition> partitions = new HashMap<>();
@Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PROTECTED) @Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PROTECTED)
private String gamePartitionName; private String gamePartitionName;
WUDInfo(){ private WUDGamePartition cachedGamePartition = null;
public void addPartion(String partitionName, WUDGamePartition partition){
getPartitions().put(partitionName, partition);
} }
private WUDGamePartition cachedGamePartition = null;
public WUDGamePartition getGamePartition(){ public WUDGamePartition getGamePartition(){
if(cachedGamePartition == null){ if(cachedGamePartition == null){
cachedGamePartition = findGamePartition(); cachedGamePartition = findGamePartition();

View File

@ -13,12 +13,11 @@ import de.mas.jnus.lib.entities.fst.FST;
import de.mas.jnus.lib.entities.fst.FSTEntry; import de.mas.jnus.lib.entities.fst.FSTEntry;
import de.mas.jnus.lib.implementations.wud.reader.WUDDiscReader; import de.mas.jnus.lib.implementations.wud.reader.WUDDiscReader;
import de.mas.jnus.lib.utils.ByteUtils; import de.mas.jnus.lib.utils.ByteUtils;
import de.mas.jnus.lib.utils.FileUtils;
import de.mas.jnus.lib.utils.Utils; import de.mas.jnus.lib.utils.Utils;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class WUDInfoParser { public final class WUDInfoParser {
public static byte[] DECRYPTED_AREA_SIGNATURE = new byte[] { (byte) 0xCC, (byte) 0xA6, (byte) 0xE6, 0x7B }; public static byte[] DECRYPTED_AREA_SIGNATURE = new byte[] { (byte) 0xCC, (byte) 0xA6, (byte) 0xE6, 0x7B };
public static byte[] PARTITION_FILE_TABLE_SIGNATURE = new byte[] { 0x46, 0x53, 0x54, 0x00 }; // "FST" public static byte[] PARTITION_FILE_TABLE_SIGNATURE = new byte[] { 0x46, 0x53, 0x54, 0x00 }; // "FST"
public final static int PARTITION_TOC_OFFSET = 0x800; public final static int PARTITION_TOC_OFFSET = 0x800;
@ -28,11 +27,12 @@ public class WUDInfoParser {
public static final String WUD_TICKET_FILENAME = "title.tik"; public static final String WUD_TICKET_FILENAME = "title.tik";
public static final String WUD_CERT_FILENAME = "title.cert"; public static final String WUD_CERT_FILENAME = "title.cert";
public static WUDInfo createAndLoad(WUDDiscReader discReader,byte[] titleKey) throws IOException { private WUDInfoParser(){
WUDInfo result = new WUDInfo(); //
}
result.setTitleKey(titleKey); public static WUDInfo createAndLoad(WUDDiscReader discReader,byte[] titleKey) throws IOException {
result.setWUDDiscReader(discReader); WUDInfo result = new WUDInfo(titleKey,discReader);
byte[] PartitionTocBlock = discReader.readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET, 0, 0x8000, titleKey, null); byte[] PartitionTocBlock = discReader.readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET, 0, 0x8000, titleKey, null);
@ -43,7 +43,7 @@ public class WUDInfoParser {
} }
Map<String,WUDPartition> partitions = readPartitions(result,PartitionTocBlock); Map<String,WUDPartition> partitions = readPartitions(result,PartitionTocBlock);
result.setPartitions(partitions); result.getPartitions().putAll(partitions);
//parsePartitions(wudInfo,partitions); //parsePartitions(wudInfo,partitions);
return result; return result;
@ -61,12 +61,14 @@ public class WUDInfoParser {
Map<String,WUDPartition> partitions = new HashMap<>(); Map<String,WUDPartition> partitions = new HashMap<>();
WUDGamePartition gamePartition = new WUDGamePartition(); byte[] gamePartitionTMD = new byte[0];
byte[] gamePartitionTicket = new byte[0];
byte[] gamePartitionCert = new byte[0];
String realGamePartitionName = null; String realGamePartitionName = null;
// populate partition information from decrypted TOC // populate partition information from decrypted TOC
for (int i = 0; i < partitionCount; i++){ for (int i = 0; i < partitionCount; i++){
WUDPartition partition = new WUDPartition();
int offset = (PARTITION_TOC_OFFSET + (i * PARTITION_TOC_ENTRY_SIZE)); int offset = (PARTITION_TOC_OFFSET + (i * PARTITION_TOC_ENTRY_SIZE));
byte[] partitionIdentifier = Arrays.copyOfRange(partitionTocBlock, offset, offset+ 0x19); byte[] partitionIdentifier = Arrays.copyOfRange(partitionTocBlock, offset, offset+ 0x19);
@ -83,9 +85,7 @@ public class WUDInfoParser {
long partitionOffset = ((tmp * (long)0x8000) - 0x10000); long partitionOffset = ((tmp * (long)0x8000) - 0x10000);
WUDPartition partition = new WUDPartition(partitionName,partitionOffset);
partition.setPartitionName(partitionName);
partition.setPartitionOffset(partitionOffset);
if(partitionName.startsWith("SI")){ if(partitionName.startsWith("SI")){
byte[] fileTableBlock = wudInfo.getWUDDiscReader().readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET + partitionOffset,0, 0x8000, wudInfo.getTitleKey(),null); byte[] fileTableBlock = wudInfo.getWUDDiscReader().readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET + partitionOffset,0, 0x8000, wudInfo.getTitleKey(),null);
@ -100,18 +100,14 @@ public class WUDInfoParser {
byte[] rawTMD = getFSTEntryAsByte(WUD_TMD_FILENAME,partition,fst,wudInfo.getWUDDiscReader(),wudInfo.getTitleKey()); byte[] rawTMD = getFSTEntryAsByte(WUD_TMD_FILENAME,partition,fst,wudInfo.getWUDDiscReader(),wudInfo.getTitleKey());
byte[] rawCert = getFSTEntryAsByte(WUD_CERT_FILENAME,partition,fst,wudInfo.getWUDDiscReader(),wudInfo.getTitleKey()); byte[] rawCert = getFSTEntryAsByte(WUD_CERT_FILENAME,partition,fst,wudInfo.getWUDDiscReader(),wudInfo.getTitleKey());
gamePartition.setRawTMD(rawTMD); gamePartitionTMD = rawTMD;
gamePartition.setRawTicket(rawTIK); gamePartitionTicket = rawTIK;
gamePartition.setRawCert(rawCert); gamePartitionCert = rawCert;
//We want to use the real game partition //We want to use the real game partition
realGamePartitionName = partitionName = "GM" + Utils.ByteArrayToString(Arrays.copyOfRange(rawTIK, 0x1DC, 0x1DC + 0x08)); realGamePartitionName = partitionName = "GM" + Utils.ByteArrayToString(Arrays.copyOfRange(rawTIK, 0x1DC, 0x1DC + 0x08));
}else if(partitionName.startsWith(realGamePartitionName)){ }else if(partitionName.startsWith(realGamePartitionName)){
gamePartition.setPartitionOffset(partitionOffset); partition = new WUDGamePartition(partitionName, partitionOffset, gamePartitionTMD, gamePartitionCert, gamePartitionTicket);
gamePartition.setPartitionName(partitionName);
wudInfo.setGamePartitionName(partitionName);
partition = gamePartition;
} }
byte [] header = wudInfo.getWUDDiscReader().readEncryptedToByteArray(partition.getPartitionOffset()+0x10000,0,0x8000); byte [] header = wudInfo.getWUDDiscReader().readEncryptedToByteArray(partition.getPartitionOffset()+0x10000,0,0x8000);
WUDPartitionHeader partitionHeader = WUDPartitionHeader.parseHeader(header); WUDPartitionHeader partitionHeader = WUDPartitionHeader.parseHeader(header);

View File

@ -3,8 +3,8 @@ package de.mas.jnus.lib.implementations.wud.parser;
import lombok.Data; import lombok.Data;
@Data @Data
public class WUDPartition { public class WUDPartition {
private String partitionName = ""; private final String partitionName;
private long partitionOffset = 0; private final long partitionOffset;
private WUDPartitionHeader partitionHeader; private WUDPartitionHeader partitionHeader;
} }

View File

@ -17,10 +17,11 @@ import lombok.Setter;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class WUDPartitionHeader { public final class WUDPartitionHeader {
@Getter @Setter @Getter @Setter
private boolean calculatedHashes = false; private boolean calculatedHashes = false;
private HashMap<Short,byte[]> h3Hashes; @Getter
private final HashMap<Short,byte[]> h3Hashes = new HashMap<>();
@Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PRIVATE) @Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PRIVATE)
private byte[] rawData; private byte[] rawData;
@ -34,13 +35,6 @@ public class WUDPartitionHeader {
return result; return result;
} }
public HashMap<Short,byte[]> getH3Hashes() {
if(h3Hashes == null){
h3Hashes = new HashMap<>();
}
return h3Hashes;
}
public void addH3Hashes(short index, byte[] hash) { public void addH3Hashes(short index, byte[] hash) {
getH3Hashes().put(index, hash); getH3Hashes().put(index, hash);
} }

View File

@ -13,14 +13,13 @@ import java.util.Arrays;
import de.mas.jnus.lib.implementations.wud.WUDImage; import de.mas.jnus.lib.implementations.wud.WUDImage;
import de.mas.jnus.lib.utils.cryptography.AESDecryption; import de.mas.jnus.lib.utils.cryptography.AESDecryption;
import lombok.Getter; import lombok.Getter;
import lombok.Setter;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public abstract class WUDDiscReader { public abstract class WUDDiscReader {
@Getter @Setter private WUDImage image = null; @Getter private final WUDImage image;
public WUDDiscReader(WUDImage image){ public WUDDiscReader(WUDImage image){
setImage(image); this.image = image;
} }
public InputStream readEncryptedToInputStream(long offset,long size) throws IOException { public InputStream readEncryptedToInputStream(long offset,long size) throws IOException {
@ -81,10 +80,12 @@ public abstract class WUDDiscReader {
} }
public void readDecryptedToOutputStream(OutputStream outputStream,long clusterOffset, long fileOffset, long size,byte[] key,byte[] IV) throws IOException { public void readDecryptedToOutputStream(OutputStream outputStream,long clusterOffset, long fileOffset, long size,byte[] key,byte[] IV) throws IOException {
if(IV == null){ byte[] usedIV = IV;
IV = new byte[0x10]; if(usedIV == null){
usedIV = new byte[0x10];
} }
long usedSize = size;
long usedFileOffset = fileOffset;
byte[] buffer; byte[] buffer;
long maxCopySize; long maxCopySize;
@ -96,23 +97,23 @@ public abstract class WUDDiscReader {
long totalread = 0; long totalread = 0;
do{ do{
long blockNumber = (fileOffset / blockSize); long blockNumber = (usedFileOffset / blockSize);
long blockOffset = (fileOffset % blockSize); long blockOffset = (usedFileOffset % blockSize);
readOffset = clusterOffset + (blockNumber * blockSize); readOffset = clusterOffset + (blockNumber * blockSize);
// (long)WiiUDisc.WIIU_DECRYPTED_AREA_OFFSET + volumeOffset + clusterOffset + (blockStructure.getBlockNumber() * 0x8000); // (long)WiiUDisc.WIIU_DECRYPTED_AREA_OFFSET + volumeOffset + clusterOffset + (blockStructure.getBlockNumber() * 0x8000);
buffer = readDecryptedChunk(readOffset,key, IV); buffer = readDecryptedChunk(readOffset,key, usedIV);
maxCopySize = 0x8000 - blockOffset; maxCopySize = 0x8000 - blockOffset;
copySize = (size > maxCopySize) ? maxCopySize : size; copySize = (usedSize > maxCopySize) ? maxCopySize : usedSize;
outputStream.write(Arrays.copyOfRange(buffer, (int) blockOffset, (int) copySize)); outputStream.write(Arrays.copyOfRange(buffer, (int) blockOffset, (int) copySize));
totalread += copySize; totalread += copySize;
// update counters // update counters
size -= copySize; usedSize -= copySize;
fileOffset += copySize; usedFileOffset += copySize;
}while(totalread < size); }while(totalread < usedSize);
outputStream.close(); outputStream.close();
} }

View File

@ -25,12 +25,16 @@ public class WUDDiscReaderCompressed extends WUDDiscReader{
WUDImageCompressedInfo info = getImage().getCompressedInfo(); WUDImageCompressedInfo info = getImage().getCompressedInfo();
long fileBytesLeft = info.getUncompressedSize() - offset; long fileBytesLeft = info.getUncompressedSize() - offset;
long usedOffset = offset;
long usedSize = size;
if( fileBytesLeft <= 0 ){ if( fileBytesLeft <= 0 ){
log.warning("offset too big"); log.warning("offset too big");
System.exit(1); System.exit(1);
} }
if( fileBytesLeft < size ){ if( fileBytesLeft < usedSize ){
size = fileBytesLeft; usedSize = fileBytesLeft;
} }
// compressed read must be handled on a per-sector level // compressed read must be handled on a per-sector level
@ -38,11 +42,11 @@ public class WUDDiscReaderCompressed extends WUDDiscReader{
byte[] buffer = new byte[bufferSize]; byte[] buffer = new byte[bufferSize];
RandomAccessFile input = getRandomAccessFileStream(); RandomAccessFile input = getRandomAccessFileStream();
while( size > 0 ){ while( usedSize > 0 ){
long sectorOffset = (offset % info.getSectorSize()); long sectorOffset = (usedOffset % info.getSectorSize());
long remainingSectorBytes = info.getSectorSize() - sectorOffset; long remainingSectorBytes = info.getSectorSize() - sectorOffset;
long sectorIndex = (offset / info.getSectorSize()); long sectorIndex = (usedOffset / info.getSectorSize());
int bytesToRead = (int) ((remainingSectorBytes<size)?remainingSectorBytes:size); // read only up to the end of the current sector int bytesToRead = (int) ((remainingSectorBytes<usedSize)?remainingSectorBytes:usedSize); // read only up to the end of the current sector
// look up real sector index // look up real sector index
long realSectorIndex = info.getSectorIndex((int) sectorIndex); long realSectorIndex = info.getSectorIndex((int) sectorIndex);
long offset2 = info.getOffsetSectorArray() + realSectorIndex*info.getSectorSize()+sectorOffset; long offset2 = info.getOffsetSectorArray() + realSectorIndex*info.getSectorSize()+sectorOffset;
@ -60,10 +64,9 @@ public class WUDDiscReaderCompressed extends WUDDiscReader{
} }
} }
size -= bytesToRead; usedSize -= bytesToRead;
offset += bytesToRead; usedOffset += bytesToRead;
} }
input.close(); input.close();
} }
} }

View File

@ -9,13 +9,14 @@ import java.util.Arrays;
import de.mas.jnus.lib.implementations.wud.WUDImage; import de.mas.jnus.lib.implementations.wud.WUDImage;
public class WUDDiscReaderSplitted extends WUDDiscReader{ public class WUDDiscReaderSplitted extends WUDDiscReader{
public WUDDiscReaderSplitted(WUDImage image) {
super(image);
}
public static long WUD_SPLITTED_FILE_SIZE = 0x100000L * 0x800L; public static long WUD_SPLITTED_FILE_SIZE = 0x100000L * 0x800L;
public static long NUMBER_OF_FILES = 12; public static long NUMBER_OF_FILES = 12;
public static String WUD_SPLITTED_DEFAULT_FILEPATTERN = "game_part%d.wud"; public static String WUD_SPLITTED_DEFAULT_FILEPATTERN = "game_part%d.wud";
public WUDDiscReaderSplitted(WUDImage image) {
super(image);
}
@Override @Override
protected void readEncryptedToOutputStream(OutputStream outputStream, long offset, long size) throws IOException { protected void readEncryptedToOutputStream(OutputStream outputStream, long offset, long size) throws IOException {
RandomAccessFile input = getFileByOffset(offset); RandomAccessFile input = getFileByOffset(offset);

View File

@ -6,6 +6,7 @@ import java.io.OutputStream;
import java.util.Arrays; import java.util.Arrays;
import de.mas.jnus.lib.implementations.wud.WUDImage; import de.mas.jnus.lib.implementations.wud.WUDImage;
import de.mas.jnus.lib.utils.StreamUtils;
public class WUDDiscReaderUncompressed extends WUDDiscReader { public class WUDDiscReaderUncompressed extends WUDDiscReader {
public WUDDiscReaderUncompressed(WUDImage image) { public WUDDiscReaderUncompressed(WUDImage image) {
@ -16,7 +17,9 @@ public class WUDDiscReaderUncompressed extends WUDDiscReader {
protected void readEncryptedToOutputStream(OutputStream outputStream, long offset,long size) throws IOException{ protected void readEncryptedToOutputStream(OutputStream outputStream, long offset,long size) throws IOException{
FileInputStream input = new FileInputStream(getImage().getFileHandle()); FileInputStream input = new FileInputStream(getImage().getFileHandle());
input.skip(offset);
StreamUtils.skipExactly(input,offset);
int bufferSize = 0x8000; int bufferSize = 0x8000;
byte[] buffer = new byte[bufferSize]; byte[] buffer = new byte[bufferSize];
long totalread = 0; long totalread = 0;

View File

@ -4,7 +4,12 @@ import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.Arrays; import java.util.Arrays;
public class ByteUtils { public final class ByteUtils {
private ByteUtils(){
//Utility Class
}
public static int getIntFromBytes(byte[] input,int offset){ public static int getIntFromBytes(byte[] input,int offset){
return getIntFromBytes(input, offset, ByteOrder.BIG_ENDIAN); return getIntFromBytes(input, offset, ByteOrder.BIG_ENDIAN);
} }

View File

@ -7,8 +7,10 @@ import java.io.InputStream;
import lombok.NonNull; import lombok.NonNull;
public class FileUtils { public final class FileUtils {
private FileUtils(){
//Utility Class
}
public static boolean saveByteArrayToFile(String filePath,byte[] data) throws IOException { public static boolean saveByteArrayToFile(String filePath,byte[] data) throws IOException {
File target = new File(filePath); File target = new File(filePath);
if(target.isDirectory()){ if(target.isDirectory()){

View File

@ -12,7 +12,10 @@ import java.util.Arrays;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class HashUtil { public final class HashUtil {
private HashUtil(){
//Utility class
}
public static byte[] hashSHA256(byte[] data){ public static byte[] hashSHA256(byte[] data){
MessageDigest sha256; MessageDigest sha256;
try { try {

View File

@ -1,5 +1,6 @@
package de.mas.jnus.lib.utils; package de.mas.jnus.lib.utils;
import java.io.EOFException;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
@ -10,7 +11,10 @@ import java.util.Arrays;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
public class StreamUtils { public final class StreamUtils {
private StreamUtils(){
//Utility class
}
public static byte[] getBytesFromStream(InputStream in,int size) throws IOException{ public static byte[] getBytesFromStream(InputStream in,int size) throws IOException{
byte[] result = new byte[size]; byte[] result = new byte[size];
byte[] buffer = new byte[0x8000]; byte[] buffer = new byte[0x8000];
@ -124,4 +128,16 @@ public class StreamUtils {
outputStream.close(); outputStream.close();
inputStream.close(); inputStream.close();
} }
public static void skipExactly(InputStream in, long offset) throws IOException {
long n = offset;
while (n != 0) {
long skipped = in.skip(n);
if (skipped == 0){
in.close();
throw new EOFException();
}
n -= skipped;
}
}
} }

View File

@ -2,8 +2,10 @@ package de.mas.jnus.lib.utils;
import java.io.File; import java.io.File;
public class Utils { public final class Utils {
private Utils(){
//Utility class
}
public static long align(long numToRound, int multiple){ public static long align(long numToRound, int multiple){
if((multiple>0) && ((multiple & (multiple -1)) == 0)){ if((multiple>0) && ((multiple & (multiple -1)) == 0)){
return alignPower2(numToRound, multiple); return alignPower2(numToRound, multiple);

View File

@ -31,7 +31,7 @@ public class AESDecryption {
init(); init();
} }
protected void init() { protected final void init() {
init(getAESKey(),getIV()); init(getAESKey(),getIV());
} }

View File

@ -96,7 +96,7 @@ public class NUSDecryption extends AESDecryption{
log.info(Utils.ByteArrayToString(expected_hash)); log.info(Utils.ByteArrayToString(expected_hash));
log.info("Hash doesn't match decrypted content."); log.info("Hash doesn't match decrypted content.");
}else{ }else{
//log.warning("###################################################Hash DOES match saves output stream."); //log.warning("Hash DOES match saves output stream.");
} }
} }

View File

@ -5,7 +5,10 @@ import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
public class Downloader { public abstract class Downloader {
public Downloader() {
//
}
public static byte[] downloadFileToByteArray(String fileURL) throws IOException { public static byte[] downloadFileToByteArray(String fileURL) throws IOException {
int BUFFER_SIZE = 0x800; int BUFFER_SIZE = 0x800;
URL url = new URL(fileURL); URL url = new URL(fileURL);

View File

@ -4,34 +4,38 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import de.mas.jnus.lib.Settings; import de.mas.jnus.lib.Settings;
import lombok.Setter;
public class NUSDownloadService extends Downloader{ public final class NUSDownloadService extends Downloader{
private static NUSDownloadService defaultInstance; private static NUSDownloadService defaultInstance;
private static Map<String,NUSDownloadService> instances = new HashMap<>();
private final String URL_BASE;
private NUSDownloadService(String URL){
this.URL_BASE = URL;
}
public static NUSDownloadService getDefaultInstance(){ public static NUSDownloadService getDefaultInstance(){
synchronized (defaultInstance) {
if(defaultInstance == null){ if(defaultInstance == null){
defaultInstance = new NUSDownloadService(); defaultInstance = new NUSDownloadService(Settings.URL_BASE);
defaultInstance.setURL_BASE(Settings.URL_BASE); }
} }
return defaultInstance; return defaultInstance;
} }
public static NUSDownloadService getInstance(String URL){ public static NUSDownloadService getInstance(String URL){
NUSDownloadService instance = new NUSDownloadService(); if(!instances.containsKey(URL)){
instance.setURL_BASE(URL); NUSDownloadService instance = new NUSDownloadService(URL);
return instance; instances.put(URL, instance);
} }
return instances.get(URL);
private NUSDownloadService(){
} }
@Setter private String URL_BASE = "";
public byte[] downloadTMDToByteArray(long titleID, int version) throws IOException { public byte[] downloadTMDToByteArray(long titleID, int version) throws IOException {
String version_suf = ""; String version_suf = "";
if(version > Settings.LATEST_TMD_VERSION) version_suf = "." + version; if(version > Settings.LATEST_TMD_VERSION) version_suf = "." + version;