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,11 +25,11 @@ 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)){
|
||||||
instances.put(nustitle, new DecryptionService(nustitle));
|
instances.put(nustitle, new DecryptionService(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();
|
||||||
|
@ -12,22 +12,21 @@ 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));
|
||||||
}
|
}
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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(TMDParam param) {
|
||||||
private TMD(){
|
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);
|
||||||
@ -105,6 +116,25 @@ public class TMD {
|
|||||||
buffer.get(contentInfo, 0, 0x24);
|
buffer.get(contentInfo, 0, 0x24);
|
||||||
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++){
|
||||||
@ -115,22 +145,6 @@ public class TMD {
|
|||||||
result.setContentToIndex(c.getIndex(),c);
|
result.setContentToIndex(c.getIndex(),c);
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
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.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(byte[] encryptedKey, byte[] decryptedKey,byte[] IV) {
|
||||||
|
super();
|
||||||
private Ticket(){
|
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
|
||||||
|
@ -20,17 +20,25 @@ 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
|
||||||
* @param input 0x30 byte of data from the TMD (starting at 0xB04)
|
* @param input 0x30 byte of data from the TMD (starting at 0xB04)
|
||||||
@ -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,17 +60,17 @@ 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);
|
|
||||||
|
ContentParam param = new ContentParam();
|
||||||
|
param.setID(ID);
|
||||||
|
param.setIndex(index);
|
||||||
|
param.setType(type);
|
||||||
|
param.setEncryptedFileSize(encryptedFileSize);
|
||||||
|
param.setSHA2Hash(hash);
|
||||||
|
|
||||||
|
return new Content(param);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Content(int ID, short index, short type, long encryptedFileSize, byte[] hash) {
|
|
||||||
setID(ID);
|
|
||||||
setIndex(index);
|
|
||||||
setType(type);
|
|
||||||
setEncryptedFileSize(encryptedFileSize);
|
|
||||||
setSHA2Hash(hash);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns if the content is hashed
|
* Returns if the content is hashed
|
||||||
* @return true if hashed
|
* @return true if hashed
|
||||||
@ -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
|
||||||
|
@ -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,19 +12,22 @@ 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
|
||||||
@ -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);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -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.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;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
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.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;
|
||||||
|
|
||||||
@ -70,12 +72,7 @@ public class FST {
|
|||||||
FSTEntry root = result.getRoot();
|
FSTEntry root = result.getRoot();
|
||||||
|
|
||||||
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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
@ -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 @Setter private boolean isDir = false;
|
@Getter private final boolean isNotInPackage;
|
||||||
@Getter @Setter private boolean isRoot = false;
|
|
||||||
@Getter @Setter private boolean notInPackage = false;
|
|
||||||
|
|
||||||
@Getter @Setter private short contentFSTID = 0;
|
@Getter private final short contentFSTID;
|
||||||
|
|
||||||
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);
|
||||||
}
|
}
|
||||||
@ -120,16 +117,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()){
|
||||||
@ -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 + "]";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
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,17 +11,19 @@ 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;
|
||||||
int[] LEntry = new int[16];
|
int[] LEntry = new int[16];
|
||||||
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,20 +49,16 @@ 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,8 +92,11 @@ 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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -123,7 +119,7 @@ public class FSTService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
public static String getFullPath(int level,byte[] fstSection,byte[] namesSection, int[] Entry){
|
public static String getFullPath(int level,byte[] fstSection,byte[] namesSection, int[] Entry){
|
||||||
StringBuilder sb = new StringBuilder();
|
StringBuilder sb = new StringBuilder();
|
||||||
for(int i=0; i < level; i++){
|
for(int i=0; i < level; i++){
|
||||||
int entryOffset = Entry[i]*0x10;
|
int entryOffset = Entry[i]*0x10;
|
||||||
|
@ -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);
|
||||||
|
@ -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
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() {
|
||||||
|
@ -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,17 +72,9 @@ 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) {
|
||||||
return getIndexTable().get(sectorIndex);
|
return getIndexTable().get(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());
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
|
@ -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";
|
||||||
|
|
||||||
|
private WUDInfoParser(){
|
||||||
|
//
|
||||||
|
}
|
||||||
|
|
||||||
public static WUDInfo createAndLoad(WUDDiscReader discReader,byte[] titleKey) throws IOException {
|
public static WUDInfo createAndLoad(WUDDiscReader discReader,byte[] titleKey) throws IOException {
|
||||||
WUDInfo result = new WUDInfo();
|
WUDInfo result = new WUDInfo(titleKey,discReader);
|
||||||
|
|
||||||
result.setTitleKey(titleKey);
|
|
||||||
result.setWUDDiscReader(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);
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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();
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,18 @@ public class WUDDiscReaderCompressed extends WUDDiscReader{
|
|||||||
protected void readEncryptedToOutputStream(OutputStream out, long offset, long size) throws IOException {
|
protected void readEncryptedToOutputStream(OutputStream out, long offset, long size) throws IOException {
|
||||||
// make sure there is no out-of-bounds read
|
// make sure there is no out-of-bounds read
|
||||||
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();
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -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;
|
||||||
|
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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()){
|
||||||
|
@ -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 {
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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);
|
||||||
|
@ -31,7 +31,7 @@ public class AESDecryption {
|
|||||||
init();
|
init();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected void init() {
|
protected final void init() {
|
||||||
init(getAESKey(),getIV());
|
init(getAESKey(),getIV());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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.");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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);
|
||||||
|
@ -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(){
|
||||||
if(defaultInstance == null){
|
synchronized (defaultInstance) {
|
||||||
defaultInstance = new NUSDownloadService();
|
if(defaultInstance == null){
|
||||||
defaultInstance.setURL_BASE(Settings.URL_BASE);
|
defaultInstance = new NUSDownloadService(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;
|
||||||
|
Loading…
Reference in New Issue
Block a user