- The NUSTitleLoaders now don't inherit anymore from a class, but use a static function from NUSTitleLoader. The NUSDataProvider is supplier by a Supplier argument. This simplifies the config.

- Remove the global common key. Now the common key needs to be provided to every loading instance. This way loading with different common keys is much easier (and more robust)
This commit is contained in:
Maschell 2019-04-10 18:47:22 +02:00
parent d08a42719a
commit 651e32e7ba
8 changed files with 69 additions and 105 deletions

View File

@ -1,5 +1,5 @@
/**************************************************************************** /****************************************************************************
* Copyright (C) 2016-2018 Maschell * Copyright (C) 2016-2019 Maschell
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,23 +17,11 @@
package de.mas.wiiu.jnus; 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.wud.parser.WUDGIPartitionTitle;
import de.mas.wiiu.jnus.implementations.wud.parser.WUDGamePartition;
import de.mas.wiiu.jnus.implementations.wud.parser.WUDInfo;
import lombok.Data; import lombok.Data;
@Data @Data
public class NUSTitleConfig { public class NUSTitleConfig {
private String inputPath;
private WUDGamePartition WUDGamePartition = null;
private WUDGIPartitionTitle WUDGIPartitionTitle = null;
private WUDInfo WUDInfo;
private Ticket ticket; private Ticket ticket;
private int version = Settings.LATEST_TMD_VERSION;
private long titleID = 0x0L;
private WoomyInfo woomyInfo;
private boolean noDecryption; private boolean noDecryption;
private byte[] commonKey;
} }

View File

@ -16,8 +16,13 @@
****************************************************************************/ ****************************************************************************/
package de.mas.wiiu.jnus; package de.mas.wiiu.jnus;
import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.text.ParseException;
import java.util.Arrays;
import java.util.Map; import java.util.Map;
import java.util.Optional;
import java.util.function.Supplier;
import de.mas.wiiu.jnus.entities.TMD; import de.mas.wiiu.jnus.entities.TMD;
import de.mas.wiiu.jnus.entities.Ticket; import de.mas.wiiu.jnus.entities.Ticket;
@ -25,19 +30,19 @@ import de.mas.wiiu.jnus.entities.content.Content;
import de.mas.wiiu.jnus.entities.fst.FST; import de.mas.wiiu.jnus.entities.fst.FST;
import de.mas.wiiu.jnus.implementations.NUSDataProvider; import de.mas.wiiu.jnus.implementations.NUSDataProvider;
import de.mas.wiiu.jnus.utils.StreamUtils; import de.mas.wiiu.jnus.utils.StreamUtils;
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.extern.java.Log;
@Log public class NUSTitleLoader {
abstract class NUSTitleLoader { private NUSTitleLoader() {
protected NUSTitleLoader() {
// should be empty // should be empty
} }
public NUSTitle loadNusTitle(NUSTitleConfig config) throws Exception { public static NUSTitle loadNusTitle(NUSTitleConfig config, Supplier<NUSDataProvider> dataProviderFunction)
throws IOException, ParseException {
NUSTitle result = new NUSTitle(); NUSTitle result = new NUSTitle();
NUSDataProvider dataProvider = getDataProvider(result, config); NUSDataProvider dataProvider = dataProviderFunction.get();
result.setDataProvider(dataProvider); result.setDataProvider(dataProvider);
byte[] tmdData = dataProvider.getRawTMD().orElseThrow(() -> new ParseException("No TMD data found", 0)); byte[] tmdData = dataProvider.getRawTMD().orElseThrow(() -> new ParseException("No TMD data found", 0));
@ -79,6 +84,4 @@ abstract class NUSTitleLoader {
return result; return result;
} }
protected abstract NUSDataProvider getDataProvider(NUSTitle title, NUSTitleConfig config);
} }

View File

@ -16,35 +16,34 @@
****************************************************************************/ ****************************************************************************/
package de.mas.wiiu.jnus; package de.mas.wiiu.jnus;
import java.io.IOException;
import java.text.ParseException;
import de.mas.wiiu.jnus.entities.Ticket; import de.mas.wiiu.jnus.entities.Ticket;
import de.mas.wiiu.jnus.implementations.NUSDataProvider;
import de.mas.wiiu.jnus.implementations.NUSDataProviderLocal; import de.mas.wiiu.jnus.implementations.NUSDataProviderLocal;
public final class NUSTitleLoaderLocal extends NUSTitleLoader { public final class NUSTitleLoaderLocal {
private NUSTitleLoaderLocal() { public static NUSTitle loadNUSTitle(String inputPath, byte[] commonKey) throws Exception {
super(); return loadNUSTitle(inputPath, null, commonKey);
} }
public static NUSTitle loadNUSTitle(String inputPath) throws Exception { public static NUSTitle loadNUSTitle(String inputPath, Ticket ticket) throws IOException, ParseException {
return loadNUSTitle(inputPath, null); return loadNUSTitle(inputPath, ticket, null);
} }
public static NUSTitle loadNUSTitle(String inputPath, Ticket ticket) throws Exception { public static NUSTitle loadNUSTitle(String inputPath, Ticket ticket, byte[] commonKey) throws IOException, ParseException {
NUSTitleLoader loader = new NUSTitleLoaderLocal();
NUSTitleConfig config = new NUSTitleConfig(); NUSTitleConfig config = new NUSTitleConfig();
config.setCommonKey(commonKey);
if (ticket != null) { if (ticket != null) {
config.setTicket(ticket); config.setTicket(ticket);
} } else if (commonKey == null) {
config.setInputPath(inputPath); throw new IOException("Ticket was null and no commonKey was given");
return loader.loadNusTitle(config);
} }
@Override return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderLocal(inputPath));
protected NUSDataProvider getDataProvider(NUSTitle title, NUSTitleConfig config) {
return new NUSDataProviderLocal(title, config.getInputPath());
} }
} }

