mirror of
https://github.com/Maschell/JNUSLib.git
synced 2024-11-26 09:54:18 +01:00
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:
parent
bfbbafc269
commit
7e07765fa1
@ -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;
|
||||
|
||||
|
@ -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<NUSTitle> 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<NUSTitle> 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<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;
|
||||
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<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
|
||||
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());
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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 + "]";
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -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<Integer, Long> indexTable = new HashMap<>();
|
||||
|
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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<WUDGamePartition> getGamePartitions() {
|
||||
return partitions.values().stream().filter(p -> p instanceof WUDGamePartition).map(p -> (WUDGamePartition) p).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private WUDGamePartition findGamePartition() {
|
||||
for (Entry<String, WUDPartition> e : getPartitions().entrySet()) {
|
||||
if (e.getKey().equals(getGamePartitionName())) {
|
||||
return (WUDGamePartition) e.getValue();
|
||||
}
|
||||
}
|
||||
return null;
|
||||
public List<WUDGIPartitionTitle> getGIPartitionTitles() {
|
||||
return partitions.values().stream().filter(p -> p instanceof WUDGIPartition).flatMap(p -> ((WUDGIPartition) p).getTitles().stream())
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
@ -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<String, WUDPartition> partitions = readPartitions(result, PartitionTocBlock);
|
||||
result.getPartitions().clear();
|
||||
result.getPartitions().putAll(partitions);
|
||||
|
||||
result.getPartitions().putAll(readGamePartitions(result, PartitionTocBlock));
|
||||
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);
|
||||
|
||||
buffer.order(ByteOrder.BIG_ENDIAN);
|
||||
@ -75,16 +84,11 @@ public final class WUDInfoParser {
|
||||
|
||||
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
|
||||
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);
|
||||
}
|
||||
|
||||
private static byte[] getFSTEntryAsByte(String filename, WUDPartition partition, FST fst, WUDDiscReader discReader, byte[] key) throws IOException {
|
||||
FSTEntry entry = getEntryByName(fst.getRoot(), filename);
|
||||
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 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;
|
||||
}
|
||||
|
@ -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) {
|
||||
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.");
|
||||
|
Loading…
Reference in New Issue
Block a user