mirror of
https://github.com/Maschell/JNUSLib.git
synced 2024-11-26 01:44:17 +01:00
Convert the NUSDataProvider class into an interfacer. All Utility functions are moved to a new DataProviderUtils class.
Changes the return values of the raw TMD/TICKET/CERT to Optionals. Replacing some more null values with Optionals
This commit is contained in:
parent
40cb09873d
commit
d08a42719a
@ -17,17 +17,20 @@
|
|||||||
package de.mas.wiiu.jnus;
|
package de.mas.wiiu.jnus;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.concurrent.CompletableFuture;
|
import java.util.concurrent.CompletableFuture;
|
||||||
import java.util.concurrent.CompletionException;
|
import java.util.concurrent.CompletionException;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
|
||||||
import de.mas.wiiu.jnus.entities.content.Content;
|
import de.mas.wiiu.jnus.entities.content.Content;
|
||||||
import de.mas.wiiu.jnus.implementations.NUSDataProvider;
|
import de.mas.wiiu.jnus.implementations.NUSDataProvider;
|
||||||
|
import de.mas.wiiu.jnus.utils.DataProviderUtils;
|
||||||
import de.mas.wiiu.jnus.utils.FileUtils;
|
import de.mas.wiiu.jnus.utils.FileUtils;
|
||||||
import de.mas.wiiu.jnus.utils.Parallelizable;
|
import de.mas.wiiu.jnus.utils.Parallelizable;
|
||||||
import de.mas.wiiu.jnus.utils.Utils;
|
import de.mas.wiiu.jnus.utils.Utils;
|
||||||
@ -68,7 +71,7 @@ public final class ExtractionService {
|
|||||||
Utils.createDir(outputFolder);
|
Utils.createDir(outputFolder);
|
||||||
NUSDataProvider dataProvider = getDataProvider();
|
NUSDataProvider dataProvider = getDataProvider();
|
||||||
for (Content c : list) {
|
for (Content c : list) {
|
||||||
dataProvider.saveContentH3Hash(c, outputFolder);
|
DataProviderUtils.saveContentH3Hash(dataProvider, c, outputFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -87,9 +90,9 @@ public final class ExtractionService {
|
|||||||
public void extractEncryptedContentTo(Content content, String outputFolder, boolean withHashes) throws IOException {
|
public void extractEncryptedContentTo(Content content, String outputFolder, boolean withHashes) throws IOException {
|
||||||
NUSDataProvider dataProvider = getDataProvider();
|
NUSDataProvider dataProvider = getDataProvider();
|
||||||
if (withHashes) {
|
if (withHashes) {
|
||||||
dataProvider.saveEncryptedContentWithH3Hash(content, outputFolder);
|
DataProviderUtils.saveEncryptedContentWithH3Hash(dataProvider, content, outputFolder);
|
||||||
} else {
|
} else {
|
||||||
dataProvider.saveEncryptedContent(content, outputFolder);
|
DataProviderUtils.saveEncryptedContent(dataProvider, content, outputFolder);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -117,12 +120,7 @@ public final class ExtractionService {
|
|||||||
public void extractTMDTo(String output) throws IOException {
|
public void extractTMDTo(String output) throws IOException {
|
||||||
Utils.createDir(output);
|
Utils.createDir(output);
|
||||||
|
|
||||||
byte[] rawTMD = getDataProvider().getRawTMD();
|
byte[] rawTMD = getDataProvider().getRawTMD().orElseThrow(() -> new FileNotFoundException("TMD not found"));
|
||||||
|
|
||||||
if (rawTMD == null || rawTMD.length == 0) {
|
|
||||||
log.info("Couldn't write TMD: No TMD loaded");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String tmd_path = output + File.separator + Settings.TMD_FILENAME;
|
String tmd_path = output + File.separator + Settings.TMD_FILENAME;
|
||||||
log.info("Extracting TMD to: " + tmd_path);
|
log.info("Extracting TMD to: " + tmd_path);
|
||||||
FileUtils.saveByteArrayToFile(tmd_path, rawTMD);
|
FileUtils.saveByteArrayToFile(tmd_path, rawTMD);
|
||||||
@ -131,29 +129,25 @@ public final class ExtractionService {
|
|||||||
public boolean extractTicketTo(String output) throws IOException {
|
public boolean extractTicketTo(String output) throws IOException {
|
||||||
Utils.createDir(output);
|
Utils.createDir(output);
|
||||||
|
|
||||||
byte[] rawTicket = getDataProvider().getRawTicket();
|
byte[] rawTicket = getDataProvider().getRawTicket().orElseThrow(() -> new FileNotFoundException("Ticket not found"));
|
||||||
|
|
||||||
if (rawTicket == null || rawTicket.length == 0) {
|
|
||||||
log.info("Couldn't write Ticket: No Ticket loaded");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
String ticket_path = output + File.separator + Settings.TICKET_FILENAME;
|
String ticket_path = output + File.separator + Settings.TICKET_FILENAME;
|
||||||
log.info("Extracting Ticket to: " + ticket_path);
|
log.info("Extracting Ticket to: " + ticket_path);
|
||||||
return FileUtils.saveByteArrayToFile(ticket_path, rawTicket);
|
return FileUtils.saveByteArrayToFile(ticket_path, rawTicket);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void extractCertTo(String output) throws IOException {
|
public boolean extractCertTo(String output) throws IOException {
|
||||||
Utils.createDir(output);
|
Utils.createDir(output);
|
||||||
|
|
||||||
byte[] rawCert = getDataProvider().getRawCert();
|
Optional<byte[]> dataOpt = getDataProvider().getRawCert();
|
||||||
|
|
||||||
if (rawCert == null || rawCert.length == 0) {
|
if (!dataOpt.isPresent()) {
|
||||||
log.info("Couldn't write Cert: No Cert loaded");
|
return false;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
String cert_path = output + File.separator + Settings.CERT_FILENAME;
|
|
||||||
log.info("Extracting Cert to: " + cert_path);
|
String path = output + File.separator + Settings.CERT_FILENAME;
|
||||||
FileUtils.saveByteArrayToFile(cert_path, rawCert);
|
log.info("Extracting Cert to: " + path);
|
||||||
|
return FileUtils.saveByteArrayToFile(path, dataOpt.get());
|
||||||
}
|
}
|
||||||
|
|
||||||
public void extractAll(String outputFolder) throws IOException {
|
public void extractAll(String outputFolder) throws IOException {
|
||||||
@ -166,8 +160,5 @@ public final class ExtractionService {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getBytesFromContent(Content c, long offset, long size) throws IOException {
|
|
||||||
return getDataProvider().getChunkFromContent(c, offset, (int) size);
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -40,13 +40,10 @@ abstract class NUSTitleLoader {
|
|||||||
NUSDataProvider dataProvider = getDataProvider(result, config);
|
NUSDataProvider dataProvider = getDataProvider(result, config);
|
||||||
result.setDataProvider(dataProvider);
|
result.setDataProvider(dataProvider);
|
||||||
|
|
||||||
TMD tmd = TMD.parseTMD(dataProvider.getRawTMD());
|
byte[] tmdData = dataProvider.getRawTMD().orElseThrow(() -> new ParseException("No TMD data found", 0));
|
||||||
result.setTMD(tmd);
|
|
||||||
|
|
||||||
if (tmd == null) {
|
TMD tmd = TMD.parseTMD(tmdData);
|
||||||
log.info("TMD not found.");
|
result.setTMD(tmd);
|
||||||
throw new Exception();
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.isNoDecryption()) {
|
if (config.isNoDecryption()) {
|
||||||
return result;
|
return result;
|
||||||
@ -54,17 +51,19 @@ abstract class NUSTitleLoader {
|
|||||||
|
|
||||||
Ticket ticket = config.getTicket();
|
Ticket ticket = config.getTicket();
|
||||||
if (ticket == null) {
|
if (ticket == null) {
|
||||||
ticket = Ticket.parseTicket(dataProvider.getRawTicket());
|
Optional<byte[]> ticketOpt = dataProvider.getRawTicket();
|
||||||
|
if (ticketOpt.isPresent()) {
|
||||||
|
ticket = Ticket.parseTicket(ticketOpt.get(), config.getCommonKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(ticket == null) {
|
||||||
|
new ParseException("Failed to get ticket data",0);
|
||||||
}
|
}
|
||||||
result.setTicket(ticket);
|
result.setTicket(ticket);
|
||||||
|
|
||||||
Content fstContent = tmd.getContentByIndex(0);
|
Content fstContent = tmd.getContentByIndex(0);
|
||||||
|
|
||||||
InputStream fstContentEncryptedStream = dataProvider.getInputStreamFromContent(fstContent, 0);
|
InputStream fstContentEncryptedStream = dataProvider.getInputStreamFromContent(fstContent, 0, Optional.of(fstContent.getEncryptedFileSize()));
|
||||||
if (fstContentEncryptedStream == null) {
|
|
||||||
log.warning("FST is null");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] fstBytes = StreamUtils.getBytesFromStream(fstContentEncryptedStream, (int) fstContent.getEncryptedFileSize());
|
byte[] fstBytes = StreamUtils.getBytesFromStream(fstContentEncryptedStream, (int) fstContent.getEncryptedFileSize());
|
||||||
|
|
||||||
|
@ -14,149 +14,21 @@
|
|||||||
* You should have received a copy of the GNU General Public License
|
* You should have received a copy of the GNU General Public License
|
||||||
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
package de.mas.wiiu.jnus.implementations;
|
package de.mas.wiiu.jnus.implementations;
|
||||||
|
|
||||||
import java.io.File;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Arrays;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import de.mas.wiiu.jnus.NUSTitle;
|
|
||||||
import de.mas.wiiu.jnus.Settings;
|
|
||||||
import de.mas.wiiu.jnus.entities.content.Content;
|
import de.mas.wiiu.jnus.entities.content.Content;
|
||||||
import de.mas.wiiu.jnus.utils.FileUtils;
|
|
||||||
import de.mas.wiiu.jnus.utils.HashUtil;
|
|
||||||
import de.mas.wiiu.jnus.utils.StreamUtils;
|
import de.mas.wiiu.jnus.utils.StreamUtils;
|
||||||
import de.mas.wiiu.jnus.utils.Utils;
|
|
||||||
import lombok.Getter;
|
|
||||||
import lombok.NonNull;
|
|
||||||
import lombok.extern.java.Log;
|
|
||||||
|
|
||||||
@Log
|
public interface NUSDataProvider {
|
||||||
/**
|
|
||||||
* Service Methods for loading NUS/Content data from different sources
|
|
||||||
*
|
|
||||||
* @author Maschell
|
|
||||||
*
|
|
||||||
*/
|
|
||||||
public abstract class NUSDataProvider {
|
|
||||||
@Getter private final NUSTitle NUSTitle;
|
|
||||||
|
|
||||||
public NUSDataProvider(NUSTitle title) {
|
default public byte[] getChunkFromContent(Content content, long offset, int size) throws IOException {
|
||||||
this.NUSTitle = title;
|
return StreamUtils.getBytesFromStream(getInputStreamFromContent(content, offset, Optional.of((long) size)), size);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the given content encrypted with his .h3 file in the given directory. The Target directory will be created if it's missing. If the content is not
|
|
||||||
* hashed, no .h3 will be saved
|
|
||||||
*
|
|
||||||
* @param content
|
|
||||||
* Content that should be saved
|
|
||||||
* @param outputFolder
|
|
||||||
* Target directory where the files will be stored in.
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void saveEncryptedContentWithH3Hash(@NonNull Content content, @NonNull String outputFolder) throws IOException {
|
|
||||||
saveContentH3Hash(content, outputFolder);
|
|
||||||
saveEncryptedContent(content, outputFolder);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the .h3 file of the given content into the given directory. The Target directory will be created if it's missing. If the content is not hashed, no
|
|
||||||
* .h3 will be saved
|
|
||||||
*
|
|
||||||
* @param content
|
|
||||||
* The content of which the h3 hashes should be saved
|
|
||||||
* @param outputFolder
|
|
||||||
* @return
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void saveContentH3Hash(@NonNull Content content, @NonNull String outputFolder) throws IOException {
|
|
||||||
if (!content.isHashed()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
String h3Filename = String.format("%08X%s", content.getID(), Settings.H3_EXTENTION);
|
|
||||||
File output = new File(outputFolder + File.separator + h3Filename);
|
|
||||||
|
|
||||||
if (output.exists()) {
|
|
||||||
if (Arrays.equals(content.getSHA2Hash(), HashUtil.hashSHA1(output))) {
|
|
||||||
log.info(h3Filename + " already exists");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
if (Arrays.equals(content.getSHA2Hash(), Arrays.copyOf(HashUtil.hashSHA256(output), 20))) { // 0005000c1f941200 used sha256 instead of SHA1
|
|
||||||
log.info(h3Filename + " already exists");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.warning(h3Filename + " already exists but hash is differrent than expected.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
byte[] hash = getContentH3Hash(content);
|
|
||||||
if (hash == null || hash.length == 0) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
log.warning("Saving " + h3Filename + " ");
|
|
||||||
|
|
||||||
FileUtils.saveByteArrayToFile(output, hash);
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Saves the given content encrypted in the given directory. The Target directory will be created if it's missing. If the content is not encrypted at all,
|
|
||||||
* it will be just saved anyway.
|
|
||||||
*
|
|
||||||
* @param content
|
|
||||||
* Content that should be saved
|
|
||||||
* @param outputFolder
|
|
||||||
* Target directory where the files will be stored in.
|
|
||||||
* @return
|
|
||||||
* @throws IOException
|
|
||||||
*/
|
|
||||||
public void saveEncryptedContent(@NonNull Content content, @NonNull String outputFolder) throws IOException {
|
|
||||||
int maxTries = 3;
|
|
||||||
int i = 0;
|
|
||||||
while (i < maxTries) {
|
|
||||||
File output = new File(outputFolder + File.separator + content.getFilename());
|
|
||||||
if (output.exists()) {
|
|
||||||
if (output.length() == content.getEncryptedFileSizeAligned()) {
|
|
||||||
log.info(content.getFilename() + "Encrypted content alreadys exists, skipped");
|
|
||||||
return;
|
|
||||||
} else {
|
|
||||||
log.warning(content.getFilename() + " Encrypted content alreadys exists, but the length is not as expected. Saving it again. "
|
|
||||||
+ output.length() + " " + content.getEncryptedFileSizeAligned() + " Difference: "
|
|
||||||
+ (output.length() - content.getEncryptedFileSizeAligned()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Utils.createDir(outputFolder);
|
|
||||||
InputStream inputStream = getInputStreamFromContent(content, 0);
|
|
||||||
if (inputStream == null) {
|
|
||||||
log.info(content.getFilename() + " Couldn't save encrypted content. Input stream was null");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
log.warning("loading " + content.getFilename());
|
|
||||||
FileUtils.saveInputStreamToFile(output, inputStream, content.getEncryptedFileSizeAligned());
|
|
||||||
|
|
||||||
File outputNow = new File(outputFolder + File.separator + content.getFilename());
|
|
||||||
if (outputNow.exists()) {
|
|
||||||
if (outputNow.length() != content.getEncryptedFileSizeAligned()) {
|
|
||||||
log.warning(content.getFilename() + " Encrypted content length is not as expected. Saving it again. Loaded: " + outputNow.length()
|
|
||||||
+ " Expected: " + content.getEncryptedFileSizeAligned() + " Difference: "
|
|
||||||
+ (outputNow.length() - content.getEncryptedFileSizeAligned()));
|
|
||||||
i++;
|
|
||||||
continue;
|
|
||||||
} else {
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
public byte[] getChunkFromContent(Content content, long offset, int size) throws IOException {
|
|
||||||
return StreamUtils.getBytesFromStream(getInputStreamFromContent(content, offset, Optional.of((long)size)), size);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -168,18 +40,17 @@ public abstract class NUSDataProvider {
|
|||||||
*/
|
*/
|
||||||
public abstract InputStream getInputStreamFromContent(Content content, long offset, Optional<Long> size) throws IOException;
|
public abstract InputStream getInputStreamFromContent(Content content, long offset, Optional<Long> size) throws IOException;
|
||||||
|
|
||||||
public InputStream getInputStreamFromContent(Content content, long offset) throws IOException {
|
default public InputStream getInputStreamFromContent(Content content, long offset) throws IOException {
|
||||||
return getInputStreamFromContent(content, offset, Optional.empty());
|
return getInputStreamFromContent(content, offset, Optional.empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: JavaDocs
|
public abstract Optional<byte[]> getContentH3Hash(Content content) throws IOException;
|
||||||
public abstract byte[] getContentH3Hash(Content content) throws IOException;
|
|
||||||
|
|
||||||
public abstract byte[] getRawTMD() throws IOException;
|
public abstract Optional<byte[]> getRawTMD() throws IOException;
|
||||||
|
|
||||||
public abstract byte[] getRawTicket() throws IOException;
|
public abstract Optional<byte[]> getRawTicket() throws IOException;
|
||||||
|
|
||||||
public abstract byte[] getRawCert() throws IOException;
|
public abstract Optional<byte[]> getRawCert() throws IOException;
|
||||||
|
|
||||||
public abstract void cleanup() throws IOException;
|
public abstract void cleanup() throws IOException;
|
||||||
|
|
||||||
|
@ -33,11 +33,10 @@ import lombok.Getter;
|
|||||||
import lombok.extern.java.Log;
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
public final class NUSDataProviderLocal extends NUSDataProvider {
|
public final class NUSDataProviderLocal implements NUSDataProvider {
|
||||||
@Getter private final String localPath;
|
@Getter private final String localPath;
|
||||||
|
|
||||||
public NUSDataProviderLocal(NUSTitle nustitle, String localPath) {
|
public NUSDataProviderLocal(String localPath) {
|
||||||
super(nustitle);
|
|
||||||
this.localPath = localPath;
|
this.localPath = localPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -59,48 +58,48 @@ public final class NUSDataProviderLocal extends NUSDataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getContentH3Hash(Content content) throws IOException {
|
public Optional<byte[]> getContentH3Hash(Content content) throws IOException {
|
||||||
String h3Filename = String.format("%08X%s", content.getID(), Settings.H3_EXTENTION);
|
String h3Filename = String.format("%08X%s", content.getID(), Settings.H3_EXTENTION);
|
||||||
File filepath = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), h3Filename);
|
File filepath = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), h3Filename);
|
||||||
if (filepath == null || !filepath.exists()) {
|
if (filepath == null || !filepath.exists()) {
|
||||||
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + h3Filename + "\", file does not exist";
|
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + h3Filename + "\", file does not exist";
|
||||||
log.warning(errormsg);
|
log.warning(errormsg);
|
||||||
return new byte[0];
|
throw new FileNotFoundException(errormsg);
|
||||||
}
|
}
|
||||||
return Files.readAllBytes(filepath.toPath());
|
return Optional.of(Files.readAllBytes(filepath.toPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawTMD() throws IOException {
|
public Optional<byte[]> getRawTMD() throws IOException {
|
||||||
File file = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), Settings.TMD_FILENAME);
|
File file = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), Settings.TMD_FILENAME);
|
||||||
if (file == null || !file.exists()) {
|
if (file == null || !file.exists()) {
|
||||||
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + Settings.TMD_FILENAME + "\", file does not exist";
|
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + Settings.TMD_FILENAME + "\", file does not exist";
|
||||||
log.warning(errormsg);
|
log.warning(errormsg);
|
||||||
throw new FileNotFoundException(errormsg);
|
throw new FileNotFoundException(errormsg);
|
||||||
}
|
}
|
||||||
return Files.readAllBytes(file.toPath());
|
return Optional.of(Files.readAllBytes(file.toPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawTicket() throws IOException {
|
public Optional<byte[]> getRawTicket() throws IOException {
|
||||||
File file = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), Settings.TICKET_FILENAME);
|
File file = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), Settings.TICKET_FILENAME);
|
||||||
if (file == null || !file.exists()) {
|
if (file == null || !file.exists()) {
|
||||||
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + Settings.TICKET_FILENAME + "\", file does not exist";
|
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + Settings.TICKET_FILENAME + "\", file does not exist";
|
||||||
log.warning(errormsg);
|
log.warning(errormsg);
|
||||||
throw new FileNotFoundException(errormsg);
|
throw new FileNotFoundException(errormsg);
|
||||||
}
|
}
|
||||||
return Files.readAllBytes(file.toPath());
|
return Optional.of(Files.readAllBytes(file.toPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawCert() throws IOException {
|
public Optional<byte[]> getRawCert() throws IOException {
|
||||||
File file = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), Settings.CERT_FILENAME);
|
File file = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), Settings.CERT_FILENAME);
|
||||||
if (file == null || !file.exists()) {
|
if (file == null || !file.exists()) {
|
||||||
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + Settings.CERT_FILENAME + "\", file does not exist";
|
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + Settings.CERT_FILENAME + "\", file does not exist";
|
||||||
log.warning(errormsg);
|
log.warning(errormsg);
|
||||||
throw new FileNotFoundException(errormsg);
|
throw new FileNotFoundException(errormsg);
|
||||||
}
|
}
|
||||||
return Files.readAllBytes(file.toPath());
|
return Optional.of(Files.readAllBytes(file.toPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -2,28 +2,26 @@ package de.mas.wiiu.jnus.implementations;
|
|||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.FileInputStream;
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileNotFoundException;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import de.mas.wiiu.jnus.NUSTitle;
|
|
||||||
import de.mas.wiiu.jnus.Settings;
|
import de.mas.wiiu.jnus.Settings;
|
||||||
import de.mas.wiiu.jnus.entities.content.Content;
|
import de.mas.wiiu.jnus.entities.content.Content;
|
||||||
import de.mas.wiiu.jnus.implementations.NUSDataProvider;
|
|
||||||
import de.mas.wiiu.jnus.utils.StreamUtils;
|
import de.mas.wiiu.jnus.utils.StreamUtils;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
public class NUSDataProviderLocalBackup extends NUSDataProvider {
|
public class NUSDataProviderLocalBackup implements NUSDataProvider {
|
||||||
@Getter private final String localPath;
|
@Getter private final String localPath;
|
||||||
private final short titleVersion;
|
private final short titleVersion;
|
||||||
|
|
||||||
public NUSDataProviderLocalBackup(NUSTitle nustitle, String localPath) {
|
public NUSDataProviderLocalBackup(String localPath) {
|
||||||
this(nustitle, localPath, (short) Settings.LATEST_TMD_VERSION);
|
this(localPath, (short) Settings.LATEST_TMD_VERSION);
|
||||||
}
|
}
|
||||||
|
|
||||||
public NUSDataProviderLocalBackup(NUSTitle nustitle, String localPath, short version) {
|
public NUSDataProviderLocalBackup(String localPath, short version) {
|
||||||
super(nustitle);
|
|
||||||
this.localPath = localPath;
|
this.localPath = localPath;
|
||||||
this.titleVersion = version;
|
this.titleVersion = version;
|
||||||
}
|
}
|
||||||
@ -36,7 +34,7 @@ public class NUSDataProviderLocalBackup extends NUSDataProvider {
|
|||||||
public InputStream getInputStreamFromContent(Content content, long offset, Optional<Long> size) throws IOException {
|
public InputStream getInputStreamFromContent(Content content, long offset, Optional<Long> size) throws IOException {
|
||||||
File filepath = new File(getFilePathOnDisk(content));
|
File filepath = new File(getFilePathOnDisk(content));
|
||||||
if (!filepath.exists()) {
|
if (!filepath.exists()) {
|
||||||
return null;
|
throw new FileNotFoundException(filepath.getAbsolutePath() + " was not found.");
|
||||||
}
|
}
|
||||||
InputStream in = new FileInputStream(filepath);
|
InputStream in = new FileInputStream(filepath);
|
||||||
StreamUtils.skipExactly(in, offset);
|
StreamUtils.skipExactly(in, offset);
|
||||||
@ -44,40 +42,38 @@ public class NUSDataProviderLocalBackup extends NUSDataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getContentH3Hash(Content content) throws IOException {
|
public Optional<byte[]> getContentH3Hash(Content content) throws IOException {
|
||||||
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()) {
|
|
||||||
return new byte[0];
|
return Optional.of(Files.readAllBytes(h3File.toPath()));
|
||||||
}
|
|
||||||
return Files.readAllBytes(h3File.toPath());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawTMD() throws IOException {
|
public Optional<byte[]> getRawTMD() throws IOException {
|
||||||
String inputPath = getLocalPath();
|
String inputPath = getLocalPath();
|
||||||
String tmdPath = inputPath + File.separator + Settings.TMD_FILENAME;
|
String tmdPath = inputPath + File.separator + Settings.TMD_FILENAME;
|
||||||
if (titleVersion != Settings.LATEST_TMD_VERSION) {
|
if (titleVersion != Settings.LATEST_TMD_VERSION) {
|
||||||
tmdPath = inputPath + File.separator + "v" + titleVersion + File.separator + Settings.TMD_FILENAME;
|
tmdPath = inputPath + File.separator + "v" + titleVersion + File.separator + Settings.TMD_FILENAME;
|
||||||
}
|
}
|
||||||
File tmdFile = new File(tmdPath);
|
File tmdFile = new File(tmdPath);
|
||||||
return Files.readAllBytes(tmdFile.toPath());
|
return Optional.of(Files.readAllBytes(tmdFile.toPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawTicket() throws IOException {
|
public Optional<byte[]> getRawTicket() throws IOException {
|
||||||
String inputPath = getLocalPath();
|
String inputPath = getLocalPath();
|
||||||
String ticketPath = inputPath + File.separator + Settings.TICKET_FILENAME;
|
String ticketPath = inputPath + File.separator + Settings.TICKET_FILENAME;
|
||||||
File ticketFile = new File(ticketPath);
|
File ticketFile = new File(ticketPath);
|
||||||
return Files.readAllBytes(ticketFile.toPath());
|
return Optional.of(Files.readAllBytes(ticketFile.toPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawCert() throws IOException {
|
public Optional<byte[]> getRawCert() throws IOException {
|
||||||
String inputPath = getLocalPath();
|
String inputPath = getLocalPath();
|
||||||
String certPath = inputPath + File.separator + Settings.CERT_FILENAME;
|
String certPath = inputPath + File.separator + Settings.CERT_FILENAME;
|
||||||
File certFile = new File(certPath);
|
File certFile = new File(certPath);
|
||||||
return Files.readAllBytes(certFile.toPath());
|
return Optional.of(Files.readAllBytes(certFile.toPath()));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -16,25 +16,21 @@
|
|||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
package de.mas.wiiu.jnus.implementations;
|
package de.mas.wiiu.jnus.implementations;
|
||||||
|
|
||||||
import java.io.ByteArrayOutputStream;
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import de.mas.wiiu.jnus.NUSTitle;
|
|
||||||
import de.mas.wiiu.jnus.Settings;
|
import de.mas.wiiu.jnus.Settings;
|
||||||
import de.mas.wiiu.jnus.entities.TMD;
|
|
||||||
import de.mas.wiiu.jnus.entities.content.Content;
|
import de.mas.wiiu.jnus.entities.content.Content;
|
||||||
import de.mas.wiiu.jnus.utils.Parallelizable;
|
import de.mas.wiiu.jnus.utils.Parallelizable;
|
||||||
import de.mas.wiiu.jnus.utils.download.NUSDownloadService;
|
import de.mas.wiiu.jnus.utils.download.NUSDownloadService;
|
||||||
import lombok.Getter;
|
import lombok.Getter;
|
||||||
|
|
||||||
public class NUSDataProviderRemote extends NUSDataProvider implements Parallelizable {
|
public class NUSDataProviderRemote implements NUSDataProvider, Parallelizable {
|
||||||
@Getter private final int version;
|
@Getter private final int version;
|
||||||
@Getter private final long titleID;
|
@Getter private final long titleID;
|
||||||
|
|
||||||
public NUSDataProviderRemote(NUSTitle title, int version, long titleID) {
|
public NUSDataProviderRemote(int version, long titleID) {
|
||||||
super(title);
|
|
||||||
this.version = version;
|
this.version = version;
|
||||||
this.titleID = titleID;
|
this.titleID = titleID;
|
||||||
}
|
}
|
||||||
@ -46,53 +42,49 @@ public class NUSDataProviderRemote extends NUSDataProvider implements Paralleliz
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String getRemoteURL(Content content) {
|
private String getRemoteURL(Content content) {
|
||||||
return String.format("%016x/%08X", getNUSTitle().getTMD().getTitleID(), content.getID());
|
return String.format("%016x/%08X", titleID, content.getID());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getContentH3Hash(Content content) throws IOException {
|
public Optional<byte[]> getContentH3Hash(Content content) throws IOException {
|
||||||
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
|
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
|
||||||
String url = getRemoteURL(content) + Settings.H3_EXTENTION;
|
String url = getRemoteURL(content) + Settings.H3_EXTENTION;
|
||||||
return downloadService.downloadToByteArray(url);
|
|
||||||
|
byte[] res = downloadService.downloadToByteArray(url);
|
||||||
|
if (res == null || res.length == 0) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(res);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawTMD() throws IOException {
|
public Optional<byte[]> getRawTMD() throws IOException {
|
||||||
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
|
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
|
||||||
|
|
||||||
long titleID = getTitleID();
|
long titleID = getTitleID();
|
||||||
int version = getVersion();
|
int version = getVersion();
|
||||||
|
|
||||||
return downloadService.downloadTMDToByteArray(titleID, version);
|
byte[] res = downloadService.downloadTMDToByteArray(titleID, version);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
if (res == null || res.length == 0) {
|
||||||
public byte[] getRawTicket() throws IOException {
|
return Optional.empty();
|
||||||
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
|
|
||||||
|
|
||||||
long titleID = getNUSTitle().getTMD().getTitleID();
|
|
||||||
|
|
||||||
return downloadService.downloadTicketToByteArray(titleID);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getRawCert() throws IOException {
|
|
||||||
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
|
|
||||||
byte[] defaultCert = downloadService.downloadDefaultCertToByteArray();
|
|
||||||
|
|
||||||
TMD tmd = getNUSTitle().getTMD();
|
|
||||||
byte[] result = new byte[0];
|
|
||||||
try {
|
|
||||||
ByteArrayOutputStream fos = new ByteArrayOutputStream();
|
|
||||||
fos.write(tmd.getCert1());
|
|
||||||
fos.write(tmd.getCert2());
|
|
||||||
fos.write(defaultCert);
|
|
||||||
result = fos.toByteArray();
|
|
||||||
fos.close();
|
|
||||||
} catch (Exception e) {
|
|
||||||
e.printStackTrace();
|
|
||||||
}
|
}
|
||||||
return result;
|
return Optional.of(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<byte[]> getRawTicket() throws IOException {
|
||||||
|
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
|
||||||
|
byte[] res = downloadService.downloadTicketToByteArray(titleID);
|
||||||
|
if (res == null || res.length == 0) {
|
||||||
|
return Optional.empty();
|
||||||
|
}
|
||||||
|
return Optional.of(res);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Optional<byte[]> getRawCert() throws IOException {
|
||||||
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Copyright (C) 2016-2018 Maschell
|
* Copyright (C) 2016-2019 Maschell
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -20,9 +20,6 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
import de.mas.wiiu.jnus.NUSTitle;
|
|
||||||
import de.mas.wiiu.jnus.Settings;
|
|
||||||
import de.mas.wiiu.jnus.entities.TMD;
|
|
||||||
import de.mas.wiiu.jnus.entities.content.Content;
|
import de.mas.wiiu.jnus.entities.content.Content;
|
||||||
import de.mas.wiiu.jnus.implementations.wud.parser.WUDGamePartition;
|
import de.mas.wiiu.jnus.implementations.wud.parser.WUDGamePartition;
|
||||||
import de.mas.wiiu.jnus.implementations.wud.parser.WUDPartitionHeader;
|
import de.mas.wiiu.jnus.implementations.wud.parser.WUDPartitionHeader;
|
||||||
@ -31,29 +28,20 @@ import lombok.Getter;
|
|||||||
import lombok.extern.java.Log;
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
public class NUSDataProviderWUD extends NUSDataProvider {
|
public class NUSDataProviderWUD implements NUSDataProvider {
|
||||||
@Getter private final WUDGamePartition gamePartition;
|
@Getter private final WUDGamePartition gamePartition;
|
||||||
@Getter private final WUDDiscReader discReader;
|
@Getter private final WUDDiscReader discReader;
|
||||||
|
|
||||||
private final TMD tmd;
|
public NUSDataProviderWUD(WUDGamePartition gamePartition, WUDDiscReader discReader) {
|
||||||
|
|
||||||
public NUSDataProviderWUD(NUSTitle title, WUDGamePartition gamePartition, WUDDiscReader discReader) {
|
|
||||||
super(title);
|
|
||||||
this.gamePartition = gamePartition;
|
this.gamePartition = gamePartition;
|
||||||
this.discReader = discReader;
|
this.discReader = discReader;
|
||||||
this.tmd = TMD.parseTMD(getRawTMD());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getOffsetInWUD(Content content) {
|
public long getOffsetInWUD(Content content) {
|
||||||
if (content.getContentFSTInfo() == null) {
|
if (content.getIndex() == 0) { // Index 0 is the FST which is at the beginning of the partion;
|
||||||
return getAbsoluteReadOffset();
|
return getGamePartition().getAbsolutePartitionOffset();
|
||||||
} else {
|
|
||||||
return getAbsoluteReadOffset() + content.getContentFSTInfo().getOffset();
|
|
||||||
}
|
}
|
||||||
}
|
return getGamePartition().getAbsolutePartitionOffset() + content.getContentFSTInfo().getOffset();
|
||||||
|
|
||||||
public long getAbsoluteReadOffset() {
|
|
||||||
return (long) Settings.WIIU_DECRYPTED_AREA_OFFSET + getGamePartition().getPartitionOffset();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -66,45 +54,34 @@ public class NUSDataProviderWUD extends NUSDataProvider {
|
|||||||
WUDDiscReader discReader = getDiscReader();
|
WUDDiscReader discReader = getDiscReader();
|
||||||
long offset = getOffsetInWUD(content) + fileOffsetBlock;
|
long offset = getOffsetInWUD(content) + fileOffsetBlock;
|
||||||
long usedSize = content.getEncryptedFileSize() - fileOffsetBlock;
|
long usedSize = content.getEncryptedFileSize() - fileOffsetBlock;
|
||||||
if(size.isPresent()) {
|
if (size.isPresent()) {
|
||||||
usedSize = size.get();
|
usedSize = size.get();
|
||||||
}
|
}
|
||||||
return discReader.readEncryptedToInputStream(offset, usedSize);
|
return discReader.readEncryptedToInputStream(offset, usedSize);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getContentH3Hash(Content content) throws IOException {
|
public Optional<byte[]> getContentH3Hash(Content content) throws IOException {
|
||||||
|
|
||||||
if (getGamePartitionHeader() == null) {
|
|
||||||
log.warning("GamePartitionHeader is null");
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!getGamePartitionHeader().isCalculatedHashes()) {
|
if (!getGamePartitionHeader().isCalculatedHashes()) {
|
||||||
log.info("Calculating h3 hashes");
|
log.info("Calculating h3 hashes");
|
||||||
getGamePartitionHeader().calculateHashes(getTMD().getAllContents());
|
getGamePartitionHeader().calculateHashes(getGamePartition().getTmd().getAllContents());
|
||||||
|
|
||||||
}
|
}
|
||||||
return getGamePartitionHeader().getH3Hash(content);
|
return getGamePartitionHeader().getH3Hash(content);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TMD getTMD() {
|
@Override
|
||||||
return tmd;
|
public Optional<byte[]> getRawTMD() {
|
||||||
|
return Optional.of(getGamePartition().getRawTMD());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawTMD() {
|
public Optional<byte[]> getRawTicket() {
|
||||||
return getGamePartition().getRawTMD();
|
return Optional.of(getGamePartition().getRawTicket());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawTicket() {
|
public Optional<byte[]> getRawCert() throws IOException {
|
||||||
return getGamePartition().getRawTicket();
|
return Optional.of(getGamePartition().getRawCert());
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public byte[] getRawCert() throws IOException {
|
|
||||||
return getGamePartition().getRawCert();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public WUDPartitionHeader getGamePartitionHeader() {
|
public WUDPartitionHeader getGamePartitionHeader() {
|
||||||
|
@ -35,12 +35,11 @@ import lombok.Setter;
|
|||||||
import lombok.extern.java.Log;
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
@Log
|
@Log
|
||||||
public class NUSDataProviderWoomy extends NUSDataProvider {
|
public class NUSDataProviderWoomy implements NUSDataProvider {
|
||||||
@Getter private final WoomyInfo woomyInfo;
|
@Getter private final WoomyInfo woomyInfo;
|
||||||
@Setter(AccessLevel.PRIVATE) private WoomyZipFile woomyZipFile;
|
@Setter(AccessLevel.PRIVATE) private WoomyZipFile woomyZipFile;
|
||||||
|
|
||||||
public NUSDataProviderWoomy(NUSTitle title, WoomyInfo woomyInfo) {
|
public NUSDataProviderWoomy(WoomyInfo woomyInfo) {
|
||||||
super(title);
|
|
||||||
this.woomyInfo = woomyInfo;
|
this.woomyInfo = woomyInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -56,19 +55,19 @@ public class NUSDataProviderWoomy extends NUSDataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getContentH3Hash(Content content) throws IOException {
|
public Optional<byte[]> getContentH3Hash(Content content) throws IOException {
|
||||||
ZipEntry entry = getWoomyInfo().getContentFiles().get(content.getFilename().toLowerCase());
|
ZipEntry entry = getWoomyInfo().getContentFiles().get(content.getFilename().toLowerCase());
|
||||||
if (entry != null) {
|
if (entry != null) {
|
||||||
WoomyZipFile zipFile = getNewWoomyZipFile();
|
WoomyZipFile zipFile = getNewWoomyZipFile();
|
||||||
byte[] result = zipFile.getEntryAsByte(entry);
|
byte[] result = zipFile.getEntryAsByte(entry);
|
||||||
zipFile.close();
|
zipFile.close();
|
||||||
return result;
|
return Optional.of(result);
|
||||||
}
|
}
|
||||||
return new byte[0];
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawTMD() throws IOException {
|
public Optional<byte[]> getRawTMD() throws IOException {
|
||||||
ZipEntry entry = getWoomyInfo().getContentFiles().get(Settings.TMD_FILENAME);
|
ZipEntry entry = getWoomyInfo().getContentFiles().get(Settings.TMD_FILENAME);
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
log.warning(Settings.TMD_FILENAME + " not found in woomy file");
|
log.warning(Settings.TMD_FILENAME + " not found in woomy file");
|
||||||
@ -77,11 +76,11 @@ public class NUSDataProviderWoomy extends NUSDataProvider {
|
|||||||
WoomyZipFile zipFile = getNewWoomyZipFile();
|
WoomyZipFile zipFile = getNewWoomyZipFile();
|
||||||
byte[] result = zipFile.getEntryAsByte(entry);
|
byte[] result = zipFile.getEntryAsByte(entry);
|
||||||
zipFile.close();
|
zipFile.close();
|
||||||
return result;
|
return Optional.of(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawTicket() throws IOException {
|
public Optional<byte[]> getRawTicket() throws IOException {
|
||||||
ZipEntry entry = getWoomyInfo().getContentFiles().get(Settings.TICKET_FILENAME);
|
ZipEntry entry = getWoomyInfo().getContentFiles().get(Settings.TICKET_FILENAME);
|
||||||
if (entry == null) {
|
if (entry == null) {
|
||||||
log.warning(Settings.TICKET_FILENAME + " not found in woomy file");
|
log.warning(Settings.TICKET_FILENAME + " not found in woomy file");
|
||||||
@ -91,7 +90,7 @@ public class NUSDataProviderWoomy extends NUSDataProvider {
|
|||||||
WoomyZipFile zipFile = getNewWoomyZipFile();
|
WoomyZipFile zipFile = getNewWoomyZipFile();
|
||||||
byte[] result = zipFile.getEntryAsByte(entry);
|
byte[] result = zipFile.getEntryAsByte(entry);
|
||||||
zipFile.close();
|
zipFile.close();
|
||||||
return result;
|
return Optional.of(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
public WoomyZipFile getSharedWoomyZipFile() throws ZipException, IOException {
|
public WoomyZipFile getSharedWoomyZipFile() throws ZipException, IOException {
|
||||||
@ -113,7 +112,7 @@ public class NUSDataProviderWoomy extends NUSDataProvider {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public byte[] getRawCert() throws IOException {
|
public Optional<byte[]> getRawCert() throws IOException {
|
||||||
return new byte[0];
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Copyright (C) 2016-2018 Maschell
|
* Copyright (C) 2016-2019 Maschell
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -16,19 +16,24 @@
|
|||||||
****************************************************************************/
|
****************************************************************************/
|
||||||
package de.mas.wiiu.jnus.implementations.wud.parser;
|
package de.mas.wiiu.jnus.implementations.wud.parser;
|
||||||
|
|
||||||
|
import java.text.ParseException;
|
||||||
|
|
||||||
|
import de.mas.wiiu.jnus.entities.TMD;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public class WUDGamePartition extends WUDPartition {
|
public class WUDGamePartition extends WUDPartition {
|
||||||
|
private final TMD tmd;
|
||||||
private final byte[] rawTMD;
|
private final byte[] rawTMD;
|
||||||
private final byte[] rawCert;
|
private final byte[] rawCert;
|
||||||
private final byte[] rawTicket;
|
private final byte[] rawTicket;
|
||||||
|
|
||||||
public WUDGamePartition(String partitionName, long partitionOffset, byte[] rawTMD, byte[] rawCert, byte[] rawTicket) {
|
public WUDGamePartition(String partitionName, long partitionOffset, byte[] rawTMD, byte[] rawCert, byte[] rawTicket) throws ParseException {
|
||||||
super(partitionName, partitionOffset);
|
super(partitionName, partitionOffset);
|
||||||
this.rawTMD = rawTMD;
|
this.rawTMD = rawTMD;
|
||||||
|
this.tmd = TMD.parseTMD(rawTMD);
|
||||||
this.rawCert = rawCert;
|
this.rawCert = rawCert;
|
||||||
this.rawTicket = rawTicket;
|
this.rawTicket = rawTicket;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
/****************************************************************************
|
/****************************************************************************
|
||||||
* Copyright (C) 2016-2018 Maschell
|
* Copyright (C) 2016-2019 Maschell
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* 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
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -19,10 +19,10 @@ package de.mas.wiiu.jnus.implementations.wud.parser;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
import de.mas.wiiu.jnus.entities.content.Content;
|
import de.mas.wiiu.jnus.entities.content.Content;
|
||||||
import de.mas.wiiu.jnus.utils.ByteUtils;
|
import de.mas.wiiu.jnus.utils.ByteUtils;
|
||||||
@ -52,13 +52,13 @@ public final class WUDPartitionHeader {
|
|||||||
getH3Hashes().put(index, hash);
|
getH3Hashes().put(index, hash);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] getH3Hash(Content content) {
|
public Optional<byte[]> getH3Hash(Content content) {
|
||||||
if (content == null) {
|
if (content == null) {
|
||||||
log.info("Can't find h3 hash, given content is null.");
|
log.info("Can't find h3 hash, given content is null.");
|
||||||
return null;
|
return Optional.empty();
|
||||||
}
|
}
|
||||||
|
|
||||||
return getH3Hashes().get(content.getIndex());
|
return Optional.of(getH3Hashes().get(content.getIndex()));
|
||||||
}
|
}
|
||||||
|
|
||||||
public void calculateHashes(Map<Integer, Content> allContents) {
|
public void calculateHashes(Map<Integer, Content> allContents) {
|
||||||
|
152
src/de/mas/wiiu/jnus/utils/DataProviderUtils.java
Normal file
152
src/de/mas/wiiu/jnus/utils/DataProviderUtils.java
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
/****************************************************************************
|
||||||
|
* Copyright (C) 2016-2018 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 <http://www.gnu.org/licenses/>.
|
||||||
|
****************************************************************************/
|
||||||
|
package de.mas.wiiu.jnus.utils;
|
||||||
|
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import de.mas.wiiu.jnus.Settings;
|
||||||
|
import de.mas.wiiu.jnus.entities.content.Content;
|
||||||
|
import de.mas.wiiu.jnus.implementations.NUSDataProvider;
|
||||||
|
import lombok.NonNull;
|
||||||
|
import lombok.extern.java.Log;
|
||||||
|
|
||||||
|
@Log
|
||||||
|
/**
|
||||||
|
* Service Methods for loading NUS/Content data from different sources
|
||||||
|
*
|
||||||
|
* @author Maschell
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
public class DataProviderUtils {
|
||||||
|
|
||||||
|
private DataProviderUtils() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given content encrypted with his .h3 file in the given directory. The Target directory will be created if it's missing. If the content is not
|
||||||
|
* hashed, no .h3 will be saved
|
||||||
|
*
|
||||||
|
* @param content
|
||||||
|
* Content that should be saved
|
||||||
|
* @param outputFolder
|
||||||
|
* Target directory where the files will be stored in.
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void saveEncryptedContentWithH3Hash(@NonNull NUSDataProvider dataProvider, @NonNull Content content, @NonNull String outputFolder)
|
||||||
|
throws IOException {
|
||||||
|
saveContentH3Hash(dataProvider, content, outputFolder);
|
||||||
|
saveEncryptedContent(dataProvider, content, outputFolder);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the .h3 file of the given content into the given directory. The Target directory will be created if it's missing. If the content is not hashed, no
|
||||||
|
* .h3 will be saved
|
||||||
|
*
|
||||||
|
* @param content
|
||||||
|
* The content of which the h3 hashes should be saved
|
||||||
|
* @param outputFolder
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static boolean saveContentH3Hash(@NonNull NUSDataProvider dataProvider, @NonNull Content content, @NonNull String outputFolder) throws IOException {
|
||||||
|
if (!content.isHashed()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String h3Filename = String.format("%08X%s", content.getID(), Settings.H3_EXTENTION);
|
||||||
|
File output = new File(outputFolder + File.separator + h3Filename);
|
||||||
|
|
||||||
|
if (output.exists()) {
|
||||||
|
if (Arrays.equals(content.getSHA2Hash(), HashUtil.hashSHA1(output))) {
|
||||||
|
log.info(h3Filename + " already exists");
|
||||||
|
return false;
|
||||||
|
} else {
|
||||||
|
if (Arrays.equals(content.getSHA2Hash(), Arrays.copyOf(HashUtil.hashSHA256(output), 20))) { // 0005000c1f941200 used sha256 instead of SHA1
|
||||||
|
log.info(h3Filename + " already exists");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
log.warning(h3Filename + " already exists but hash is differrent than expected.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<byte[]> hashOpt = dataProvider.getContentH3Hash(content);
|
||||||
|
if (!hashOpt.isPresent()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
byte[] hash = hashOpt.get();
|
||||||
|
|
||||||
|
log.warning("Saving " + h3Filename + " ");
|
||||||
|
|
||||||
|
return FileUtils.saveByteArrayToFile(output, hash);
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Saves the given content encrypted in the given directory. The Target directory will be created if it's missing. If the content is not encrypted at all,
|
||||||
|
* it will be just saved anyway.
|
||||||
|
*
|
||||||
|
* @param content
|
||||||
|
* Content that should be saved
|
||||||
|
* @param outputFolder
|
||||||
|
* Target directory where the files will be stored in.
|
||||||
|
* @return
|
||||||
|
* @throws IOException
|
||||||
|
*/
|
||||||
|
public static void saveEncryptedContent(@NonNull NUSDataProvider dataProvider, @NonNull Content content, @NonNull String outputFolder) throws IOException {
|
||||||
|
int maxTries = 3;
|
||||||
|
int i = 0;
|
||||||
|
while (i < maxTries) {
|
||||||
|
File output = new File(outputFolder + File.separator + content.getFilename());
|
||||||
|
if (output.exists()) {
|
||||||
|
if (output.length() == content.getEncryptedFileSizeAligned()) {
|
||||||
|
log.info(content.getFilename() + "Encrypted content alreadys exists, skipped");
|
||||||
|
return;
|
||||||
|
} else {
|
||||||
|
log.warning(content.getFilename() + " Encrypted content alreadys exists, but the length is not as expected. Saving it again. "
|
||||||
|
+ output.length() + " " + content.getEncryptedFileSizeAligned() + " Difference: "
|
||||||
|
+ (output.length() - content.getEncryptedFileSizeAligned()));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Utils.createDir(outputFolder);
|
||||||
|
InputStream inputStream = dataProvider.getInputStreamFromContent(content, 0);
|
||||||
|
if (inputStream == null) {
|
||||||
|
log.info(content.getFilename() + " Couldn't save encrypted content. Input stream was null");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
log.warning("loading " + content.getFilename());
|
||||||
|
FileUtils.saveInputStreamToFile(output, inputStream, content.getEncryptedFileSizeAligned());
|
||||||
|
|
||||||
|
File outputNow = new File(outputFolder + File.separator + content.getFilename());
|
||||||
|
if (outputNow.exists()) {
|
||||||
|
if (outputNow.length() != content.getEncryptedFileSizeAligned()) {
|
||||||
|
log.warning(content.getFilename() + " Encrypted content length is not as expected. Saving it again. Loaded: " + outputNow.length()
|
||||||
|
+ " Expected: " + content.getEncryptedFileSizeAligned() + " Difference: "
|
||||||
|
+ (outputNow.length() - content.getEncryptedFileSizeAligned()));
|
||||||
|
i++;
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user