View File

@ -18,25 +18,14 @@
package de.mas.wiiu.jnus; 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.NUSDataProvider;
import de.mas.wiiu.jnus.implementations.NUSDataProviderLocalBackup; import de.mas.wiiu.jnus.implementations.NUSDataProviderLocalBackup;
public final class NUSTitleLoaderLocalBackup extends NUSTitleLoader { public final class NUSTitleLoaderLocalBackup {
private NUSTitleLoaderLocalBackup() { private NUSTitleLoaderLocalBackup() {
super();
}
public static NUSTitle loadNUSTitle(String inputPath) throws Exception {
return loadNUSTitle(inputPath, (short) Settings.LATEST_TMD_VERSION);
} }
public static NUSTitle loadNUSTitle(String inputPath, short titleVersion) throws Exception {
return loadNUSTitle(inputPath, titleVersion, null);
}
public static NUSTitle loadNUSTitle(String inputPath, short titleVersion, Ticket ticket) throws Exception { public static NUSTitle loadNUSTitle(String inputPath, short titleVersion, Ticket ticket) throws Exception {
NUSTitleLoader loader = new NUSTitleLoaderLocalBackup();
NUSTitleConfig config = new NUSTitleConfig(); NUSTitleConfig config = new NUSTitleConfig();
if (ticket != null) { if (ticket != null) {
@ -45,15 +34,7 @@ public final class NUSTitleLoaderLocalBackup extends NUSTitleLoader {
config.setNoDecryption(true); config.setNoDecryption(true);
} }
config.setVersion(titleVersion); return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderLocalBackup(inputPath, titleVersion));
config.setInputPath(inputPath);
return loader.loadNusTitle(config);
}
@Override
protected NUSDataProvider getDataProvider(NUSTitle title, NUSTitleConfig config) {
return new NUSDataProviderLocalBackup(title, config.getInputPath(), (short) config.getVersion());
} }
} }

View File

