The NUSTitleLoaderWUD can now handle multiple GM partition and can mount contents of a GI partition. Added support for handling Kiosk discs where the SI parition is NOT encrypted with a title key.

This commit is contained in:
Maschell 2018-12-06 15:55:31 +01:00
parent bfbbafc269
commit 7e07765fa1
10 changed files with 382 additions and 94 deletions

View File

@ -18,12 +18,17 @@ package de.mas.wiiu.jnus;
import de.mas.wiiu.jnus.entities.Ticket; import de.mas.wiiu.jnus.entities.Ticket;
import de.mas.wiiu.jnus.implementations.woomy.WoomyInfo; 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.parser.WUDInfo;
import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader;
import lombok.Data; import lombok.Data;
@Data @Data
public class NUSTitleConfig { public class NUSTitleConfig {
private String inputPath; private String inputPath;
private WUDGamePartition WUDGamePartition = null;
private WUDGIPartitionTitle WUDGIPartitionTitle = null;
private WUDInfo WUDInfo; private WUDInfo WUDInfo;
private Ticket ticket; private Ticket ticket;

View File

@ -18,12 +18,16 @@ package de.mas.wiiu.jnus;
import java.io.File; import java.io.File;
import java.nio.file.Files; 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.NUSDataProvider;
import de.mas.wiiu.jnus.implementations.NUSDataProviderWUD; 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.WUDImage;
import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfo; import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfo;
import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfoParser; import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfoParser;
import lombok.val;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
@ -33,13 +37,28 @@ public final class NUSTitleLoaderWUD extends NUSTitleLoader {
super(); super();
} }
public static NUSTitle loadNUSTitle(String WUDPath) throws Exception { public static List<NUSTitle> loadNUSTitle(String WUDPath) throws Exception {
return loadNUSTitle(WUDPath, null); return loadNUSTitle(WUDPath, (byte[]) null);
} }
public static NUSTitle loadNUSTitle(String WUDPath, byte[] titleKey) throws Exception { public static List<NUSTitle> loadNUSTitle(String WUDPath, File key) throws Exception {
NUSTitleLoader loader = new NUSTitleLoaderWUD(); byte[] data = Files.readAllBytes(key.toPath());
NUSTitleConfig config = new NUSTitleConfig(); if (data == null) {
System.out.println("Failed to read the key file.");
return new ArrayList<>();
}
return loadNUSTitle(WUDPath, data);
}
public static List<NUSTitle> loadNUSTitleDev(String WUDPath) throws Exception {
return loadNUSTitle(WUDPath, null, true);
}
public static List<NUSTitle> loadNUSTitle(String WUDPath, byte[] titleKey) throws Exception {
return loadNUSTitle(WUDPath, titleKey, false);
}
public static List<NUSTitle> loadNUSTitle(String WUDPath, byte[] titleKey, boolean forceNoKey) throws Exception {
byte[] usedTitleKey = titleKey; byte[] usedTitleKey = titleKey;
File wudFile = new File(WUDPath); File wudFile = new File(WUDPath);
if (!wudFile.exists()) { if (!wudFile.exists()) {
@ -48,26 +67,49 @@ public final class NUSTitleLoaderWUD extends NUSTitleLoader {
} }
WUDImage image = new WUDImage(wudFile); 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); File keyFile = new File(wudFile.getParentFile().getPath() + File.separator + Settings.WUD_KEY_FILENAME);
if (!keyFile.exists()) { if (!keyFile.exists()) {
log.info(keyFile.getAbsolutePath() + " does not exist and no title key was provided."); log.info(keyFile.getAbsolutePath() + " does not exist and no title key was provided.");
return null; return new ArrayList<>();
} }
usedTitleKey = Files.readAllBytes(keyFile.toPath()); usedTitleKey = Files.readAllBytes(keyFile.toPath());
} }
WUDInfo wudInfo = WUDInfoParser.createAndLoad(image.getWUDDiscReader(), usedTitleKey); WUDInfo wudInfo = WUDInfoParser.createAndLoad(image.getWUDDiscReader(), usedTitleKey);
if (wudInfo == null) { if (wudInfo == null) {
return null; System.out.println("WTF. ERROR.");
return new ArrayList<>();
} }
config.setWUDInfo(wudInfo); List<NUSTitle> 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 @Override
protected NUSDataProvider getDataProvider(NUSTitle title, NUSTitleConfig config) { 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());
} }
} }

View File

