mirror of
https://github.com/Maschell/JNUSLib.git
synced 2024-11-25 01:16:55 +01:00
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:
parent
6b8efcaf53
commit
a9b989ee0e
@ -25,10 +25,10 @@ import de.mas.jnus.lib.utils.StreamUtils;
|
||||
import de.mas.jnus.lib.utils.Utils;
|
||||
import de.mas.jnus.lib.utils.cryptography.NUSDecryption;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class DecryptionService {
|
||||
public final class DecryptionService {
|
||||
private static Map<NUSTitle,DecryptionService> instances = new HashMap<>();
|
||||
@Getter private final NUSTitle NUSTitle;
|
||||
|
||||
public static DecryptionService getInstance(NUSTitle nustitle) {
|
||||
if(!instances.containsKey(nustitle)){
|
||||
@ -37,10 +37,8 @@ public class DecryptionService {
|
||||
return instances.get(nustitle);
|
||||
}
|
||||
|
||||
@Getter @Setter private NUSTitle NUSTitle = null;
|
||||
|
||||
private DecryptionService(NUSTitle nustitle){
|
||||
setNUSTitle(nustitle);
|
||||
this.NUSTitle = nustitle;
|
||||
}
|
||||
|
||||
public Ticket getTicket() {
|
||||
@ -80,16 +78,16 @@ public class DecryptionService {
|
||||
}
|
||||
if(targetFile.length() == entry.getFileSize()){
|
||||
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())){
|
||||
System.out.println("File already exists: " + entry.getFilename());
|
||||
return;
|
||||
}else{
|
||||
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{
|
||||
@ -138,9 +136,7 @@ public class DecryptionService {
|
||||
short contentIndex = (short)content.getIndex();
|
||||
|
||||
long encryptedFileSize = content.getEncryptedFileSize();
|
||||
if(!content.isEncrypted()){
|
||||
StreamUtils.saveInputStreamToOutputStreamWithHash(inputStream, outputStream,size,content.getSHA2Hash(),encryptedFileSize);
|
||||
}else{
|
||||
if(content.isEncrypted()){
|
||||
if(content.isHashed()){
|
||||
NUSDataProvider dataProvider = getNUSTitle().getDataProvider();
|
||||
byte[] h3 = dataProvider.getContentH3Hash(content);
|
||||
@ -148,6 +144,8 @@ public class DecryptionService {
|
||||
}else{
|
||||
nusdecryption.decryptFileStream(inputStream, outputStream, size, (short)contentIndex,content.getSHA2Hash(),encryptedFileSize);
|
||||
}
|
||||
}else{
|
||||
StreamUtils.saveInputStreamToOutputStreamWithHash(inputStream, outputStream,size,content.getSHA2Hash(),encryptedFileSize);
|
||||
}
|
||||
|
||||
inputStream.close();
|
||||
|
@ -12,11 +12,12 @@ import de.mas.jnus.lib.implementations.NUSDataProvider;
|
||||
import de.mas.jnus.lib.utils.FileUtils;
|
||||
import de.mas.jnus.lib.utils.Utils;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class ExtractionService {
|
||||
public final class ExtractionService {
|
||||
private static Map<NUSTitle,ExtractionService> instances = new HashMap<>();
|
||||
|
||||
@Getter private final NUSTitle NUSTitle;
|
||||
|
||||
public static ExtractionService getInstance(NUSTitle nustitle) {
|
||||
if(!instances.containsKey(nustitle)){
|
||||
instances.put(nustitle, new ExtractionService(nustitle));
|
||||
@ -24,10 +25,8 @@ public class ExtractionService {
|
||||
return instances.get(nustitle);
|
||||
}
|
||||
|
||||
@Getter @Setter private NUSTitle NUSTitle = null;
|
||||
|
||||
private ExtractionService(NUSTitle nustitle){
|
||||
setNUSTitle(nustitle);
|
||||
this.NUSTitle = nustitle;
|
||||
}
|
||||
|
||||
private NUSDataProvider getDataProvider(){
|
||||
@ -76,7 +75,7 @@ public class ExtractionService {
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
@ -90,7 +89,7 @@ public class ExtractionService {
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
@ -104,7 +103,7 @@ public class ExtractionService {
|
||||
|
||||
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");
|
||||
return;
|
||||
}
|
||||
|
@ -17,8 +17,6 @@ import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class NUSTitle {
|
||||
@Getter @Setter private String inputPath = "";
|
||||
|
||||
@Getter @Setter private FST FST;
|
||||
@Getter @Setter private TMD TMD;
|
||||
@Getter @Setter private Ticket ticket;
|
||||
@ -53,10 +51,10 @@ public class NUSTitle {
|
||||
}
|
||||
|
||||
public FSTEntry getFSTEntryByFullPath(String givenFullPath) {
|
||||
givenFullPath = givenFullPath.replaceAll("/", "\\\\");
|
||||
if(!givenFullPath.startsWith("\\")) givenFullPath = "\\" +givenFullPath;
|
||||
String fullPath = givenFullPath.replaceAll("/", "\\\\");
|
||||
if(!fullPath.startsWith("\\")) fullPath = "\\" +fullPath;
|
||||
for(FSTEntry f :getAllFSTEntriesFlat()){
|
||||
if(f.getFullPath().equals(givenFullPath)){
|
||||
if(f.getFullPath().equals(fullPath)){
|
||||
return f;
|
||||
}
|
||||
}
|
||||
|
@ -7,9 +7,9 @@ import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class NUSTitleConfig {
|
||||
private String inputPath = "";
|
||||
private WUDInfo WUDInfo = null;
|
||||
private Ticket ticket = null;
|
||||
private String inputPath;
|
||||
private WUDInfo WUDInfo;
|
||||
private Ticket ticket;
|
||||
|
||||
private int version = Settings.LATEST_TMD_VERSION;
|
||||
private long titleID = 0x0L;
|
||||
|
@ -13,16 +13,14 @@ import de.mas.jnus.lib.utils.cryptography.AESDecryption;
|
||||
|
||||
abstract class NUSTitleLoader {
|
||||
protected NUSTitleLoader(){
|
||||
|
||||
//should be empty
|
||||
}
|
||||
|
||||
public NUSTitle loadNusTitle(NUSTitleConfig config) throws Exception{
|
||||
NUSTitle result = new NUSTitle();
|
||||
|
||||
NUSDataProvider dataProvider = getDataProvider(config);
|
||||
NUSDataProvider dataProvider = getDataProvider(result, config);
|
||||
result.setDataProvider(dataProvider);
|
||||
dataProvider.setNUSTitle(result);
|
||||
|
||||
|
||||
TMD tmd = TMD.parseTMD(dataProvider.getRawTMD());
|
||||
result.setTMD(tmd);
|
||||
@ -63,5 +61,5 @@ abstract class NUSTitleLoader {
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract NUSDataProvider getDataProvider(NUSTitleConfig config);
|
||||
protected abstract NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config);
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import de.mas.jnus.lib.entities.Ticket;
|
||||
import de.mas.jnus.lib.implementations.NUSDataProviderLocal;
|
||||
import de.mas.jnus.lib.implementations.NUSDataProvider;
|
||||
|
||||
public class NUSTitleLoaderLocal extends NUSTitleLoader {
|
||||
public final class NUSTitleLoaderLocal extends NUSTitleLoader {
|
||||
|
||||
private NUSTitleLoaderLocal(){
|
||||
super();
|
||||
@ -26,10 +26,8 @@ public class NUSTitleLoaderLocal extends NUSTitleLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NUSDataProvider getDataProvider(NUSTitleConfig config) {
|
||||
NUSDataProviderLocal result = new NUSDataProviderLocal();
|
||||
result.setLocalPath(config.getInputPath());
|
||||
return result;
|
||||
protected NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config) {
|
||||
return new NUSDataProviderLocal(title,config.getInputPath());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -4,7 +4,7 @@ import de.mas.jnus.lib.entities.Ticket;
|
||||
import de.mas.jnus.lib.implementations.NUSDataProviderRemote;
|
||||
import de.mas.jnus.lib.implementations.NUSDataProvider;
|
||||
|
||||
public class NUSTitleLoaderRemote extends NUSTitleLoader{
|
||||
public final class NUSTitleLoaderRemote extends NUSTitleLoader{
|
||||
|
||||
private NUSTitleLoaderRemote(){
|
||||
super();
|
||||
@ -33,11 +33,8 @@ public class NUSTitleLoaderRemote extends NUSTitleLoader{
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NUSDataProvider getDataProvider(NUSTitleConfig config) {
|
||||
NUSDataProviderRemote result = new NUSDataProviderRemote();
|
||||
result.setVersion(config.getVersion());
|
||||
result.setTitleID(config.getTitleID());
|
||||
return result;
|
||||
protected NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config) {
|
||||
return new NUSDataProviderRemote(title,config.getVersion(),config.getTitleID());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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.WUDInfoParser;
|
||||
|
||||
public class NUSTitleLoaderWUD extends NUSTitleLoader {
|
||||
public final class NUSTitleLoaderWUD extends NUSTitleLoader {
|
||||
|
||||
private NUSTitleLoaderWUD(){
|
||||
super();
|
||||
@ -20,7 +20,7 @@ public class NUSTitleLoaderWUD extends NUSTitleLoader {
|
||||
public static NUSTitle loadNUSTitle(String WUDPath, byte[] titleKey) throws Exception{
|
||||
NUSTitleLoader loader = new NUSTitleLoaderWUD();
|
||||
NUSTitleConfig config = new NUSTitleConfig();
|
||||
|
||||
byte[] usedTitleKey = titleKey;
|
||||
File wudFile = new File(WUDPath);
|
||||
if(!wudFile.exists()){
|
||||
System.out.println(WUDPath + " does not exist.");
|
||||
@ -28,15 +28,15 @@ public class NUSTitleLoaderWUD extends NUSTitleLoader {
|
||||
}
|
||||
|
||||
WUDImage image = new WUDImage(wudFile);
|
||||
if(titleKey == null){
|
||||
if(usedTitleKey == null){
|
||||
File keyFile = new File(wudFile.getParentFile().getPath() + File.separator + Settings.WUD_KEY_FILENAME);
|
||||
if(!keyFile.exists()){
|
||||
System.out.println(keyFile.getAbsolutePath() + " does not exist and no title key was provided.");
|
||||
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){
|
||||
return null;
|
||||
}
|
||||
@ -47,10 +47,8 @@ public class NUSTitleLoaderWUD extends NUSTitleLoader {
|
||||
}
|
||||
|
||||
@Override
|
||||
protected NUSDataProvider getDataProvider(NUSTitleConfig config) {
|
||||
NUSDataProviderWUD result = new NUSDataProviderWUD();
|
||||
result.setWUDInfo(config.getWUDInfo());
|
||||
return result;
|
||||
protected NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config) {
|
||||
return new NUSDataProviderWUD(title,config.getWUDInfo());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import de.mas.jnus.lib.implementations.woomy.WoomyParser;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
@Log
|
||||
public class NUSTitleLoaderWoomy extends NUSTitleLoader {
|
||||
public final class NUSTitleLoaderWoomy extends NUSTitleLoader {
|
||||
|
||||
public static NUSTitle loadNUSTitle(String inputFile) throws Exception{
|
||||
NUSTitleLoaderWoomy loader = new NUSTitleLoaderWoomy();
|
||||
@ -24,10 +24,8 @@ public class NUSTitleLoaderWoomy extends NUSTitleLoader {
|
||||
return loader.loadNusTitle(config);
|
||||
}
|
||||
@Override
|
||||
protected NUSDataProvider getDataProvider(NUSTitleConfig config) {
|
||||
NUSDataProviderWoomy dataProvider = new NUSDataProviderWoomy();
|
||||
dataProvider.setWoomyInfo(config.getWoomyInfo());
|
||||
return dataProvider;
|
||||
protected NUSDataProvider getDataProvider(NUSTitle title,NUSTitleConfig config) {
|
||||
return new NUSDataProviderWoomy(title,config.getWoomyInfo());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -23,7 +23,11 @@ import de.mas.jnus.lib.utils.Utils;
|
||||
import lombok.extern.java.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{
|
||||
return compressWUDToWUX(image, outputFolder, "game.wux",false);
|
||||
}
|
||||
@ -44,16 +48,17 @@ public class WUDService {
|
||||
return null;
|
||||
}
|
||||
|
||||
Utils.createDir(outputFolder);
|
||||
String usedOutputFolder = outputFolder;
|
||||
if(usedOutputFolder == null) usedOutputFolder = "";
|
||||
Utils.createDir(usedOutputFolder);
|
||||
|
||||
String filePath;
|
||||
if(outputFolder == null) outputFolder = "";
|
||||
|
||||
if(!outputFolder.isEmpty()){
|
||||
filePath = outputFolder+ File.separator + filename;
|
||||
}else{
|
||||
if(usedOutputFolder.isEmpty()){
|
||||
filePath = filename;
|
||||
}else{
|
||||
filePath = usedOutputFolder + File.separator + filename;
|
||||
}
|
||||
|
||||
File outputFile = new File(filePath);
|
||||
|
||||
if(outputFile.exists() && !overwrite){
|
||||
@ -98,14 +103,14 @@ public class WUDService {
|
||||
int read = StreamUtils.getChunkFromStream(in, blockBuffer, overflow, bufferSize);
|
||||
ByteArrayWrapper hash = new ByteArrayWrapper(HashUtil.hashSHA1(blockBuffer));
|
||||
|
||||
if((oldOffset = sectorHashes.get(hash)) != null){
|
||||
sectorMapping.put(curSector, oldOffset);
|
||||
oldOffset = null;
|
||||
}else{ //its a new sector
|
||||
if((oldOffset = sectorHashes.get(hash)) == null){
|
||||
sectorMapping.put(curSector, realSector);
|
||||
sectorHashes.put(hash, realSector);
|
||||
fileOutput.write(blockBuffer);
|
||||
realSector++;
|
||||
}else{
|
||||
sectorMapping.put(curSector, oldOffset);
|
||||
oldOffset = null;
|
||||
}
|
||||
|
||||
written += read;
|
||||
|
@ -4,39 +4,53 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.file.Files;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import de.mas.jnus.lib.entities.content.Content;
|
||||
import de.mas.jnus.lib.entities.content.ContentInfo;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class TMD {
|
||||
@Getter @Setter private int signatureType; // 0x000
|
||||
@Getter @Setter private byte[] signature = new byte[0x100]; // 0x004
|
||||
@Getter @Setter private byte[] issuer = new byte[0x40]; // 0x140
|
||||
@Getter @Setter private byte version; // 0x180
|
||||
@Getter @Setter private byte CACRLVersion; // 0x181
|
||||
@Getter @Setter private byte signerCRLVersion; // 0x182
|
||||
@Getter @Setter private long systemVersion; // 0x184
|
||||
@Getter @Setter private long titleID; // 0x18C
|
||||
@Getter @Setter private int titleType; // 0x194
|
||||
@Getter @Setter private short groupID; // 0x198
|
||||
@Getter @Setter private byte[] reserved = new byte[62]; // 0x19A
|
||||
@Getter @Setter private int accessRights; // 0x1D8
|
||||
@Getter @Setter private short titleVersion; // 0x1DC
|
||||
@Getter @Setter private short contentCount; // 0x1DE
|
||||
@Getter @Setter private short bootIndex; // 0x1E0
|
||||
@Getter @Setter private byte[] SHA2 = new byte[0x20]; // 0x1E4
|
||||
@Getter @Setter private ContentInfo[] contentInfos = new ContentInfo[0x40];
|
||||
Map<Integer,Content> contentToIndex = new HashMap<>();
|
||||
Map<Integer,Content> contentToID = new HashMap<>();
|
||||
|
||||
@Getter @Setter private byte[] rawTMD = new byte[0];
|
||||
private TMD(){
|
||||
public final class TMD {
|
||||
@Getter private final int signatureType; // 0x000
|
||||
@Getter private final byte[] signature; // 0x004
|
||||
@Getter private final byte[] issuer; // 0x140
|
||||
@Getter private final byte version; // 0x180
|
||||
@Getter private final byte CACRLVersion; // 0x181
|
||||
@Getter private final byte signerCRLVersion; // 0x182
|
||||
@Getter private final long systemVersion; // 0x184
|
||||
@Getter private final long titleID; // 0x18C
|
||||
@Getter private final int titleType; // 0x194
|
||||
@Getter private final short groupID; // 0x198
|
||||
@Getter private final byte[] reserved; // 0x19A
|
||||
@Getter private final int accessRights; // 0x1D8
|
||||
@Getter private final short titleVersion; // 0x1DC
|
||||
@Getter private final short contentCount; // 0x1DE
|
||||
@Getter private final short bootIndex; // 0x1E0
|
||||
@Getter private final byte[] SHA2; // 0x1E4
|
||||
@Getter private final ContentInfo[] contentInfos;
|
||||
private final Map<Integer,Content> contentToIndex = new HashMap<>();
|
||||
private final Map<Integer,Content> contentToID = new HashMap<>();
|
||||
|
||||
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 {
|
||||
@ -48,15 +62,12 @@ public class TMD {
|
||||
}
|
||||
|
||||
public static TMD parseTMD(byte[] input) {
|
||||
|
||||
TMD result = new TMD();
|
||||
result.setRawTMD(Arrays.copyOf(input,input.length));
|
||||
byte[] signature = new byte[0x100];
|
||||
byte[] issuer = new byte[0x40];
|
||||
byte[] reserved = new byte[62];
|
||||
byte[] SHA2 = new byte[0x20];
|
||||
|
||||
ContentInfo[] contentInfos = result.getContentInfos();
|
||||
ContentInfo[] contentInfos = new ContentInfo[0x40];
|
||||
|
||||
ByteBuffer buffer = ByteBuffer.allocate(input.length);
|
||||
buffer.put(input);
|
||||
@ -106,6 +117,25 @@ public class TMD {
|
||||
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
|
||||
for(int i =0;i<contentCount;i++){
|
||||
buffer.position(0xB04+(0x30*i));
|
||||
@ -116,22 +146,6 @@ public class TMD {
|
||||
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;
|
||||
}
|
||||
|
||||
|
25
src/de/mas/jnus/lib/entities/TMDParam.java
Normal file
25
src/de/mas/jnus/lib/entities/TMDParam.java
Normal 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]; //
|
||||
}
|
@ -10,23 +10,20 @@ import de.mas.jnus.lib.Settings;
|
||||
import de.mas.jnus.lib.utils.Utils;
|
||||
import de.mas.jnus.lib.utils.cryptography.AESDecryption;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
@Log
|
||||
public class Ticket {
|
||||
@Getter @Setter private byte[] encryptedKey = new byte[0x10];
|
||||
@Getter @Setter private byte[] decryptedKey = new byte[0x10];
|
||||
public final class Ticket {
|
||||
@Getter private final byte[] encryptedKey;
|
||||
@Getter private final byte[] decryptedKey;
|
||||
|
||||
@Getter @Setter private byte[] IV = new byte[0x10];
|
||||
|
||||
@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(){
|
||||
@Getter private final byte[] IV;
|
||||
|
||||
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 {
|
||||
@ -55,36 +52,15 @@ public class Ticket {
|
||||
long titleID = buffer.getLong();
|
||||
|
||||
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;
|
||||
}
|
||||
|
||||
public static Ticket createTicket(byte[] encryptedKey, long titleID) {
|
||||
Ticket result = new Ticket();
|
||||
result.encryptedKey = encryptedKey;
|
||||
|
||||
byte[] IV = ByteBuffer.allocate(0x10).putLong(titleID).array();
|
||||
result.decryptedKey = calculateDecryptedKey(result.encryptedKey,IV);
|
||||
result.setIV(IV);
|
||||
byte[] decryptedKey = calculateDecryptedKey(encryptedKey,IV);
|
||||
|
||||
return result;
|
||||
return new Ticket(encryptedKey,decryptedKey,IV);
|
||||
}
|
||||
|
||||
private static byte[] calculateDecryptedKey(byte[] encryptedKey, byte[] IV) {
|
||||
@ -109,9 +85,7 @@ public class Ticket {
|
||||
if (getClass() != obj.getClass())
|
||||
return false;
|
||||
Ticket other = (Ticket) obj;
|
||||
if (!Arrays.equals(encryptedKey, other.encryptedKey))
|
||||
return false;
|
||||
return true;
|
||||
return Arrays.equals(encryptedKey, other.encryptedKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -20,16 +20,24 @@ public class Content{
|
||||
public static final short CONTENT_HASHED = 0x0002;
|
||||
public static final short CONTENT_ENCRYPTED = 0x0001;
|
||||
|
||||
@Getter @Setter private int ID = 0x00;
|
||||
@Getter @Setter private short index = 0x00;
|
||||
@Getter @Setter private short type = 0x0000;
|
||||
@Getter private final int ID;
|
||||
@Getter private final short index;
|
||||
@Getter private final short type;
|
||||
|
||||
@Getter @Setter private long encryptedFileSize = 0;
|
||||
@Getter @Setter private byte[] SHA2Hash = new byte[0x14];
|
||||
@Getter private final long encryptedFileSize;
|
||||
@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
|
||||
@ -44,6 +52,7 @@ public class Content{
|
||||
ByteBuffer buffer = ByteBuffer.allocate(input.length);
|
||||
buffer.put(input);
|
||||
buffer.position(0);
|
||||
|
||||
int ID = buffer.getInt(0x00);
|
||||
short index = buffer.getShort(0x04);
|
||||
short type = buffer.getShort(0x06);
|
||||
@ -51,15 +60,15 @@ public class Content{
|
||||
buffer.position(0x10);
|
||||
byte[] hash = new byte[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) {
|
||||
setID(ID);
|
||||
setIndex(index);
|
||||
setType(type);
|
||||
setEncryptedFileSize(encryptedFileSize);
|
||||
setSHA2Hash(hash);
|
||||
ContentParam param = new ContentParam();
|
||||
param.setID(ID);
|
||||
param.setIndex(index);
|
||||
param.setType(type);
|
||||
param.setEncryptedFileSize(encryptedFileSize);
|
||||
param.setSHA2Hash(hash);
|
||||
|
||||
return new Content(param);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -137,9 +146,7 @@ public class Content{
|
||||
Content other = (Content) obj;
|
||||
if (ID != other.ID)
|
||||
return false;
|
||||
if (!Arrays.equals(SHA2Hash, other.SHA2Hash))
|
||||
return false;
|
||||
return true;
|
||||
return Arrays.equals(SHA2Hash, other.SHA2Hash);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -4,7 +4,6 @@ import java.nio.ByteBuffer;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@EqualsAndHashCode
|
||||
/**
|
||||
@ -13,20 +12,23 @@ import lombok.Setter;
|
||||
* @author Maschell
|
||||
*
|
||||
*/
|
||||
public class ContentFSTInfo {
|
||||
@Getter @Setter private long offsetSector;
|
||||
@Getter @Setter private long sizeSector;
|
||||
@Getter @Setter private long ownerTitleID;
|
||||
@Getter @Setter private int groupID;
|
||||
@Getter @Setter private byte unkown;
|
||||
public final class ContentFSTInfo {
|
||||
@Getter private final long offsetSector;
|
||||
@Getter private final long sizeSector;
|
||||
@Getter private final long ownerTitleID;
|
||||
@Getter private final int groupID;
|
||||
@Getter private final byte unkown;
|
||||
|
||||
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
|
||||
* @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");
|
||||
return null;
|
||||
}
|
||||
ContentFSTInfo cFSTInfo = new ContentFSTInfo();
|
||||
ContentFSTInfoParam param = new ContentFSTInfoParam();
|
||||
ByteBuffer buffer = ByteBuffer.allocate(input.length);
|
||||
buffer.put(input);
|
||||
|
||||
@ -48,13 +50,13 @@ public class ContentFSTInfo {
|
||||
int groupID = buffer.getInt();
|
||||
byte unkown = buffer.get();
|
||||
|
||||
cFSTInfo.setOffsetSector(offset);
|
||||
cFSTInfo.setSizeSector(size);
|
||||
cFSTInfo.setOwnerTitleID(ownerTitleID);
|
||||
cFSTInfo.setGroupID(groupID);
|
||||
cFSTInfo.setUnkown(unkown);
|
||||
param.setOffsetSector(offset);
|
||||
param.setSizeSector(size);
|
||||
param.setOwnerTitleID(ownerTitleID);
|
||||
param.setGroupID(groupID);
|
||||
param.setUnkown(unkown);
|
||||
|
||||
return cFSTInfo;
|
||||
return new ContentFSTInfo(param);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
}
|
@ -5,7 +5,6 @@ import java.util.Arrays;
|
||||
|
||||
import lombok.EqualsAndHashCode;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
@EqualsAndHashCode
|
||||
/**
|
||||
@ -14,9 +13,9 @@ import lombok.Setter;
|
||||
*
|
||||
*/
|
||||
public class ContentInfo{
|
||||
@Getter @Setter private short indexOffset = 0x00;
|
||||
@Getter @Setter private short commandCount = 0x00;
|
||||
@Getter @Setter private byte[] SHA2Hash = new byte[0x20];
|
||||
@Getter private final short indexOffset;
|
||||
@Getter private final short commandCount;
|
||||
@Getter private final byte[] SHA2Hash;
|
||||
|
||||
public ContentInfo() {
|
||||
this((short) 0);
|
||||
@ -29,9 +28,9 @@ public class ContentInfo{
|
||||
this(indexOffset,commandCount,null);
|
||||
}
|
||||
public ContentInfo(short indexOffset,short commandCount,byte[] SHA2Hash) {
|
||||
setIndexOffset(indexOffset);
|
||||
setCommandCount(commandCount);
|
||||
setSHA2Hash(SHA2Hash);
|
||||
this.indexOffset = indexOffset;
|
||||
this.commandCount = commandCount;
|
||||
this.SHA2Hash = SHA2Hash;
|
||||
}
|
||||
|
||||
/**
|
||||
|
14
src/de/mas/jnus/lib/entities/content/ContentParam.java
Normal file
14
src/de/mas/jnus/lib/entities/content/ContentParam.java
Normal 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;
|
||||
}
|
@ -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.utils.ByteUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
/**
|
||||
* Represents the FST
|
||||
* @author Maschell
|
||||
*
|
||||
*/
|
||||
public class FST {
|
||||
@Getter @Setter private FSTEntry root = FSTEntry.getRootFSTEntry();
|
||||
public final class FST {
|
||||
@Getter private final FSTEntry root = FSTEntry.getRootFSTEntry();
|
||||
|
||||
@Getter @Setter private int unknown;
|
||||
@Getter @Setter private int contentCount = 0;
|
||||
@Getter private final int unknown;
|
||||
@Getter private final int contentCount;
|
||||
|
||||
@Getter @Setter private Map<Integer,ContentFSTInfo> contentFSTInfos = new HashMap<>();
|
||||
|
||||
private FST(){
|
||||
@Getter private final Map<Integer,ContentFSTInfo> contentFSTInfos = new HashMap<>();
|
||||
|
||||
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})){
|
||||
throw new IllegalArgumentException("Not a FST. Maybe a wrong key?");
|
||||
}
|
||||
FST result = new FST();
|
||||
|
||||
int unknownValue = ByteUtils.getIntFromBytes(fstData, 0x04);
|
||||
int contentCount = ByteUtils.getIntFromBytes(fstData, 0x08);
|
||||
|
||||
FST result = new FST(unknownValue,contentCount);
|
||||
|
||||
int contentfst_offset = 0x20;
|
||||
int contentfst_size = 0x20*contentCount;
|
||||
|
||||
@ -71,11 +73,6 @@ public class FST {
|
||||
|
||||
FSTService.parseFST(root,fstSection,nameSection,contentsMappedByIndex,contentFSTInfos);
|
||||
|
||||
result.setContentCount(contentCount);
|
||||
result.setUnknown(unknownValue);
|
||||
result.setContentFSTInfos(contentFSTInfos);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -5,7 +5,6 @@ import java.util.List;
|
||||
|
||||
import de.mas.jnus.lib.entities.content.Content;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
@Log
|
||||
@ -18,29 +17,38 @@ public class FSTEntry{
|
||||
public static final byte FSTEntry_DIR = (byte)0x01;
|
||||
public static final byte FSTEntry_notInNUS = (byte)0x80;
|
||||
|
||||
@Getter @Setter private String filename = "";
|
||||
@Getter @Setter private String path = "";
|
||||
@Getter @Setter private FSTEntry parent = null;
|
||||
@Getter private final String filename;
|
||||
@Getter private final String path;
|
||||
@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 @Setter private long fileOffset = 0;
|
||||
@Getter private final long fileSize;
|
||||
@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 @Setter private boolean isRoot = false;
|
||||
@Getter @Setter private boolean notInPackage = false;
|
||||
|
||||
@Getter @Setter private short contentFSTID = 0;
|
||||
|
||||
public FSTEntry(){
|
||||
@Getter private final short contentFSTID;
|
||||
|
||||
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
|
||||
*/
|
||||
public static FSTEntry getRootFSTEntry(){
|
||||
FSTEntry entry = new FSTEntry();
|
||||
entry.setRoot(true);
|
||||
return entry;
|
||||
FSTEntryParam param = new FSTEntryParam();
|
||||
param.setRoot(true);
|
||||
param.setDir(true);
|
||||
return new FSTEntry(param);
|
||||
}
|
||||
|
||||
public String getFullPath() {
|
||||
@ -65,18 +74,6 @@ public class FSTEntry{
|
||||
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(){
|
||||
return getDirChildren(false);
|
||||
}
|
||||
@ -121,16 +118,6 @@ public class FSTEntry{
|
||||
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() {
|
||||
if(getContent().isHashed()){
|
||||
return (getFileOffset()/0xFC00) * 0x10000;
|
||||
@ -160,6 +147,6 @@ public class FSTEntry{
|
||||
public String toString() {
|
||||
return "FSTEntry [filename=" + filename + ", path=" + path + ", flags=" + flags + ", filesize=" + fileSize
|
||||
+ ", fileoffset=" + fileOffset + ", content=" + content + ", isDir=" + isDir + ", isRoot=" + isRoot
|
||||
+ ", notInPackage=" + notInPackage + "]";
|
||||
+ ", notInPackage=" + isNotInPackage + "]";
|
||||
}
|
||||
}
|
||||
|
24
src/de/mas/jnus/lib/entities/fst/FSTEntryParam.java
Normal file
24
src/de/mas/jnus/lib/entities/fst/FSTEntryParam.java
Normal 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;
|
||||
}
|
@ -11,8 +11,12 @@ import de.mas.jnus.lib.utils.ByteUtils;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
@Log
|
||||
public class FSTService {
|
||||
protected static void parseFST(FSTEntry rootEntry, byte[] fstSection, byte[] namesSection,Map<Integer,Content> contentsByIndex,Map<Integer,ContentFSTInfo> contentsFSTByIndex) {
|
||||
public final class FSTService {
|
||||
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 level = 0;
|
||||
@ -20,8 +24,6 @@ public class FSTService {
|
||||
int[] Entry = new int[16];
|
||||
|
||||
HashMap<Integer,FSTEntry> fstEntryToOffsetMap = new HashMap<>();
|
||||
|
||||
rootEntry.setDir(true);
|
||||
Entry[level] = 0;
|
||||
LEntry[level++] = 0;
|
||||
|
||||
@ -35,7 +37,7 @@ public class FSTService {
|
||||
|
||||
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 filename = getName(curEntry,namesSection);
|
||||
@ -47,19 +49,15 @@ public class FSTService {
|
||||
short contentIndex = ByteUtils.getShortFromBytes(curEntry, 0x0E);
|
||||
|
||||
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){
|
||||
entry.setDir(true);
|
||||
entryParam.setDir(true);
|
||||
int parentOffset = (int) fileOffset;
|
||||
int nextOffset = (int) fileSize;
|
||||
|
||||
FSTEntry parent = fstEntryToOffsetMap.get(parentOffset);
|
||||
if(parent != null){
|
||||
log.fine("no parent found for a FSTEntry");
|
||||
parent.addChildren(entry);
|
||||
}
|
||||
parent = fstEntryToOffsetMap.get(parentOffset);
|
||||
|
||||
Entry[level] = i;
|
||||
LEntry[level++] = nextOffset ;
|
||||
@ -69,26 +67,21 @@ public class FSTService {
|
||||
break;
|
||||
}
|
||||
}else{
|
||||
entry.setFileOffset(fileOffset<<5);
|
||||
entry.setFileSize(fileSize);
|
||||
FSTEntry parent = fstEntryToOffsetMap.get(Entry[level-1]);
|
||||
if(parent != null){
|
||||
parent.addChildren(entry);
|
||||
}else{
|
||||
log.warning(entryOffset +"couldn't find parent @ " + Entry[level-1]);
|
||||
}
|
||||
entryParam.setFileOffset(fileOffset<<5);
|
||||
entryParam.setFileSize(fileSize);
|
||||
parent = fstEntryToOffsetMap.get(Entry[level-1]);
|
||||
}
|
||||
|
||||
entry.setFlags(flags);
|
||||
entry.setFilename(filename);
|
||||
entry.setPath(path);
|
||||
entryParam.setFlags(flags);
|
||||
entryParam.setFilename(filename);
|
||||
entryParam.setPath(path);
|
||||
|
||||
if(contentsByIndex != null){
|
||||
Content content = contentsByIndex.get((int)contentIndex);
|
||||
if(content == null){
|
||||
log.warning("Content for FST Entry not found");
|
||||
}else{
|
||||
entry.setContent(content);
|
||||
entryParam.setContent(content);
|
||||
|
||||
ContentFSTInfo contentFSTInfo = contentsFSTByIndex.get((int)contentIndex);
|
||||
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);
|
||||
}
|
||||
|
@ -3,7 +3,6 @@ package de.mas.jnus.lib.implementations;
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.concurrent.SynchronousQueue;
|
||||
|
||||
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.Utils;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
@Log
|
||||
@ -24,10 +22,10 @@ import lombok.extern.java.Log;
|
||||
*
|
||||
*/
|
||||
public abstract class NUSDataProvider {
|
||||
@Getter private final NUSTitle NUSTitle;
|
||||
|
||||
@Getter @Setter private NUSTitle NUSTitle = null;
|
||||
|
||||
public NUSDataProvider () {
|
||||
public NUSDataProvider (NUSTitle title) {
|
||||
this.NUSTitle = title;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -56,7 +54,7 @@ public abstract class NUSDataProvider {
|
||||
return;
|
||||
}
|
||||
byte[] hash = getContentH3Hash(content);
|
||||
if(hash == null){
|
||||
if(hash == null || hash.length == 0){
|
||||
return;
|
||||
}
|
||||
String h3Filename = String.format("%08X%s", content.getID(),Settings.H3_EXTENTION);
|
||||
|
@ -6,15 +6,18 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
|
||||
import de.mas.jnus.lib.NUSTitle;
|
||||
import de.mas.jnus.lib.Settings;
|
||||
import de.mas.jnus.lib.entities.content.Content;
|
||||
import de.mas.jnus.lib.utils.StreamUtils;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class NUSDataProviderLocal extends NUSDataProvider {
|
||||
@Getter @Setter private String localPath = "";
|
||||
public final class NUSDataProviderLocal extends NUSDataProvider {
|
||||
@Getter private final String localPath;
|
||||
|
||||
public NUSDataProviderLocal() {
|
||||
public NUSDataProviderLocal(NUSTitle nustitle, String localPath) {
|
||||
super(nustitle);
|
||||
this.localPath = localPath;
|
||||
}
|
||||
|
||||
public String getFilePathOnDisk(Content c) {
|
||||
@ -29,7 +32,7 @@ public class NUSDataProviderLocal extends NUSDataProvider {
|
||||
return null;
|
||||
}
|
||||
InputStream in = new FileInputStream(filepath);
|
||||
in.skip(offset);
|
||||
StreamUtils.skipExactly(in,offset);
|
||||
return in;
|
||||
}
|
||||
|
||||
@ -38,7 +41,7 @@ public class NUSDataProviderLocal extends NUSDataProvider {
|
||||
String h3Path = getLocalPath() + File.separator + String.format("%08X.h3", content.getID());
|
||||
File h3File = new File(h3Path);
|
||||
if(!h3File.exists()){
|
||||
return null;
|
||||
return new byte[0];
|
||||
}
|
||||
return Files.readAllBytes(h3File.toPath());
|
||||
}
|
||||
@ -59,10 +62,6 @@ public class NUSDataProviderLocal extends NUSDataProvider {
|
||||
return Files.readAllBytes(ticketFile.toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() throws IOException {
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRawCert() throws IOException {
|
||||
String inputPath = getLocalPath();
|
||||
@ -70,4 +69,9 @@ public class NUSDataProviderLocal extends NUSDataProvider {
|
||||
File certFile = new File(certPath);
|
||||
return Files.readAllBytes(certFile.toPath());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() throws IOException {
|
||||
//We don't need this
|
||||
}
|
||||
}
|
||||
|
@ -3,21 +3,25 @@ package de.mas.jnus.lib.implementations;
|
||||
import java.io.IOException;
|
||||
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.utils.download.NUSDownloadService;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
public class NUSDataProviderRemote extends NUSDataProvider {
|
||||
@Getter @Setter private int version = Settings.LATEST_TMD_VERSION;
|
||||
@Getter @Setter private long titleID = 0L;
|
||||
@Getter private final int version;
|
||||
@Getter private final long titleID;
|
||||
|
||||
public NUSDataProviderRemote(NUSTitle title,int version, long titleID) {
|
||||
super(title);
|
||||
this.version = version;
|
||||
this.titleID = titleID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStreamFromContent(Content content, long fileOffsetBlock) throws IOException {
|
||||
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
|
||||
InputStream in = downloadService.getInputStreamForURL(getRemoteURL(content),fileOffsetBlock);
|
||||
return in;
|
||||
return downloadService.getInputStreamForURL(getRemoteURL(content),fileOffsetBlock);
|
||||
}
|
||||
|
||||
private String getRemoteURL(Content content) {
|
||||
@ -51,12 +55,12 @@ public class NUSDataProviderRemote extends NUSDataProvider {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
public byte[] getRawCert() throws IOException {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRawCert() throws IOException {
|
||||
return null;
|
||||
public void cleanup() throws IOException {
|
||||
//We don't need this
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package de.mas.jnus.lib.implementations;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
import de.mas.jnus.lib.NUSTitle;
|
||||
import de.mas.jnus.lib.Settings;
|
||||
import de.mas.jnus.lib.entities.TMD;
|
||||
import de.mas.jnus.lib.entities.content.Content;
|
||||
@ -16,12 +17,13 @@ import lombok.Setter;
|
||||
import lombok.extern.java.Log;
|
||||
@Log
|
||||
public class NUSDataProviderWUD extends NUSDataProvider {
|
||||
@Getter @Setter private WUDInfo WUDInfo = null;
|
||||
@Getter private final WUDInfo WUDInfo;
|
||||
|
||||
@Setter(AccessLevel.PRIVATE) private TMD TMD = null;
|
||||
|
||||
public NUSDataProviderWUD() {
|
||||
super();
|
||||
public NUSDataProviderWUD(NUSTitle title,WUDInfo wudinfo) {
|
||||
super(title);
|
||||
this.WUDInfo = wudinfo;
|
||||
}
|
||||
|
||||
public long getOffsetInWUD(Content content) {
|
||||
@ -97,6 +99,7 @@ public class NUSDataProviderWUD extends NUSDataProvider {
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
//We don't need it
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -5,10 +5,12 @@ import java.io.InputStream;
|
||||
import java.util.zip.ZipEntry;
|
||||
import java.util.zip.ZipException;
|
||||
|
||||
import de.mas.jnus.lib.NUSTitle;
|
||||
import de.mas.jnus.lib.Settings;
|
||||
import de.mas.jnus.lib.entities.content.Content;
|
||||
import de.mas.jnus.lib.implementations.woomy.WoomyInfo;
|
||||
import de.mas.jnus.lib.implementations.woomy.WoomyZipFile;
|
||||
import lombok.AccessLevel;
|
||||
import lombok.Getter;
|
||||
import lombok.NonNull;
|
||||
import lombok.Setter;
|
||||
@ -16,8 +18,13 @@ import lombok.extern.java.Log;
|
||||
|
||||
@Log
|
||||
public class NUSDataProviderWoomy extends NUSDataProvider{
|
||||
@Getter @Setter private WoomyInfo woomyInfo;
|
||||
@Setter private WoomyZipFile woomyZipFile;
|
||||
@Getter private final WoomyInfo woomyInfo;
|
||||
@Setter(AccessLevel.PRIVATE) private WoomyZipFile woomyZipFile;
|
||||
|
||||
public NUSDataProviderWoomy(NUSTitle title,WoomyInfo woomyInfo) {
|
||||
super(title);
|
||||
this.woomyInfo = woomyInfo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream getInputStreamFromContent(@NonNull Content content, long fileOffsetBlock) throws IOException {
|
||||
@ -39,7 +46,7 @@ public class NUSDataProviderWoomy extends NUSDataProvider{
|
||||
zipFile.close();
|
||||
return result;
|
||||
}
|
||||
return null;
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -70,10 +77,10 @@ public class NUSDataProviderWoomy extends NUSDataProvider{
|
||||
}
|
||||
|
||||
public WoomyZipFile getSharedWoomyZipFile() throws ZipException, IOException {
|
||||
if(woomyZipFile == null || woomyZipFile.isClosed()){
|
||||
woomyZipFile = getNewWoomyZipFile();
|
||||
if(this.woomyZipFile == null || this.woomyZipFile.isClosed()){
|
||||
this.woomyZipFile = getNewWoomyZipFile();
|
||||
}
|
||||
return woomyZipFile;
|
||||
return this.woomyZipFile;
|
||||
}
|
||||
|
||||
private WoomyZipFile getNewWoomyZipFile() throws ZipException, IOException {
|
||||
@ -82,14 +89,13 @@ public class NUSDataProviderWoomy extends NUSDataProvider{
|
||||
|
||||
@Override
|
||||
public void cleanup() throws IOException {
|
||||
if(woomyZipFile != null && woomyZipFile.isClosed()){
|
||||
woomyZipFile.close();
|
||||
if(this.woomyZipFile != null && this.woomyZipFile.isClosed()){
|
||||
this.woomyZipFile.close();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] getRawCert() throws IOException {
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
return new byte[0];
|
||||
}
|
||||
}
|
||||
|
@ -7,33 +7,19 @@ import lombok.Data;
|
||||
|
||||
@Data
|
||||
public class WoomyMeta {
|
||||
private String name;
|
||||
private int icon;
|
||||
private List<WoomyEntry> entries;
|
||||
private final String name;
|
||||
private final int icon;
|
||||
private final List<WoomyEntry> entries = new ArrayList<>();
|
||||
|
||||
public void addEntry(String name,String folder, int entryCount){
|
||||
WoomyEntry entry = new WoomyEntry(name, folder, entryCount);
|
||||
getEntries().add(entry);
|
||||
}
|
||||
|
||||
public List<WoomyEntry> getEntries(){
|
||||
if(entries == null){
|
||||
setEntries(new ArrayList<>());
|
||||
}
|
||||
return entries;
|
||||
}
|
||||
|
||||
@Data
|
||||
public class WoomyEntry {
|
||||
|
||||
public WoomyEntry(String name, String folder, int entryCount) {
|
||||
setName(name);
|
||||
setFolder(folder);
|
||||
setEntryCount(entryCount);
|
||||
}
|
||||
|
||||
private String name;
|
||||
private String folder;
|
||||
private int entryCount;
|
||||
private final String name;
|
||||
private final String folder;
|
||||
private final int entryCount;
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ import de.mas.jnus.lib.utils.XMLParser;
|
||||
import lombok.extern.java.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_ICON = "icon";
|
||||
|
||||
@ -25,7 +25,8 @@ public class WoomyMetaParser extends XMLParser{
|
||||
|
||||
public static WoomyMeta parseMeta(InputStream data){
|
||||
XMLParser parser = new WoomyMetaParser();
|
||||
WoomyMeta result = new WoomyMeta();
|
||||
String resultName = "";
|
||||
int resultIcon = 0;
|
||||
try {
|
||||
parser.loadDocument(data);
|
||||
} catch(Exception e){
|
||||
@ -35,14 +36,17 @@ public class WoomyMetaParser extends XMLParser{
|
||||
|
||||
String name = parser.getValueOfElement(WOOMY_METADATA_NAME);
|
||||
if(name != null && !name.isEmpty()){
|
||||
result.setName(name);
|
||||
resultName = name;
|
||||
}
|
||||
|
||||
String icon = parser.getValueOfElement(WOOMY_METADATA_ICON);
|
||||
if(icon != null && !icon.isEmpty()){
|
||||
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);
|
||||
|
||||
NodeList entry_list = entries_node.getChildNodes();
|
||||
|
@ -4,6 +4,7 @@ import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.Enumeration;
|
||||
import java.util.HashMap;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
@ -27,7 +28,10 @@ import lombok.extern.java.Log;
|
||||
*
|
||||
*/
|
||||
@Log
|
||||
public class WoomyParser {
|
||||
public final class WoomyParser {
|
||||
private WoomyParser(){
|
||||
//
|
||||
}
|
||||
public static WoomyInfo createWoomyInfo(File woomyFile) throws IOException, ParserConfigurationException, SAXException{
|
||||
WoomyInfo result = new WoomyInfo();
|
||||
if(!woomyFile.exists()){
|
||||
@ -72,7 +76,7 @@ public class WoomyParser {
|
||||
if(matcher.matches()){
|
||||
String[] tokens = entryName.split("[\\\\|/]"); //We only want the filename!
|
||||
String filename = tokens[tokens.length - 1];
|
||||
result.put(filename.toLowerCase(), entry);
|
||||
result.put(filename.toLowerCase(Locale.ENGLISH), entry);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,13 +20,14 @@ import lombok.extern.java.Log;
|
||||
public class WUDImage {
|
||||
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 boolean isCompressed = false;
|
||||
@Getter @Setter private boolean isSplitted = false;
|
||||
@Getter private final boolean isCompressed;
|
||||
@Getter private final boolean isSplitted;
|
||||
|
||||
private long inputFileSize = 0L;
|
||||
@Setter private WUDDiscReader WUDDiscReader = null;
|
||||
@Getter private final WUDDiscReader WUDDiscReader;
|
||||
|
||||
public WUDImage(File file) throws IOException{
|
||||
if(file == null || !file.exists()){
|
||||
@ -42,7 +43,8 @@ public class WUDImage {
|
||||
|
||||
if(compressedInfo.isWUX()){
|
||||
log.info("Image is compressed");
|
||||
setCompressed(true);
|
||||
this.isCompressed = true;
|
||||
this.isSplitted = false;
|
||||
Map<Integer,Long> indexTable = new HashMap<>();
|
||||
long offsetIndexTable = compressedInfo.getOffsetIndexTable();
|
||||
fileStream.seek(offsetIndexTable);
|
||||
@ -56,27 +58,27 @@ public class WUDImage {
|
||||
}
|
||||
compressedInfo.setIndexTable(indexTable);
|
||||
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)){
|
||||
setSplitted(true);
|
||||
this.isSplitted = true;
|
||||
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();
|
||||
setFileHandle(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;
|
||||
this.fileHandle = file;
|
||||
}
|
||||
|
||||
public long getWUDFileSize() {
|
||||
|
@ -15,16 +15,16 @@ public class WUDImageCompressedInfo {
|
||||
public static final int WUX_MAGIC_1 = 0x1099d02e;
|
||||
public static final int SECTOR_SIZE = 0x8000;
|
||||
|
||||
@Getter @Setter private int magic0;
|
||||
@Getter @Setter private int magic1;
|
||||
@Getter @Setter private int sectorSize;
|
||||
@Getter @Setter private long uncompressedSize;
|
||||
@Getter @Setter private int flags;
|
||||
@Getter private final int sectorSize;
|
||||
@Getter private final long uncompressedSize;
|
||||
@Getter private final int flags;
|
||||
|
||||
@Getter @Setter private long indexTableEntryCount = 0;
|
||||
@Getter @Setter private long offsetIndexTable = 0;
|
||||
@Getter @Setter private long offsetSectorArray = 0;
|
||||
@Getter @Setter private long indexTableSize = 0;
|
||||
@Getter @Setter private long indexTableEntryCount;
|
||||
@Getter private final long offsetIndexTable = WUX_HEADER_SIZE;
|
||||
@Getter @Setter private long offsetSectorArray;
|
||||
@Getter @Setter private long indexTableSize;
|
||||
|
||||
private final boolean valid;
|
||||
|
||||
@Getter private Map<Integer,Long> indexTable = new HashMap<>();
|
||||
|
||||
@ -33,11 +33,16 @@ public class WUDImageCompressedInfo {
|
||||
System.out.println("WUX header length wrong");
|
||||
System.exit(1);
|
||||
}
|
||||
setMagic0(ByteUtils.getIntFromBytes(headData, 0x00,ByteOrder.LITTLE_ENDIAN));
|
||||
setMagic1(ByteUtils.getIntFromBytes(headData, 0x04,ByteOrder.LITTLE_ENDIAN));
|
||||
setSectorSize(ByteUtils.getIntFromBytes(headData, 0x08,ByteOrder.LITTLE_ENDIAN));
|
||||
setFlags(ByteUtils.getIntFromBytes(headData, 0x0C,ByteOrder.LITTLE_ENDIAN));
|
||||
setUncompressedSize(ByteUtils.getLongFromBytes(headData, 0x10,ByteOrder.LITTLE_ENDIAN));
|
||||
int magic0 = ByteUtils.getIntFromBytes(headData, 0x00,ByteOrder.LITTLE_ENDIAN);
|
||||
int magic1 = ByteUtils.getIntFromBytes(headData, 0x04,ByteOrder.LITTLE_ENDIAN);
|
||||
if(magic0 == WUX_MAGIC_0 && magic1 == WUX_MAGIC_1){
|
||||
valid = true;
|
||||
}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();
|
||||
}
|
||||
@ -47,17 +52,16 @@ public class WUDImageCompressedInfo {
|
||||
}
|
||||
|
||||
public WUDImageCompressedInfo(int sectorSize,int flags, long uncompressedSize) {
|
||||
setMagic0(WUX_MAGIC_0);
|
||||
setMagic1(WUX_MAGIC_1);
|
||||
setSectorSize(sectorSize);
|
||||
setFlags(flags);
|
||||
setUncompressedSize(uncompressedSize);
|
||||
this.sectorSize = sectorSize;
|
||||
this.flags = flags;
|
||||
this.uncompressedSize= uncompressedSize;
|
||||
valid = true;
|
||||
calculateOffsets();
|
||||
}
|
||||
|
||||
private void calculateOffsets() {
|
||||
long indexTableEntryCount = (getUncompressedSize()+ getSectorSize()-1) / getSectorSize();
|
||||
setIndexTableEntryCount(indexTableEntryCount);
|
||||
setOffsetIndexTable(0x20);
|
||||
long offsetSectorArray = (getOffsetIndexTable() + ((long)getIndexTableEntryCount() * 0x04L));
|
||||
// align to SECTOR_SIZE
|
||||
offsetSectorArray = (offsetSectorArray + (long)(getSectorSize()-1));
|
||||
@ -68,15 +72,7 @@ public class WUDImageCompressedInfo {
|
||||
}
|
||||
|
||||
public boolean isWUX() {
|
||||
return (getMagic0() == WUX_MAGIC_0 && getMagic1() == WUX_MAGIC_1);
|
||||
}
|
||||
|
||||
@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 + "]";
|
||||
return valid;
|
||||
}
|
||||
|
||||
public long getSectorIndex(int sectorIndex) {
|
||||
@ -90,8 +86,8 @@ public class WUDImageCompressedInfo {
|
||||
public byte[] getHeaderAsBytes() {
|
||||
ByteBuffer result = ByteBuffer.allocate(WUX_HEADER_SIZE);
|
||||
result.order(ByteOrder.LITTLE_ENDIAN);
|
||||
result.putInt(getMagic0());
|
||||
result.putInt(getMagic1());
|
||||
result.putInt(WUX_MAGIC_0);
|
||||
result.putInt(WUX_MAGIC_1);
|
||||
result.putInt(getSectorSize());
|
||||
result.putInt(getFlags());
|
||||
result.putLong(getUncompressedSize());
|
||||
|
@ -6,7 +6,15 @@ import lombok.EqualsAndHashCode;
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper=true)
|
||||
public class WUDGamePartition extends WUDPartition {
|
||||
private byte[] rawTMD;
|
||||
private byte[] rawCert;
|
||||
private byte[] rawTicket;
|
||||
private final byte[] rawTMD;
|
||||
private final byte[] rawCert;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package de.mas.jnus.lib.implementations.wud.parser;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
|
||||
@ -11,19 +12,20 @@ import lombok.Setter;
|
||||
|
||||
@Data
|
||||
public class WUDInfo {
|
||||
private byte[] titleKey = null;
|
||||
private final byte[] titleKey;
|
||||
|
||||
private WUDDiscReader WUDDiscReader = null;
|
||||
private Map<String,WUDPartition> partitions = null;
|
||||
private final WUDDiscReader WUDDiscReader;
|
||||
private final Map<String,WUDPartition> partitions = new HashMap<>();
|
||||
|
||||
@Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PROTECTED)
|
||||
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(){
|
||||
if(cachedGamePartition == null){
|
||||
cachedGamePartition = findGamePartition();
|
||||
|
@ -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.implementations.wud.reader.WUDDiscReader;
|
||||
import de.mas.jnus.lib.utils.ByteUtils;
|
||||
import de.mas.jnus.lib.utils.FileUtils;
|
||||
import de.mas.jnus.lib.utils.Utils;
|
||||
import lombok.extern.java.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[] PARTITION_FILE_TABLE_SIGNATURE = new byte[] { 0x46, 0x53, 0x54, 0x00 }; // "FST"
|
||||
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_CERT_FILENAME = "title.cert";
|
||||
|
||||
public static WUDInfo createAndLoad(WUDDiscReader discReader,byte[] titleKey) throws IOException {
|
||||
WUDInfo result = new WUDInfo();
|
||||
private WUDInfoParser(){
|
||||
//
|
||||
}
|
||||
|
||||
result.setTitleKey(titleKey);
|
||||
result.setWUDDiscReader(discReader);
|
||||
public static WUDInfo createAndLoad(WUDDiscReader discReader,byte[] titleKey) throws IOException {
|
||||
WUDInfo result = new WUDInfo(titleKey,discReader);
|
||||
|
||||
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);
|
||||
result.setPartitions(partitions);
|
||||
result.getPartitions().putAll(partitions);
|
||||
//parsePartitions(wudInfo,partitions);
|
||||
|
||||
return result;
|
||||
@ -61,12 +61,14 @@ public class WUDInfoParser {
|
||||
|
||||
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;
|
||||
// populate partition information from decrypted TOC
|
||||
for (int i = 0; i < partitionCount; i++){
|
||||
WUDPartition partition = new WUDPartition();
|
||||
|
||||
|
||||
int offset = (PARTITION_TOC_OFFSET + (i * PARTITION_TOC_ENTRY_SIZE));
|
||||
byte[] partitionIdentifier = Arrays.copyOfRange(partitionTocBlock, offset, offset+ 0x19);
|
||||
@ -83,9 +85,7 @@ public class WUDInfoParser {
|
||||
|
||||
long partitionOffset = ((tmp * (long)0x8000) - 0x10000);
|
||||
|
||||
|
||||
partition.setPartitionName(partitionName);
|
||||
partition.setPartitionOffset(partitionOffset);
|
||||
WUDPartition partition = new WUDPartition(partitionName,partitionOffset);
|
||||
|
||||
if(partitionName.startsWith("SI")){
|
||||
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[] rawCert = getFSTEntryAsByte(WUD_CERT_FILENAME,partition,fst,wudInfo.getWUDDiscReader(),wudInfo.getTitleKey());
|
||||
|
||||
gamePartition.setRawTMD(rawTMD);
|
||||
gamePartition.setRawTicket(rawTIK);
|
||||
gamePartition.setRawCert(rawCert);
|
||||
gamePartitionTMD = rawTMD;
|
||||
gamePartitionTicket = rawTIK;
|
||||
gamePartitionCert = rawCert;
|
||||
|
||||
//We want to use the real game partition
|
||||
realGamePartitionName = partitionName = "GM" + Utils.ByteArrayToString(Arrays.copyOfRange(rawTIK, 0x1DC, 0x1DC + 0x08));
|
||||
}else if(partitionName.startsWith(realGamePartitionName)){
|
||||
gamePartition.setPartitionOffset(partitionOffset);
|
||||
gamePartition.setPartitionName(partitionName);
|
||||
|
||||
wudInfo.setGamePartitionName(partitionName);
|
||||
partition = gamePartition;
|
||||
partition = new WUDGamePartition(partitionName, partitionOffset, gamePartitionTMD, gamePartitionCert, gamePartitionTicket);
|
||||
}
|
||||
byte [] header = wudInfo.getWUDDiscReader().readEncryptedToByteArray(partition.getPartitionOffset()+0x10000,0,0x8000);
|
||||
WUDPartitionHeader partitionHeader = WUDPartitionHeader.parseHeader(header);
|
||||
|
@ -3,8 +3,8 @@ package de.mas.jnus.lib.implementations.wud.parser;
|
||||
import lombok.Data;
|
||||
@Data
|
||||
public class WUDPartition {
|
||||
private String partitionName = "";
|
||||
private long partitionOffset = 0;
|
||||
private final String partitionName;
|
||||
private final long partitionOffset;
|
||||
|
||||
private WUDPartitionHeader partitionHeader;
|
||||
}
|
||||
|
@ -17,10 +17,11 @@ import lombok.Setter;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
@Log
|
||||
public class WUDPartitionHeader {
|
||||
public final class WUDPartitionHeader {
|
||||
@Getter @Setter
|
||||
private boolean calculatedHashes = false;
|
||||
private HashMap<Short,byte[]> h3Hashes;
|
||||
@Getter
|
||||
private final HashMap<Short,byte[]> h3Hashes = new HashMap<>();
|
||||
@Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PRIVATE)
|
||||
private byte[] rawData;
|
||||
|
||||
@ -34,13 +35,6 @@ public class WUDPartitionHeader {
|
||||
return result;
|
||||
}
|
||||
|
||||
public HashMap<Short,byte[]> getH3Hashes() {
|
||||
if(h3Hashes == null){
|
||||
h3Hashes = new HashMap<>();
|
||||
}
|
||||
return h3Hashes;
|
||||
}
|
||||
|
||||
public void addH3Hashes(short index, byte[] hash) {
|
||||
getH3Hashes().put(index, hash);
|
||||
}
|
||||
|
@ -13,14 +13,13 @@ import java.util.Arrays;
|
||||
import de.mas.jnus.lib.implementations.wud.WUDImage;
|
||||
import de.mas.jnus.lib.utils.cryptography.AESDecryption;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
import lombok.extern.java.Log;
|
||||
@Log
|
||||
public abstract class WUDDiscReader {
|
||||
@Getter @Setter private WUDImage image = null;
|
||||
@Getter private final WUDImage image;
|
||||
|
||||
public WUDDiscReader(WUDImage image){
|
||||
setImage(image);
|
||||
this.image = image;
|
||||
}
|
||||
|
||||
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 {
|
||||
if(IV == null){
|
||||
IV = new byte[0x10];
|
||||
byte[] usedIV = IV;
|
||||
if(usedIV == null){
|
||||
usedIV = new byte[0x10];
|
||||
}
|
||||
|
||||
long usedSize = size;
|
||||
long usedFileOffset = fileOffset;
|
||||
byte[] buffer;
|
||||
|
||||
long maxCopySize;
|
||||
@ -96,23 +97,23 @@ public abstract class WUDDiscReader {
|
||||
long totalread = 0;
|
||||
|
||||
do{
|
||||
long blockNumber = (fileOffset / blockSize);
|
||||
long blockOffset = (fileOffset % blockSize);
|
||||
long blockNumber = (usedFileOffset / blockSize);
|
||||
long blockOffset = (usedFileOffset % blockSize);
|
||||
|
||||
readOffset = clusterOffset + (blockNumber * blockSize);
|
||||
// (long)WiiUDisc.WIIU_DECRYPTED_AREA_OFFSET + volumeOffset + clusterOffset + (blockStructure.getBlockNumber() * 0x8000);
|
||||
|
||||
buffer = readDecryptedChunk(readOffset,key, IV);
|
||||
buffer = readDecryptedChunk(readOffset,key, usedIV);
|
||||
maxCopySize = 0x8000 - blockOffset;
|
||||
copySize = (size > maxCopySize) ? maxCopySize : size;
|
||||
copySize = (usedSize > maxCopySize) ? maxCopySize : usedSize;
|
||||
|
||||
outputStream.write(Arrays.copyOfRange(buffer, (int) blockOffset, (int) copySize));
|
||||
totalread += copySize;
|
||||
|
||||
// update counters
|
||||
size -= copySize;
|
||||
fileOffset += copySize;
|
||||
}while(totalread < size);
|
||||
usedSize -= copySize;
|
||||
usedFileOffset += copySize;
|
||||
}while(totalread < usedSize);
|
||||
|
||||
outputStream.close();
|
||||
}
|
||||
|
@ -25,12 +25,16 @@ public class WUDDiscReaderCompressed extends WUDDiscReader{
|
||||
WUDImageCompressedInfo info = getImage().getCompressedInfo();
|
||||
|
||||
long fileBytesLeft = info.getUncompressedSize() - offset;
|
||||
|
||||
long usedOffset = offset;
|
||||
long usedSize = size;
|
||||
|
||||
if( fileBytesLeft <= 0 ){
|
||||
log.warning("offset too big");
|
||||
System.exit(1);
|
||||
}
|
||||
if( fileBytesLeft < size ){
|
||||
size = fileBytesLeft;
|
||||
if( fileBytesLeft < usedSize ){
|
||||
usedSize = fileBytesLeft;
|
||||
}
|
||||
// compressed read must be handled on a per-sector level
|
||||
|
||||
@ -38,11 +42,11 @@ public class WUDDiscReaderCompressed extends WUDDiscReader{
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
|
||||
RandomAccessFile input = getRandomAccessFileStream();
|
||||
while( size > 0 ){
|
||||
long sectorOffset = (offset % info.getSectorSize());
|
||||
while( usedSize > 0 ){
|
||||
long sectorOffset = (usedOffset % info.getSectorSize());
|
||||
long remainingSectorBytes = info.getSectorSize() - sectorOffset;
|
||||
long sectorIndex = (offset / info.getSectorSize());
|
||||
int bytesToRead = (int) ((remainingSectorBytes<size)?remainingSectorBytes:size); // read only up to the end of the current sector
|
||||
long sectorIndex = (usedOffset / info.getSectorSize());
|
||||
int bytesToRead = (int) ((remainingSectorBytes<usedSize)?remainingSectorBytes:usedSize); // read only up to the end of the current sector
|
||||
// look up real sector index
|
||||
long realSectorIndex = info.getSectorIndex((int) sectorIndex);
|
||||
long offset2 = info.getOffsetSectorArray() + realSectorIndex*info.getSectorSize()+sectorOffset;
|
||||
@ -60,10 +64,9 @@ public class WUDDiscReaderCompressed extends WUDDiscReader{
|
||||
}
|
||||
}
|
||||
|
||||
size -= bytesToRead;
|
||||
offset += bytesToRead;
|
||||
usedSize -= bytesToRead;
|
||||
usedOffset += bytesToRead;
|
||||
}
|
||||
input.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,13 +9,14 @@ import java.util.Arrays;
|
||||
import de.mas.jnus.lib.implementations.wud.WUDImage;
|
||||
|
||||
public class WUDDiscReaderSplitted extends WUDDiscReader{
|
||||
public WUDDiscReaderSplitted(WUDImage image) {
|
||||
super(image);
|
||||
}
|
||||
public static long WUD_SPLITTED_FILE_SIZE = 0x100000L * 0x800L;
|
||||
public static long NUMBER_OF_FILES = 12;
|
||||
public static String WUD_SPLITTED_DEFAULT_FILEPATTERN = "game_part%d.wud";
|
||||
|
||||
public WUDDiscReaderSplitted(WUDImage image) {
|
||||
super(image);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void readEncryptedToOutputStream(OutputStream outputStream, long offset, long size) throws IOException {
|
||||
RandomAccessFile input = getFileByOffset(offset);
|
||||
|
@ -6,6 +6,7 @@ import java.io.OutputStream;
|
||||
import java.util.Arrays;
|
||||
|
||||
import de.mas.jnus.lib.implementations.wud.WUDImage;
|
||||
import de.mas.jnus.lib.utils.StreamUtils;
|
||||
|
||||
public class WUDDiscReaderUncompressed extends WUDDiscReader {
|
||||
public WUDDiscReaderUncompressed(WUDImage image) {
|
||||
@ -16,7 +17,9 @@ public class WUDDiscReaderUncompressed extends WUDDiscReader {
|
||||
protected void readEncryptedToOutputStream(OutputStream outputStream, long offset,long size) throws IOException{
|
||||
|
||||
FileInputStream input = new FileInputStream(getImage().getFileHandle());
|
||||
input.skip(offset);
|
||||
|
||||
StreamUtils.skipExactly(input,offset);
|
||||
|
||||
int bufferSize = 0x8000;
|
||||
byte[] buffer = new byte[bufferSize];
|
||||
long totalread = 0;
|
||||
|
@ -4,7 +4,12 @@ import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.Arrays;
|
||||
|
||||
public class ByteUtils {
|
||||
public final class ByteUtils {
|
||||
|
||||
private ByteUtils(){
|
||||
//Utility Class
|
||||
}
|
||||
|
||||
public static int getIntFromBytes(byte[] input,int offset){
|
||||
return getIntFromBytes(input, offset, ByteOrder.BIG_ENDIAN);
|
||||
}
|
||||
|
@ -7,8 +7,10 @@ import java.io.InputStream;
|
||||
|
||||
import lombok.NonNull;
|
||||
|
||||
public class FileUtils {
|
||||
|
||||
public final class FileUtils {
|
||||
private FileUtils(){
|
||||
//Utility Class
|
||||
}
|
||||
public static boolean saveByteArrayToFile(String filePath,byte[] data) throws IOException {
|
||||
File target = new File(filePath);
|
||||
if(target.isDirectory()){
|
||||
|
@ -12,7 +12,10 @@ import java.util.Arrays;
|
||||
|
||||
import lombok.extern.java.Log;
|
||||
@Log
|
||||
public class HashUtil {
|
||||
public final class HashUtil {
|
||||
private HashUtil(){
|
||||
//Utility class
|
||||
}
|
||||
public static byte[] hashSHA256(byte[] data){
|
||||
MessageDigest sha256;
|
||||
try {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package de.mas.jnus.lib.utils;
|
||||
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.OutputStream;
|
||||
@ -10,7 +11,10 @@ import java.util.Arrays;
|
||||
import lombok.extern.java.Log;
|
||||
|
||||
@Log
|
||||
public class StreamUtils {
|
||||
public final class StreamUtils {
|
||||
private StreamUtils(){
|
||||
//Utility class
|
||||
}
|
||||
public static byte[] getBytesFromStream(InputStream in,int size) throws IOException{
|
||||
byte[] result = new byte[size];
|
||||
byte[] buffer = new byte[0x8000];
|
||||
@ -124,4 +128,16 @@ public class StreamUtils {
|
||||
outputStream.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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -2,8 +2,10 @@ package de.mas.jnus.lib.utils;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public class Utils {
|
||||
|
||||
public final class Utils {
|
||||
private Utils(){
|
||||
//Utility class
|
||||
}
|
||||
public static long align(long numToRound, int multiple){
|
||||
if((multiple>0) && ((multiple & (multiple -1)) == 0)){
|
||||
return alignPower2(numToRound, multiple);
|
||||
|
@ -31,7 +31,7 @@ public class AESDecryption {
|
||||
init();
|
||||
}
|
||||
|
||||
protected void init() {
|
||||
protected final void init() {
|
||||
init(getAESKey(),getIV());
|
||||
}
|
||||
|
||||
|
@ -96,7 +96,7 @@ public class NUSDecryption extends AESDecryption{
|
||||
log.info(Utils.ByteArrayToString(expected_hash));
|
||||
log.info("Hash doesn't match decrypted content.");
|
||||
}else{
|
||||
//log.warning("###################################################Hash DOES match saves output stream.");
|
||||
//log.warning("Hash DOES match saves output stream.");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -5,7 +5,10 @@ import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
public class Downloader {
|
||||
public abstract class Downloader {
|
||||
public Downloader() {
|
||||
//
|
||||
}
|
||||
public static byte[] downloadFileToByteArray(String fileURL) throws IOException {
|
||||
int BUFFER_SIZE = 0x800;
|
||||
URL url = new URL(fileURL);
|
||||
|
@ -4,34 +4,38 @@ import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
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 Map<String,NUSDownloadService> instances = new HashMap<>();
|
||||
|
||||
private final String URL_BASE;
|
||||
|
||||
private NUSDownloadService(String URL){
|
||||
this.URL_BASE = URL;
|
||||
}
|
||||
|
||||
public static NUSDownloadService getDefaultInstance(){
|
||||
synchronized (defaultInstance) {
|
||||
if(defaultInstance == null){
|
||||
defaultInstance = new NUSDownloadService();
|
||||
defaultInstance.setURL_BASE(Settings.URL_BASE);
|
||||
defaultInstance = new NUSDownloadService(Settings.URL_BASE);
|
||||
}
|
||||
}
|
||||
return defaultInstance;
|
||||
}
|
||||
|
||||
public static NUSDownloadService getInstance(String URL){
|
||||
NUSDownloadService instance = new NUSDownloadService();
|
||||
instance.setURL_BASE(URL);
|
||||
return instance;
|
||||
if(!instances.containsKey(URL)){
|
||||
NUSDownloadService instance = new NUSDownloadService(URL);
|
||||
instances.put(URL, instance);
|
||||
}
|
||||
|
||||
private NUSDownloadService(){
|
||||
|
||||
return instances.get(URL);
|
||||
}
|
||||
|
||||
@Setter private String URL_BASE = "";
|
||||
|
||||
|
||||
public byte[] downloadTMDToByteArray(long titleID, int version) throws IOException {
|
||||
String version_suf = "";
|
||||
if(version > Settings.LATEST_TMD_VERSION) version_suf = "." + version;
|
||||
|
Loading…
Reference in New Issue
Block a user