First commit, have fun!

This commit is contained in:
Maschell 2016-12-13 20:35:50 +01:00
parent dd4200084e
commit 7613953ffa
6 changed files with 405 additions and 47 deletions

37
.classpath Normal file
View File

@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<classpath>
<classpathentry kind="src" output="target/classes" path="src/main/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/classes" path="src/main/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="src" output="target/test-classes" path="src/test/java">
<attributes>
<attribute name="optional" value="true"/>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry excluding="**" kind="src" output="target/test-classes" path="src/test/resources">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.5">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="con" path="org.eclipse.m2e.MAVEN2_CLASSPATH_CONTAINER">
<attributes>
<attribute name="maven.pomderived" value="true"/>
</attributes>
</classpathentry>
<classpathentry kind="lib" path="G:/Programmieren/JWUDTool/libs/jnuslib.jar"/>
<classpathentry kind="output" path="target/classes"/>
</classpath>

49
.gitignore vendored
View File

@ -1,47 +1,2 @@
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Windows Installer files
*.cab
*.msi
*.msm
*.msp
# Windows shortcuts
*.lnk
# =========================
# Operating System Files
# =========================
# OSX
# =========================
.DS_Store
.AppleDouble
.LSOverride
# Thumbnails
._*
# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk
/bin/
/target/

23
.project Normal file
View File

@ -0,0 +1,23 @@
<?xml version="1.0" encoding="UTF-8"?>
<projectDescription>
<name>jwudtool</name>
<comment></comment>
<projects>
</projects>
<buildSpec>
<buildCommand>
<name>org.eclipse.jdt.core.javabuilder</name>
<arguments>
</arguments>
</buildCommand>
<buildCommand>
<name>org.eclipse.m2e.core.maven2Builder</name>
<arguments>
</arguments>
</buildCommand>
</buildSpec>
<natures>
<nature>org.eclipse.jdt.core.javanature</nature>
<nature>org.eclipse.m2e.core.maven2Nature</nature>
</natures>
</projectDescription>

BIN
libs/jnuslib.jar Normal file

Binary file not shown.

27
pom.xml Normal file
View File

@ -0,0 +1,27 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>de.mas</groupId>
<artifactId>jwudtool</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.16.12</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>commons-cli</groupId>
<artifactId>commons-cli</artifactId>
<version>1.3.1</version>
</dependency>
</dependencies>
</project>

View File

