diff options
| author | 2019-05-15 22:03:13 -0700 | |
|---|---|---|
| committer | 2019-05-16 07:03:13 +0200 | |
| commit | cb8823eb0b446d5c1b9b580e5578866e691771d8 (patch) | |
| tree | 1e8c1a5b981f3ad42c393f5d7cb75754f25f51ba /src/main/java/cuchaz/enigma/CommandMain.java | |
| parent | checkmappings command (#137) (diff) | |
| download | enigma-fork-cb8823eb0b446d5c1b9b580e5578866e691771d8.tar.gz enigma-fork-cb8823eb0b446d5c1b9b580e5578866e691771d8.tar.xz enigma-fork-cb8823eb0b446d5c1b9b580e5578866e691771d8.zip | |
Feature/weave (#138)
* Add weave/stitch style command system to enigma
Also fixed divide by zero stupidity
Signed-off-by: liach <liach@users.noreply.github.com>
* Add tests for package access index and command
Signed-off-by: liach <liach@users.noreply.github.com>
* Minor tweaks
Signed-off-by: liach <liach@users.noreply.github.com>
Diffstat (limited to 'src/main/java/cuchaz/enigma/CommandMain.java')
| -rw-r--r-- | src/main/java/cuchaz/enigma/CommandMain.java | 248 |
1 files changed, 52 insertions, 196 deletions
diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java index db4fd12..5b25087 100644 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ b/src/main/java/cuchaz/enigma/CommandMain.java | |||
| @@ -11,43 +11,45 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma; | 12 | package cuchaz.enigma; |
| 13 | 13 | ||
| 14 | import cuchaz.enigma.analysis.index.JarIndex; | 14 | import cuchaz.enigma.command.*; |
| 15 | import cuchaz.enigma.translation.mapping.EntryMapping; | 15 | |
| 16 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | 16 | import java.util.LinkedHashMap; |
| 17 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 19 | |||
| 20 | import java.io.File; | ||
| 21 | import java.nio.file.Files; | ||
| 22 | import java.nio.file.Path; | ||
| 23 | import java.nio.file.Paths; | ||
| 24 | import java.util.Locale; | 17 | import java.util.Locale; |
| 25 | import java.util.Set; | 18 | import java.util.Map; |
| 26 | import java.util.jar.JarFile; | ||
| 27 | import java.util.stream.Collectors; | ||
| 28 | 19 | ||
| 29 | public class CommandMain { | 20 | public class CommandMain { |
| 30 | 21 | ||
| 31 | public static void main(String[] args) throws Exception { | 22 | private static final Map<String, Command> COMMANDS = new LinkedHashMap<>(); |
| 23 | |||
| 24 | public static void main(String... args) throws Exception { | ||
| 32 | try { | 25 | try { |
| 33 | // process the command | 26 | // process the command |
| 34 | String command = getArg(args, 0, "command", true).toLowerCase(Locale.ROOT); | 27 | if (args.length < 1) |
| 35 | switch (command) { | 28 | throw new IllegalArgumentException("Requires a command"); |
| 36 | case "deobfuscate": | 29 | String command = args[0].toLowerCase(Locale.ROOT); |
| 37 | deobfuscate(args); | 30 | |
| 38 | break; | 31 | Command cmd = COMMANDS.get(command); |
| 39 | case "decompile": | 32 | if (cmd == null) |
| 40 | decompile(args); | 33 | throw new IllegalArgumentException("Command not recognized: " + command); |
| 41 | break; | 34 | |
| 42 | case "convertmappings": | 35 | if (!cmd.isValidArgument(args.length - 1)) { |
| 43 | convertMappings(args); | 36 | throw new CommandHelpException(cmd); |
| 44 | break; | 37 | } |
| 45 | case "checkmappings": | 38 | |
| 46 | checkMappings(args); | 39 | String[] cmdArgs = new String[args.length - 1]; |
| 47 | break; | 40 | System.arraycopy(args, 1, cmdArgs, 0, args.length - 1); |
| 48 | default: | 41 | |
| 49 | throw new IllegalArgumentException("Command not recognized: " + command); | 42 | try { |
| 43 | cmd.run(cmdArgs); | ||
| 44 | } catch (Exception ex) { | ||
| 45 | throw new CommandHelpException(cmd, ex); | ||
| 50 | } | 46 | } |
| 47 | } catch (CommandHelpException ex) { | ||
| 48 | System.err.println(ex.getMessage()); | ||
| 49 | System.out.println(String.format("%s - %s", Constants.NAME, Constants.VERSION)); | ||
| 50 | System.out.println("Command " + ex.command.name + " has encountered an error! Usage:"); | ||
| 51 | printHelp(ex.command); | ||
| 52 | System.exit(1); | ||
| 51 | } catch (IllegalArgumentException ex) { | 53 | } catch (IllegalArgumentException ex) { |
| 52 | System.err.println(ex.getMessage()); | 54 | System.err.println(ex.getMessage()); |
| 53 | printHelp(); | 55 | printHelp(); |
| @@ -60,187 +62,41 @@ public class CommandMain { | |||
| 60 | System.out.println("Usage:"); | 62 | System.out.println("Usage:"); |
| 61 | System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>"); | 63 | System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>"); |
| 62 | System.out.println("\twhere <command> is one of:"); | 64 | System.out.println("\twhere <command> is one of:"); |
| 63 | System.out.println("\t\tdeobfuscate <in jar> <out jar> [<mappings file>]"); | ||
| 64 | System.out.println("\t\tdecompile <in jar> <out folder> [<mappings file>]"); | ||
| 65 | System.out.println("\t\tconvertmappings <enigma mappings> <converted mappings> <ENIGMA_FILE|ENIGMA_DIRECTORY|SRG_FILE>"); | ||
| 66 | System.out.println("\t\tcheckmappings <in jar> <mappings file>"); | ||
| 67 | } | ||
| 68 | |||
| 69 | private static void decompile(String[] args) throws Exception { | ||
| 70 | File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); | ||
| 71 | File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)); | ||
| 72 | Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); | ||
| 73 | Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); | ||
| 74 | deobfuscator.writeSources(fileJarOut.toPath(), new ConsoleProgressListener()); | ||
| 75 | } | ||
| 76 | |||
| 77 | private static void deobfuscate(String[] args) throws Exception { | ||
| 78 | File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); | ||
| 79 | File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); | ||
| 80 | Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); | ||
| 81 | Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); | ||
| 82 | deobfuscator.writeTransformedJar(fileJarOut, new ConsoleProgressListener()); | ||
| 83 | } | ||
| 84 | 65 | ||
| 85 | private static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception { | 66 | for (Command command : COMMANDS.values()) { |
| 86 | System.out.println("Reading jar..."); | 67 | printHelp(command); |
| 87 | Deobfuscator deobfuscator = new Deobfuscator(jar); | ||
| 88 | if (fileMappings != null) { | ||
| 89 | System.out.println("Reading mappings..."); | ||
| 90 | EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, new ConsoleProgressListener()); | ||
| 91 | deobfuscator.setMappings(mappings); | ||
| 92 | } | 68 | } |
| 93 | return deobfuscator; | ||
| 94 | } | 69 | } |
| 95 | 70 | ||
| 96 | private static void convertMappings(String[] args) throws Exception { | 71 | private static void printHelp(Command command) { |
| 97 | Path fileMappings = getReadablePath(getArg(args, 1, "enigma mapping", true)); | 72 | System.out.println("\t\t" + command.name + " " + command.getUsage()); |
| 98 | File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); | ||
| 99 | String name = getArg(args, 3, "format desc", true); | ||
| 100 | MappingFormat saveFormat; | ||
| 101 | try { | ||
| 102 | saveFormat = MappingFormat.valueOf(name.toUpperCase(Locale.ROOT)); | ||
| 103 | } catch (IllegalArgumentException e) { | ||
| 104 | throw new IllegalArgumentException(name + "is not a valid mapping format!"); | ||
| 105 | } | ||
| 106 | |||
| 107 | System.out.println("Reading mappings..."); | ||
| 108 | |||
| 109 | MappingFormat readFormat = chooseEnigmaFormat(fileMappings); | ||
| 110 | EntryTree<EntryMapping> mappings = readFormat.read(fileMappings, new ConsoleProgressListener()); | ||
| 111 | System.out.println("Saving new mappings..."); | ||
| 112 | |||
| 113 | saveFormat.write(mappings, result.toPath(), new ConsoleProgressListener()); | ||
| 114 | } | 73 | } |
| 115 | 74 | ||
| 116 | private static void checkMappings(String[] args) throws Exception { | 75 | private static void register(Command command) { |
| 117 | File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); | 76 | Command old = COMMANDS.put(command.name, command); |
| 118 | Path fileMappings = getReadablePath(getArg(args, 2, "enigma mapping", true)); | 77 | if (old != null) { |
| 119 | 78 | System.err.println("Command " + old + " with name " + command.name + " has been substituted by " + command); | |
| 120 | System.out.println("Reading JAR..."); | ||
| 121 | Deobfuscator deobfuscator = new Deobfuscator(new JarFile(fileJarIn)); | ||
| 122 | System.out.println("Reading mappings..."); | ||
| 123 | |||
| 124 | MappingFormat format = chooseEnigmaFormat(fileMappings); | ||
| 125 | EntryTree<EntryMapping> mappings = format.read(fileMappings, ProgressListener.VOID); | ||
| 126 | deobfuscator.setMappings(mappings); | ||
| 127 | |||
| 128 | JarIndex idx = deobfuscator.getJarIndex(); | ||
| 129 | |||
| 130 | boolean error = false; | ||
| 131 | |||
| 132 | for (Set<ClassEntry> partition : idx.getPackageVisibilityIndex().getPartitions()) { | ||
| 133 | long packages = partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::getPackageName).distinct().count(); | ||
| 134 | if (packages > 1) { | ||
| 135 | error = true; | ||
| 136 | System.err.println("ERROR: Must be in one package:\n" + partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::toString).sorted().collect(Collectors.joining("\n"))); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | |||
| 140 | if (error) { | ||
| 141 | throw new Exception("Access violations detected"); | ||
| 142 | } | 79 | } |
| 143 | } | 80 | } |
| 144 | 81 | ||
| 145 | private static MappingFormat chooseEnigmaFormat(Path path) { | 82 | static { |
| 146 | if (Files.isDirectory(path)) { | 83 | register(new DeobfuscateCommand()); |
| 147 | return MappingFormat.ENIGMA_DIRECTORY; | 84 | register(new DecompileCommand()); |
| 148 | } else { | 85 | register(new ConvertMappingsCommand()); |
| 149 | return MappingFormat.ENIGMA_FILE; | 86 | register(new CheckMappingsCommand()); |
| 150 | } | ||
| 151 | } | ||
| 152 | |||
| 153 | private static String getArg(String[] args, int i, String name, boolean required) { | ||
| 154 | if (i >= args.length) { | ||
| 155 | if (required) { | ||
| 156 | throw new IllegalArgumentException(name + " is required"); | ||
| 157 | } else { | ||
| 158 | return null; | ||
| 159 | } | ||
| 160 | } | ||
| 161 | return args[i]; | ||
| 162 | } | ||
| 163 | |||
| 164 | private static File getWritableFile(String path) { | ||
| 165 | if (path == null) { | ||
| 166 | return null; | ||
| 167 | } | ||
| 168 | File file = new File(path).getAbsoluteFile(); | ||
| 169 | File dir = file.getParentFile(); | ||
| 170 | if (dir == null) { | ||
| 171 | throw new IllegalArgumentException("Cannot write file: " + path); | ||
| 172 | } | ||
| 173 | // quick fix to avoid stupid stuff in Gradle code | ||
| 174 | if (!dir.isDirectory()) { | ||
| 175 | dir.mkdirs(); | ||
| 176 | } | ||
| 177 | return file; | ||
| 178 | } | ||
| 179 | |||
| 180 | private static File getWritableFolder(String path) { | ||
| 181 | if (path == null) { | ||
| 182 | return null; | ||
| 183 | } | ||
| 184 | File dir = new File(path).getAbsoluteFile(); | ||
| 185 | if (!dir.exists()) { | ||
| 186 | throw new IllegalArgumentException("Cannot write to folder: " + dir); | ||
| 187 | } | ||
| 188 | return dir; | ||
| 189 | } | 87 | } |
| 190 | 88 | ||
| 191 | private static File getReadableFile(String path) { | 89 | private static final class CommandHelpException extends IllegalArgumentException { |
| 192 | if (path == null) { | ||
| 193 | return null; | ||
| 194 | } | ||
| 195 | File file = new File(path).getAbsoluteFile(); | ||
| 196 | if (!file.exists()) { | ||
| 197 | throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); | ||
| 198 | } | ||
| 199 | return file; | ||
| 200 | } | ||
| 201 | 90 | ||
| 202 | private static Path getReadablePath(String path) { | 91 | final Command command; |
| 203 | if (path == null) { | ||
| 204 | return null; | ||
| 205 | } | ||
| 206 | Path file = Paths.get(path).toAbsolutePath(); | ||
| 207 | if (!Files.exists(file)) { | ||
| 208 | throw new IllegalArgumentException("Cannot find file: " + file.toString()); | ||
| 209 | } | ||
| 210 | return file; | ||
| 211 | } | ||
| 212 | 92 | ||
| 213 | public static class ConsoleProgressListener implements ProgressListener { | 93 | CommandHelpException(Command command) { |
| 214 | 94 | this.command = command; | |
| 215 | private static final int ReportTime = 5000; // 5s | ||
| 216 | |||
| 217 | private int totalWork; | ||
| 218 | private long startTime; | ||
| 219 | private long lastReportTime; | ||
| 220 | |||
| 221 | @Override | ||
| 222 | public void init(int totalWork, String title) { | ||
| 223 | this.totalWork = totalWork; | ||
| 224 | this.startTime = System.currentTimeMillis(); | ||
| 225 | this.lastReportTime = this.startTime; | ||
| 226 | System.out.println(title); | ||
| 227 | } | 95 | } |
| 228 | 96 | ||
| 229 | @Override | 97 | CommandHelpException(Command command, Throwable cause) { |
| 230 | public void step(int numDone, String message) { | 98 | super(cause); |
| 231 | long now = System.currentTimeMillis(); | 99 | this.command = command; |
| 232 | boolean isLastUpdate = numDone == this.totalWork; | ||
| 233 | boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; | ||
| 234 | |||
| 235 | if (shouldReport) { | ||
| 236 | int percent = numDone * 100 / this.totalWork; | ||
| 237 | System.out.println(String.format("\tProgress: %3d%%", percent)); | ||
| 238 | this.lastReportTime = now; | ||
| 239 | } | ||
| 240 | if (isLastUpdate) { | ||
| 241 | double elapsedSeconds = (now - this.startTime) / 1000.0; | ||
| 242 | System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); | ||
| 243 | } | ||
| 244 | } | 100 | } |
| 245 | } | 101 | } |
| 246 | } | 102 | } |