From d08a42719ac6196a1699d716060584ef267038ae Mon Sep 17 00:00:00 2001 From: Maschell Date: Wed, 10 Apr 2019 18:43:44 +0200 Subject: [PATCH] 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 --- src/de/mas/wiiu/jnus/ExtractionService.java | 41 ++--- src/de/mas/wiiu/jnus/NUSTitleLoader.java | 23 ++- .../jnus/implementations/NUSDataProvider.java | 151 ++--------------- .../implementations/NUSDataProviderLocal.java | 23 ++- .../NUSDataProviderLocalBackup.java | 36 ++--- .../NUSDataProviderRemote.java | 68 ++++---- .../implementations/NUSDataProviderWUD.java | 55 ++----- .../implementations/NUSDataProviderWoomy.java | 23 ++- .../wud/parser/WUDGamePartition.java | 11 +- .../wud/parser/WUDPartitionHeader.java | 10 +- .../wiiu/jnus/utils/DataProviderUtils.java | 152 ++++++++++++++++++ 11 files changed, 287 insertions(+), 306 deletions(-) create mode 100644 src/de/mas/wiiu/jnus/utils/DataProviderUtils.java diff --git a/src/de/mas/wiiu/jnus/ExtractionService.java b/src/de/mas/wiiu/jnus/ExtractionService.java index e29b7a5..c88ca22 100644 --- a/src/de/mas/wiiu/jnus/ExtractionService.java +++ b/src/de/mas/wiiu/jnus/ExtractionService.java @@ -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 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); - } } diff --git a/src/de/mas/wiiu/jnus/NUSTitleLoader.java b/src/de/mas/wiiu/jnus/NUSTitleLoader.java index b817687..948cb7c 100644 --- a/src/de/mas/wiiu/jnus/NUSTitleLoader.java +++ b/src/de/mas/wiiu/jnus/NUSTitleLoader.java @@ -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 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()); diff --git a/src/de/mas/wiiu/jnus/implementations/NUSDataProvider.java b/src/de/mas/wiiu/jnus/implementations/NUSDataProvider.java index 6c52336..f5769e9 100644 --- a/src/de/mas/wiiu/jnus/implementations/NUSDataProvider.java +++ b/src/de/mas/wiiu/jnus/implementations/NUSDataProvider.java @@ -14,149 +14,21 @@ * You should have received a copy of the GNU General Public License * along with this program. If not, see . ****************************************************************************/ + + 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 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 getContentH3Hash(Content content) throws IOException; - public abstract byte[] getRawTMD() throws IOException; + public abstract Optional getRawTMD() throws IOException; - public abstract byte[] getRawTicket() throws IOException; + public abstract Optional getRawTicket() throws IOException; - public abstract byte[] getRawCert() throws IOException; + public abstract Optional getRawCert() throws IOException; public abstract void cleanup() throws IOException; -} +} \ No newline at end of file diff --git a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java index f63952f..5ab75ad 100644 --- a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java +++ b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderLocal.java @@ -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 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 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 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 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 diff --git a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java index e391a23..85a36ce 100644 --- a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java +++ b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderLocalBackup.java @@ -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 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 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 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 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 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 diff --git a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java index 14befdf..d76c62a 100644 --- a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java +++ b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderRemote.java @@ -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 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 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 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 getRawCert() throws IOException { + return Optional.empty(); } @Override diff --git a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java index 1fb422a..7e453be 100644 --- a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java +++ b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java @@ -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 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 getRawTMD() { + return Optional.of(getGamePartition().getRawTMD()); } @Override - public byte[] getRawTMD() { - return getGamePartition().getRawTMD(); + public Optional getRawTicket() { + return Optional.of(getGamePartition().getRawTicket()); } @Override - public byte[] getRawTicket() { - return getGamePartition().getRawTicket(); - } - - @Override - public byte[] getRawCert() throws IOException { - return getGamePartition().getRawCert(); + public Optional getRawCert() throws IOException { + return Optional.of(getGamePartition().getRawCert()); } public WUDPartitionHeader getGamePartitionHeader() { diff --git a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java index 4646b72..5a9b3a4 100644 --- a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java +++ b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWoomy.java @@ -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 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 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 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 getRawCert() throws IOException { + return Optional.empty(); } } diff --git a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGamePartition.java b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGamePartition.java index 321ac05..cef55ab 100644 --- a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGamePartition.java +++ b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGamePartition.java @@ -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; } -} +} \ No newline at end of file diff --git a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDPartitionHeader.java b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDPartitionHeader.java index be3622e..46094b6 100644 --- a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDPartitionHeader.java +++ b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDPartitionHeader.java @@ -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 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 allContents) { diff --git a/src/de/mas/wiiu/jnus/utils/DataProviderUtils.java b/src/de/mas/wiiu/jnus/utils/DataProviderUtils.java new file mode 100644 index 0000000..7a9c99a --- /dev/null +++ b/src/de/mas/wiiu/jnus/utils/DataProviderUtils.java @@ -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 . + ****************************************************************************/ +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 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; + } + } + } + } +}