@ -0,0 +1,316 @@
package de.mas.jwudtool;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import org.apache.commons.cli.CommandLine;
import org.apache.commons.cli.CommandLineParser;
import org.apache.commons.cli.DefaultParser;
import org.apache.commons.cli.HelpFormatter;
import org.apache.commons.cli.MissingArgumentException;
import org.apache.commons.cli.Option;
import org.apache.commons.cli.Options;
import org.apache.commons.cli.UnrecognizedOptionException;
import de.mas.jnus.lib.DecryptionService;
import de.mas.jnus.lib.ExtractionService;
import de.mas.jnus.lib.NUSTitle;
import de.mas.jnus.lib.NUSTitleLoaderWUD;
import de.mas.jnus.lib.Settings;
import de.mas.jnus.lib.WUDService;
import de.mas.jnus.lib.implementations.wud.WUDImage;
import de.mas.jnus.lib.utils.Utils;
public class Main {
public static void main(String[] args) throws Exception {
System.out.println("JWUDTool 0.1 - Maschell");
System.out.println();
Options options = getOptions();
if (args.length == 0) {
showHelp(options);
return;
}
CommandLineParser parser = new DefaultParser();
CommandLine cmd = null;
try{
cmd = parser.parse(options, args);
}catch(MissingArgumentException e){
System.out.println(e.getMessage());
return ;
}catch(UnrecognizedOptionException e){
System.out.println(e.getMessage());
showHelp(options);
return;
}
String input = null;
String output = null;
boolean overwrite = false;
byte[] titlekey = null;
readKey();
if(cmd.hasOption("help")){
showHelp(options);
return;
}
if(cmd.hasOption("in")){
input = cmd.getOptionValue("in");
}
if(cmd.hasOption("out")){
output = cmd.getOptionValue("out");
}
if(cmd.hasOption("commonkey")){
String commonKey = cmd.getOptionValue("commonkey");
byte[] key = Utils.StringToByteArray(commonKey);
if(key.length == 0x10){
Settings.commonKey = key;
System.out.println("Commonkey was set to: " + Utils.ByteArrayToString(key));
}
}
if(cmd.hasOption("titlekey")){
String titlekey_string = cmd.getOptionValue("titlekey");
titlekey = Utils.StringToByteArray(titlekey_string);
if(titlekey.length != 0x10){
titlekey = null;
}else{
System.out.println("Titlekey was set to: " + Utils.ByteArrayToString(titlekey));
}
}
if(cmd.hasOption("overwrite")){
overwrite = true;
}
if(cmd.hasOption("compress")){
boolean verify = true;
System.out.println("Compressing: " + input);
if(cmd.hasOption("noVerify")){
System.out.println("Verification disabled.");
verify = false;
}
compressWUD(input,output,verify,overwrite);
return;
}else if(cmd.hasOption("verify")){
System.out.println("Comparing images.");
String[] verifyArgs = cmd.getOptionValues("verify");
File input1 = new File(verifyArgs[0]);
File input2 = new File(verifyArgs[1]);
verifyImages(input1,input2);
return;
}else{
if(cmd.hasOption("decrypt")){
System.out.println("Decrypting full game partition.");
decrypt(input,output,overwrite,titlekey);
return;
}else if(cmd.hasOption("decryptFile")){
String regex = cmd.getOptionValue("decryptFile");
System.out.println("Decrypting files matching \"" +regex + "\"");
decryptFile(input,output,regex,overwrite,titlekey);
return;
}else if(cmd.hasOption("extract")){
System.out.println("Extracting WUD");
String arg = cmd.getOptionValue("extract");
if(arg == null){
arg = "all";
}
extract(input,output,overwrite,titlekey,arg);
return;
}
}
}
private static void extract(String input, String output, boolean overwrite, byte[] titlekey, String arg) throws Exception {
if(input == null){
System.out.println("You need to provide an input file");
}
boolean extractAll = false;
boolean extractContent = false;
boolean extractTicket = false;
boolean extractHashes = false;
switch(arg){
case "all":
extractAll = true;
break;
case "content":
extractContent = true;
break;
case "ticket":
extractTicket = true;
break;
case "hashes":
extractHashes = true;
break;
default:
System.out.println("Argument not found:" + arg);
return;
}
File inputFile = new File(input);
System.out.println("Extracting: " + inputFile.getAbsolutePath());
NUSTitle title = NUSTitleLoaderWUD.loadNUSTitle(inputFile.getAbsolutePath(),titlekey);
if(title == null){
return;
}
if(output == null){
output = String.format("%016X", title.getTMD().getTitleID());
}else{
output += File.separator + String.format("%016X", title.getTMD().getTitleID());
}
File outputFolder = new File(output);
System.out.println("To the folder: " + outputFolder.getAbsolutePath());
ExtractionService extractionService = ExtractionService.getInstance(title);
if(extractAll){
extractionService.extractAll(outputFolder.getAbsolutePath());
}else if(extractTicket){
extractionService.extractTicketTo(outputFolder.getAbsolutePath());
}else if(extractContent){
extractionService.extractAllEncryptedContentFilesWithoutHashesTo(outputFolder.getAbsolutePath());
}else if(extractHashes){
extractionService.extractAllEncrpytedContentFileHashes(outputFolder.getAbsolutePath());
}
System.out.println("Extraction done!");
}
private static void decryptFile(String input, String output, String regex, boolean overwrite, byte[] titlekey) throws Exception {
if(input == null){
System.out.println("You need to provide an input file");
}
File inputFile = new File(input);
System.out.println("Decrypting: " + inputFile.getAbsolutePath());
NUSTitle title = NUSTitleLoaderWUD.loadNUSTitle(inputFile.getAbsolutePath(),titlekey);
if(title == null){
return;
}
if(output == null){
output = String.format("%016X", title.getTMD().getTitleID());
}else{
output += File.separator + String.format("%016X", title.getTMD().getTitleID());
}
File outputFolder = new File(output);
System.out.println("To the folder: " + outputFolder.getAbsolutePath());
title.setSkipExistingFiles(!overwrite);
DecryptionService decryption = DecryptionService.getInstance(title);
decryption.decryptFSTEntriesTo(regex,outputFolder.getAbsolutePath());
System.out.println("Decryption done");
}
private static void decrypt(String input,String output, boolean overwrite,byte[] titlekey) throws Exception {
decryptFile(input,output,".*",overwrite,titlekey);
}
private static void verifyImages(File input1, File input2) throws IOException {
System.out.println("Input 1: " +input1.getAbsolutePath());
WUDImage image1 = new WUDImage(input1);
System.out.println("Input 2: " +input2.getAbsolutePath());
WUDImage image2 = new WUDImage(input2);
if(WUDService.compareWUDImage(image1, image2)){
System.out.println("Both images have the same data");
}else{
System.out.println("The images are different!");
}
}
private static void compressWUD(String input,String output, boolean verify, boolean overwrite) throws IOException {
if(input == null){
System.out.println("-in null");
return;
}
File inputImage = new File(input);
if(inputImage.isDirectory() || !inputImage.exists()){
System.out.println(inputImage.getAbsolutePath() + " is no file or does not exist");
return;
}
System.out.println("Parsing WUD image.");
WUDImage image = new WUDImage(inputImage);
File compressedImage = WUDService.compressWUDToWUX(image, output,overwrite);
if(compressedImage != null){
System.out.println("Compression successful!");
}
if(verify){
if(compressedImage != null){
WUDImage image2 = new WUDImage(compressedImage);
if(WUDService.compareWUDImage(image, image2)){
System.out.println("Compressed files is valid.");
}else{
System.out.println("Warning! Compressed file in INVALID!");
}
}
}else{
System.out.println("Verfication skipped");
}
}
private static void readKey() throws IOException {
File file = new File("common.key");
if(file.isFile()){
byte[] key = Files.readAllBytes(file.toPath());
Settings.commonKey = key;
System.out.println("Commonkey was set to: " + Utils.ByteArrayToString(key));
}
}
private static Options getOptions() { // TODO: schöner machen?
Options options = new Options();
options.addOption(Option.builder("in").argName("input file").hasArg().desc("Input file. Can be a .wux, .wud or a game_part1.wud").build());
options.addOption(Option.builder("out").argName("output path").hasArg().desc("The path where the result will be saved").build());
options.addOption(Option.builder("compress").desc("Compresses the input to a .wux file.").build());
options.addOption(Option.builder("noVerify").desc("Disables verification after compressing").build());
options.addOption(Option.builder("verify").argName("wudimage1|wudimage2").hasArg().numberOfArgs(2).desc("Compares two WUD images to find differences").build());
options.addOption(Option.builder("overwrite").desc("Optional. Overwrites existing files").build());
options.addOption(Option.builder("commonkey").argName("WiiU common key").hasArg().desc("Optional. HexString. Will be used if no \"common.key\" in the folder of this .jar is found").build());
options.addOption(Option.builder("decrypt").desc("Decrypts full the game partition of the given wud.").build());
options.addOption(Option.builder("titlekey").argName("WUD title key").hasArg().desc("Optional. HexString. Will be used if no \"game.key\" in the folder of the wud image is found").build());
options.addOption(Option.builder("decryptFile").argName("regular expression").hasArg().desc("Decrypts full the game partition of the given wud.").build());
options.addOption(Option.builder("extract").argName("all|content|ticket|hashes").hasArg().optionalArg(true).desc("Extracts files from the game partition of the given wud (Arguments optional)").build());
options.addOption("help", false, "shows this text");
return options;
}
private static void showHelp(Options options) {
HelpFormatter formatter = new HelpFormatter();
formatter.setWidth(100);
formatter.printHelp(" ", options);
}
}