@ -16,22 +16,23 @@
****************************************************************************/ ****************************************************************************/
package de.mas.wiiu.jnus; package de.mas.wiiu.jnus;
import java.io.IOException;
import java.text.ParseException;
import de.mas.wiiu.jnus.entities.Ticket; import de.mas.wiiu.jnus.entities.Ticket;
import de.mas.wiiu.jnus.implementations.NUSDataProvider;
import de.mas.wiiu.jnus.implementations.NUSDataProviderRemote; import de.mas.wiiu.jnus.implementations.NUSDataProviderRemote;
public final class NUSTitleLoaderRemote extends NUSTitleLoader { public final class NUSTitleLoaderRemote {
private NUSTitleLoaderRemote() { private NUSTitleLoaderRemote() {
super();
} }
public static NUSTitle loadNUSTitle(long titleID) throws Exception { public static NUSTitle loadNUSTitle(long titleID, byte[] commonKey) throws Exception {
return loadNUSTitle(titleID, Settings.LATEST_TMD_VERSION, null); return loadNUSTitle(titleID, Settings.LATEST_TMD_VERSION, commonKey);
} }
public static NUSTitle loadNUSTitle(long titleID, int version) throws Exception { public static NUSTitle loadNUSTitle(long titleID, int version, byte[] commonKey) throws Exception {
return loadNUSTitle(titleID, version, null); return loadNUSTitle(titleID, version, null, false, commonKey);
} }
public static NUSTitle loadNUSTitle(long titleID, Ticket ticket) throws Exception { public static NUSTitle loadNUSTitle(long titleID, Ticket ticket) throws Exception {
@ -39,24 +40,20 @@ public final class NUSTitleLoaderRemote extends NUSTitleLoader {
} }
public static NUSTitle loadNUSTitle(long titleID, int version, Ticket ticket) throws Exception { public static NUSTitle loadNUSTitle(long titleID, int version, Ticket ticket) throws Exception {
return loadNUSTitle(titleID, version, ticket, false); return loadNUSTitle(titleID, version, ticket, false, null);
} }
public static NUSTitle loadNUSTitle(long titleID, int version, Ticket ticket, boolean noEncryption) throws Exception { public static NUSTitle loadNUSTitle(long titleID, int version, Ticket ticket, boolean noEncryption, byte[] commonKey) throws IOException, ParseException {
NUSTitleLoader loader = new NUSTitleLoaderRemote();
NUSTitleConfig config = new NUSTitleConfig(); NUSTitleConfig config = new NUSTitleConfig();
config.setVersion(version);
config.setTitleID(titleID);
config.setTicket(ticket); config.setTicket(ticket);
config.setNoDecryption(noEncryption); config.setNoDecryption(noEncryption);
config.setCommonKey(commonKey);
return loader.loadNusTitle(config); if (ticket == null && !noEncryption && commonKey == null) {
throw new IOException("Ticket was null and no commonKey was given");
} }
@Override return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderRemote(version, titleID));
protected NUSDataProvider getDataProvider(NUSTitle title, NUSTitleConfig config) {
return new NUSDataProviderRemote(title, config.getVersion(), config.getTitleID());
} }
} }

View File

