mirror of
https://github.com/Maschell/JNUSLib.git
synced 2024-11-22 07:59:19 +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;
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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() {
|
||||
|
@ -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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -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) {
|
||||
|
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