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:
Maschell 2019-04-10 18:43:44 +02:00
parent 40cb09873d
commit d08a42719a
11 changed files with 287 additions and 306 deletions

View File

@ -17,17 +17,20 @@
package de.mas.wiiu.jnus;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CompletionException;
import java.util.concurrent.ExecutionException;
import de.mas.wiiu.jnus.entities.content.Content;
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.Parallelizable;
import de.mas.wiiu.jnus.utils.Utils;
@ -68,7 +71,7 @@ public final class ExtractionService {
Utils.createDir(outputFolder);
NUSDataProvider dataProvider = getDataProvider();
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 {
NUSDataProvider dataProvider = getDataProvider();
if (withHashes) {
dataProvider.saveEncryptedContentWithH3Hash(content, outputFolder);
DataProviderUtils.saveEncryptedContentWithH3Hash(dataProvider, content, outputFolder);
} 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 {
Utils.createDir(output);
byte[] rawTMD = getDataProvider().getRawTMD();
if (rawTMD == null || rawTMD.length == 0) {
log.info("Couldn't write TMD: No TMD loaded");
return;
}
byte[] rawTMD = getDataProvider().getRawTMD().orElseThrow(() -> new FileNotFoundException("TMD not found"));
String tmd_path = output + File.separator + Settings.TMD_FILENAME;
log.info("Extracting TMD to: " + tmd_path);
FileUtils.saveByteArrayToFile(tmd_path, rawTMD);
@ -131,29 +129,25 @@ public final class ExtractionService {
public boolean extractTicketTo(String output) throws IOException {
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;
log.info("Extracting Ticket to: " + ticket_path);
return FileUtils.saveByteArrayToFile(ticket_path, rawTicket);
}
public void extractCertTo(String output) throws IOException {
public boolean extractCertTo(String output) throws IOException {
Utils.createDir(output);
byte[] rawCert = getDataProvider().getRawCert();
Optional<byte[]> dataOpt = getDataProvider().getRawCert();
if (rawCert == null || rawCert.length == 0) {
log.info("Couldn't write Cert: No Cert loaded");
return;
if (!dataOpt.isPresent()) {
return false;
}
String cert_path = output + File.separator + Settings.CERT_FILENAME;
log.info("Extracting Cert to: " + cert_path);
FileUtils.saveByteArrayToFile(cert_path, rawCert);
String path = output + File.separator + Settings.CERT_FILENAME;
log.info("Extracting Cert to: " + path);
return FileUtils.saveByteArrayToFile(path, dataOpt.get());
}
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);
}
}

View File

@ -40,13 +40,10 @@ abstract class NUSTitleLoader {
NUSDataProvider dataProvider = getDataProvider(result, config);
result.setDataProvider(dataProvider);
TMD tmd = TMD.parseTMD(dataProvider.getRawTMD());
result.setTMD(tmd);
byte[] tmdData = dataProvider.getRawTMD().orElseThrow(() -> new ParseException("No TMD data found", 0));
if (tmd == null) {
log.info("TMD not found.");
throw new Exception();
}
TMD tmd = TMD.parseTMD(tmdData);
result.setTMD(tmd);
if (config.isNoDecryption()) {
return result;
@ -54,17 +51,19 @@ abstract class NUSTitleLoader {
Ticket ticket = config.getTicket();
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);
Content fstContent = tmd.getContentByIndex(0);
InputStream fstContentEncryptedStream = dataProvider.getInputStreamFromContent(fstContent, 0);
if (fstContentEncryptedStream == null) {
log.warning("FST is null");
return null;
}
InputStream fstContentEncryptedStream = dataProvider.getInputStreamFromContent(fstContent, 0, Optional.of(fstContent.getEncryptedFileSize()));
byte[] fstBytes = StreamUtils.getBytesFromStream(fstContentEncryptedStream, (int) fstContent.getEncryptedFileSize());

View File

@ -14,149 +14,21 @@
* 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.implementations;
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.NUSTitle;
import de.mas.wiiu.jnus.Settings;
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.Utils;
import lombok.Getter;
import lombok.NonNull;
import lombok.extern.java.Log;
@Log
/**
* Service Methods for loading NUS/Content data from different sources
*
* @author Maschell
*
*/
public abstract class NUSDataProvider {
@Getter private final NUSTitle NUSTitle;
public interface NUSDataProvider {
public NUSDataProvider(NUSTitle title) {
this.NUSTitle = title;
}
/**
* 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);
default public byte[] getChunkFromContent(Content content, long offset, int size) throws IOException {
return StreamUtils.getBytesFromStream(getInputStreamFromContent(content, offset, Optional.of((long) size)), size);
}
/**
@ -168,19 +40,18 @@ public abstract class NUSDataProvider {
*/
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());
}
// TODO: JavaDocs
public abstract byte[] getContentH3Hash(Content content) throws IOException;
public abstract Optional<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;
}
}

View File

@ -33,11 +33,10 @@ import lombok.Getter;
import lombok.extern.java.Log;
@Log
public final class NUSDataProviderLocal extends NUSDataProvider {
public final class NUSDataProviderLocal implements NUSDataProvider {
@Getter private final String localPath;
public NUSDataProviderLocal(NUSTitle nustitle, String localPath) {
super(nustitle);
public NUSDataProviderLocal(String localPath) {
this.localPath = localPath;
}
@ -59,48 +58,48 @@ public final class NUSDataProviderLocal extends NUSDataProvider {
}
@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);
File filepath = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), h3Filename);
if (filepath == null || !filepath.exists()) {
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + h3Filename + "\", file does not exist";
log.warning(errormsg);
return new byte[0];
throw new FileNotFoundException(errormsg);
}
return Files.readAllBytes(filepath.toPath());
return Optional.of(Files.readAllBytes(filepath.toPath()));
}
@Override
public byte[] getRawTMD() throws IOException {
public Optional<byte[]> getRawTMD() throws IOException {
File file = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), Settings.TMD_FILENAME);
if (file == null || !file.exists()) {
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + Settings.TMD_FILENAME + "\", file does not exist";
log.warning(errormsg);
throw new FileNotFoundException(errormsg);
}
return Files.readAllBytes(file.toPath());
return Optional.of(Files.readAllBytes(file.toPath()));
}
@Override
public byte[] getRawTicket() throws IOException {
public Optional<byte[]> getRawTicket() throws IOException {
File file = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), Settings.TICKET_FILENAME);
if (file == null || !file.exists()) {
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + Settings.TICKET_FILENAME + "\", file does not exist";
log.warning(errormsg);
throw new FileNotFoundException(errormsg);
}
return Files.readAllBytes(file.toPath());
return Optional.of(Files.readAllBytes(file.toPath()));
}
@Override
public byte[] getRawCert() throws IOException {
public Optional<byte[]> getRawCert() throws IOException {
File file = FileUtils.getFileIgnoringFilenameCases(getLocalPath(), Settings.CERT_FILENAME);
if (file == null || !file.exists()) {
String errormsg = "Couldn't open \"" + getLocalPath() + File.separator + Settings.CERT_FILENAME + "\", file does not exist";
log.warning(errormsg);
throw new FileNotFoundException(errormsg);
}
return Files.readAllBytes(file.toPath());
return Optional.of(Files.readAllBytes(file.toPath()));
}
@Override

View File

@ -2,28 +2,26 @@ package de.mas.wiiu.jnus.implementations;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.nio.file.Files;
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.implementations.NUSDataProvider;
import de.mas.wiiu.jnus.utils.StreamUtils;
import lombok.Getter;
public class NUSDataProviderLocalBackup extends NUSDataProvider {
public class NUSDataProviderLocalBackup implements NUSDataProvider {
@Getter private final String localPath;
private final short titleVersion;
public NUSDataProviderLocalBackup(NUSTitle nustitle, String localPath) {
this(nustitle, localPath, (short) Settings.LATEST_TMD_VERSION);
public NUSDataProviderLocalBackup(String localPath) {
this(localPath, (short) Settings.LATEST_TMD_VERSION);
}
public NUSDataProviderLocalBackup(NUSTitle nustitle, String localPath, short version) {
super(nustitle);
public NUSDataProviderLocalBackup(String localPath, short version) {
this.localPath = localPath;
this.titleVersion = version;
}
@ -36,7 +34,7 @@ public class NUSDataProviderLocalBackup extends NUSDataProvider {
public InputStream getInputStreamFromContent(Content content, long offset, Optional<Long> size) throws IOException {
File filepath = new File(getFilePathOnDisk(content));
if (!filepath.exists()) {
return null;
throw new FileNotFoundException(filepath.getAbsolutePath() + " was not found.");
}
InputStream in = new FileInputStream(filepath);
StreamUtils.skipExactly(in, offset);
@ -44,40 +42,38 @@ public class NUSDataProviderLocalBackup extends NUSDataProvider {
}
@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());
File h3File = new File(h3Path);
if (!h3File.exists()) {
return new byte[0];
}
return Files.readAllBytes(h3File.toPath());
return Optional.of(Files.readAllBytes(h3File.toPath()));
}
@Override
public byte[] getRawTMD() throws IOException {
public Optional<byte[]> getRawTMD() throws IOException {
String inputPath = getLocalPath();
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;
}
File tmdFile = new File(tmdPath);
return Files.readAllBytes(tmdFile.toPath());
return Optional.of(Files.readAllBytes(tmdFile.toPath()));
}
@Override
public byte[] getRawTicket() throws IOException {
public Optional<byte[]> getRawTicket() throws IOException {
String inputPath = getLocalPath();
String ticketPath = inputPath + File.separator + Settings.TICKET_FILENAME;
File ticketFile = new File(ticketPath);
return Files.readAllBytes(ticketFile.toPath());
return Optional.of(Files.readAllBytes(ticketFile.toPath()));
}
@Override
public byte[] getRawCert() throws IOException {
public Optional<byte[]> getRawCert() throws IOException {
String inputPath = getLocalPath();
String certPath = inputPath + File.separator + Settings.CERT_FILENAME;
File certFile = new File(certPath);
return Files.readAllBytes(certFile.toPath());
return Optional.of(Files.readAllBytes(certFile.toPath()));
}
@Override

View File

@ -16,25 +16,21 @@
****************************************************************************/
package de.mas.wiiu.jnus.implementations;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
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.utils.Parallelizable;
import de.mas.wiiu.jnus.utils.download.NUSDownloadService;
import lombok.Getter;
public class NUSDataProviderRemote extends NUSDataProvider implements Parallelizable {
public class NUSDataProviderRemote implements NUSDataProvider, Parallelizable {
@Getter private final int version;
@Getter private final long titleID;
public NUSDataProviderRemote(NUSTitle title, int version, long titleID) {
super(title);
public NUSDataProviderRemote(int version, long titleID) {
this.version = version;
this.titleID = titleID;
}
@ -46,53 +42,49 @@ public class NUSDataProviderRemote extends NUSDataProvider implements Paralleliz
}
private String getRemoteURL(Content content) {
return String.format("%016x/%08X", getNUSTitle().getTMD().getTitleID(), content.getID());
return String.format("%016x/%08X", titleID, content.getID());
}
@Override
public byte[] getContentH3Hash(Content content) throws IOException {
public Optional<byte[]> getContentH3Hash(Content content) throws IOException {
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
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
public byte[] getRawTMD() throws IOException {
public Optional<byte[]> getRawTMD() throws IOException {
NUSDownloadService downloadService = NUSDownloadService.getDefaultInstance();
long titleID = getTitleID();
int version = getVersion();
return downloadService.downloadTMDToByteArray(titleID, version);
}
byte[] res = downloadService.downloadTMDToByteArray(titleID, version);
@Override
public byte[] getRawTicket() throws IOException {
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();
if (res == null || res.length == 0) {
return Optional.empty();
}
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

View File

@ -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
* 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.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.implementations.wud.parser.WUDGamePartition;
import de.mas.wiiu.jnus.implementations.wud.parser.WUDPartitionHeader;
@ -31,29 +28,20 @@ import lombok.Getter;
import lombok.extern.java.Log;
@Log
public class NUSDataProviderWUD extends NUSDataProvider {
public class NUSDataProviderWUD implements NUSDataProvider {
@Getter private final WUDGamePartition gamePartition;
@Getter private final WUDDiscReader discReader;
private final TMD tmd;
public NUSDataProviderWUD(NUSTitle title, WUDGamePartition gamePartition, WUDDiscReader discReader) {
super(title);
public NUSDataProviderWUD(WUDGamePartition gamePartition, WUDDiscReader discReader) {
this.gamePartition = gamePartition;
this.discReader = discReader;
this.tmd = TMD.parseTMD(getRawTMD());
}
public long getOffsetInWUD(Content content) {
if (content.getContentFSTInfo() == null) {
return getAbsoluteReadOffset();
} else {
return getAbsoluteReadOffset() + content.getContentFSTInfo().getOffset();
if (content.getIndex() == 0) { // Index 0 is the FST which is at the beginning of the partion;
return getGamePartition().getAbsolutePartitionOffset();
}
}
public long getAbsoluteReadOffset() {
return (long) Settings.WIIU_DECRYPTED_AREA_OFFSET + getGamePartition().getPartitionOffset();
return getGamePartition().getAbsolutePartitionOffset() + content.getContentFSTInfo().getOffset();
}
@Override
@ -66,45 +54,34 @@ public class NUSDataProviderWUD extends NUSDataProvider {
WUDDiscReader discReader = getDiscReader();
long offset = getOffsetInWUD(content) + fileOffsetBlock;
long usedSize = content.getEncryptedFileSize() - fileOffsetBlock;
if(size.isPresent()) {
if (size.isPresent()) {
usedSize = size.get();
}
return discReader.readEncryptedToInputStream(offset, usedSize);
}
@Override
public byte[] getContentH3Hash(Content content) throws IOException {
if (getGamePartitionHeader() == null) {
log.warning("GamePartitionHeader is null");
return null;
}
public Optional<byte[]> getContentH3Hash(Content content) throws IOException {
if (!getGamePartitionHeader().isCalculatedHashes()) {
log.info("Calculating h3 hashes");
getGamePartitionHeader().calculateHashes(getTMD().getAllContents());
getGamePartitionHeader().calculateHashes(getGamePartition().getTmd().getAllContents());
}
return getGamePartitionHeader().getH3Hash(content);
}
public TMD getTMD() {
return tmd;
@Override
public Optional<byte[]> getRawTMD() {
return Optional.of(getGamePartition().getRawTMD());
}
@Override
public byte[] getRawTMD() {
return getGamePartition().getRawTMD();
public Optional<byte[]> getRawTicket() {
return Optional.of(getGamePartition().getRawTicket());
}
@Override
public byte[] getRawTicket() {
return getGamePartition().getRawTicket();
}
@Override
public byte[] getRawCert() throws IOException {
return getGamePartition().getRawCert();
public Optional<byte[]> getRawCert() throws IOException {
return Optional.of(getGamePartition().getRawCert());
}
public WUDPartitionHeader getGamePartitionHeader() {

View File

@ -35,12 +35,11 @@ import lombok.Setter;
import lombok.extern.java.Log;
@Log
public class NUSDataProviderWoomy extends NUSDataProvider {
public class NUSDataProviderWoomy implements NUSDataProvider {
@Getter private final WoomyInfo woomyInfo;
@Setter(AccessLevel.PRIVATE) private WoomyZipFile woomyZipFile;
public NUSDataProviderWoomy(NUSTitle title, WoomyInfo woomyInfo) {
super(title);
public NUSDataProviderWoomy(WoomyInfo woomyInfo) {
this.woomyInfo = woomyInfo;
}
@ -56,19 +55,19 @@ public class NUSDataProviderWoomy extends NUSDataProvider {
}
@Override
public byte[] getContentH3Hash(Content content) throws IOException {
public Optional<byte[]> getContentH3Hash(Content content) throws IOException {
ZipEntry entry = getWoomyInfo().getContentFiles().get(content.getFilename().toLowerCase());
if (entry != null) {
WoomyZipFile zipFile = getNewWoomyZipFile();
byte[] result = zipFile.getEntryAsByte(entry);
zipFile.close();
return result;
return Optional.of(result);
}
return new byte[0];
return Optional.empty();
}
@Override
public byte[] getRawTMD() throws IOException {
public Optional<byte[]> getRawTMD() throws IOException {
ZipEntry entry = getWoomyInfo().getContentFiles().get(Settings.TMD_FILENAME);
if (entry == null) {
log.warning(Settings.TMD_FILENAME + " not found in woomy file");
@ -77,11 +76,11 @@ public class NUSDataProviderWoomy extends NUSDataProvider {
WoomyZipFile zipFile = getNewWoomyZipFile();
byte[] result = zipFile.getEntryAsByte(entry);
zipFile.close();
return result;
return Optional.of(result);
}
@Override
public byte[] getRawTicket() throws IOException {
public Optional<byte[]> getRawTicket() throws IOException {
ZipEntry entry = getWoomyInfo().getContentFiles().get(Settings.TICKET_FILENAME);
if (entry == null) {
log.warning(Settings.TICKET_FILENAME + " not found in woomy file");
@ -91,7 +90,7 @@ public class NUSDataProviderWoomy extends NUSDataProvider {
WoomyZipFile zipFile = getNewWoomyZipFile();
byte[] result = zipFile.getEntryAsByte(entry);
zipFile.close();
return result;
return Optional.of(result);
}
public WoomyZipFile getSharedWoomyZipFile() throws ZipException, IOException {
@ -113,7 +112,7 @@ public class NUSDataProviderWoomy extends NUSDataProvider {
}
@Override
public byte[] getRawCert() throws IOException {
return new byte[0];
public Optional<byte[]> getRawCert() throws IOException {
return Optional.empty();
}
}

View File

@ -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
* it under the terms of the GNU General Public License as published by
@ -16,20 +16,25 @@
****************************************************************************/
package de.mas.wiiu.jnus.implementations.wud.parser;
import java.text.ParseException;
import de.mas.wiiu.jnus.entities.TMD;
import lombok.Data;
import lombok.EqualsAndHashCode;
@Data
@EqualsAndHashCode(callSuper = true)
public class WUDGamePartition extends WUDPartition {
private final TMD tmd;
private final byte[] rawTMD;
private final byte[] rawCert;
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);
this.rawTMD = rawTMD;
this.tmd = TMD.parseTMD(rawTMD);
this.rawCert = rawCert;
this.rawTicket = rawTicket;
}
}
}

View File

@ -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
* 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.Arrays;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import de.mas.wiiu.jnus.entities.content.Content;
import de.mas.wiiu.jnus.utils.ByteUtils;
@ -52,13 +52,13 @@ public final class WUDPartitionHeader {
getH3Hashes().put(index, hash);
}
public byte[] getH3Hash(Content content) {
public Optional<byte[]> getH3Hash(Content content) {
if (content == 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) {

View 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;
}
}
}
}
}