@ -1,5 +1,5 @@
/**************************************************************************** /****************************************************************************
* Copyright (C) 2016-2018 Maschell * Copyright (C) 2016-2019 Maschell
* *
* This program is free software: you can redistribute it and/or modify * 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 * it under the terms of the GNU General Public License as published by
@ -17,32 +17,29 @@
package de.mas.wiiu.jnus; package de.mas.wiiu.jnus;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.text.ParseException;
import javax.xml.parsers.ParserConfigurationException;
import org.xml.sax.SAXException;
import de.mas.wiiu.jnus.implementations.NUSDataProvider;
import de.mas.wiiu.jnus.implementations.NUSDataProviderWoomy; import de.mas.wiiu.jnus.implementations.NUSDataProviderWoomy;
import de.mas.wiiu.jnus.implementations.woomy.WoomyInfo; import de.mas.wiiu.jnus.implementations.woomy.WoomyInfo;
import de.mas.wiiu.jnus.implementations.woomy.WoomyParser; import de.mas.wiiu.jnus.implementations.woomy.WoomyParser;
import lombok.extern.java.Log;
@Log public final class NUSTitleLoaderWoomy {
public final class NUSTitleLoaderWoomy extends NUSTitleLoader {
public static NUSTitle loadNUSTitle(String inputFile) throws Exception { private NUSTitleLoaderWoomy() {
NUSTitleLoaderWoomy loader = new NUSTitleLoaderWoomy();
}
public static NUSTitle loadNUSTitle(String inputFile) throws IOException, ParserConfigurationException, SAXException, ParseException {
NUSTitleConfig config = new NUSTitleConfig(); NUSTitleConfig config = new NUSTitleConfig();
WoomyInfo woomyInfo = WoomyParser.createWoomyInfo(new File(inputFile)); WoomyInfo woomyInfo = WoomyParser.createWoomyInfo(new File(inputFile));
if (woomyInfo == null) {
log.info("Created woomy is null.");
return null;
}
config.setWoomyInfo(woomyInfo);
return loader.loadNusTitle(config);
}
@Override return NUSTitleLoader.loadNusTitle(config, () -> new NUSDataProviderWoomy(woomyInfo));
protected NUSDataProvider getDataProvider(NUSTitle title, NUSTitleConfig config) {
return new NUSDataProviderWoomy(title, config.getWoomyInfo());
} }
} }

View File

@ -31,6 +31,5 @@ public class Settings {
public static final String USER_AGENT = "Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.28 (KHTML, like Gecko) NX/3.0.3.12.12 NintendoBrowser/3.0.0.9561.US"; public static final String USER_AGENT = "Mozilla/5.0 (Nintendo WiiU) AppleWebKit/536.28 (KHTML, like Gecko) NX/3.0.3.12.12 NintendoBrowser/3.0.0.9561.US";
public static final boolean ALLOW_PARALLELISATION = true; public static final boolean ALLOW_PARALLELISATION = true;
public static byte[] commonKey = new byte[0x10];
public static int WIIU_DECRYPTED_AREA_OFFSET = 0x18000; public static int WIIU_DECRYPTED_AREA_OFFSET = 0x18000;
} }

View File

@ -22,7 +22,6 @@ import java.nio.ByteBuffer;
import java.nio.file.Files; import java.nio.file.Files;
import java.util.Arrays; import java.util.Arrays;
import de.mas.wiiu.jnus.Settings;
import de.mas.wiiu.jnus.utils.Utils; 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;
@ -44,17 +43,17 @@ public final class Ticket {
this.IV = IV; this.IV = IV;
} }
public static Ticket parseTicket(File ticket) throws IOException { public static Ticket parseTicket(File ticket, byte[] commonKey) throws IOException {
if (ticket == null || !ticket.exists()) { if (ticket == null || !ticket.exists()) {
log.warning("Ticket input file null or doesn't exist."); log.warning("Ticket input file null or doesn't exist.");
return null; throw new IOException("Ticket input file null or doesn't exist.");
} }
return parseTicket(Files.readAllBytes(ticket.toPath())); return parseTicket(Files.readAllBytes(ticket.toPath()), commonKey);
} }
public static Ticket parseTicket(byte[] ticket) throws IOException { public static Ticket parseTicket(byte[] ticket, byte[] commonKey) throws IOException {
if (ticket == null) { if (ticket == null) {
return null; throw new IOException("Ticket input file null or doesn't exist.");
} }
ByteBuffer buffer = ByteBuffer.allocate(ticket.length); ByteBuffer buffer = ByteBuffer.allocate(ticket.length);
@ -69,20 +68,20 @@ public final class Ticket {
buffer.position(POSITION_TITLEID); buffer.position(POSITION_TITLEID);
long titleID = buffer.getLong(); long titleID = buffer.getLong();
Ticket result = createTicket(encryptedKey, titleID); Ticket result = createTicket(encryptedKey, titleID, commonKey);
return result; return result;
} }
public static Ticket createTicket(byte[] encryptedKey, long titleID) { public static Ticket createTicket(byte[] encryptedKey, long titleID, byte[] commonKey) {
byte[] IV = ByteBuffer.allocate(0x10).putLong(titleID).array(); byte[] IV = ByteBuffer.allocate(0x10).putLong(titleID).array();
byte[] decryptedKey = calculateDecryptedKey(encryptedKey, IV); byte[] decryptedKey = calculateDecryptedKey(encryptedKey, IV, commonKey);
return new Ticket(encryptedKey, decryptedKey, IV); return new Ticket(encryptedKey, decryptedKey, IV);
} }
private static byte[] calculateDecryptedKey(byte[] encryptedKey, byte[] IV) { private static byte[] calculateDecryptedKey(byte[] encryptedKey, byte[] IV, byte[] commonKey) {
AESDecryption decryption = new AESDecryption(Settings.commonKey, IV); AESDecryption decryption = new AESDecryption(commonKey, IV);
return decryption.decrypt(encryptedKey); return decryption.decrypt(encryptedKey);
} }
@ -107,4 +106,5 @@ public final class Ticket {
public String toString() { public String toString() {
return "Ticket [encryptedKey=" + Utils.ByteArrayToString(encryptedKey) + ", decryptedKey=" + Utils.ByteArrayToString(decryptedKey) + "]"; return "Ticket [encryptedKey=" + Utils.ByteArrayToString(encryptedKey) + ", decryptedKey=" + Utils.ByteArrayToString(decryptedKey) + "]";
} }
} }