diff --git a/.classpath b/.classpath
index 7026f46..4f8e155 100644
--- a/.classpath
+++ b/.classpath
@@ -1,7 +1,18 @@
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -12,5 +23,11 @@
+
+
+
+
+
+
diff --git a/pom.xml b/pom.xml
index 826c20d..eeade7b 100644
--- a/pom.xml
+++ b/pom.xml
@@ -11,7 +11,7 @@
- src
+ src/main/java
maven-compiler-plugin
diff --git a/src/main/java/de/mas/wiiu/jnus/ExtractionService.java b/src/main/java/de/mas/wiiu/jnus/ExtractionService.java
index 0fb5d3f..785f826 100644
--- a/src/main/java/de/mas/wiiu/jnus/ExtractionService.java
+++ b/src/main/java/de/mas/wiiu/jnus/ExtractionService.java
@@ -19,6 +19,7 @@ package de.mas.wiiu.jnus;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -52,14 +53,14 @@ public final class ExtractionService {
}
private ExtractionService(NUSTitle nustitle) {
- if (nustitle.getDataProvider() instanceof Parallelizable) {
+ if (nustitle.getDataProcessor().getDataProvider() instanceof Parallelizable) {
parallelizable = true;
}
this.NUSTitle = nustitle;
}
private NUSDataProvider getDataProvider() {
- return getNUSTitle().getDataProvider();
+ return getNUSTitle().getDataProcessor().getDataProvider();
}
public void extractAllEncrpytedContentFileHashes(String outputFolder) throws IOException {
@@ -95,7 +96,7 @@ public final class ExtractionService {
}
}
- public void extractEncryptedContentFilesTo(List list, String outputFolder, boolean withHashes) throws IOException {
+ public void extractEncryptedContentFilesTo(Collection list, String outputFolder, boolean withHashes) throws IOException {
Utils.createDir(outputFolder);
if (parallelizable && Settings.ALLOW_PARALLELISATION) {
try {
diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitle.java b/src/main/java/de/mas/wiiu/jnus/NUSTitle.java
index 26b9c2b..2b23566 100644
--- a/src/main/java/de/mas/wiiu/jnus/NUSTitle.java
+++ b/src/main/java/de/mas/wiiu/jnus/NUSTitle.java
@@ -17,7 +17,6 @@
package de.mas.wiiu.jnus;
import java.io.IOException;
-import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
@@ -27,10 +26,9 @@ import de.mas.wiiu.jnus.entities.TMD;
import de.mas.wiiu.jnus.entities.Ticket;
import de.mas.wiiu.jnus.entities.fst.FST;
import de.mas.wiiu.jnus.entities.fst.FSTEntry;
-import de.mas.wiiu.jnus.interfaces.NUSDataProvider;
+import de.mas.wiiu.jnus.interfaces.NUSDataProcessor;
import de.mas.wiiu.jnus.utils.FSTUtils;
import lombok.Getter;
-import lombok.NonNull;
import lombok.Setter;
public class NUSTitle {
@@ -39,12 +37,18 @@ public class NUSTitle {
@Getter private final TMD TMD;
- @Getter private final NUSDataProvider dataProvider;
+ @Getter private final NUSDataProcessor dataProcessor;
- public NUSTitle(@NonNull NUSDataProvider dataProvider) throws ParseException, IOException {
- byte[] tmdData = dataProvider.getRawTMD().orElseThrow(() -> new ParseException("No TMD data found", 0));
- this.TMD = de.mas.wiiu.jnus.entities.TMD.parseTMD(tmdData);
- this.dataProvider = dataProvider;
+ private NUSTitle(TMD tmd, NUSDataProcessor dataProcessor) {
+ this.TMD = tmd;
+ this.dataProcessor = dataProcessor;
+ }
+
+ public static NUSTitle create(TMD tmd, NUSDataProcessor dataProcessor, Optional ticket, Optional fst) {
+ NUSTitle result = new NUSTitle(tmd, dataProcessor);
+ result.setTicket(ticket);
+ result.setFST(fst);
+ return result;
}
public Stream getAllFSTEntriesAsStream() {
@@ -66,13 +70,13 @@ public class NUSTitle {
}
public void cleanup() throws IOException {
- if (getDataProvider() != null) {
- getDataProvider().cleanup();
+ if (getDataProcessor() != null && getDataProcessor().getDataProvider() != null) {
+ getDataProcessor().getDataProvider().cleanup();
}
}
@Override
public String toString() {
- return "NUSTitle [dataProvider=" + dataProvider + "]";
+ return "NUSTitle [dataProcessor=" + dataProcessor + "]";
}
}
diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoader.java b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoader.java
index 9bbacf6..defa7de 100644
--- a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoader.java
+++ b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoader.java
@@ -1,5 +1,5 @@
/****************************************************************************
- * Copyright (C) 2016-2019 Maschell
+ * Copyright (C) 2016-2020 Maschell
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
@@ -16,54 +16,78 @@
****************************************************************************/
package de.mas.wiiu.jnus;
+import java.io.FileNotFoundException;
import java.io.IOException;
-import java.io.InputStream;
+import java.security.NoSuchProviderException;
import java.text.ParseException;
import java.util.Optional;
import java.util.function.Supplier;
+import de.mas.wiiu.jnus.entities.TMD;
import de.mas.wiiu.jnus.entities.Ticket;
import de.mas.wiiu.jnus.entities.content.Content;
import de.mas.wiiu.jnus.entities.fst.FST;
import de.mas.wiiu.jnus.entities.fst.FSTEntry;
import de.mas.wiiu.jnus.implementations.FSTDataProviderNUSTitle;
+import de.mas.wiiu.jnus.interfaces.ContentDecryptor;
+import de.mas.wiiu.jnus.interfaces.ContentEncryptor;
import de.mas.wiiu.jnus.interfaces.FSTDataProvider;
+import de.mas.wiiu.jnus.interfaces.NUSDataProcessor;
import de.mas.wiiu.jnus.interfaces.NUSDataProvider;
-import de.mas.wiiu.jnus.utils.StreamUtils;
-import de.mas.wiiu.jnus.utils.cryptography.AESDecryption;
+import de.mas.wiiu.jnus.interfaces.TriFunction;
+import de.mas.wiiu.jnus.utils.cryptography.NUSDecryption;
+import de.mas.wiiu.jnus.utils.cryptography.NUSEncryption;
public class NUSTitleLoader {
private NUSTitleLoader() {
// should be empty
}
- public static NUSTitle loadNusTitle(NUSTitleConfig config, Supplier dataProviderFunction) throws IOException, ParseException {
+ public static NUSTitle loadNusTitle(NUSTitleConfig config, Supplier dataProviderFunction,
+ TriFunction, Optional, NUSDataProcessor> dataProcessorFunction)
+ throws IOException, ParseException {
NUSDataProvider dataProvider = dataProviderFunction.get();
- NUSTitle result = new NUSTitle(dataProvider);
+ TMD tmd = TMD.parseTMD(dataProvider.getRawTMD().orElseThrow(() -> new FileNotFoundException("No TMD data found")));
if (config.isNoDecryption()) {
+ NUSTitle result = NUSTitle.create(tmd, dataProcessorFunction.apply(dataProvider, Optional.empty(), Optional.empty()), Optional.empty(),
+ Optional.empty());
return result;
}
- Ticket ticket = null;
+ Optional ticket = Optional.empty();
+ Optional decryption = Optional.empty();
+ Optional encryption = Optional.empty();
if (config.isTicketNeeded()) {
- ticket = config.getTicket();
- if (ticket == null) {
+ Ticket ticketT = config.getTicket();
+ if (ticketT == null) {
Optional ticketOpt = dataProvider.getRawTicket();
if (ticketOpt.isPresent()) {
- ticket = Ticket.parseTicket(ticketOpt.get(), config.getCommonKey());
+ ticketT = Ticket.parseTicket(ticketOpt.get(), config.getCommonKey());
}
}
- if (ticket == null) {
- new ParseException("Failed to get ticket data", 0);
+ if (ticketT == null) {
+ throw new ParseException("Failed to get ticket data", 0);
+ }
+
+ ticket = Optional.of(ticketT);
+
+ decryption = Optional.of(new NUSDecryption(ticketT));
+ try {
+ encryption = Optional.of(new NUSEncryption(ticketT));
+ } catch (NoSuchProviderException e) {
+ throw new IOException(e);
}
- result.setTicket(Optional.of(ticket));
}
+ NUSDataProcessor dpp = dataProcessorFunction.apply(dataProvider, decryption, encryption);
+
// If we have just content, we don't have a FST.
- if (result.getTMD().getAllContents().size() == 1) {
+ if (tmd.getAllContents().size() == 1) {
// The only way to check if the key is right, is by trying to decrypt the whole thing.
+ NUSTitle result = NUSTitle.create(tmd, dpp, ticket, Optional.empty());
+
FSTDataProvider dp = new FSTDataProviderNUSTitle(result);
for (FSTEntry children : dp.getRoot().getChildren()) {
dp.readFile(children);
@@ -72,27 +96,16 @@ public class NUSTitleLoader {
return result;
}
// If we have more than one content, the index 0 is the FST.
- Content fstContent = result.getTMD().getContentByIndex(0);
-
- InputStream fstContentEncryptedStream = dataProvider.readContentAsStream(fstContent);
-
- byte[] fstBytes = StreamUtils.getBytesFromStream(fstContentEncryptedStream, (int) fstContent.getEncryptedFileSize());
-
- if (fstContent.isEncrypted()) {
- AESDecryption aesDecryption = new AESDecryption(ticket.getDecryptedKey(), new byte[0x10]);
- if (fstBytes.length % 0x10 != 0) {
- throw new IOException("FST length is not align to 16");
- }
- fstBytes = aesDecryption.decrypt(fstBytes);
- }
+ Content fstContent = tmd.getContentByIndex(0);
+ byte[] fstBytes = dpp.readPlainDecryptedContent(fstContent, true);
FST fst = FST.parseFST(fstBytes);
- result.setFST(Optional.of(fst));
// The dataprovider may need the FST to calculate the offset of a content
// on the partition.
dataProvider.setFST(fst);
- return result;
+ return NUSTitle.create(tmd, dpp, ticket, Optional.of(fst));
}
+
}
diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderFST.java b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderFST.java
index 80c6d91..4da0616 100644
--- a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderFST.java
+++ b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderFST.java
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.text.ParseException;
import de.mas.wiiu.jnus.entities.fst.FSTEntry;
+import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor;
import de.mas.wiiu.jnus.implementations.NUSDataProviderFST;
import de.mas.wiiu.jnus.interfaces.FSTDataProvider;
@@ -36,7 +37,7 @@ public final class NUSTitleLoaderFST {
NUSTitleConfig config = new NUSTitleConfig();
config.setCommonKey(commonKey);
- return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderFST(dataProvider, base));
+ return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderFST(dataProvider, base), (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd));
}
}
diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderLocal.java b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderLocal.java
index 0653c58..1acd502 100644
--- a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderLocal.java
+++ b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderLocal.java
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.text.ParseException;
import de.mas.wiiu.jnus.entities.Ticket;
+import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor;
import de.mas.wiiu.jnus.implementations.NUSDataProviderLocal;
public final class NUSTitleLoaderLocal {
@@ -43,7 +44,7 @@ public final class NUSTitleLoaderLocal {
throw new IOException("Ticket was null and no commonKey was given");
}
- return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderLocal(inputPath));
+ return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderLocal(inputPath), (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd));
}
}
diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderLocalBackup.java b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderLocalBackup.java
index 95a30cf..c57906a 100644
--- a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderLocalBackup.java
+++ b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderLocalBackup.java
@@ -18,6 +18,7 @@
package de.mas.wiiu.jnus;
import de.mas.wiiu.jnus.entities.Ticket;
+import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor;
import de.mas.wiiu.jnus.implementations.NUSDataProviderLocalBackup;
public final class NUSTitleLoaderLocalBackup {
@@ -35,7 +36,8 @@ public final class NUSTitleLoaderLocalBackup {
config.setNoDecryption(true);
}
- return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderLocalBackup(inputPath, titleVersion));
+ return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderLocalBackup(inputPath, titleVersion),
+ (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd));
}
}
diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderRemote.java b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderRemote.java
index ba7516e..0c3e03f 100644
--- a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderRemote.java
+++ b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderRemote.java
@@ -20,6 +20,7 @@ import java.io.IOException;
import java.text.ParseException;
import de.mas.wiiu.jnus.entities.Ticket;
+import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor;
import de.mas.wiiu.jnus.implementations.NUSDataProviderRemote;
public final class NUSTitleLoaderRemote {
@@ -53,7 +54,7 @@ public final class NUSTitleLoaderRemote {
throw new IOException("Ticket was null and no commonKey was given");
}
- return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderRemote(version, titleID));
+ return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderRemote(version, titleID), (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd));
}
}
diff --git a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderWoomy.java b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderWoomy.java
index d66b6b9..f8b2565 100644
--- a/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderWoomy.java
+++ b/src/main/java/de/mas/wiiu/jnus/NUSTitleLoaderWoomy.java
@@ -24,6 +24,7 @@ import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
+import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor;
import de.mas.wiiu.jnus.implementations.NUSDataProviderWoomy;
import de.mas.wiiu.jnus.implementations.woomy.WoomyInfo;
import de.mas.wiiu.jnus.implementations.woomy.WoomyParser;
@@ -38,10 +39,10 @@ public final class NUSTitleLoaderWoomy {
NUSTitleConfig config = new NUSTitleConfig();
config.setTicketNeeded(false);
-
+
WoomyInfo woomyInfo = WoomyParser.createWoomyInfo(new File(inputFile));
- return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWoomy(woomyInfo));
+ return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWoomy(woomyInfo), (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd));
}
}
diff --git a/src/main/java/de/mas/wiiu/jnus/WUDLoader.java b/src/main/java/de/mas/wiiu/jnus/WUDLoader.java
index b51c75c..036d66a 100644
--- a/src/main/java/de/mas/wiiu/jnus/WUDLoader.java
+++ b/src/main/java/de/mas/wiiu/jnus/WUDLoader.java
@@ -24,6 +24,7 @@ import java.text.ParseException;
import java.util.ArrayList;
import java.util.List;
+import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor;
import de.mas.wiiu.jnus.implementations.FSTDataProviderNUSTitle;
import de.mas.wiiu.jnus.implementations.FSTDataProviderWUDDataPartition;
import de.mas.wiiu.jnus.implementations.NUSDataProviderWUD;
@@ -95,7 +96,7 @@ public final class WUDLoader {
final NUSTitleConfig config = new NUSTitleConfig();
config.setCommonKey(commonKey);
gamePartition.getTmd();
- return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWUD(gamePartition, discReader));
+ return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWUD(gamePartition, discReader), (dp, cd, en) -> new DefaultNUSDataProcessor(dp, cd));
}
public static List getPartitonsAsFSTDataProvider(@NonNull WUDInfo wudInfo, byte[] commonKey) throws IOException, ParseException {
diff --git a/src/main/java/de/mas/wiiu/jnus/WumadLoader.java b/src/main/java/de/mas/wiiu/jnus/WumadLoader.java
index fc43e4c..d65bc27 100644
--- a/src/main/java/de/mas/wiiu/jnus/WumadLoader.java
+++ b/src/main/java/de/mas/wiiu/jnus/WumadLoader.java
@@ -11,6 +11,7 @@ import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
+import de.mas.wiiu.jnus.implementations.DefaultNUSDataProcessor;
import de.mas.wiiu.jnus.implementations.FSTDataProviderNUSTitle;
import de.mas.wiiu.jnus.implementations.FSTDataProviderWumadDataPartition;
import de.mas.wiiu.jnus.implementations.NUSDataProviderWumad;
@@ -40,7 +41,7 @@ public class WumadLoader {
final NUSTitleConfig config = new NUSTitleConfig();
config.setCommonKey(commonKey);
gamePartition.getTmd();
- return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWumad(gamePartition, wudmadFile));
+ return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWumad(gamePartition, wudmadFile), (dp, cd, ce) -> new DefaultNUSDataProcessor(dp, cd));
}
public static List getPartitonsAsFSTDataProvider(@NonNull WumadInfo wumadInfo, byte[] commonKey) throws IOException, ParseException {
@@ -57,7 +58,6 @@ public class WumadLoader {
}
return result;
-
}
}
diff --git a/src/main/java/de/mas/wiiu/jnus/entities/content/Content.java b/src/main/java/de/mas/wiiu/jnus/entities/content/Content.java
index ac2a645..9fdc5fb 100644
--- a/src/main/java/de/mas/wiiu/jnus/entities/content/Content.java
+++ b/src/main/java/de/mas/wiiu/jnus/entities/content/Content.java
@@ -37,6 +37,7 @@ public class Content implements Comparable {
public static final short CONTENT_FLAG_UNKWN1 = 0x4000;
public static final short CONTENT_HASHED = 0x0002;
public static final short CONTENT_ENCRYPTED = 0x0001;
+
public static final int CONTENT_SIZE = 0x30;
@Getter private final int ID;
@@ -127,7 +128,7 @@ public class Content implements Comparable {
*/
public long getDecryptedFileSize() {
if (isHashed()) {
- return getEncryptedFileSize() / 0x10000 * 0xFC00;
+ return (getEncryptedFileSize() / 0x10000) * 0xFC00;
} else {
return getEncryptedFileSize();
}
diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/FST.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/FST.java
index 32293ab..2f1d8c0 100644
--- a/src/main/java/de/mas/wiiu/jnus/entities/fst/FST.java
+++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/FST.java
@@ -61,7 +61,7 @@ public final class FST {
int contentCount = ByteUtils.getIntFromBytes(fstData, 0x08);
FST result = new FST(sectorSize, contentCount);
-
+
int contentfst_offset = 0x20;
int contentfst_size = 0x20 * contentCount;
@@ -71,8 +71,8 @@ public final class FST {
int fst_size = fileCount * 0x10;
int nameOff = fst_offset + fst_size;
- int nameSize = nameOff + 1;
-
+ int nameSize = fstData.length - nameOff;
+
// Get list with null-terminated Strings. Ends with \0\0.
for (int i = nameOff; i < fstData.length - 1; i++) {
if (fstData[i] == 0 && fstData[i + 1] == 0) {
@@ -88,7 +88,7 @@ public final class FST {
byte fstSection[] = Arrays.copyOfRange(fstData, fst_offset, fst_offset + fst_size);
byte nameSection[] = Arrays.copyOfRange(fstData, nameOff, nameOff + nameSize);
-
+
FSTEntry root = result.getRoot();
FSTService.parseFST(root, fstSection, nameSection, sectorSize);
diff --git a/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTService.java b/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTService.java
index daa6cc3..52de2f1 100644
--- a/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTService.java
+++ b/src/main/java/de/mas/wiiu/jnus/entities/fst/FSTService.java
@@ -104,7 +104,6 @@ public final class FSTService {
while ((nameOffset + j) < namesSection.length && namesSection[nameOffset + j] != 0) {
j++;
}
-
return (new String(Arrays.copyOfRange(namesSection, nameOffset, nameOffset + j)));
}
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/DefaultNUSDataProcessor.java b/src/main/java/de/mas/wiiu/jnus/implementations/DefaultNUSDataProcessor.java
new file mode 100644
index 0000000..436119f
--- /dev/null
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/DefaultNUSDataProcessor.java
@@ -0,0 +1,352 @@
+package de.mas.wiiu.jnus.implementations;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.io.PipedOutputStream;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
+import java.util.Arrays;
+import java.util.Optional;
+
+import de.mas.wiiu.jnus.entities.content.Content;
+import de.mas.wiiu.jnus.interfaces.ContentDecryptor;
+import de.mas.wiiu.jnus.interfaces.NUSDataProcessor;
+import de.mas.wiiu.jnus.interfaces.NUSDataProvider;
+import de.mas.wiiu.jnus.utils.ByteArrayBuffer;
+import de.mas.wiiu.jnus.utils.CheckSumWrongException;
+import de.mas.wiiu.jnus.utils.HashUtil;
+import de.mas.wiiu.jnus.utils.PipedInputStreamWithException;
+import de.mas.wiiu.jnus.utils.StreamUtils;
+import de.mas.wiiu.jnus.utils.Utils;
+import lombok.extern.java.Log;
+
+@Log
+public class DefaultNUSDataProcessor implements NUSDataProcessor {
+ protected final NUSDataProvider dataProvider;
+ private final Optional decryptor;
+
+ public DefaultNUSDataProcessor(NUSDataProvider dataProvider, Optional decryptor) {
+ this.dataProvider = dataProvider;
+ this.decryptor = decryptor;
+ }
+
+ @Override
+ public InputStream readContentAsStream(Content c, long offset, long size) throws IOException {
+ return dataProvider.readRawContentAsStream(c, offset, size);
+ }
+
+ @Override
+ public InputStream readDecryptedContentAsStream(Content c, long offset, long size) throws IOException {
+ if (!c.isEncrypted()) {
+ return dataProvider.readRawContentAsStream(c, offset, size);
+ }
+
+ PipedOutputStream out = new PipedOutputStream();
+ PipedInputStreamWithException in = new PipedInputStreamWithException(out, 0x10000);
+
+ new Thread(() -> {
+ try {
+ readDecryptedContentToStream(out, c, offset, size);
+ in.throwException(null);
+ } catch (Exception e) {
+ in.throwException(e);
+ try {
+ out.close();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
+ }
+ }).start();
+
+ return in;
+ }
+
+ @Override
+ public long readDecryptedContentToStream(OutputStream out, Content c, long offset, long size) throws IOException {
+ if (!c.isEncrypted()) {
+ InputStream in = dataProvider.readRawContentAsStream(c, offset, size);
+ return StreamUtils.saveInputStreamToOutputStream(in, out, size);
+ }
+
+ if (!decryptor.isPresent()) {
+ throw new IOException("Decryptor was null. Maybe the ticket is missing?");
+ }
+
+ if (c.isHashed()) {
+ long stream_offset = (offset / 0x10000) * 0x10000;
+
+ InputStream in = dataProvider.readRawContentAsStream(c, stream_offset, size + offset - stream_offset);
+
+ return decryptor.get().readDecryptedContentToStreamHashed(in, out, offset, size, offset - stream_offset, dataProvider.getContentH3Hash(c).get());
+ } else {
+ byte[] IV = new byte[0x10];
+ IV[0] = (byte) ((c.getIndex() >> 8) & 0xFF);
+ IV[1] = (byte) (c.getIndex() & 0xFF);
+
+ long streamOffset = (offset / 16) * 16;
+ long streamFilesize = size;
+
+ // if we have an offset we can't calculate the hash anymore
+ // we need a new IV
+ if (streamOffset > 15) {
+ streamFilesize = size;
+
+ streamOffset -= 16;
+ streamFilesize += 16;
+
+ // We need to get the current IV as soon as we get the InputStream.
+ IV = null;
+ } else if ((offset > 0 && offset < 16) && size < 16) {
+ streamFilesize = 16;
+ }
+
+ long curStreamOffset = streamOffset;
+
+ InputStream in = dataProvider.readRawContentAsStream(c, streamOffset, streamFilesize);
+ if (IV == null) {
+ // If we read with an offset > 16 we need the previous 16 bytes because they are the IV.
+ // The input stream has been prepared to start 16 bytes earlier on this case.
+ int toRead = 16;
+ byte[] data = new byte[toRead];
+ int readTotal = 0;
+ while (readTotal < toRead) {
+ int res = in.read(data, readTotal, toRead - readTotal);
+ if (res < 0) {
+ StreamUtils.closeAll(in, out);
+ return -1;
+ }
+ readTotal += res;
+ }
+ IV = Arrays.copyOfRange(data, 0, toRead);
+ curStreamOffset = streamOffset + 16;
+ }
+ long res = decryptor.get().readDecryptedContentToStreamNonHashed(in, out, curStreamOffset, size, offset - curStreamOffset, IV);
+
+ return res;
+ }
+ }
+
+ @Override
+ public InputStream readPlainDecryptedContentAsStream(Content c, long offset, long size, boolean forceCheckHash) throws IOException {
+ PipedOutputStream out = new PipedOutputStream();
+ PipedInputStreamWithException in = new PipedInputStreamWithException(out, 0x10000);
+
+ new Thread(() -> {
+ try {
+ readPlainDecryptedContentToStream(out, c, offset, size, forceCheckHash);
+ in.throwException(null);
+ } catch (Exception e) {
+ in.throwException(e);
+ try {
+ in.close();
+ } catch (IOException e1) {
+ // TODO Auto-generated catch block
+ e1.printStackTrace();
+ }
+ }
+ }).start();
+
+ return in;
+ }
+
+ @Override
+ public long readPlainDecryptedContentToStream(OutputStream out, Content c, long offset, long size, boolean forceCheckHash) throws IOException {
+ if (c.isHashed()) {
+ long payloadOffset = offset;
+ long streamOffset = payloadOffset;
+ long streamFilesize = 0;
+
+ streamOffset = (payloadOffset / 0xFC00) * 0x10000;
+ long offsetInBlock = payloadOffset - ((streamOffset / 0x10000) * 0xFC00);
+ if (offsetInBlock + size < 0xFC00) {
+ streamFilesize = 0x10000L;
+ } else {
+ long curVal = 0x10000;
+ long missing = (size - (0xFC00 - offsetInBlock));
+
+ curVal += (missing / 0xFC00) * 0x10000;
+
+ if (missing % 0xFC00 > 0) {
+ curVal += 0x10000;
+ }
+
+ streamFilesize = curVal;
+ }
+
+ InputStream in = readDecryptedContentAsStream(c, streamOffset, streamFilesize);
+
+ try {
+ return processHashedStream(in, out, (int) (offset / 0xFC00), size, offsetInBlock, dataProvider.getContentH3Hash(c).get());
+ } catch (NoSuchAlgorithmException | CheckSumWrongException e) {
+ throw new IOException(e);
+ }
+ } else {
+ InputStream in = readDecryptedContentAsStream(c, offset, size);
+
+ byte[] hash = null;
+ if (forceCheckHash) {
+ hash = c.getSHA2Hash();
+ }
+
+ try {
+ return processNonHashedStream(in, out, offset, size, hash, c.getEncryptedFileSize());
+ } catch (CheckSumWrongException e) {
+ throw new IOException(e);
+ }
+ }
+ }
+
+ private long processNonHashedStream(InputStream inputStream, OutputStream outputStream, long payloadOffset, long filesize, byte[] hash,
+ long expectedSizeForHash) throws IOException, CheckSumWrongException {
+ MessageDigest sha1 = null;
+ MessageDigest sha1fallback = null;
+
+ if (hash != null) {
+ try {
+ sha1 = MessageDigest.getInstance("SHA1");
+ sha1fallback = MessageDigest.getInstance("SHA1");
+ } catch (NoSuchAlgorithmException e) {
+ e.printStackTrace();
+ }
+ }
+
+ int BLOCKSIZE = 0x8000;
+
+ byte[] blockBuffer = new byte[BLOCKSIZE];
+
+ int inBlockBuffer;
+ long written = 0;
+ long writtenFallback = 0;
+
+ try {
+ ByteArrayBuffer overflow = new ByteArrayBuffer(BLOCKSIZE);
+
+ // We can only decrypt multiples of 16. So we need to align it.
+ long toRead = Utils.align(filesize, 16);
+
+ do {
+ int curReadSize = BLOCKSIZE;
+ if (toRead < BLOCKSIZE) {
+ curReadSize = (int) toRead;
+ }
+ inBlockBuffer = StreamUtils.getChunkFromStream(inputStream, blockBuffer, overflow, curReadSize);
+ if (inBlockBuffer <= 0) {
+ break;
+ }
+
+ byte[] output = blockBuffer;
+
+ int toWrite = inBlockBuffer;
+
+ if ((written + inBlockBuffer) > filesize) {
+ toWrite = (int) (filesize - written);
+ }
+
+ written += toWrite;
+ toRead -= toWrite;
+
+ outputStream.write(output, 0, toWrite);
+
+ if (sha1 != null && sha1fallback != null) {
+ sha1.update(output, 0, toWrite);
+
+ // In some cases it's using the hash of the whole .app file instead of the part
+ // that's been actually used.
+ long toFallback = inBlockBuffer;
+ if (writtenFallback + toFallback > expectedSizeForHash) {
+ toFallback = expectedSizeForHash - writtenFallback;
+ }
+ sha1fallback.update(output, 0, (int) toFallback);
+ writtenFallback += toFallback;
+ }
+
+ if (written >= filesize && hash == null) {
+ break;
+ }
+ } while (inBlockBuffer == BLOCKSIZE);
+
+ if (sha1 != null && sha1fallback != null) {
+ long missingInHash = expectedSizeForHash - writtenFallback;
+ if (missingInHash > 0) {
+ sha1fallback.update(new byte[(int) missingInHash]);
+ }
+
+ byte[] calculated_hash1 = sha1.digest();
+ byte[] calculated_hash2 = sha1fallback.digest();
+ byte[] expected_hash = hash;
+ if (!Arrays.equals(calculated_hash1, expected_hash) && !Arrays.equals(calculated_hash2, expected_hash)) {
+ throw new CheckSumWrongException("hash checksum failed ", calculated_hash1, expected_hash);
+ } else {
+ log.fine("Hash DOES match saves output stream.");
+ }
+
+ }
+ } finally {
+ StreamUtils.closeAll(inputStream, outputStream);
+ }
+ return written;
+ }
+
+ private long processHashedStream(InputStream inputStream, OutputStream outputStream, int block, long filesize, long payloadOffset, byte[] h3_hashes)
+ throws IOException, NoSuchAlgorithmException, CheckSumWrongException {
+ int BLOCKSIZE = 0x10000;
+ int HASHBLOCKSIZE = 0xFC00;
+ int HASHSIZE = BLOCKSIZE - HASHBLOCKSIZE;
+
+ long curBlock = block;
+
+ byte[] blockBuffer = new byte[BLOCKSIZE];
+ ByteArrayBuffer overflow = new ByteArrayBuffer(BLOCKSIZE);
+ long written = 0;
+ int inBlockBuffer = 0;
+
+ long writeOffset = payloadOffset;
+
+ try {
+ do {
+ inBlockBuffer = StreamUtils.getChunkFromStream(inputStream, blockBuffer, overflow, BLOCKSIZE);
+ if (inBlockBuffer < 0) {
+ break;
+ }
+
+ if (inBlockBuffer != BLOCKSIZE) {
+ throw new IOException("buffer was not " + BLOCKSIZE + " bytes");
+ }
+
+ byte[] hashes = null;
+ byte[] output = null;
+
+ hashes = Arrays.copyOfRange(blockBuffer, 0, HASHSIZE);
+ output = Arrays.copyOfRange(blockBuffer, HASHSIZE, BLOCKSIZE);
+
+ HashUtil.checkFileChunkHashes(hashes, h3_hashes, output, (int) curBlock);
+
+ try {
+ long writeLength = Math.min((output.length - writeOffset), (filesize - written));
+ outputStream.write(output, (int) writeOffset, (int) writeLength);
+ written += writeLength;
+ } catch (IOException e) {
+ if (e.getMessage().equals("Pipe closed")) {
+ break;
+ }
+ e.printStackTrace();
+ throw e;
+ }
+ writeOffset = 0;
+
+ curBlock++;
+ } while (written < filesize);
+ log.finest("Decryption okay");
+ } finally {
+ StreamUtils.closeAll(inputStream, outputStream);
+ }
+ return written > 0 ? written : -1;
+ }
+
+ @Override
+ public NUSDataProvider getDataProvider() {
+ return dataProvider;
+ }
+
+}
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderNUSTitle.java b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderNUSTitle.java
index cca3f21..b7eecf2 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderNUSTitle.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderNUSTitle.java
@@ -17,10 +17,7 @@
package de.mas.wiiu.jnus.implementations;
import java.io.IOException;
-import java.io.InputStream;
import java.io.OutputStream;
-import java.security.NoSuchAlgorithmException;
-import java.util.Arrays;
import java.util.stream.Collectors;
import de.mas.wiiu.jnus.NUSTitle;
@@ -28,22 +25,20 @@ import de.mas.wiiu.jnus.entities.content.Content;
import de.mas.wiiu.jnus.entities.fst.FSTEntry;
import de.mas.wiiu.jnus.interfaces.FSTDataProvider;
import de.mas.wiiu.jnus.interfaces.HasNUSTitle;
-import de.mas.wiiu.jnus.interfaces.NUSDataProvider;
-import de.mas.wiiu.jnus.utils.CheckSumWrongException;
-import de.mas.wiiu.jnus.utils.StreamUtils;
-import de.mas.wiiu.jnus.utils.Utils;
-import de.mas.wiiu.jnus.utils.cryptography.NUSDecryption;
+import de.mas.wiiu.jnus.interfaces.NUSDataProcessor;
import lombok.Getter;
import lombok.Setter;
import lombok.extern.java.Log;
@Log
public class FSTDataProviderNUSTitle implements FSTDataProvider, HasNUSTitle {
+ private final NUSDataProcessor dataProcessor;
private final NUSTitle title;
private final FSTEntry rootEntry;
@Getter @Setter private String name;
public FSTDataProviderNUSTitle(NUSTitle title) throws IOException {
+ this.dataProcessor = title.getDataProcessor();
this.title = title;
this.name = String.format("%016X", title.getTMD().getTitleID());
@@ -59,157 +54,25 @@ public class FSTDataProviderNUSTitle implements FSTDataProvider, HasNUSTitle {
throw new IOException("No FST root entry was found");
}
}
-
+
@Override
public FSTEntry getRoot() {
return rootEntry;
}
@Override
- public boolean readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException {
- try {
- return decryptFSTEntryToStream(entry, out, offset, size);
- } catch (CheckSumWrongException | NoSuchAlgorithmException e) {
- throw new IOException(e);
- }
- }
-
- private boolean decryptFSTEntryToStreamHashed(FSTEntry entry, OutputStream outputStream, long offset, long size)
- throws IOException, CheckSumWrongException, NoSuchAlgorithmException {
- Content c = title.getTMD().getContentByIndex(entry.getContentIndex());
-
- long payloadOffset = entry.getFileOffset() + offset;
- long streamOffset = payloadOffset;
- long streamFilesize = 0;
-
- streamOffset = (payloadOffset / 0xFC00) * 0x10000;
- long offsetInBlock = payloadOffset - ((streamOffset / 0x10000) * 0xFC00);
- if (offsetInBlock + size < 0xFC00) {
- streamFilesize = 0x10000L;
- } else {
- long curVal = 0x10000;
- long missing = (size - (0xFC00 - offsetInBlock));
-
- curVal += (missing / 0xFC00) * 0x10000;
-
- if (missing % 0xFC00 > 0) {
- curVal += 0x10000;
- }
-
- streamFilesize = curVal;
- }
-
- NUSDataProvider dataProvider = title.getDataProvider();
- InputStream in = dataProvider.readContentAsStream(c, streamOffset, streamFilesize);
-
- NUSDecryption nusdecryption = new NUSDecryption(title.getTicket().get());
-
- return nusdecryption.decryptStreamsHashed(in, outputStream, payloadOffset, size, dataProvider.getContentH3Hash(c));
- }
-
- private boolean decryptFSTEntryToStreamNonHashed(FSTEntry entry, OutputStream outputStream, long offset, long size)
- throws IOException, CheckSumWrongException, NoSuchAlgorithmException {
-
- Content c = title.getTMD().getContentByIndex(entry.getContentIndex());
-
- byte[] IV = new byte[0x10];
- IV[0] = (byte) ((c.getIndex() >> 8) & 0xFF);
- IV[1] = (byte) (c.getIndex() & 0xFF);
-
- long payloadOffset = entry.getFileOffset() + offset;
- long streamOffset = payloadOffset;
- long streamFilesize = c.getEncryptedFileSize();
-
- // if we have an offset we can't calculate the hash anymore
- // we need a new IV
- if (streamOffset > 0) {
- streamFilesize = size;
-
- streamOffset -= 16;
- streamFilesize += 16;
-
- // We need to get the current IV as soon as we get the InputStream.
- IV = null;
- }
-
- NUSDataProvider dataProvider = title.getDataProvider();
- InputStream in = dataProvider.readContentAsStream(c, streamOffset, streamFilesize);
-
- if (IV == null) {
- // If we read with an offset > 16 we need the previous 16 bytes because they are the IV.
- // The input stream has been prepared to start 16 bytes earlier on this case.
- int toRead = 16;
- byte[] data = new byte[toRead];
- int readTotal = 0;
- while (readTotal < toRead) {
- int res = in.read(data, readTotal, toRead - readTotal);
- if (res < 0) {
- // This should NEVER happen.
- throw new IOException();
- }
- readTotal += res;
- }
- IV = Arrays.copyOfRange(data, 0, toRead);
- }
- NUSDecryption nusdecryption = new NUSDecryption(title.getTicket().get());
-
- return nusdecryption.decryptStreamsNonHashed(in, outputStream, payloadOffset, size, c, IV, size != entry.getFileSize());
- }
-
- private boolean decryptFSTEntryToStream(FSTEntry entry, OutputStream outputStream, long offset, long size)
- throws IOException, CheckSumWrongException, NoSuchAlgorithmException {
+ public long readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException {
if (entry.isNotInPackage()) {
if (entry.isNotInPackage()) {
log.info("Decryption not possible because the FSTEntry is not in this package");
}
- outputStream.close();
- return false;
- }
- if (offset % 16 != 0) {
- throw new IOException("The offset for decryption need to be aligned to 16");
+ out.close();
+ return -1;
}
Content c = title.getTMD().getContentByIndex(entry.getContentIndex());
- try {
- if (c.isEncrypted()) {
- if (!title.getTicket().isPresent()) {
- log.info("Decryption not possible because no ticket was set.");
- outputStream.close();
- return false;
- }
- if (c.isHashed()) {
- return decryptFSTEntryToStreamHashed(entry, outputStream, offset, size);
- } else {
- return decryptFSTEntryToStreamNonHashed(entry, outputStream, offset, size);
- }
- } else {
- InputStream in = title.getDataProvider().readContentAsStream(c, offset, size);
-
- try {
- StreamUtils.saveInputStreamToOutputStreamWithHash(in, outputStream, size, c.getSHA2Hash(), c.getEncryptedFileSize(),
- size != entry.getFileSize());
- return true;
- } finally {
- StreamUtils.closeAll(in, outputStream);
- }
- }
- } catch (CheckSumWrongException e) {
- if (c.isUNKNWNFlag1Set()) {
- log.info("Hash doesn't match. But file is optional. Don't worry.");
- } else {
- StringBuilder sb = new StringBuilder();
- sb.append("Hash doesn't match").append(System.lineSeparator());
- sb.append("Detailed info:").append(System.lineSeparator());
- sb.append(entry).append(System.lineSeparator());
- sb.append(String.format("%016x", title.getTMD().getTitleID()));
- sb.append(e.getMessage() + " Calculated Hash: " + Utils.ByteArrayToString(e.getGivenHash()) + ", expected hash: "
- + Utils.ByteArrayToString(e.getExpectedHash()));
- log.info(sb.toString());
- throw e;
- }
- }
- return false;
+ return dataProcessor.readPlainDecryptedContentToStream(out, c, offset + entry.getFileOffset(), size, size == entry.getFileSize());
}
@Override
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWUDDataPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWUDDataPartition.java
index 708bec3..c2fc82f 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWUDDataPartition.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWUDDataPartition.java
@@ -49,7 +49,7 @@ public class FSTDataProviderWUDDataPartition implements FSTDataProvider {
}
@Override
- public boolean readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException {
+ public long readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException {
ContentFSTInfo info = FSTUtils.getFSTInfoForContent(partition.getFST(), entry.getContentIndex())
.orElseThrow(() -> new IOException("Failed to find FSTInfo"));
if (titleKey == null) {
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWumadDataPartition.java b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWumadDataPartition.java
index df084b7..23dc7ad 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWumadDataPartition.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/FSTDataProviderWumadDataPartition.java
@@ -32,15 +32,15 @@ public class FSTDataProviderWumadDataPartition implements FSTDataProvider {
}
@Override
- public boolean readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException {
- StreamUtils.saveInputStreamToOutputStream(readFileAsStream(entry, offset, size), out, size);
- return true;
+ public long readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException {
+ return StreamUtils.saveInputStreamToOutputStream(readFileAsStream(entry, offset, size), out, size);
}
@Override
public InputStream readFileAsStream(FSTEntry entry, long offset, long size) throws IOException {
ZipEntry zipEntry = zipFile.stream()
- .filter(e -> e.getName().equals(String.format("p%s.s%04d.00000000.app", dataPartition.getPartitionName(), entry.getContentIndex()))).findFirst()
+ .filter(e -> e.getName().equals(String.format("p%s.s%04d.00000000.app", dataPartition.getPartitionName(), entry.getContentIndex())))
+ .findFirst()
.orElseThrow(() -> new FileNotFoundException());
InputStream in = zipFile.getInputStream(zipEntry);
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderFST.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderFST.java
index ee7835e..b700db8 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderFST.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderFST.java
@@ -26,6 +26,7 @@ import java.util.Optional;
import de.mas.wiiu.jnus.Settings;
import de.mas.wiiu.jnus.entities.content.Content;
import de.mas.wiiu.jnus.entities.fst.FSTEntry;
+import de.mas.wiiu.jnus.interfaces.ContentDecryptor;
import de.mas.wiiu.jnus.interfaces.FSTDataProvider;
import de.mas.wiiu.jnus.interfaces.NUSDataProvider;
import de.mas.wiiu.jnus.utils.FSTUtils;
@@ -39,12 +40,12 @@ public class NUSDataProviderFST implements NUSDataProvider {
this.fstDataProvider = fstDataProvider;
}
- public NUSDataProviderFST(FSTDataProvider fstDataProvider) {
+ public NUSDataProviderFST(FSTDataProvider fstDataProvider, ContentDecryptor decryptor) {
this(fstDataProvider, fstDataProvider.getRoot());
}
@Override
- public InputStream readContentAsStream(Content content, long offset, long size) throws IOException {
+ public InputStream readRawContentAsStream(Content content, long offset, long size) throws IOException {
String filename = content.getFilename();
Optional contentFileOpt = FSTUtils.getChildOfDirectory(base, filename);
FSTEntry contentFile = contentFileOpt.orElseThrow(() -> new FileNotFoundException(filename + " was not found."));
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java
index 1c3ca14..03034c1 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java
@@ -45,7 +45,7 @@ public final class NUSDataProviderLocal implements NUSDataProvider {
}
@Override
- public InputStream readContentAsStream(Content content, long offset, long size) throws IOException {
+ public InputStream readRawContentAsStream(Content content, long offset, long size) throws IOException {
File filepath = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), content.getFilename());
if (filepath == null || !filepath.exists()) {
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + content.getFilename() + "\", file does not exist";
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java
index 9b16f84..0417bb2 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java
@@ -40,7 +40,7 @@ public class NUSDataProviderLocalBackup implements NUSDataProvider {
public NUSDataProviderLocalBackup(String localPath, short version) {
this.localPath = localPath;
- this.titleVersion = version;
+ this.titleVersion = version;
}
private String getFilePathOnDisk(Content c) {
@@ -48,7 +48,7 @@ public class NUSDataProviderLocalBackup implements NUSDataProvider {
}
@Override
- public InputStream readContentAsStream(Content content, long offset, long size) throws IOException {
+ public InputStream readRawContentAsStream(Content content, long offset, long size) throws IOException {
File filepath = new File(getFilePathOnDisk(content));
if (!filepath.exists()) {
throw new FileNotFoundException(filepath.getAbsolutePath() + " was not found.");
@@ -62,7 +62,9 @@ public class NUSDataProviderLocalBackup implements NUSDataProvider {
public Optional getContentH3Hash(Content content) throws IOException {
String h3Path = getLocalPath() + File.separator + String.format("%08X.h3", content.getID());
File h3File = new File(h3Path);
-
+ if (!h3File.exists()) {
+ throw new FileNotFoundException(h3File.getAbsolutePath() + " was not found.");
+ }
return Optional.of(Files.readAllBytes(h3File.toPath()));
}
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java
index a34eed8..8872a45 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java
@@ -39,7 +39,7 @@ public class NUSDataProviderRemote implements NUSDataProvider, Parallelizable {
}
@Override
- public InputStream readContentAsStream(Content content, long fileOffsetBlock, long size) throws IOException {
+ public InputStream readRawContentAsStream(Content content, long fileOffsetBlock, long size) throws IOException {
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
return downloadService.getInputStreamForURL(getRemoteURL(content), fileOffsetBlock, size);
}
@@ -56,7 +56,6 @@ public class NUSDataProviderRemote implements NUSDataProvider, Parallelizable {
if (resOpt == null) {
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
String url = getRemoteURL(content) + Settings.H3_EXTENTION;
- System.out.println(url);
byte[] res = downloadService.downloadToByteArray(url);
if (res == null || res.length == 0) {
@@ -69,29 +68,39 @@ public class NUSDataProviderRemote implements NUSDataProvider, Parallelizable {
return resOpt;
}
+ Optional tmdCache = null;
+
@Override
public Optional getRawTMD() throws IOException {
- NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
+ if (tmdCache == null) {
+ NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
- long titleID = getTitleID();
- int version = getVersion();
+ long titleID = getTitleID();
+ int version = getVersion();
- byte[] res = downloadService.downloadTMDToByteArray(titleID, version);
+ byte[] res = downloadService.downloadTMDToByteArray(titleID, version);
- if (res == null || res.length == 0) {
- return Optional.empty();
+ if (res == null || res.length == 0) {
+ return Optional.empty();
+ }
+ tmdCache = Optional.of(res);
}
- return Optional.of(res);
+ return tmdCache;
}
+ Optional ticketCache = null;
+
@Override
public Optional getRawTicket() throws IOException {
- NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
- byte[] res = downloadService.downloadTicketToByteArray(titleID);
- if (res == null || res.length == 0) {
- return Optional.empty();
+ if (ticketCache == null) {
+ NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
+ byte[] res = downloadService.downloadTicketToByteArray(titleID);
+ if (res == null || res.length == 0) {
+ return Optional.empty();
+ }
+ ticketCache = Optional.of(res);
}
- return Optional.of(res);
+ return ticketCache;
}
@Override
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java
index 445994f..1f5f43b 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java
@@ -56,7 +56,7 @@ public class NUSDataProviderWUD implements NUSDataProvider {
}
@Override
- public InputStream readContentAsStream(Content content, long fileOffsetBlock, long size) throws IOException {
+ public InputStream readRawContentAsStream(Content content, long fileOffsetBlock, long size) throws IOException {
WUDDiscReader discReader = getDiscReader();
long offset = getOffsetInWUD(content) + fileOffsetBlock;
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java
index 424ccc2..ba2117c 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java
@@ -16,7 +16,6 @@
****************************************************************************/
package de.mas.wiiu.jnus.implementations;
-import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
@@ -46,7 +45,7 @@ public class NUSDataProviderWoomy implements NUSDataProvider {
}
@Override
- public InputStream readContentAsStream(@NonNull Content content, long offset, long size) throws IOException {
+ public InputStream readRawContentAsStream(@NonNull Content content, long offset, long size) throws IOException {
WoomyZipFile zipFile = getSharedWoomyZipFile();
ZipEntry entry = getWoomyInfo().getContentFiles().get(content.getFilename().toLowerCase());
if (entry == null) {
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWumad.java b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWumad.java
index 29970a0..01caa73 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWumad.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/NUSDataProviderWumad.java
@@ -61,7 +61,7 @@ public class NUSDataProviderWumad implements NUSDataProvider {
}
@Override
- public InputStream readContentAsStream(Content content, long offset, long size) throws IOException {
+ public InputStream readRawContentAsStream(Content content, long offset, long size) throws IOException {
ZipEntry entry = files.values().stream().filter(e -> e.getName().startsWith("p" + partition.getPartitionName() + "."))
.filter(e -> e.getName().endsWith(content.getFilename().toLowerCase())).findFirst().orElseThrow(() -> new FileNotFoundException());
InputStream in = wumad.getInputStream(entry);
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java
index a01bc63..3439d13 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java
@@ -54,7 +54,7 @@ public abstract class WUDDiscReader {
return out.toByteArray();
}
- public abstract boolean readEncryptedToStream(OutputStream out, long offset, long size) throws IOException;
+ public abstract long readEncryptedToStream(OutputStream out, long offset, long size) throws IOException;
public InputStream readEncryptedToStream(long offset, long size) throws IOException {
PipedOutputStream out = new PipedOutputStream();
@@ -72,6 +72,23 @@ public abstract class WUDDiscReader {
return in;
}
+ public InputStream readDecryptedToStream(long offset, long fileOffset, long size, byte[] key, byte[] IV,
+ boolean useFixedIV) throws IOException {
+ PipedOutputStream out = new PipedOutputStream();
+ PipedInputStreamWithException in = new PipedInputStreamWithException(out, 0x8000);
+
+ new Thread(() -> {
+ try {
+ readDecryptedToOutputStream(out, offset, fileOffset, size, key, IV, useFixedIV);
+ in.throwException(null);
+ } catch (Exception e) {
+ in.throwException(e);
+ }
+ }).start();
+
+ return in;
+ }
+
/**
*
* @param readOffset
@@ -93,7 +110,7 @@ public abstract class WUDDiscReader {
return decryptedChunk;
}
- public boolean readDecryptedToOutputStream(OutputStream outputStream, long clusterOffset, long fileOffset, long size, byte[] key, byte[] IV,
+ public long readDecryptedToOutputStream(OutputStream outputStream, long clusterOffset, long fileOffset, long size, byte[] key, byte[] IV,
boolean useFixedIV) throws IOException {
byte[] usedIV = null;
if (useFixedIV) {
@@ -153,7 +170,7 @@ public abstract class WUDDiscReader {
StreamUtils.closeAll(outputStream);
}
- return totalread >= size;
+ return totalread;
}
/**
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderCompressed.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderCompressed.java
index e64be60..381b8ac 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderCompressed.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderCompressed.java
@@ -35,7 +35,7 @@ public class WUDDiscReaderCompressed extends WUDDiscReader {
* Expects the .wux format by Exzap. You can more infos about it here. https://gbatemp.net/threads/wii-u-image-wud-compression-tool.397901/
*/
@Override
- public boolean readEncryptedToStream(OutputStream out, long offset, long size) throws IOException {
+ public long readEncryptedToStream(OutputStream out, long offset, long size) throws IOException {
// make sure there is no out-of-bounds read
WUDImageCompressedInfo info = getImage().getCompressedInfo();
@@ -91,6 +91,6 @@ public class WUDDiscReaderCompressed extends WUDDiscReader {
} finally {
StreamUtils.closeAll(input, out);
}
- return usedSize == 0;
+ return size - usedSize;
}
}
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderSplitted.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderSplitted.java
index 7a34c6f..0973ba9 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderSplitted.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderSplitted.java
@@ -37,7 +37,7 @@ public class WUDDiscReaderSplitted extends WUDDiscReader {
}
@Override
- public boolean readEncryptedToStream(OutputStream outputStream, long offset, long size) throws IOException {
+ public long readEncryptedToStream(OutputStream outputStream, long offset, long size) throws IOException {
RandomAccessFile input = getFileByOffset(offset);
int bufferSize = 0x8000;
@@ -86,7 +86,7 @@ public class WUDDiscReaderSplitted extends WUDDiscReader {
input.close();
outputStream.close();
- return totalread >= size;
+ return totalread;
}
private int getFilePartByOffset(long offset) {
diff --git a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderUncompressed.java b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderUncompressed.java
index 5e55ecf..ab497b5 100644
--- a/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderUncompressed.java
+++ b/src/main/java/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReaderUncompressed.java
@@ -31,7 +31,7 @@ public class WUDDiscReaderUncompressed extends WUDDiscReader {
}
@Override
- public boolean readEncryptedToStream(OutputStream outputStream, long offset, long size) throws IOException {
+ public long readEncryptedToStream(OutputStream outputStream, long offset, long size) throws IOException {
FileInputStream input = new FileInputStream(getImage().getFileHandle());
@@ -62,7 +62,7 @@ public class WUDDiscReaderUncompressed extends WUDDiscReader {
} while (totalread < size);
input.close();
outputStream.close();
- return totalread >= size;
+ return totalread;
}
@Override
diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/ContentDecryptor.java b/src/main/java/de/mas/wiiu/jnus/interfaces/ContentDecryptor.java
new file mode 100644
index 0000000..f6c808d
--- /dev/null
+++ b/src/main/java/de/mas/wiiu/jnus/interfaces/ContentDecryptor.java
@@ -0,0 +1,24 @@
+package de.mas.wiiu.jnus.interfaces;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+public interface ContentDecryptor {
+
+ /**
+ *
+ * @param in InputStream of the Encrypted Data with hashed
+ * @param out OutputStream of the decrypted data with hashes
+ * @param offset absolute offset in this Content stream
+ * @param size size of the payload that will be written to the outputstream
+ * @param payloadOffset relative offset to the start of the inputstream
+ * @param h3_hashes level 3 hashes of the content file
+ * @return
+ * @throws IOException
+ */
+ long readDecryptedContentToStreamHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] h3_hashes) throws IOException;
+
+ long readDecryptedContentToStreamNonHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] IV) throws IOException;
+
+}
diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/ContentEncryptor.java b/src/main/java/de/mas/wiiu/jnus/interfaces/ContentEncryptor.java
new file mode 100644
index 0000000..d7f451a
--- /dev/null
+++ b/src/main/java/de/mas/wiiu/jnus/interfaces/ContentEncryptor.java
@@ -0,0 +1,15 @@
+package de.mas.wiiu.jnus.interfaces;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import de.mas.wiiu.jnus.utils.IVCache;
+
+public interface ContentEncryptor {
+
+ long readEncryptedContentToStreamHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset) throws IOException;
+
+ long readEncryptedContentToStreamNonHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] IV, IVCache ivcache) throws IOException;
+
+}
diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/FSTDataProvider.java b/src/main/java/de/mas/wiiu/jnus/interfaces/FSTDataProvider.java
index 5aee03f..f5fdae8 100644
--- a/src/main/java/de/mas/wiiu/jnus/interfaces/FSTDataProvider.java
+++ b/src/main/java/de/mas/wiiu/jnus/interfaces/FSTDataProvider.java
@@ -56,20 +56,25 @@ public interface FSTDataProvider {
in.throwException(null);
} catch (Exception e) {
in.throwException(e);
+ try {
+ out.close();
+ } catch (IOException e1) {
+ e1.printStackTrace();
+ }
}
}).start();
return in;
}
- default public boolean readFileToStream(OutputStream out, FSTEntry entry) throws IOException {
+ default public long readFileToStream(OutputStream out, FSTEntry entry) throws IOException {
return readFileToStream(out, entry, 0, entry.getFileSize());
}
- default public boolean readFileToStream(OutputStream out, FSTEntry entry, long offset) throws IOException {
+ default public long readFileToStream(OutputStream out, FSTEntry entry, long offset) throws IOException {
return readFileToStream(out, entry, offset, entry.getFileSize());
}
- public boolean readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException;
+ public long readFileToStream(OutputStream out, FSTEntry entry, long offset, long size) throws IOException;
}
diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProcessor.java b/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProcessor.java
new file mode 100644
index 0000000..3e8922c
--- /dev/null
+++ b/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProcessor.java
@@ -0,0 +1,111 @@
+package de.mas.wiiu.jnus.interfaces;
+
+import java.io.ByteArrayOutputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+
+import de.mas.wiiu.jnus.entities.content.Content;
+import de.mas.wiiu.jnus.utils.StreamUtils;
+
+public interface NUSDataProcessor {
+
+ public NUSDataProvider getDataProvider();
+
+ default public byte[] readContent(Content c) throws IOException {
+ return readContent(c, 0, c.getEncryptedFileSizeAligned());
+ }
+
+ default public byte[] readContent(Content c, long offset, long size) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ readContentToStream(out, c, offset, size);
+
+ return out.toByteArray();
+ }
+
+ default public InputStream readContentAsStream(Content c) throws IOException {
+ return readContentAsStream(c, 0, c.getEncryptedFileSizeAligned());
+ }
+
+ public InputStream readContentAsStream(Content c, long offset, long size) throws IOException;
+
+ default public long readContentToStream(OutputStream out, Content entry) throws IOException {
+ return readContentToStream(out, entry, 0, entry.getEncryptedFileSizeAligned());
+ }
+
+ default public long readContentToStream(OutputStream out, Content entry, long offset) throws IOException {
+ return readContentToStream(out, entry, offset, entry.getEncryptedFileSizeAligned());
+ }
+
+ default public long readContentToStream(OutputStream out, Content c, long offset, long size) throws IOException {
+ InputStream in = readContentAsStream(c, offset, size);
+ return StreamUtils.saveInputStreamToOutputStream(in, out, size);
+ }
+
+ default public byte[] readDecryptedContent(Content c) throws IOException {
+ return readDecryptedContent(c, 0, c.getEncryptedFileSizeAligned());
+ }
+
+ default public byte[] readDecryptedContent(Content c, long offset, long size) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+ long len = readDecryptedContentToStream(out, c, offset, size);
+ if(len < 0) {
+ return new byte[0];
+ }
+ return out.toByteArray();
+ }
+
+ default public InputStream readDecryptedContentAsStream(Content c) throws IOException {
+ return readDecryptedContentAsStream(c, 0, c.getEncryptedFileSizeAligned());
+ }
+
+ public InputStream readDecryptedContentAsStream(Content c, long offset, long size) throws IOException;
+
+ default public long readDecryptedContentToStream(OutputStream out, Content c) throws IOException {
+ return readDecryptedContentToStream(out, c, 0, c.getEncryptedFileSizeAligned());
+ }
+
+ default public long readDecryptedContentToStream(OutputStream out, Content c, long offset) throws IOException {
+ return readDecryptedContentToStream(out, c, offset, c.getEncryptedFileSizeAligned());
+ }
+
+ default public long readDecryptedContentToStream(OutputStream out, Content c, long offset, long size) throws IOException {
+ InputStream in = readDecryptedContentAsStream(c, offset, size);
+ return StreamUtils.saveInputStreamToOutputStream(in, out, size);
+ }
+
+ default public byte[] readPlainDecryptedContent(Content c, boolean forceCheckHash) throws IOException {
+ return readPlainDecryptedContent(c, 0, c.getEncryptedFileSizeAligned(), forceCheckHash);
+ }
+
+ default public byte[] readPlainDecryptedContent(Content c, long offset, long size, boolean forceCheckHash) throws IOException {
+ ByteArrayOutputStream out = new ByteArrayOutputStream();
+
+ long len = readPlainDecryptedContentToStream(out, c, offset, size, forceCheckHash);
+ if(len < 0) {
+ return new byte[0];
+ }
+
+ return out.toByteArray();
+ }
+
+ default public InputStream readPlainDecryptedContentAsStream(Content c, boolean forceCheckHash) throws IOException {
+ return readPlainDecryptedContentAsStream(c, 0, c.getEncryptedFileSizeAligned(), forceCheckHash);
+ }
+
+ public InputStream readPlainDecryptedContentAsStream(Content c, long offset, long size, boolean forceCheckHash) throws IOException;
+
+ default public long readPlainDecryptedContentToStream(OutputStream out, Content entry, boolean forceCheckHash) throws IOException {
+ return readPlainDecryptedContentToStream(out, entry, 0, entry.getEncryptedFileSizeAligned(), forceCheckHash);
+ }
+
+ default public long readPlainDecryptedContentToStream(OutputStream out, Content entry, long offset, boolean forceCheckHash) throws IOException {
+ return readPlainDecryptedContentToStream(out, entry, offset, entry.getEncryptedFileSizeAligned(), forceCheckHash);
+ }
+
+ default public long readPlainDecryptedContentToStream(OutputStream out, Content c, long offset, long size, boolean forceCheckHash) throws IOException {
+ InputStream in = readPlainDecryptedContentAsStream(c, offset, size, forceCheckHash);
+ return StreamUtils.saveInputStreamToOutputStream(in, out, size);
+ }
+}
diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProvider.java b/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProvider.java
index 866a556..f4573be 100644
--- a/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProvider.java
+++ b/src/main/java/de/mas/wiiu/jnus/interfaces/NUSDataProvider.java
@@ -26,19 +26,19 @@ import de.mas.wiiu.jnus.entities.fst.FST;
import de.mas.wiiu.jnus.utils.StreamUtils;
public interface NUSDataProvider {
- default public byte[] readContent(Content content, long offset, int size) throws IOException {
- return StreamUtils.getBytesFromStream(readContentAsStream(content, offset, size), size);
+ default byte[] readRawContent(Content content, long offset, int size) throws IOException {
+ return StreamUtils.getBytesFromStream(readRawContentAsStream(content, offset, size), size);
}
- default public InputStream readContentAsStream(Content content) throws IOException {
- return readContentAsStream(content, 0);
+ default InputStream readRawContentAsStream(Content content) throws IOException {
+ return readRawContentAsStream(content, 0);
}
- default public InputStream readContentAsStream(Content content, long offset) throws IOException {
- return readContentAsStream(content, offset, content.getEncryptedFileSizeAligned() - offset);
+ default InputStream readRawContentAsStream(Content content, long offset) throws IOException {
+ return readRawContentAsStream(content, offset, content.getEncryptedFileSizeAligned() - offset);
}
- public InputStream readContentAsStream(Content content, long offset, long size) throws IOException;
+ public InputStream readRawContentAsStream(Content content, long offset, long size) throws IOException;
public Optional getContentH3Hash(Content content) throws IOException;
@@ -53,5 +53,4 @@ public interface NUSDataProvider {
default public void setFST(FST fst) {
}
-
}
\ No newline at end of file
diff --git a/src/main/java/de/mas/wiiu/jnus/interfaces/TriFunction.java b/src/main/java/de/mas/wiiu/jnus/interfaces/TriFunction.java
new file mode 100644
index 0000000..b1f7f4b
--- /dev/null
+++ b/src/main/java/de/mas/wiiu/jnus/interfaces/TriFunction.java
@@ -0,0 +1,17 @@
+package de.mas.wiiu.jnus.interfaces;
+
+import java.util.Objects;
+import java.util.function.Function;
+
+
+@FunctionalInterface
+public interface TriFunction {
+
+ R apply(A a, B b, C c);
+
+ default TriFunction andThen(
+ Function super R, ? extends V> after) {
+ Objects.requireNonNull(after);
+ return (A a, B b, C c) -> after.apply(apply(a, b, c));
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/ByteUtils.java b/src/main/java/de/mas/wiiu/jnus/utils/ByteUtils.java
index f6c7574..202444a 100644
--- a/src/main/java/de/mas/wiiu/jnus/utils/ByteUtils.java
+++ b/src/main/java/de/mas/wiiu/jnus/utils/ByteUtils.java
@@ -88,7 +88,9 @@ public final class ByteUtils {
public static byte[] getBytesFromInt(int value, ByteOrder bo) {
byte[] result = new byte[0x04];
- ByteBuffer.allocate(4).order(bo).putInt(value).get(result);
+ ByteBuffer buffer = ByteBuffer.allocate(4).order(bo).putInt(value);
+ buffer.position(0);
+ buffer.get(result);
return result;
}
@@ -97,5 +99,11 @@ public final class ByteUtils {
ByteBuffer.allocate(2).putShort(value).get(result);
return result;
}
+
+ public static short getByteFromBytes(byte[] input, int offset) {
+ ByteBuffer buffer = ByteBuffer.allocate(2).put(Arrays.copyOfRange(input, offset, offset + 1)).order(ByteOrder.BIG_ENDIAN);
+ buffer.position(0);
+ return (short) ((buffer.getShort() & 0xFF00) >> 8);
+ }
}
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/DataProviderUtils.java b/src/main/java/de/mas/wiiu/jnus/utils/DataProviderUtils.java
index 3a457ec..5aa43aa 100644
--- a/src/main/java/de/mas/wiiu/jnus/utils/DataProviderUtils.java
+++ b/src/main/java/de/mas/wiiu/jnus/utils/DataProviderUtils.java
@@ -133,7 +133,7 @@ public class DataProviderUtils {
}
Utils.createDir(outputFolder);
- InputStream inputStream = dataProvider.readContentAsStream(content);
+ InputStream inputStream = dataProvider.readRawContentAsStream(content);
if (inputStream == null) {
log.warning(content.getFilename() + " Couldn't save encrypted content. Input stream was null");
return;
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/IVCache.java b/src/main/java/de/mas/wiiu/jnus/utils/IVCache.java
new file mode 100644
index 0000000..d91bbb9
--- /dev/null
+++ b/src/main/java/de/mas/wiiu/jnus/utils/IVCache.java
@@ -0,0 +1,38 @@
+package de.mas.wiiu.jnus.utils;
+
+import java.util.Map;
+import java.util.Optional;
+import java.util.TreeMap;
+
+import lombok.val;
+
+public class IVCache {
+ private final Map cache = new TreeMap<>();
+
+ public IVCache(long first, byte[] IV) {
+ if (!addForOffset(first, IV)) {
+ throw new IllegalArgumentException("IV was null or not 16 bytes big");
+ }
+ }
+
+ public boolean addForOffset(long offset, byte[] IV) {
+ if (IV == null || IV.length != 16) {
+ return false;
+ }
+
+ cache.put(offset, IV);
+ return true;
+ }
+
+ public Optional> getNearestForOffset(long offset) {
+ Optional> result = Optional.empty();
+ for (val e : cache.entrySet()) {
+ if (e.getKey().longValue() <= offset) {
+ result = Optional.of(new Pair<>(e.getKey(), e.getValue()));
+ } else {
+ break;
+ }
+ }
+ return result;
+ }
+}
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/Pair.java b/src/main/java/de/mas/wiiu/jnus/utils/Pair.java
new file mode 100644
index 0000000..7da7369
--- /dev/null
+++ b/src/main/java/de/mas/wiiu/jnus/utils/Pair.java
@@ -0,0 +1,9 @@
+package de.mas.wiiu.jnus.utils;
+
+import lombok.Data;
+
+@Data
+public class Pair {
+ public final T1 k;
+ public final T2 v;
+}
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/StreamUtils.java b/src/main/java/de/mas/wiiu/jnus/utils/StreamUtils.java
index 1cf4286..96be838 100644
--- a/src/main/java/de/mas/wiiu/jnus/utils/StreamUtils.java
+++ b/src/main/java/de/mas/wiiu/jnus/utils/StreamUtils.java
@@ -89,8 +89,11 @@ public final class StreamUtils {
if (overflowbuffer.getLengthOfDataInBuffer() > 0) {
System.arraycopy(overflowbuf, 0, output, 0, overflowbuffer.getLengthOfDataInBuffer());
inBlockBuffer = overflowbuffer.getLengthOfDataInBuffer();
+ } else {
+ if (inBlockBuffer == 0) {
+ return bytesRead;
+ }
}
-
break;
}
@@ -124,19 +127,21 @@ public final class StreamUtils {
}
}
- public static void saveInputStreamToOutputStream(InputStream inputStream, OutputStream outputStream, long filesize) throws IOException {
+ public static long saveInputStreamToOutputStream(InputStream inputStream, OutputStream outputStream, long filesize) throws IOException {
try {
- saveInputStreamToOutputStreamWithHash(inputStream, outputStream, filesize, null, 0L, true);
+ return saveInputStreamToOutputStreamWithHash(inputStream, outputStream, filesize, null, 0L, true);
} catch (CheckSumWrongException e) {
// Should never happen because the hash is not set. Lets print it anyway.
e.printStackTrace();
}
+ return -1;
}
- public static void saveInputStreamToOutputStreamWithHash(InputStream inputStream, OutputStream outputStream, long filesize, byte[] hash,
+ public static long saveInputStreamToOutputStreamWithHash(InputStream inputStream, OutputStream outputStream, long filesize, byte[] hash,
long expectedSizeForHash, boolean partial) throws IOException, CheckSumWrongException {
- synchronized (inputStream) {
+ long written = 0;
+ synchronized (inputStream) {
MessageDigest sha1 = null;
if (hash != null && !partial) {
try {
@@ -150,7 +155,6 @@ public final class StreamUtils {
byte[] buffer = new byte[BUFFER_SIZE];
int read = 0;
long totalRead = 0;
- long written = 0;
try {
do {
@@ -189,6 +193,7 @@ public final class StreamUtils {
StreamUtils.closeAll(inputStream, outputStream);
}
}
+ return written > 0 ? written : -1;
}
public static void skipExactly(InputStream in, long offset) throws IOException {
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/Utils.java b/src/main/java/de/mas/wiiu/jnus/utils/Utils.java
index 6e7e4be..5cf3879 100644
--- a/src/main/java/de/mas/wiiu/jnus/utils/Utils.java
+++ b/src/main/java/de/mas/wiiu/jnus/utils/Utils.java
@@ -23,12 +23,14 @@ import java.io.InputStream;
import java.math.BigInteger;
import java.net.HttpURLConnection;
import java.net.URL;
+import java.net.URLConnection;
import java.util.Arrays;
import java.util.logging.Level;
import java.util.logging.LogManager;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
+import javax.net.ssl.HttpsURLConnection;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
@@ -238,4 +240,31 @@ public final class Utils {
return null;
}
+ public static Long getLastModifiedURL(HttpURLConnection connectionForURL, int timeout) throws IOException {
+ HttpURLConnection connection = connectionForURL;
+ connection.setRequestProperty("User-Agent", Settings.USER_AGENT);
+ connection.setConnectTimeout(timeout);
+ connection.setReadTimeout(timeout);
+
+ int responseCode = connection.getResponseCode();
+
+ if (responseCode == HttpsURLConnection.HTTP_OK) {
+ InputStream inputStream = connection.getInputStream();
+ byte[] buffer = new byte[0x10];
+ inputStream.read(buffer);
+ inputStream.close();
+ } else {
+ return null;
+ }
+
+ Long dateTime = connection.getLastModified();
+
+ if (200 <= responseCode && responseCode <= 399) {
+ return dateTime;
+ }
+
+ return null;
+ }
+
+
}
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESDecryption.java b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESDecryption.java
index 027ef0a..6339006 100644
--- a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESDecryption.java
+++ b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESDecryption.java
@@ -29,6 +29,7 @@ import javax.crypto.spec.SecretKeySpec;
import lombok.Getter;
import lombok.Setter;
+import lombok.Synchronized;
public class AESDecryption {
private Cipher cipher;
@@ -51,6 +52,7 @@ public class AESDecryption {
init(getAESKey(), getIV());
}
+ @Synchronized("cipher")
protected void init(byte[] decryptedKey, byte[] iv) {
SecretKeySpec secretKeySpec = new SecretKeySpec(decryptedKey, "AES");
try {
@@ -61,6 +63,7 @@ public class AESDecryption {
}
}
+ @Synchronized("cipher")
public byte[] decrypt(byte[] input) {
try {
return cipher.doFinal(input);
@@ -75,6 +78,7 @@ public class AESDecryption {
return decrypt(input, 0, len);
}
+ @Synchronized("cipher")
public byte[] decrypt(byte[] input, int offset, int len) {
try {
return cipher.doFinal(input, offset, len);
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESEncryption.java b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESEncryption.java
new file mode 100644
index 0000000..9cccf70
--- /dev/null
+++ b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/AESEncryption.java
@@ -0,0 +1,92 @@
+/****************************************************************************
+ * Copyright (C) 2016-2019 Maschell
+ *
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation, either version 3 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program. If not, see .
+ ****************************************************************************/
+package de.mas.wiiu.jnus.utils.cryptography;
+
+import java.security.InvalidAlgorithmParameterException;
+import java.security.InvalidKeyException;
+import java.security.NoSuchAlgorithmException;
+import java.security.NoSuchProviderException;
+
+import javax.crypto.BadPaddingException;
+import javax.crypto.Cipher;
+import javax.crypto.IllegalBlockSizeException;
+import javax.crypto.NoSuchPaddingException;
+import javax.crypto.spec.IvParameterSpec;
+import javax.crypto.spec.SecretKeySpec;
+
+import lombok.Getter;
+import lombok.Setter;
+import lombok.Synchronized;
+
+public class AESEncryption {
+ private Cipher cipher;
+
+ @Getter @Setter private byte[] AESKey;
+ @Getter @Setter private byte[] IV;
+
+ public AESEncryption(byte[] AESKey, byte[] IV) throws NoSuchProviderException {
+ try {
+ cipher = Cipher.getInstance("AES/CBC/NoPadding", "SunJCE");
+ } catch (NoSuchAlgorithmException | NoSuchPaddingException e) {
+ e.printStackTrace();
+ }
+ setAESKey(AESKey);
+ setIV(IV);
+ init();
+ }
+
+ protected final void init() {
+ init(getAESKey(), getIV());
+ }
+
+ @Synchronized("cipher")
+ protected void init(byte[] decryptedKey, byte[] iv) {
+ SecretKeySpec secretKeySpec = new SecretKeySpec(decryptedKey, "AES");
+ try {
+ cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, new IvParameterSpec(iv));
+ } catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
+ e.printStackTrace();
+ System.exit(2);
+ }
+ }
+
+ @Synchronized("cipher")
+ public byte[] encrypt(byte[] input) {
+ try {
+ return cipher.doFinal(input);
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ e.printStackTrace();
+ System.exit(2);
+ }
+ return input;
+ }
+
+ public byte[] encrypt(byte[] input, int len) {
+ return encrypt(input, 0, len);
+ }
+
+ @Synchronized("cipher")
+ public byte[] encrypt(byte[] input, int offset, int len) {
+ try {
+ return cipher.doFinal(input, offset, len);
+ } catch (IllegalBlockSizeException | BadPaddingException e) {
+ e.printStackTrace();
+ System.exit(2);
+ }
+ return input;
+ }
+}
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSDecryption.java b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSDecryption.java
index f46ffa0..eb91b21 100644
--- a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSDecryption.java
+++ b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSDecryption.java
@@ -16,26 +16,18 @@
****************************************************************************/
package de.mas.wiiu.jnus.utils.cryptography;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
-import java.security.MessageDigest;
-import java.security.NoSuchAlgorithmException;
import java.util.Arrays;
-import java.util.Optional;
import de.mas.wiiu.jnus.entities.Ticket;
-import de.mas.wiiu.jnus.entities.content.Content;
+import de.mas.wiiu.jnus.interfaces.ContentDecryptor;
import de.mas.wiiu.jnus.utils.ByteArrayBuffer;
-import de.mas.wiiu.jnus.utils.CheckSumWrongException;
-import de.mas.wiiu.jnus.utils.HashUtil;
import de.mas.wiiu.jnus.utils.StreamUtils;
import de.mas.wiiu.jnus.utils.Utils;
-import lombok.extern.java.Log;
-@Log
-public class NUSDecryption extends AESDecryption {
+public class NUSDecryption extends AESDecryption implements ContentDecryptor {
public NUSDecryption(byte[] AESKey, byte[] IV) {
super(AESKey, IV);
}
@@ -56,144 +48,82 @@ public class NUSDecryption extends AESDecryption {
return decrypt(blockBuffer, offset, BLOCKSIZE);
}
- public void decryptFileStream(InputStream inputStream, OutputStream outputStream, long fileOffset, long filesize, byte[] IV, byte[] h3hash,
- long expectedSizeForHash) throws IOException, CheckSumWrongException {
- MessageDigest sha1 = null;
- MessageDigest sha1fallback = null;
-
- if (h3hash != null) {
- try {
- sha1 = MessageDigest.getInstance("SHA1");
- sha1fallback = MessageDigest.getInstance("SHA1");
- } catch (NoSuchAlgorithmException e) {
- e.printStackTrace();
- }
- }
-
- int BLOCKSIZE = 0x8000;
-
- byte[] blockBuffer = new byte[BLOCKSIZE];
-
- int inBlockBuffer;
- long written = 0;
- long writtenFallback = 0;
-
- try {
- ByteArrayBuffer overflow = new ByteArrayBuffer(BLOCKSIZE);
-
- // We can only decrypt multiples of 16. So we need to align it.
- long toRead = Utils.align(filesize, 16);
-
- do {
-
- int curReadSize = BLOCKSIZE;
- if (toRead < BLOCKSIZE) {
- curReadSize = (int) toRead;
- }
-
- inBlockBuffer = StreamUtils.getChunkFromStream(inputStream, blockBuffer, overflow, curReadSize);
-
- byte[] output = decryptFileChunk(blockBuffer, (int) Utils.align(inBlockBuffer, 16), IV);
-
- if (inBlockBuffer == BLOCKSIZE) {
- IV = Arrays.copyOfRange(blockBuffer, BLOCKSIZE - 16, BLOCKSIZE);
- }
-
- int toWrite = inBlockBuffer;
-
- if ((written + inBlockBuffer) > filesize) {
- toWrite = (int) (filesize - written);
- }
-
- written += toWrite;
- toRead -= toWrite;
-
- outputStream.write(output, 0, toWrite);
-
- if (sha1 != null && sha1fallback != null) {
- sha1.update(output, 0, toWrite);
-
- // In some cases it's using the hash of the whole .app file instead of the part
- // that's been actually used.
- long toFallback = inBlockBuffer;
- if (writtenFallback + toFallback > expectedSizeForHash) {
- toFallback = expectedSizeForHash - writtenFallback;
- }
- sha1fallback.update(output, 0, (int) toFallback);
- writtenFallback += toFallback;
- }
-
- if (written >= filesize && h3hash == null) {
- break;
- }
- } while (inBlockBuffer == BLOCKSIZE);
-
- if (sha1 != null && sha1fallback != null) {
- long missingInHash = expectedSizeForHash - writtenFallback;
- if (missingInHash > 0) {
- sha1fallback.update(new byte[(int) missingInHash]);
- }
-
- byte[] calculated_hash1 = sha1.digest();
- byte[] calculated_hash2 = sha1fallback.digest();
- byte[] expected_hash = h3hash;
- if (!Arrays.equals(calculated_hash1, expected_hash) && !Arrays.equals(calculated_hash2, expected_hash)) {
- throw new CheckSumWrongException("hash checksum failed ", calculated_hash1, expected_hash);
- } else {
- log.finest("Hash DOES match saves output stream.");
- }
-
- }
- } finally {
- StreamUtils.closeAll(inputStream, outputStream);
- }
- if (written < filesize) {
- throw new IOException("Failed to read. Missing " + (filesize - written));
- }
- }
-
- public void decryptFileStreamHashed(InputStream inputStream, OutputStream outputStream, long fileoffset, long filesize, byte[] h3Hash)
- throws IOException, CheckSumWrongException, NoSuchAlgorithmException {
+ @Override
+ public long readDecryptedContentToStreamHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] h3_hashes)
+ throws IOException {
int BLOCKSIZE = 0x10000;
- int HASHBLOCKSIZE = 0xFC00;
+ int HASHEDBLOCKSIZE = 0xFC00;
+ int HASHSIZE = BLOCKSIZE - HASHEDBLOCKSIZE;
- long writeSize = HASHBLOCKSIZE;
+ long block = (offset / BLOCKSIZE);
+ long writeSize = BLOCKSIZE;
- long block = (fileoffset / HASHBLOCKSIZE);
- long soffset = fileoffset - (fileoffset / HASHBLOCKSIZE * HASHBLOCKSIZE);
-
- if (soffset + filesize > writeSize) {
- writeSize = writeSize - soffset;
-
- }
+ long soffset = payloadOffset;
byte[] encryptedBlockBuffer = new byte[BLOCKSIZE];
ByteArrayBuffer overflow = new ByteArrayBuffer(BLOCKSIZE);
+
long wrote = 0;
int inBlockBuffer = 0;
try {
do {
- inBlockBuffer = StreamUtils.getChunkFromStream(inputStream, encryptedBlockBuffer, overflow, BLOCKSIZE);
- if (writeSize > filesize) writeSize = filesize;
-
+ inBlockBuffer = StreamUtils.getChunkFromStream(in, encryptedBlockBuffer, overflow, BLOCKSIZE);
+ if (inBlockBuffer < 0) {
+ return wrote;
+ }
if (inBlockBuffer != BLOCKSIZE) {
+
throw new IOException("wasn't able to read " + BLOCKSIZE);
}
- byte[] output;
- try {
- output = decryptFileChunkHash(encryptedBlockBuffer, (int) block, h3Hash);
- } catch (CheckSumWrongException | NoSuchAlgorithmException e) {
- throw e;
- }
+ byte[] hashes = decryptFileChunk(encryptedBlockBuffer, HASHSIZE, new byte[16]);
- if ((wrote + writeSize) > filesize) {
- writeSize = (int) (filesize - wrote);
- }
+ int H0_start = (int) (((int) block % 16) * 20);
+
+ byte[] IV = Arrays.copyOfRange(hashes, H0_start, H0_start + 16);
+ byte[] output = decryptFileChunk(encryptedBlockBuffer, HASHSIZE, HASHEDBLOCKSIZE, IV);
try {
- outputStream.write(output, (int) (0 + soffset), (int) writeSize);
+ if (writeSize > size) {
+ writeSize = size;
+ }
+ if (writeSize + wrote > size) {
+ writeSize = size - wrote;
+ }
+
+ long toBeWritten = writeSize;
+
+ if (soffset <= HASHSIZE) {
+ long writeHashSize = HASHSIZE;
+ if (writeSize < HASHSIZE) {
+ writeHashSize = writeSize;
+ }
+ if (writeHashSize + soffset > HASHSIZE) {
+ writeHashSize = HASHSIZE - soffset;
+ }
+ out.write(hashes, (int) (0 + soffset), (int) writeHashSize);
+ wrote += writeHashSize;
+ toBeWritten -= writeHashSize;
+
+ if (toBeWritten > 0) {
+ if (toBeWritten > HASHEDBLOCKSIZE) {
+ toBeWritten = HASHEDBLOCKSIZE;
+ writeSize = toBeWritten - HASHEDBLOCKSIZE;
+ }
+ out.write(output, 0, (int) toBeWritten);
+ wrote += toBeWritten;
+ }
+ } else {
+ soffset -= 0x400;
+ long writeThisTime = writeSize;
+ if (writeSize + soffset > HASHEDBLOCKSIZE) {
+ writeThisTime = HASHEDBLOCKSIZE - soffset;
+ }
+ out.write(output, (int) (0 + soffset), (int) writeThisTime);
+ wrote += writeThisTime;
+ }
+ writeSize = BLOCKSIZE;
} catch (IOException e) {
if (e.getMessage().equals("Pipe closed")) {
break;
@@ -201,62 +131,76 @@ public class NUSDecryption extends AESDecryption {
e.printStackTrace();
throw e;
}
- wrote += writeSize;
block++;
if (soffset > 0) {
- writeSize = HASHBLOCKSIZE;
soffset = 0;
}
- } while (wrote < filesize && (inBlockBuffer == BLOCKSIZE));
- log.finest("Decryption okay");
+ } while (wrote < size && (inBlockBuffer == BLOCKSIZE));
} finally {
- StreamUtils.closeAll(inputStream, outputStream);
+ StreamUtils.closeAll(in, out);
}
+ return wrote > 0 ? wrote : -1;
}
- private byte[] decryptFileChunkHash(byte[] blockBuffer, int block, byte[] h3_hashes) throws CheckSumWrongException, NoSuchAlgorithmException {
- int hashSize = 0x400;
- int blocksize = 0xFC00;
+ @Override
+ public long readDecryptedContentToStreamNonHashed(InputStream inputStream, OutputStream outputStream, long offset, long size, long payloadOffset, byte[] IV)
+ throws IOException {
+ int BLOCKSIZE = 0x80000;
- byte[] hashes = decryptFileChunk(blockBuffer, hashSize, new byte[16]);
+ byte[] blockBuffer = new byte[BLOCKSIZE];
- int H0_start = (block % 16) * 20;
+ int inBlockBuffer;
+ long written = 0;
+ long read = 0;
- byte[] IV = Arrays.copyOfRange(hashes, H0_start, H0_start + 16);
- byte[] output = decryptFileChunk(blockBuffer, hashSize, blocksize, IV);
-
- HashUtil.checkFileChunkHashes(hashes, h3_hashes, output, block);
-
- return output;
- }
-
- public boolean decryptStreamsHashed(InputStream inputStream, OutputStream outputStream, long offset, long size, Optional h3HashHashed)
- throws IOException, CheckSumWrongException, NoSuchAlgorithmException {
try {
- byte[] h3 = h3HashHashed.orElseThrow(() -> new FileNotFoundException("h3 hash not found."));
- decryptFileStreamHashed(inputStream, outputStream, offset, size, h3);
+ ByteArrayBuffer overflow = new ByteArrayBuffer(BLOCKSIZE);
+
+ // We can only decrypt multiples of 16. So we need to align it.
+ long toRead = Utils.align(size, 16);
+
+ do {
+ long writeOffset = Math.max(0, payloadOffset - read);
+ int curReadSize = BLOCKSIZE;
+ if (toRead < BLOCKSIZE) {
+ curReadSize = (int) (toRead + writeOffset);
+ }
+ inBlockBuffer = StreamUtils.getChunkFromStream(inputStream, blockBuffer, overflow, (int) Utils.align(curReadSize, 16));
+
+ if (inBlockBuffer < 0) {
+ break;
+ }
+
+ byte[] output = decryptFileChunk(blockBuffer, (int) Utils.align(inBlockBuffer, 16), IV);
+
+ if (inBlockBuffer > 16) {
+ IV = Arrays.copyOfRange(blockBuffer, BLOCKSIZE - 16, BLOCKSIZE);
+ }
+
+ long writeLength = Math.min((output.length - writeOffset), (size - written));
+
+ try {
+ read += inBlockBuffer;
+ outputStream.write(output, (int) writeOffset, (int) writeLength);
+ written += writeLength;
+ toRead -= writeLength;
+ } catch (IOException e) {
+ if (e.getMessage().equals("Pipe closed")) {
+ break;
+ } else {
+ throw e;
+ }
+ }
+ if (written >= size) {
+ break;
+ }
+ } while (true);
+
} finally {
StreamUtils.closeAll(inputStream, outputStream);
}
-
- return true;
- }
-
- public boolean decryptStreamsNonHashed(InputStream inputStream, OutputStream outputStream, long offset, long size, Content content, byte[] IV,
- boolean partial) throws IOException, CheckSumWrongException {
- try {
- byte[] h3Hash = content.getSHA2Hash();
- // Ignore the h3hash if we don't read the whole file.
- if (partial) {
- h3Hash = null;
- }
- decryptFileStream(inputStream, outputStream, offset, size, IV, h3Hash, content.getEncryptedFileSize());
- } finally {
- StreamUtils.closeAll(inputStream, outputStream);
- }
-
- return true;
+ return written > 0 ? written : -1;
}
}
diff --git a/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSEncryption.java b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSEncryption.java
new file mode 100644
index 0000000..de2a46d
--- /dev/null
+++ b/src/main/java/de/mas/wiiu/jnus/utils/cryptography/NUSEncryption.java
@@ -0,0 +1,152 @@
+package de.mas.wiiu.jnus.utils.cryptography;
+
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.OutputStream;
+import java.security.NoSuchProviderException;
+import java.util.Arrays;
+
+import de.mas.wiiu.jnus.entities.Ticket;
+import de.mas.wiiu.jnus.interfaces.ContentEncryptor;
+import de.mas.wiiu.jnus.utils.ByteArrayBuffer;
+import de.mas.wiiu.jnus.utils.IVCache;
+import de.mas.wiiu.jnus.utils.StreamUtils;
+import lombok.Synchronized;
+
+public class NUSEncryption extends AESEncryption implements ContentEncryptor {
+ public NUSEncryption(byte[] AESKey, byte[] IV) throws NoSuchProviderException {
+ super(AESKey, IV);
+ }
+
+ public NUSEncryption(Ticket ticket) throws NoSuchProviderException {
+ this(ticket.getDecryptedKey(), ticket.getIV());
+ }
+
+ @Synchronized
+ private byte[] encryptFileChunk(byte[] blockBuffer, int BLOCKSIZE, byte[] IV) {
+ return encryptFileChunk(blockBuffer, 0, BLOCKSIZE, IV);
+ }
+
+ @Synchronized
+ private byte[] encryptFileChunk(byte[] blockBuffer, int offset, int BLOCKSIZE, byte[] IV) {
+ if (IV != null) {
+ setIV(IV);
+ init();
+ }
+ return encrypt(blockBuffer, offset, BLOCKSIZE);
+ }
+
+ @Override
+ public long readEncryptedContentToStreamHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset) throws IOException {
+ int BLOCKSIZE = 0x10000;
+
+ int HASHBLOCKSIZE = 0x400;
+ int HASHEDBLOCKSIZE = 0xFC00;
+
+ int buffer_size = BLOCKSIZE;
+ byte[] decryptedBlockBuffer = new byte[buffer_size];
+ ByteArrayBuffer overflowbuffer = new ByteArrayBuffer(buffer_size);
+ int block = (int) (offset / 0x10000);
+ int inBlockBuffer = 0;
+ long read = 0;
+ long written = 0;
+
+ try {
+ do {
+ inBlockBuffer = StreamUtils.getChunkFromStream(in, decryptedBlockBuffer, overflowbuffer, BLOCKSIZE);
+ read += inBlockBuffer;
+ if (read - offset < payloadOffset) {
+ continue;
+ }
+ if (inBlockBuffer != buffer_size) {
+ break;
+ }
+
+ long curOffset = Math.max(0, payloadOffset - offset - read);
+
+ byte[] IV = new byte[16];
+ if (curOffset < HASHBLOCKSIZE) {
+ byte[] encryptedhashes = encryptFileChunk(Arrays.copyOfRange(decryptedBlockBuffer, 0, HASHBLOCKSIZE), HASHBLOCKSIZE, IV);
+
+ long writeLength = Math.min((encryptedhashes.length - curOffset), (size - written));
+
+ out.write(encryptedhashes, (int) curOffset, (int) writeLength);
+ written += writeLength;
+ } else {
+ curOffset = curOffset > HASHBLOCKSIZE ? curOffset - HASHBLOCKSIZE : 0;
+ }
+ if(curOffset < HASHEDBLOCKSIZE) {
+ int iv_start = (block % 16) * 20;
+ IV = Arrays.copyOfRange(decryptedBlockBuffer, iv_start, iv_start + 16);
+
+ byte[] encryptedContent = encryptFileChunk(Arrays.copyOfRange(decryptedBlockBuffer, HASHBLOCKSIZE, HASHEDBLOCKSIZE + HASHBLOCKSIZE),
+ HASHEDBLOCKSIZE, IV);
+
+ long writeLength = Math.min((encryptedContent.length - curOffset), (size - written));
+ out.write(encryptedContent, (int) curOffset, (int) writeLength);
+ written += writeLength;
+ }
+
+ block++;
+ } while (inBlockBuffer == buffer_size);
+ } finally {
+ StreamUtils.closeAll(in, out);
+ }
+ return written > 0 ? written : -1;
+ }
+
+ @Override
+ public long readEncryptedContentToStreamNonHashed(InputStream in, OutputStream out, long offset, long size, long payloadOffset, byte[] IV,
+ IVCache ivcache) throws IOException {
+ int BLOCKSIZE = 0x08000;
+
+ int buffer_size = BLOCKSIZE;
+ byte[] decryptedBlockBuffer = new byte[buffer_size];
+ ByteArrayBuffer overflowbuffer = new ByteArrayBuffer(buffer_size);
+ int inBlockBuffer = 0;
+
+ setIV(IV);
+ init();
+
+ long read = 0;
+ long written = 0;
+
+ long curPos = offset;
+
+ try {
+ do {
+ int curReadLength = (int) (curPos % buffer_size);
+ if (curReadLength == 0) {
+ curReadLength = buffer_size;
+ }
+ inBlockBuffer = StreamUtils.getChunkFromStream(in, decryptedBlockBuffer, overflowbuffer, curReadLength);
+ if (inBlockBuffer < 0) {
+ break;
+ }
+ curPos += inBlockBuffer;
+ read += inBlockBuffer;
+
+ byte[] output = encrypt(decryptedBlockBuffer, 0, inBlockBuffer);
+
+ byte[] curIV = Arrays.copyOfRange(output, output.length - 16, output.length);
+ ivcache.addForOffset(curPos, curIV);
+
+ setIV(Arrays.copyOfRange(output, BLOCKSIZE - 16, BLOCKSIZE));
+ init();
+
+ if (read < payloadOffset) {
+ continue;
+ }
+
+ long writeOffset = Math.max(0, payloadOffset - offset - read);
+ long writeLength = Math.min((output.length - writeOffset), (size - written));
+
+ out.write(output, (int) writeOffset, (int) writeLength);
+ written += writeLength;
+ } while (written < size);
+ } finally {
+ StreamUtils.closeAll(in, out);
+ }
+ return written > 0 ? written : -1;
+ }
+}