@ -32,13 +32,15 @@ import lombok.extern.java.Log;
@Log @Log
public class NUSDataProviderWUD extends NUSDataProvider { public class NUSDataProviderWUD extends NUSDataProvider {
@Getter private final WUDInfo WUDInfo; @Getter private final WUDGamePartition gamePartition;
@Getter private final WUDDiscReader discReader;
private final TMD tmd; private final TMD tmd;
public NUSDataProviderWUD(NUSTitle title, WUDInfo wudinfo) { public NUSDataProviderWUD(NUSTitle title, WUDGamePartition gamePartition, WUDDiscReader discReader) {
super(title); super(title);
this.WUDInfo = wudinfo; this.gamePartition = gamePartition;
this.discReader = discReader;
this.tmd = TMD.parseTMD(getRawTMD()); this.tmd = TMD.parseTMD(getRawTMD());
} }
@ -96,18 +98,10 @@ public class NUSDataProviderWUD extends NUSDataProvider {
return getGamePartition().getRawCert(); return getGamePartition().getRawCert();
} }
public WUDGamePartition getGamePartition() {
return getWUDInfo().getGamePartition();
}
public WUDPartitionHeader getGamePartitionHeader() { public WUDPartitionHeader getGamePartitionHeader() {
return getGamePartition().getPartitionHeader(); return getGamePartition().getPartitionHeader();
} }
public WUDDiscReader getDiscReader() {
return getWUDInfo().getWUDDiscReader();
}
@Override @Override
public void cleanup() { public void cleanup() {
// We don't need it // We don't need it
@ -115,6 +109,6 @@ public class NUSDataProviderWUD extends NUSDataProvider {
@Override @Override
public String toString() { public String toString() {
return "NUSDataProviderWUD [WUDInfo=" + WUDInfo + "]"; return "NUSDataProviderWUD [WUDGamePartition=" + gamePartition + "]";
} }
} }

View File

@ -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 <http://www.gnu.org/licenses/>.
****************************************************************************/
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
}
}

View File

@ -58,7 +58,7 @@ public class WUDImage {
WUDImageCompressedInfo compressedInfo = new WUDImageCompressedInfo(wuxheader); WUDImageCompressedInfo compressedInfo = new WUDImageCompressedInfo(wuxheader);
if (compressedInfo.isWUX()) { if (compressedInfo.isWUX()) {
log.info("Image is compressed"); log.fine("Image is compressed");
this.isCompressed = true; this.isCompressed = true;
this.isSplitted = false; this.isSplitted = false;
Map<Integer, Long> indexTable = new HashMap<>(); Map<Integer, Long> indexTable = new HashMap<>();

View File

@ -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<WUDGIPartitionTitle> 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));
}
}
}

View File

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

View File

@ -16,9 +16,12 @@
****************************************************************************/ ****************************************************************************/
package de.mas.wiiu.jnus.implementations.wud.parser; package de.mas.wiiu.jnus.implementations.wud.parser;
import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.stream.Collectors;
import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader;
import lombok.AccessLevel; import lombok.AccessLevel;
@ -35,25 +38,16 @@ public class WUDInfo {
@Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PROTECTED) private String gamePartitionName; @Getter(AccessLevel.PRIVATE) @Setter(AccessLevel.PROTECTED) private String gamePartitionName;
private WUDGamePartition cachedGamePartition = null;
public void addPartion(String partitionName, WUDGamePartition partition) { public void addPartion(String partitionName, WUDGamePartition partition) {
getPartitions().put(partitionName, partition); getPartitions().put(partitionName, partition);
} }
public WUDGamePartition getGamePartition() { public List<WUDGamePartition> getGamePartitions() {
if (cachedGamePartition == null) { return partitions.values().stream().filter(p -> p instanceof WUDGamePartition).map(p -> (WUDGamePartition) p).collect(Collectors.toList());
cachedGamePartition = findGamePartition();
}
return cachedGamePartition;
} }
private WUDGamePartition findGamePartition() { public List<WUDGIPartitionTitle> getGIPartitionTitles() {
for (Entry<String, WUDPartition> e : getPartitions().entrySet()) { return partitions.values().stream().filter(p -> p instanceof WUDGIPartition).flatMap(p -> ((WUDGIPartition) p).getTitles().stream())
if (e.getKey().equals(getGamePartitionName())) { .collect(Collectors.toList());
return (WUDGamePartition) e.getValue();
}
}
return null;
} }
} }

View File

