diff --git a/src/de/mas/wiiu/jnus/NUSTitleConfig.java b/src/de/mas/wiiu/jnus/NUSTitleConfig.java index 9097a89..ec0763b 100644 --- a/src/de/mas/wiiu/jnus/NUSTitleConfig.java +++ b/src/de/mas/wiiu/jnus/NUSTitleConfig.java @@ -18,12 +18,17 @@ package de.mas.wiiu.jnus; import de.mas.wiiu.jnus.entities.Ticket; import de.mas.wiiu.jnus.implementations.woomy.WoomyInfo; +import de.mas.wiiu.jnus.implementations.wud.parser.WUDGIPartitionTitle; +import de.mas.wiiu.jnus.implementations.wud.parser.WUDGamePartition; import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfo; +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; import lombok.Data; @Data public class NUSTitleConfig { private String inputPath; + private WUDGamePartition WUDGamePartition = null; + private WUDGIPartitionTitle WUDGIPartitionTitle = null; private WUDInfo WUDInfo; private Ticket ticket; diff --git a/src/de/mas/wiiu/jnus/NUSTitleLoaderWUD.java b/src/de/mas/wiiu/jnus/NUSTitleLoaderWUD.java index c81e78c..ff5a22d 100644 --- a/src/de/mas/wiiu/jnus/NUSTitleLoaderWUD.java +++ b/src/de/mas/wiiu/jnus/NUSTitleLoaderWUD.java @@ -18,12 +18,16 @@ package de.mas.wiiu.jnus; import java.io.File; import java.nio.file.Files; +import java.util.ArrayList; +import java.util.List; import de.mas.wiiu.jnus.implementations.NUSDataProvider; import de.mas.wiiu.jnus.implementations.NUSDataProviderWUD; +import de.mas.wiiu.jnus.implementations.NUSDataProviderWUDGI; import de.mas.wiiu.jnus.implementations.wud.WUDImage; import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfo; import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfoParser; +import lombok.val; import lombok.extern.java.Log; @Log @@ -33,13 +37,28 @@ public final class NUSTitleLoaderWUD extends NUSTitleLoader { super(); } - public static NUSTitle loadNUSTitle(String WUDPath) throws Exception { - return loadNUSTitle(WUDPath, null); + public static List loadNUSTitle(String WUDPath) throws Exception { + return loadNUSTitle(WUDPath, (byte[]) null); } - public static NUSTitle loadNUSTitle(String WUDPath, byte[] titleKey) throws Exception { - NUSTitleLoader loader = new NUSTitleLoaderWUD(); - NUSTitleConfig config = new NUSTitleConfig(); + public static List loadNUSTitle(String WUDPath, File key) throws Exception { + byte[] data = Files.readAllBytes(key.toPath()); + if (data == null) { + System.out.println("Failed to read the key file."); + return new ArrayList<>(); + } + return loadNUSTitle(WUDPath, data); + } + + public static List loadNUSTitleDev(String WUDPath) throws Exception { + return loadNUSTitle(WUDPath, null, true); + } + + public static List loadNUSTitle(String WUDPath, byte[] titleKey) throws Exception { + return loadNUSTitle(WUDPath, titleKey, false); + } + + public static List loadNUSTitle(String WUDPath, byte[] titleKey, boolean forceNoKey) throws Exception { byte[] usedTitleKey = titleKey; File wudFile = new File(WUDPath); if (!wudFile.exists()) { @@ -48,26 +67,49 @@ public final class NUSTitleLoaderWUD extends NUSTitleLoader { } WUDImage image = new WUDImage(wudFile); - if (usedTitleKey == null) { + if (usedTitleKey == null && !forceNoKey) { File keyFile = new File(wudFile.getParentFile().getPath() + File.separator + Settings.WUD_KEY_FILENAME); if (!keyFile.exists()) { log.info(keyFile.getAbsolutePath() + " does not exist and no title key was provided."); - return null; + return new ArrayList<>(); } usedTitleKey = Files.readAllBytes(keyFile.toPath()); } WUDInfo wudInfo = WUDInfoParser.createAndLoad(image.getWUDDiscReader(), usedTitleKey); + if (wudInfo == null) { - return null; + System.out.println("WTF. ERROR."); + return new ArrayList<>(); } - config.setWUDInfo(wudInfo); + List result = new ArrayList<>(); - return loader.loadNusTitle(config); + for (val gamePartition : wudInfo.getGamePartitions()) { + NUSTitleConfig config = new NUSTitleConfig(); + NUSTitleLoader loader = new NUSTitleLoaderWUD(); + + config.setWUDGamePartition(gamePartition); + config.setWUDInfo(wudInfo); + result.add(loader.loadNusTitle(config)); + } + + for (val giPartitionTitle : wudInfo.getGIPartitionTitles()) { + NUSTitleConfig config = new NUSTitleConfig(); + NUSTitleLoader loader = new NUSTitleLoaderWUD(); + + config.setWUDGIPartitionTitle(giPartitionTitle); + config.setWUDInfo(wudInfo); + result.add(loader.loadNusTitle(config)); + } + return result; } @Override protected NUSDataProvider getDataProvider(NUSTitle title, NUSTitleConfig config) { - return new NUSDataProviderWUD(title, config.getWUDInfo()); + if (config.getWUDGIPartitionTitle() != null) { + return new NUSDataProviderWUDGI(title, config.getWUDGIPartitionTitle(), config.getWUDInfo().getWUDDiscReader(), config.getWUDInfo().getTitleKey()); + } + return new NUSDataProviderWUD(title, config.getWUDGamePartition(), config.getWUDInfo().getWUDDiscReader()); } + } diff --git a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java index 5402d3a..eae19e7 100644 --- a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java +++ b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUD.java @@ -32,13 +32,15 @@ import lombok.extern.java.Log; @Log public class NUSDataProviderWUD extends NUSDataProvider { - @Getter private final WUDInfo WUDInfo; + @Getter private final WUDGamePartition gamePartition; + @Getter private final WUDDiscReader discReader; private final TMD tmd; - public NUSDataProviderWUD(NUSTitle title, WUDInfo wudinfo) { + public NUSDataProviderWUD(NUSTitle title, WUDGamePartition gamePartition, WUDDiscReader discReader) { super(title); - this.WUDInfo = wudinfo; + this.gamePartition = gamePartition; + this.discReader = discReader; this.tmd = TMD.parseTMD(getRawTMD()); } @@ -96,18 +98,10 @@ public class NUSDataProviderWUD extends NUSDataProvider { return getGamePartition().getRawCert(); } - public WUDGamePartition getGamePartition() { - return getWUDInfo().getGamePartition(); - } - public WUDPartitionHeader getGamePartitionHeader() { return getGamePartition().getPartitionHeader(); } - public WUDDiscReader getDiscReader() { - return getWUDInfo().getWUDDiscReader(); - } - @Override public void cleanup() { // We don't need it @@ -115,6 +109,6 @@ public class NUSDataProviderWUD extends NUSDataProvider { @Override public String toString() { - return "NUSDataProviderWUD [WUDInfo=" + WUDInfo + "]"; + return "NUSDataProviderWUD [WUDGamePartition=" + gamePartition + "]"; } } diff --git a/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUDGI.java b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUDGI.java new file mode 100644 index 0000000..d967014 --- /dev/null +++ b/src/de/mas/wiiu/jnus/implementations/NUSDataProviderWUDGI.java @@ -0,0 +1,96 @@ +/**************************************************************************** + * 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.implementations; + +import java.io.IOException; +import java.io.InputStream; + +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.WUDGIPartitionTitle; +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import lombok.Getter; +import lombok.extern.java.Log; + +@Log +public class NUSDataProviderWUDGI extends NUSDataProvider { + @Getter private final WUDGIPartitionTitle giPartitionTitle; + @Getter private final WUDDiscReader discReader; + + private final byte[] titleKey; + + private final TMD tmd; + + public NUSDataProviderWUDGI(NUSTitle title, WUDGIPartitionTitle giPartitionTitle, WUDDiscReader discReader, byte[] titleKey) { + super(title); + this.giPartitionTitle = giPartitionTitle; + this.discReader = discReader; + this.titleKey = titleKey; + this.tmd = TMD.parseTMD(getRawTMD()); + } + + @Override + public InputStream getInputStreamFromContent(Content content, long fileOffsetBlock) throws IOException { + InputStream in = getGiPartitionTitle().getFileAsStream(content.getFilename(), getDiscReader(), titleKey); + in.skip(fileOffsetBlock); + return in; + } + + @Override + public byte[] getContentH3Hash(Content content) throws IOException { + return getGiPartitionTitle().getFileAsByte(String.format("%08X.h3", content.getID()), getDiscReader(), titleKey); + } + + public TMD getTMD() { + return tmd; + } + + @Override + public byte[] getRawTMD() { + try { + return getGiPartitionTitle().getFileAsByte(Settings.TMD_FILENAME, getDiscReader(), titleKey); + } catch (IOException e) { + return new byte[0]; + } + } + + @Override + public byte[] getRawTicket() { + try { + return getGiPartitionTitle().getFileAsByte(Settings.TICKET_FILENAME, getDiscReader(), titleKey); + } catch (IOException e) { + return new byte[0]; + } + } + + @Override + public byte[] getRawCert() throws IOException { + try { + return getGiPartitionTitle().getFileAsByte(Settings.CERT_FILENAME, getDiscReader(), titleKey); + } catch (IOException e) { + return new byte[0]; + } + } + + @Override + public void cleanup() { + // We don't need it + } + +} diff --git a/src/de/mas/wiiu/jnus/implementations/wud/WUDImage.java b/src/de/mas/wiiu/jnus/implementations/wud/WUDImage.java index a490ca5..0bce19f 100644 --- a/src/de/mas/wiiu/jnus/implementations/wud/WUDImage.java +++ b/src/de/mas/wiiu/jnus/implementations/wud/WUDImage.java @@ -58,7 +58,7 @@ public class WUDImage { WUDImageCompressedInfo compressedInfo = new WUDImageCompressedInfo(wuxheader); if (compressedInfo.isWUX()) { - log.info("Image is compressed"); + log.fine("Image is compressed"); this.isCompressed = true; this.isSplitted = false; Map indexTable = new HashMap<>(); diff --git a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGIPartition.java b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGIPartition.java new file mode 100644 index 0000000..8085ab6 --- /dev/null +++ b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGIPartition.java @@ -0,0 +1,22 @@ +package de.mas.wiiu.jnus.implementations.wud.parser; + +import java.util.ArrayList; +import java.util.List; + +import de.mas.wiiu.jnus.entities.fst.FST; +import lombok.Getter; +import lombok.val; +import lombok.extern.java.Log; + +@Log +public class WUDGIPartition extends WUDPartition { + @Getter private final List titles = new ArrayList<>(); + + public WUDGIPartition(String partitionName, long partitionOffset, FST fst) { + super(partitionName, partitionOffset); + for (val curDir : fst.getRoot().getDirChildren()) { + titles.add(new WUDGIPartitionTitle(fst, curDir, partitionOffset)); + } + } + +} diff --git a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGIPartitionTitle.java b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGIPartitionTitle.java new file mode 100644 index 0000000..a11c652 --- /dev/null +++ b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDGIPartitionTitle.java @@ -0,0 +1,59 @@ +package de.mas.wiiu.jnus.implementations.wud.parser; + +import java.io.IOException; +import java.io.InputStream; +import java.nio.ByteBuffer; + +import de.mas.wiiu.jnus.Settings; +import de.mas.wiiu.jnus.entities.content.ContentFSTInfo; +import de.mas.wiiu.jnus.entities.fst.FST; +import de.mas.wiiu.jnus.entities.fst.FSTEntry; +import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; +import de.mas.wiiu.jnus.utils.StreamUtils; +import lombok.Getter; + +public class WUDGIPartitionTitle { + private final FST fst; + private final FSTEntry rootEntry; + + @Getter private final long partitionOffset; + + public WUDGIPartitionTitle(FST fst, FSTEntry rootEntry, long partitionOffset) { + this.fst = fst; + this.rootEntry = rootEntry; + this.partitionOffset = partitionOffset; + } + + public byte[] getFileAsByte(String filename, WUDDiscReader discReader, byte[] titleKey) throws IOException { + FSTEntry entry = getEntryByFilename(rootEntry, filename); + return StreamUtils.getBytesFromStream(getFileAsStream(filename, discReader, titleKey), (int) entry.getFileSize()); + } + + public InputStream getFileAsStream(String filename, WUDDiscReader discReader, byte[] titleKey) throws IOException { + FSTEntry entry = getEntryByFilename(rootEntry, filename); + ContentFSTInfo info = fst.getContentFSTInfos().get((int) entry.getContentFSTID()); + + return discReader.readDecryptedToInputStream(getAbsoluteReadOffset() + (long) info.getOffset(), entry.getFileOffset(), (int) entry.getFileSize(), + titleKey, null, false); + } + + private long getAbsoluteReadOffset() { + return (long) Settings.WIIU_DECRYPTED_AREA_OFFSET + getPartitionOffset(); + } + + private static FSTEntry getEntryByFilename(FSTEntry root, String filename) { + for (FSTEntry cur : root.getFileChildren()) { + if (cur.getFilename().equalsIgnoreCase(filename)) { + return cur; + } + } + for (FSTEntry cur : root.getDirChildren()) { + FSTEntry dir_result = getEntryByFilename(cur, filename); + if (dir_result != null) { + return dir_result; + } + } + return null; + } + +} diff --git a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfo.java b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfo.java index caac59d..bb3617f 100644 --- a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfo.java +++ b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfo.java @@ -16,9 +16,12 @@ ****************************************************************************/ package de.mas.wiiu.jnus.implementations.wud.parser; +import java.util.ArrayList; import java.util.HashMap; +import java.util.List; import java.util.Map; import java.util.Map.Entry; +import java.util.stream.Collectors; import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; import lombok.AccessLevel; @@ -35,25 +38,16 @@ public class WUDInfo { @Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PROTECTED) private String gamePartitionName; - private WUDGamePartition cachedGamePartition = null; - public void addPartion(String partitionName, WUDGamePartition partition) { getPartitions().put(partitionName, partition); } - public WUDGamePartition getGamePartition() { - if (cachedGamePartition == null) { - cachedGamePartition = findGamePartition(); - } - return cachedGamePartition; + public List getGamePartitions() { + return partitions.values().stream().filter(p -> p instanceof WUDGamePartition).map(p -> (WUDGamePartition) p).collect(Collectors.toList()); } - private WUDGamePartition findGamePartition() { - for (Entry e : getPartitions().entrySet()) { - if (e.getKey().equals(getGamePartitionName())) { - return (WUDGamePartition) e.getValue(); - } - } - return null; + public List getGIPartitionTitles() { + return partitions.values().stream().filter(p -> p instanceof WUDGIPartition).flatMap(p -> ((WUDGIPartition) p).getTitles().stream()) + .collect(Collectors.toList()); } } diff --git a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfoParser.java b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfoParser.java index 35a9134..6c0ab38 100644 --- a/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfoParser.java +++ b/src/de/mas/wiiu/jnus/implementations/wud/parser/WUDInfoParser.java @@ -16,20 +16,25 @@ ****************************************************************************/ package de.mas.wiiu.jnus.implementations.wud.parser; +import java.io.FileOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.Arrays; import java.util.HashMap; import java.util.Map; +import java.util.stream.Collectors; import de.mas.wiiu.jnus.Settings; +import de.mas.wiiu.jnus.entities.TMD; +import de.mas.wiiu.jnus.entities.Ticket; import de.mas.wiiu.jnus.entities.content.ContentFSTInfo; import de.mas.wiiu.jnus.entities.fst.FST; import de.mas.wiiu.jnus.entities.fst.FSTEntry; import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; import de.mas.wiiu.jnus.utils.ByteUtils; import de.mas.wiiu.jnus.utils.Utils; +import lombok.val; import lombok.extern.java.Log; @Log @@ -51,22 +56,26 @@ public final class WUDInfoParser { public static WUDInfo createAndLoad(WUDDiscReader discReader, byte[] titleKey) throws IOException { WUDInfo result = new WUDInfo(titleKey, discReader); - byte[] PartitionTocBlock = discReader.readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET, 0, 0x8000, titleKey, null); + byte[] PartitionTocBlock; + if (titleKey == null) { + PartitionTocBlock = discReader.readEncryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET, 0, 0x8000); + } else { + PartitionTocBlock = discReader.readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET, 0, 0x8000, titleKey, null, true); + } + // // verify DiscKey before proceeding if (!Arrays.equals(Arrays.copyOfRange(PartitionTocBlock, 0, 4), DECRYPTED_AREA_SIGNATURE)) { - log.info("Decryption of PartitionTocBlock failed"); - return null; + // log.info("Decryption of PartitionTocBlock failed"); + throw new RuntimeException("Decryption of PartitionTocBlock failed"); } - Map partitions = readPartitions(result, PartitionTocBlock); result.getPartitions().clear(); - result.getPartitions().putAll(partitions); - + result.getPartitions().putAll(readGamePartitions(result, PartitionTocBlock)); return result; } - private static Map readPartitions(WUDInfo wudInfo, byte[] partitionTocBlock) throws IOException { + private static Map readGamePartitions(WUDInfo wudInfo, byte[] partitionTocBlock) throws IOException { ByteBuffer buffer = ByteBuffer.allocate(partitionTocBlock.length); buffer.order(ByteOrder.BIG_ENDIAN); @@ -75,16 +84,11 @@ public final class WUDInfoParser { int partitionCount = (int) ByteUtils.getUnsingedIntFromBytes(partitionTocBlock, 0x1C, ByteOrder.BIG_ENDIAN); - Map partitions = new HashMap<>(); + Map internalPartitions = new HashMap<>(); + Map gamePartitions = new HashMap<>(); - byte[] gamePartitionTMD = new byte[0]; - byte[] gamePartitionTicket = new byte[0]; - byte[] gamePartitionCert = new byte[0]; - - String realGamePartitionName = null; // populate partition information from decrypted TOC for (int i = 0; i < partitionCount; i++) { - int offset = (PARTITION_TOC_OFFSET + (i * PARTITION_TOC_ENTRY_SIZE)); byte[] partitionIdentifier = Arrays.copyOfRange(partitionTocBlock, offset, offset + 0x19); int j = 0; @@ -103,61 +107,106 @@ public final class WUDInfoParser { WUDPartition partition = new WUDPartition(partitionName, partitionOffset); - if (partitionName.startsWith("SI")) { - byte[] fileTableBlock = wudInfo.getWUDDiscReader().readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET + partitionOffset, 0, 0x8000, - wudInfo.getTitleKey(), null); - if (!Arrays.equals(Arrays.copyOfRange(fileTableBlock, 0, 4), PARTITION_FILE_TABLE_SIGNATURE)) { - log.info("FST Decrpytion failed"); - continue; - } - - FST fst = FST.parseFST(fileTableBlock, null); - - byte[] rawTIK = getFSTEntryAsByte(WUD_TICKET_FILENAME, partition, fst, wudInfo.getWUDDiscReader(), wudInfo.getTitleKey()); - byte[] rawTMD = getFSTEntryAsByte(WUD_TMD_FILENAME, partition, fst, wudInfo.getWUDDiscReader(), wudInfo.getTitleKey()); - byte[] rawCert = getFSTEntryAsByte(WUD_CERT_FILENAME, partition, fst, wudInfo.getWUDDiscReader(), wudInfo.getTitleKey()); - - gamePartitionTMD = rawTMD; - gamePartitionTicket = rawTIK; - gamePartitionCert = rawCert; - - // We want to use the real game partition - realGamePartitionName = partitionName = "GM" + Utils.ByteArrayToString(Arrays.copyOfRange(rawTIK, 0x1DC, 0x1DC + 0x08)); - } else if (partitionName.startsWith(realGamePartitionName)) { - wudInfo.setGamePartitionName(partitionName); - partition = new WUDGamePartition(partitionName, partitionOffset, gamePartitionTMD, gamePartitionCert, gamePartitionTicket); - } byte[] header = wudInfo.getWUDDiscReader().readEncryptedToByteArray(partition.getPartitionOffset() + 0x10000, 0, 0x8000); WUDPartitionHeader partitionHeader = WUDPartitionHeader.parseHeader(header); partition.setPartitionHeader(partitionHeader); - partitions.put(partitionName, partition); + internalPartitions.put(partitionName, partition); } - return partitions; + val siPartitionOpt = internalPartitions.entrySet().stream().filter(e -> e.getKey().startsWith("SI")).findFirst(); + val siPartitionPair = siPartitionOpt.orElseThrow(() -> new RuntimeException("SI partition not foud.")); + + // siPartition + long siPartitionOffset = siPartitionPair.getValue().getPartitionOffset(); + val siPartition = siPartitionPair.getValue(); + + byte[] fileTableBlock; + + if (wudInfo.getTitleKey() == null) { + fileTableBlock = wudInfo.getWUDDiscReader().readEncryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET + siPartitionOffset, 0, 0x8000); + } else { + fileTableBlock = wudInfo.getWUDDiscReader().readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET + siPartitionOffset, 0, 0x8000, + wudInfo.getTitleKey(), null, true); + } + + if (!Arrays.equals(Arrays.copyOfRange(fileTableBlock, 0, 4), PARTITION_FILE_TABLE_SIGNATURE)) { + log.info("FST Decrpytion failed"); + throw new RuntimeException("Failed to decrypt the FST of the SI partition."); + } + + FST siFST = FST.parseFST(fileTableBlock, null); + + for (val dirChilden : siFST.getRoot().getDirChildren()) { + // The SI partition contains the tmd, cert and tik for every GM partition. + byte[] rawTIK = getFSTEntryAsByte(dirChilden.getFullPath() + "\\" + WUD_TICKET_FILENAME, siPartition, siFST, wudInfo.getWUDDiscReader(), + wudInfo.getTitleKey()); + byte[] rawTMD = getFSTEntryAsByte(dirChilden.getFullPath() + "\\" + WUD_TMD_FILENAME, siPartition, siFST, wudInfo.getWUDDiscReader(), + wudInfo.getTitleKey()); + byte[] rawCert = getFSTEntryAsByte(dirChilden.getFullPath() + "\\" + WUD_CERT_FILENAME, siPartition, siFST, wudInfo.getWUDDiscReader(), + wudInfo.getTitleKey()); + + String partitionName = "GM" + Utils.ByteArrayToString(Arrays.copyOfRange(rawTIK, 0x1DC, 0x1DC + 0x08)); + + val curPartitionOpt = internalPartitions.entrySet().stream().filter(e -> e.getKey().startsWith(partitionName)).findFirst(); + val curPartitionPair = curPartitionOpt.orElseThrow(() -> new RuntimeException("partition not foud.")); + + WUDGamePartition curPartition = new WUDGamePartition(curPartitionPair.getKey(), curPartitionPair.getValue().getPartitionOffset(), rawTMD, rawCert, + rawTIK); + curPartition.setPartitionHeader(curPartitionPair.getValue().getPartitionHeader()); + gamePartitions.put(curPartitionPair.getKey(), curPartition); + } + + val giPartitions = internalPartitions.entrySet().stream().filter(e -> e.getKey().startsWith("GI")).collect(Collectors.toList()); + for (val giPartition : giPartitions) { + String curPartionName = giPartition.getKey(); + WUDPartition curPartition = giPartition.getValue(); + + byte[] curFileTableBlock = wudInfo.getWUDDiscReader().readDecryptedToByteArray( + Settings.WIIU_DECRYPTED_AREA_OFFSET + curPartition.getPartitionOffset(), 0, 0x8000, wudInfo.getTitleKey(), null, true); + if (!Arrays.equals(Arrays.copyOfRange(curFileTableBlock, 0, 4), WUDInfoParser.PARTITION_FILE_TABLE_SIGNATURE)) { + log.info("FST Decrpytion failed"); + throw new RuntimeException("Failed to decrypt the FST of the SI partition."); + } + + FST curFST = FST.parseFST(curFileTableBlock, null); + + WUDGIPartition curNewPartition = new WUDGIPartition(curPartionName, curPartition.getPartitionOffset(), curFST); + curPartition.setPartitionHeader(curPartition.getPartitionHeader()); + + gamePartitions.put(curPartionName, curNewPartition); + } + + return gamePartitions; } - private static byte[] getFSTEntryAsByte(String filename, WUDPartition partition, FST fst, WUDDiscReader discReader, byte[] key) throws IOException { - FSTEntry entry = getEntryByName(fst.getRoot(), filename); + private static byte[] getFSTEntryAsByte(String filePath, WUDPartition partition, FST fst, WUDDiscReader discReader, byte[] key) throws IOException { + FSTEntry entry = getEntryByFullPath(fst.getRoot(), filePath); + ContentFSTInfo info = fst.getContentFSTInfos().get((int) entry.getContentFSTID()); + if (key == null) { + return discReader.readEncryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET + (long) partition.getPartitionOffset() + (long) info.getOffset(), + entry.getFileOffset(), (int) entry.getFileSize()); + } + // Calculating the IV ByteBuffer byteBuffer = ByteBuffer.allocate(0x10); byteBuffer.position(0x08); byte[] IV = byteBuffer.putLong(entry.getFileOffset() >> 16).array(); return discReader.readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET + (long) partition.getPartitionOffset() + (long) info.getOffset(), - entry.getFileOffset(), (int) entry.getFileSize(), key, IV); + entry.getFileOffset(), (int) entry.getFileSize(), key, IV, false); } - private static FSTEntry getEntryByName(FSTEntry root, String name) { + private static FSTEntry getEntryByFullPath(FSTEntry root, String filePath) { for (FSTEntry cur : root.getFileChildren()) { - if (cur.getFilename().equals(name)) { + if (cur.getFullPath().equals(filePath)) { return cur; } } for (FSTEntry cur : root.getDirChildren()) { - FSTEntry dir_result = getEntryByName(cur, name); + FSTEntry dir_result = getEntryByFullPath(cur, filePath); if (dir_result != null) { return dir_result; } diff --git a/src/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java b/src/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java index b7cb40a..77b0477 100644 --- a/src/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java +++ b/src/de/mas/wiiu/jnus/implementations/wud/reader/WUDDiscReader.java @@ -24,9 +24,11 @@ import java.io.OutputStream; import java.io.PipedInputStream; import java.io.PipedOutputStream; import java.io.RandomAccessFile; +import java.nio.ByteBuffer; import java.util.Arrays; import de.mas.wiiu.jnus.implementations.wud.WUDImage; +import de.mas.wiiu.jnus.utils.Utils; import de.mas.wiiu.jnus.utils.cryptography.AESDecryption; import lombok.Getter; import lombok.extern.java.Log; @@ -60,13 +62,13 @@ public abstract class WUDDiscReader { return out.toByteArray(); } - public InputStream readDecryptedToInputStream(long offset, long fileoffset, long size, byte[] key, byte[] iv) throws IOException { + public InputStream readDecryptedToInputStream(long offset, long fileoffset, long size, byte[] key, byte[] IV, boolean useFixedIV) throws IOException { PipedInputStream in = new PipedInputStream(); PipedOutputStream out = new PipedOutputStream(in); new Thread(() -> { try { - readDecryptedToOutputStream(out, offset, fileoffset, size, key, iv); + readDecryptedToOutputStream(out, offset, fileoffset, size, key, IV, useFixedIV); } catch (IOException e) { e.printStackTrace(); } @@ -75,10 +77,10 @@ public abstract class WUDDiscReader { return in; } - public byte[] readDecryptedToByteArray(long offset, long fileoffset, long size, byte[] key, byte[] iv) throws IOException { + public byte[] readDecryptedToByteArray(long offset, long fileoffset, long size, byte[] key, byte[] IV, boolean useFixedIV) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); - readDecryptedToOutputStream(out, offset, fileoffset, size, key, iv); + readDecryptedToOutputStream(out, offset, fileoffset, size, key, IV, useFixedIV); return out.toByteArray(); } @@ -105,12 +107,16 @@ public abstract class WUDDiscReader { return decryptedChunk; } - public void readDecryptedToOutputStream(OutputStream outputStream, long clusterOffset, long fileOffset, long size, byte[] key, byte[] IV) - throws IOException { - byte[] usedIV = IV; - if (usedIV == null) { - usedIV = new byte[0x10]; + public void readDecryptedToOutputStream(OutputStream outputStream, long clusterOffset, long fileOffset, long size, byte[] key, byte[] IV, + boolean useFixedIV) throws IOException { + byte[] usedIV = null; + if (useFixedIV) { + usedIV = IV; + if (IV == null) { + usedIV = new byte[0x10]; + } } + long usedSize = size; long usedFileOffset = fileOffset; byte[] buffer; @@ -130,11 +136,26 @@ public abstract class WUDDiscReader { readOffset = clusterOffset + (blockNumber * BLOCK_SIZE); // (long)WiiUDisc.WIIU_DECRYPTED_AREA_OFFSET + volumeOffset + clusterOffset + (blockStructure.getBlockNumber() * 0x8000); + if (!useFixedIV) { + ByteBuffer byteBuffer = ByteBuffer.allocate(0x10); + byteBuffer.position(0x08); + usedIV = byteBuffer.putLong(usedFileOffset >> 16).array(); + } + buffer = readDecryptedChunk(readOffset, key, usedIV); maxCopySize = BLOCK_SIZE - blockOffset; copySize = (usedSize > maxCopySize) ? maxCopySize : usedSize; - outputStream.write(Arrays.copyOfRange(buffer, (int) blockOffset, (int) copySize)); + try { + outputStream.write(Arrays.copyOfRange(buffer, (int) blockOffset, (int) (blockOffset + copySize))); + } catch (IOException e) { + if (e.getMessage().equals("Pipe closed")) { + break; + } else { + throw e; + } + } + totalread += copySize; // update counters @@ -145,6 +166,12 @@ public abstract class WUDDiscReader { outputStream.close(); } + /** + * Create a new RandomAccessFileStream + * + * @return + * @throws FileNotFoundException + */ public RandomAccessFile getRandomAccessFileStream() throws FileNotFoundException { if (getImage() == null || getImage().getFileHandle() == null) { log.warning("No image or image filehandle set.");