From cb8823eb0b446d5c1b9b580e5578866e691771d8 Mon Sep 17 00:00:00 2001 From: liach Date: Wed, 15 May 2019 22:03:13 -0700 Subject: Feature/weave (#138) * Add weave/stitch style command system to enigma Also fixed divide by zero stupidity Signed-off-by: liach * Add tests for package access index and command Signed-off-by: liach * Minor tweaks Signed-off-by: liach --- .../enigma/command/CheckMappingsCommand.java | 62 +++++++++ src/main/java/cuchaz/enigma/command/Command.java | 140 +++++++++++++++++++++ .../enigma/command/ConvertMappingsCommand.java | 47 +++++++ .../cuchaz/enigma/command/DecompileCommand.java | 33 +++++ .../cuchaz/enigma/command/DeobfuscateCommand.java | 33 +++++ 5 files changed, 315 insertions(+) create mode 100644 src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java create mode 100644 src/main/java/cuchaz/enigma/command/Command.java create mode 100644 src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java create mode 100644 src/main/java/cuchaz/enigma/command/DecompileCommand.java create mode 100644 src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java (limited to 'src/main/java/cuchaz/enigma/command') diff --git a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java new file mode 100644 index 0000000..7ec7679 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java @@ -0,0 +1,62 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; +import cuchaz.enigma.translation.representation.entry.ClassEntry; + +import java.io.File; +import java.nio.file.Path; +import java.util.Set; +import java.util.jar.JarFile; +import java.util.stream.Collectors; + +public class CheckMappingsCommand extends Command { + + public CheckMappingsCommand() { + super("checkmappings"); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 2; + } + + @Override + public void run(String... args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); + Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true)); + + System.out.println("Reading JAR..."); + Deobfuscator deobfuscator = new Deobfuscator(new JarFile(fileJarIn)); + System.out.println("Reading mappings..."); + + MappingFormat format = chooseEnigmaFormat(fileMappings); + EntryTree mappings = format.read(fileMappings, ProgressListener.VOID); + deobfuscator.setMappings(mappings); + + JarIndex idx = deobfuscator.getJarIndex(); + + boolean error = false; + + for (Set partition : idx.getPackageVisibilityIndex().getPartitions()) { + long packages = partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::getPackageName).distinct().count(); + if (packages > 1) { + error = true; + System.err.println("ERROR: Must be in one package:\n" + partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::toString).sorted().collect(Collectors.joining("\n"))); + } + } + + if (error) { + throw new IllegalStateException("Errors in package visibility detected, see SysErr above"); + } + } +} diff --git a/src/main/java/cuchaz/enigma/command/Command.java b/src/main/java/cuchaz/enigma/command/Command.java new file mode 100644 index 0000000..b107fb6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/Command.java @@ -0,0 +1,140 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Deobfuscator; +import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.io.File; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.jar.JarFile; + +public abstract class Command { + public final String name; + + protected Command(String name) { + this.name = name; + } + + public abstract String getUsage(); + + public abstract boolean isValidArgument(int length); + + public abstract void run(String... args) throws Exception; + + protected static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception { + System.out.println("Reading jar..."); + Deobfuscator deobfuscator = new Deobfuscator(jar); + if (fileMappings != null) { + System.out.println("Reading mappings..."); + EntryTree mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, new ConsoleProgressListener()); + deobfuscator.setMappings(mappings); + } + return deobfuscator; + } + + protected static MappingFormat chooseEnigmaFormat(Path path) { + if (Files.isDirectory(path)) { + return MappingFormat.ENIGMA_DIRECTORY; + } else { + return MappingFormat.ENIGMA_FILE; + } + } + + protected static File getWritableFile(String path) { + if (path == null) { + return null; + } + File file = new File(path).getAbsoluteFile(); + File dir = file.getParentFile(); + if (dir == null) { + throw new IllegalArgumentException("Cannot write file: " + path); + } + // quick fix to avoid stupid stuff in Gradle code + if (!dir.isDirectory()) { + dir.mkdirs(); + } + return file; + } + + protected static File getWritableFolder(String path) { + if (path == null) { + return null; + } + File dir = new File(path).getAbsoluteFile(); + if (!dir.exists()) { + throw new IllegalArgumentException("Cannot write to folder: " + dir); + } + return dir; + } + + protected static File getReadableFile(String path) { + if (path == null) { + return null; + } + File file = new File(path).getAbsoluteFile(); + if (!file.exists()) { + throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); + } + return file; + } + + protected static Path getReadablePath(String path) { + if (path == null) { + return null; + } + Path file = Paths.get(path).toAbsolutePath(); + if (!Files.exists(file)) { + throw new IllegalArgumentException("Cannot find file: " + file.toString()); + } + return file; + } + + protected static String getArg(String[] args, int i, String name, boolean required) { + if (i >= args.length) { + if (required) { + throw new IllegalArgumentException(name + " is required"); + } else { + return null; + } + } + return args[i]; + } + + public static class ConsoleProgressListener implements ProgressListener { + + private static final int ReportTime = 5000; // 5s + + private int totalWork; + private long startTime; + private long lastReportTime; + + @Override + public void init(int totalWork, String title) { + this.totalWork = totalWork; + this.startTime = System.currentTimeMillis(); + this.lastReportTime = this.startTime; + System.out.println(title); + } + + @Override + public void step(int numDone, String message) { + long now = System.currentTimeMillis(); + boolean isLastUpdate = numDone == this.totalWork; + boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; + + if (shouldReport) { + int percent = numDone * 100 / this.totalWork; + System.out.println(String.format("\tProgress: %3d%%", percent)); + this.lastReportTime = now; + } + if (isLastUpdate) { + double elapsedSeconds = (now - this.startTime) / 1000.0; + System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); + } + } + } +} diff --git a/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java b/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java new file mode 100644 index 0000000..75d3791 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java @@ -0,0 +1,47 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFormat; +import cuchaz.enigma.translation.mapping.tree.EntryTree; + +import java.io.File; +import java.nio.file.Path; +import java.util.Locale; + +public class ConvertMappingsCommand extends Command { + + public ConvertMappingsCommand() { + super("convertmappings"); + } + + @Override + public String getUsage() { + return " "; + } + + @Override + public boolean isValidArgument(int length) { + return length == 3; + } + + @Override + public void run(String... args) throws Exception { + Path fileMappings = getReadablePath(getArg(args, 0, "enigma mappings", true)); + File result = getWritableFile(getArg(args, 1, "converted mappings", true)); + String name = getArg(args, 2, "format desc", true); + MappingFormat saveFormat; + try { + saveFormat = MappingFormat.valueOf(name.toUpperCase(Locale.ROOT)); + } catch (IllegalArgumentException e) { + throw new IllegalArgumentException(name + "is not a valid mapping format!"); + } + + System.out.println("Reading mappings..."); + + MappingFormat readFormat = chooseEnigmaFormat(fileMappings); + EntryTree mappings = readFormat.read(fileMappings, new ConsoleProgressListener()); + System.out.println("Saving new mappings..."); + + saveFormat.write(mappings, result.toPath(), new ConsoleProgressListener()); + } +} diff --git a/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/src/main/java/cuchaz/enigma/command/DecompileCommand.java new file mode 100644 index 0000000..a58d908 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/DecompileCommand.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Deobfuscator; + +import java.io.File; +import java.nio.file.Path; +import java.util.jar.JarFile; + +public class DecompileCommand extends Command { + + public DecompileCommand() { + super("decompile"); + } + + @Override + public String getUsage() { + return " []"; + } + + @Override + public boolean isValidArgument(int length) { + return length == 2 || length == 3; + } + + @Override + public void run(String... args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); + File fileJarOut = getWritableFolder(getArg(args, 1, "out folder", true)); + Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); + Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); + deobfuscator.writeSources(fileJarOut.toPath(), new Command.ConsoleProgressListener()); + } +} diff --git a/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java new file mode 100644 index 0000000..5d49938 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java @@ -0,0 +1,33 @@ +package cuchaz.enigma.command; + +import cuchaz.enigma.Deobfuscator; + +import java.io.File; +import java.nio.file.Path; +import java.util.jar.JarFile; + +public class DeobfuscateCommand extends Command { + + public DeobfuscateCommand() { + super("deobfuscate"); + } + + @Override + public String getUsage() { + return " []"; + } + + @Override + public boolean isValidArgument(int length) { + return length == 2 || length == 3; + } + + @Override + public void run(String... args) throws Exception { + File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); + File fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)); + Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); + Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); + deobfuscator.writeTransformedJar(fileJarOut, new Command.ConsoleProgressListener()); + } +} -- cgit v1.2.3