@ -16,20 +16,25 @@
****************************************************************************/ ****************************************************************************/
package de.mas.wiiu.jnus.implementations.wud.parser; package de.mas.wiiu.jnus.implementations.wud.parser;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.Arrays; import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.stream.Collectors;
import de.mas.wiiu.jnus.Settings; import de.mas.wiiu.jnus.Settings;
import de.mas.wiiu.jnus.entities.TMD;
import de.mas.wiiu.jnus.entities.Ticket;
import de.mas.wiiu.jnus.entities.content.ContentFSTInfo; import de.mas.wiiu.jnus.entities.content.ContentFSTInfo;
import de.mas.wiiu.jnus.entities.fst.FST; import de.mas.wiiu.jnus.entities.fst.FST;
import de.mas.wiiu.jnus.entities.fst.FSTEntry; import de.mas.wiiu.jnus.entities.fst.FSTEntry;
import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader; import de.mas.wiiu.jnus.implementations.wud.reader.WUDDiscReader;
import de.mas.wiiu.jnus.utils.ByteUtils; import de.mas.wiiu.jnus.utils.ByteUtils;
import de.mas.wiiu.jnus.utils.Utils; import de.mas.wiiu.jnus.utils.Utils;
import lombok.val;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@Log @Log
@ -51,22 +56,26 @@ public final class WUDInfoParser {
public static WUDInfo createAndLoad(WUDDiscReader discReader, byte[] titleKey) throws IOException { public static WUDInfo createAndLoad(WUDDiscReader discReader, byte[] titleKey) throws IOException {
WUDInfo result = new WUDInfo(titleKey, discReader); 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 // verify DiscKey before proceeding
if (!Arrays.equals(Arrays.copyOfRange(PartitionTocBlock, 0, 4), DECRYPTED_AREA_SIGNATURE)) { if (!Arrays.equals(Arrays.copyOfRange(PartitionTocBlock, 0, 4), DECRYPTED_AREA_SIGNATURE)) {
log.info("Decryption of PartitionTocBlock failed"); // log.info("Decryption of PartitionTocBlock failed");
return null; throw new RuntimeException("Decryption of PartitionTocBlock failed");
} }
Map<String, WUDPartition> partitions = readPartitions(result, PartitionTocBlock);
result.getPartitions().clear(); result.getPartitions().clear();
result.getPartitions().putAll(partitions); result.getPartitions().putAll(readGamePartitions(result, PartitionTocBlock));
return result; return result;
} }
private static Map<String, WUDPartition> readPartitions(WUDInfo wudInfo, byte[] partitionTocBlock) throws IOException { private static Map<String, WUDPartition> readGamePartitions(WUDInfo wudInfo, byte[] partitionTocBlock) throws IOException {
ByteBuffer buffer = ByteBuffer.allocate(partitionTocBlock.length); ByteBuffer buffer = ByteBuffer.allocate(partitionTocBlock.length);
buffer.order(ByteOrder.BIG_ENDIAN); buffer.order(ByteOrder.BIG_ENDIAN);
@ -75,16 +84,11 @@ public final class WUDInfoParser {
int partitionCount = (int) ByteUtils.getUnsingedIntFromBytes(partitionTocBlock, 0x1C, ByteOrder.BIG_ENDIAN); int partitionCount = (int) ByteUtils.getUnsingedIntFromBytes(partitionTocBlock, 0x1C, ByteOrder.BIG_ENDIAN);
Map<String, WUDPartition> partitions = new HashMap<>(); Map<String, WUDPartition> internalPartitions = new HashMap<>();
Map<String, WUDPartition> 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 // populate partition information from decrypted TOC
for (int i = 0; i < partitionCount; i++) { for (int i = 0; i < partitionCount; i++) {
int offset = (PARTITION_TOC_OFFSET + (i * PARTITION_TOC_ENTRY_SIZE)); int offset = (PARTITION_TOC_OFFSET + (i * PARTITION_TOC_ENTRY_SIZE));
byte[] partitionIdentifier = Arrays.copyOfRange(partitionTocBlock, offset, offset + 0x19); byte[] partitionIdentifier = Arrays.copyOfRange(partitionTocBlock, offset, offset + 0x19);
int j = 0; int j = 0;
@ -103,61 +107,106 @@ public final class WUDInfoParser {
WUDPartition partition = new WUDPartition(partitionName, partitionOffset); 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); byte[] header = wudInfo.getWUDDiscReader().readEncryptedToByteArray(partition.getPartitionOffset() + 0x10000, 0, 0x8000);
WUDPartitionHeader partitionHeader = WUDPartitionHeader.parseHeader(header); WUDPartitionHeader partitionHeader = WUDPartitionHeader.parseHeader(header);
partition.setPartitionHeader(partitionHeader); 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 { private static byte[] getFSTEntryAsByte(String filePath, WUDPartition partition, FST fst, WUDDiscReader discReader, byte[] key) throws IOException {
FSTEntry entry = getEntryByName(fst.getRoot(), filename); FSTEntry entry = getEntryByFullPath(fst.getRoot(), filePath);
ContentFSTInfo info = fst.getContentFSTInfos().get((int) entry.getContentFSTID()); 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 // Calculating the IV
ByteBuffer byteBuffer = ByteBuffer.allocate(0x10); ByteBuffer byteBuffer = ByteBuffer.allocate(0x10);
byteBuffer.position(0x08); byteBuffer.position(0x08);
byte[] IV = byteBuffer.putLong(entry.getFileOffset() >> 16).array(); byte[] IV = byteBuffer.putLong(entry.getFileOffset() >> 16).array();
return discReader.readDecryptedToByteArray(Settings.WIIU_DECRYPTED_AREA_OFFSET + (long) partition.getPartitionOffset() + (long) info.getOffset(), 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()) { for (FSTEntry cur : root.getFileChildren()) {
if (cur.getFilename().equals(name)) { if (cur.getFullPath().equals(filePath)) {
return cur; return cur;
} }
} }
for (FSTEntry cur : root.getDirChildren()) { for (FSTEntry cur : root.getDirChildren()) {
FSTEntry dir_result = getEntryByName(cur, name); FSTEntry dir_result = getEntryByFullPath(cur, filePath);
if (dir_result != null) { if (dir_result != null) {
return dir_result; return dir_result;
} }

View File

@ -24,9 +24,11 @@ import java.io.OutputStream;
import java.io.PipedInputStream; import java.io.PipedInputStream;
import java.io.PipedOutputStream; import java.io.PipedOutputStream;
import java.io.RandomAccessFile; import java.io.RandomAccessFile;
import java.nio.ByteBuffer;
import java.util.Arrays; import java.util.Arrays;
import de.mas.wiiu.jnus.implementations.wud.WUDImage; import de.mas.wiiu.jnus.implementations.wud.WUDImage;
import de.mas.wiiu.jnus.utils.Utils;
import de.mas.wiiu.jnus.utils.cryptography.AESDecryption; import de.mas.wiiu.jnus.utils.cryptography.AESDecryption;
import lombok.Getter; import lombok.Getter;
import lombok.extern.java.Log; import lombok.extern.java.Log;
@ -60,13 +62,13 @@ public abstract class WUDDiscReader {
return out.toByteArray(); 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(); PipedInputStream in = new PipedInputStream();
PipedOutputStream out = new PipedOutputStream(in); PipedOutputStream out = new PipedOutputStream(in);
new Thread(() -> { new Thread(() -> {
try { try {
readDecryptedToOutputStream(out, offset, fileoffset, size, key, iv); readDecryptedToOutputStream(out, offset, fileoffset, size, key, IV, useFixedIV);
} catch (IOException e) { } catch (IOException e) {
e.printStackTrace(); e.printStackTrace();
} }
@ -75,10 +77,10 @@ public abstract class WUDDiscReader {
return in; 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(); ByteArrayOutputStream out = new ByteArrayOutputStream();
readDecryptedToOutputStream(out, offset, fileoffset, size, key, iv); readDecryptedToOutputStream(out, offset, fileoffset, size, key, IV, useFixedIV);
return out.toByteArray(); return out.toByteArray();
} }
@ -105,12 +107,16 @@ public abstract class WUDDiscReader {
return decryptedChunk; return decryptedChunk;
} }
public void readDecryptedToOutputStream(OutputStream outputStream, long clusterOffset, long fileOffset, long size, byte[] key, byte[] IV) public void readDecryptedToOutputStream(OutputStream outputStream, long clusterOffset, long fileOffset, long size, byte[] key, byte[] IV,
throws IOException { boolean useFixedIV) throws IOException {
byte[] usedIV = IV; byte[] usedIV = null;
if (usedIV == null) { if (useFixedIV) {
usedIV = new byte[0x10]; usedIV = IV;
if (IV == null) {
usedIV = new byte[0x10];
}
} }
long usedSize = size; long usedSize = size;
long usedFileOffset = fileOffset; long usedFileOffset = fileOffset;
byte[] buffer; byte[] buffer;
@ -130,11 +136,26 @@ public abstract class WUDDiscReader {
readOffset = clusterOffset + (blockNumber * BLOCK_SIZE); readOffset = clusterOffset + (blockNumber * BLOCK_SIZE);
// (long)WiiUDisc.WIIU_DECRYPTED_AREA_OFFSET + volumeOffset + clusterOffset + (blockStructure.getBlockNumber() * 0x8000); // (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); buffer = readDecryptedChunk(readOffset, key, usedIV);
maxCopySize = BLOCK_SIZE - blockOffset; maxCopySize = BLOCK_SIZE - blockOffset;
copySize = (usedSize > maxCopySize) ? maxCopySize : usedSize; 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; totalread += copySize;
// update counters // update counters
@ -145,6 +166,12 @@ public abstract class WUDDiscReader {
outputStream.close(); outputStream.close();
} }
/**
* Create a new RandomAccessFileStream
*
* @return
* @throws FileNotFoundException
*/
public RandomAccessFile getRandomAccessFileStream() throws FileNotFoundException { public RandomAccessFile getRandomAccessFileStream() throws FileNotFoundException {
if (getImage() == null || getImage().getFileHandle() == null) { if (getImage() == null || getImage().getFileHandle() == null) {
log.warning("No image or image filehandle set."); log.warning("No image or image filehandle set.");