diff options
Diffstat (limited to 'src/main')
8 files changed, 374 insertions, 202 deletions
diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java index db4fd125..5b250872 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 | } |
diff --git a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java index 9e9115fe..da28ac41 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java | |||
| @@ -11,7 +11,7 @@ import cuchaz.enigma.translation.representation.entry.*; | |||
| 11 | import java.util.*; | 11 | import java.util.*; |
| 12 | 12 | ||
| 13 | public class PackageVisibilityIndex implements JarIndexer { | 13 | public class PackageVisibilityIndex implements JarIndexer { |
| 14 | private static boolean isPackageVisibleOnlyRef(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) { | 14 | private static boolean requiresSamePackage(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) { |
| 15 | if (entryAcc.isPublic()) return false; | 15 | if (entryAcc.isPublic()) return false; |
| 16 | if (entryAcc.isProtected()) { | 16 | if (entryAcc.isProtected()) { |
| 17 | Set<ClassEntry> callerAncestors = inheritanceIndex.getAncestors(ref.context.getContainingClass()); | 17 | Set<ClassEntry> callerAncestors = inheritanceIndex.getAncestors(ref.context.getContainingClass()); |
| @@ -43,7 +43,7 @@ public class PackageVisibilityIndex implements JarIndexer { | |||
| 43 | AccessFlags entryAcc = entryIndex.getFieldAccess(entry); | 43 | AccessFlags entryAcc = entryIndex.getFieldAccess(entry); |
| 44 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { | 44 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { |
| 45 | for (EntryReference<FieldEntry, MethodDefEntry> ref : referenceIndex.getReferencesToField(entry)) { | 45 | for (EntryReference<FieldEntry, MethodDefEntry> ref : referenceIndex.getReferencesToField(entry)) { |
| 46 | if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { | 46 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { |
| 47 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | 47 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); |
| 48 | } | 48 | } |
| 49 | } | 49 | } |
| @@ -54,7 +54,7 @@ public class PackageVisibilityIndex implements JarIndexer { | |||
| 54 | AccessFlags entryAcc = entryIndex.getMethodAccess(entry); | 54 | AccessFlags entryAcc = entryIndex.getMethodAccess(entry); |
| 55 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { | 55 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { |
| 56 | for (EntryReference<MethodEntry, MethodDefEntry> ref : referenceIndex.getReferencesToMethod(entry)) { | 56 | for (EntryReference<MethodEntry, MethodDefEntry> ref : referenceIndex.getReferencesToMethod(entry)) { |
| 57 | if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { | 57 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { |
| 58 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | 58 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); |
| 59 | } | 59 | } |
| 60 | } | 60 | } |
| @@ -65,13 +65,13 @@ public class PackageVisibilityIndex implements JarIndexer { | |||
| 65 | AccessFlags entryAcc = entryIndex.getClassAccess(entry); | 65 | AccessFlags entryAcc = entryIndex.getClassAccess(entry); |
| 66 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { | 66 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { |
| 67 | for (EntryReference<ClassEntry, FieldDefEntry> ref : referenceIndex.getFieldTypeReferencesToClass(entry)) { | 67 | for (EntryReference<ClassEntry, FieldDefEntry> ref : referenceIndex.getFieldTypeReferencesToClass(entry)) { |
| 68 | if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { | 68 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { |
| 69 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | 69 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); |
| 70 | } | 70 | } |
| 71 | } | 71 | } |
| 72 | 72 | ||
| 73 | for (EntryReference<ClassEntry, MethodDefEntry> ref : referenceIndex.getMethodTypeReferencesToClass(entry)) { | 73 | for (EntryReference<ClassEntry, MethodDefEntry> ref : referenceIndex.getMethodTypeReferencesToClass(entry)) { |
| 74 | if (isPackageVisibleOnlyRef(entryAcc, ref, inheritanceIndex)) { | 74 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { |
| 75 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | 75 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); |
| 76 | } | 76 | } |
| 77 | } | 77 | } |
diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java index 6764ac0c..04306bd9 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java | |||
| @@ -92,7 +92,8 @@ public class ReferenceIndex implements JarIndexer { | |||
| 92 | } | 92 | } |
| 93 | 93 | ||
| 94 | private <E extends Entry<?>, C extends Entry<?>> Multimap<E, EntryReference<E, C>> remapReferencesTo(JarIndex index, Multimap<E, EntryReference<E, C>> multimap) { | 94 | private <E extends Entry<?>, C extends Entry<?>> Multimap<E, EntryReference<E, C>> remapReferencesTo(JarIndex index, Multimap<E, EntryReference<E, C>> multimap) { |
| 95 | Multimap<E, EntryReference<E, C>> resolved = HashMultimap.create(multimap.keySet().size(), multimap.size() / multimap.keySet().size()); | 95 | final int keySetSize = multimap.keySet().size(); |
| 96 | Multimap<E, EntryReference<E, C>> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize); | ||
| 96 | for (Map.Entry<E, EntryReference<E, C>> entry : multimap.entries()) { | 97 | for (Map.Entry<E, EntryReference<E, C>> entry : multimap.entries()) { |
| 97 | resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); | 98 | resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); |
| 98 | } | 99 | } |
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 00000000..7ec7679c --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java | |||
| @@ -0,0 +1,62 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.Deobfuscator; | ||
| 4 | import cuchaz.enigma.ProgressListener; | ||
| 5 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 7 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 8 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 10 | |||
| 11 | import java.io.File; | ||
| 12 | import java.nio.file.Path; | ||
| 13 | import java.util.Set; | ||
| 14 | import java.util.jar.JarFile; | ||
| 15 | import java.util.stream.Collectors; | ||
| 16 | |||
| 17 | public class CheckMappingsCommand extends Command { | ||
| 18 | |||
| 19 | public CheckMappingsCommand() { | ||
| 20 | super("checkmappings"); | ||
| 21 | } | ||
| 22 | |||
| 23 | @Override | ||
| 24 | public String getUsage() { | ||
| 25 | return "<in jar> <mappings file>"; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public boolean isValidArgument(int length) { | ||
| 30 | return length == 2; | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | public void run(String... args) throws Exception { | ||
| 35 | File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); | ||
| 36 | Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true)); | ||
| 37 | |||
| 38 | System.out.println("Reading JAR..."); | ||
| 39 | Deobfuscator deobfuscator = new Deobfuscator(new JarFile(fileJarIn)); | ||
| 40 | System.out.println("Reading mappings..."); | ||
| 41 | |||
| 42 | MappingFormat format = chooseEnigmaFormat(fileMappings); | ||
| 43 | EntryTree<EntryMapping> mappings = format.read(fileMappings, ProgressListener.VOID); | ||
| 44 | deobfuscator.setMappings(mappings); | ||
| 45 | |||
| 46 | JarIndex idx = deobfuscator.getJarIndex(); | ||
| 47 | |||
| 48 | boolean error = false; | ||
| 49 | |||
| 50 | for (Set<ClassEntry> partition : idx.getPackageVisibilityIndex().getPartitions()) { | ||
| 51 | long packages = partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::getPackageName).distinct().count(); | ||
| 52 | if (packages > 1) { | ||
| 53 | error = true; | ||
| 54 | System.err.println("ERROR: Must be in one package:\n" + partition.stream().map(deobfuscator.getMapper()::deobfuscate).map(ClassEntry::toString).sorted().collect(Collectors.joining("\n"))); | ||
| 55 | } | ||
| 56 | } | ||
| 57 | |||
| 58 | if (error) { | ||
| 59 | throw new IllegalStateException("Errors in package visibility detected, see SysErr above"); | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
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 00000000..b107fb61 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/Command.java | |||
| @@ -0,0 +1,140 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.Deobfuscator; | ||
| 4 | import cuchaz.enigma.ProgressListener; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 6 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 7 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 8 | |||
| 9 | import java.io.File; | ||
| 10 | import java.nio.file.Files; | ||
| 11 | import java.nio.file.Path; | ||
| 12 | import java.nio.file.Paths; | ||
| 13 | import java.util.jar.JarFile; | ||
| 14 | |||
| 15 | public abstract class Command { | ||
| 16 | public final String name; | ||
| 17 | |||
| 18 | protected Command(String name) { | ||
| 19 | this.name = name; | ||
| 20 | } | ||
| 21 | |||
| 22 | public abstract String getUsage(); | ||
| 23 | |||
| 24 | public abstract boolean isValidArgument(int length); | ||
| 25 | |||
| 26 | public abstract void run(String... args) throws Exception; | ||
| 27 | |||
| 28 | protected static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception { | ||
| 29 | System.out.println("Reading jar..."); | ||
| 30 | Deobfuscator deobfuscator = new Deobfuscator(jar); | ||
| 31 | if (fileMappings != null) { | ||
| 32 | System.out.println("Reading mappings..."); | ||
| 33 | EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, new ConsoleProgressListener()); | ||
| 34 | deobfuscator.setMappings(mappings); | ||
| 35 | } | ||
| 36 | return deobfuscator; | ||
| 37 | } | ||
| 38 | |||
| 39 | protected static MappingFormat chooseEnigmaFormat(Path path) { | ||
| 40 | if (Files.isDirectory(path)) { | ||
| 41 | return MappingFormat.ENIGMA_DIRECTORY; | ||
| 42 | } else { | ||
| 43 | return MappingFormat.ENIGMA_FILE; | ||
| 44 | } | ||
| 45 | } | ||
| 46 | |||
| 47 | protected static File getWritableFile(String path) { | ||
| 48 | if (path == null) { | ||
| 49 | return null; | ||
| 50 | } | ||
| 51 | File file = new File(path).getAbsoluteFile(); | ||
| 52 | File dir = file.getParentFile(); | ||
| 53 | if (dir == null) { | ||
| 54 | throw new IllegalArgumentException("Cannot write file: " + path); | ||
| 55 | } | ||
| 56 | // quick fix to avoid stupid stuff in Gradle code | ||
| 57 | if (!dir.isDirectory()) { | ||
| 58 | dir.mkdirs(); | ||
| 59 | } | ||
| 60 | return file; | ||
| 61 | } | ||
| 62 | |||
| 63 | protected static File getWritableFolder(String path) { | ||
| 64 | if (path == null) { | ||
| 65 | return null; | ||
| 66 | } | ||
| 67 | File dir = new File(path).getAbsoluteFile(); | ||
| 68 | if (!dir.exists()) { | ||
| 69 | throw new IllegalArgumentException("Cannot write to folder: " + dir); | ||
| 70 | } | ||
| 71 | return dir; | ||
| 72 | } | ||
| 73 | |||
| 74 | protected static File getReadableFile(String path) { | ||
| 75 | if (path == null) { | ||
| 76 | return null; | ||
| 77 | } | ||
| 78 | File file = new File(path).getAbsoluteFile(); | ||
| 79 | if (!file.exists()) { | ||
| 80 | throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); | ||
| 81 | } | ||
| 82 | return file; | ||
| 83 | } | ||
| 84 | |||
| 85 | protected static Path getReadablePath(String path) { | ||
| 86 | if (path == null) { | ||
| 87 | return null; | ||
| 88 | } | ||
| 89 | Path file = Paths.get(path).toAbsolutePath(); | ||
| 90 | if (!Files.exists(file)) { | ||
| 91 | throw new IllegalArgumentException("Cannot find file: " + file.toString()); | ||
| 92 | } | ||
| 93 | return file; | ||
| 94 | } | ||
| 95 | |||
| 96 | protected static String getArg(String[] args, int i, String name, boolean required) { | ||
| 97 | if (i >= args.length) { | ||
| 98 | if (required) { | ||
| 99 | throw new IllegalArgumentException(name + " is required"); | ||
| 100 | } else { | ||
| 101 | return null; | ||
| 102 | } | ||
| 103 | } | ||
| 104 | return args[i]; | ||
| 105 | } | ||
| 106 | |||
| 107 | public static class ConsoleProgressListener implements ProgressListener { | ||
| 108 | |||
| 109 | private static final int ReportTime = 5000; // 5s | ||
| 110 | |||
| 111 | private int totalWork; | ||
| 112 | private long startTime; | ||
| 113 | private long lastReportTime; | ||
| 114 | |||
| 115 | @Override | ||
| 116 | public void init(int totalWork, String title) { | ||
| 117 | this.totalWork = totalWork; | ||
| 118 | this.startTime = System.currentTimeMillis(); | ||
| 119 | this.lastReportTime = this.startTime; | ||
| 120 | System.out.println(title); | ||
| 121 | } | ||
| 122 | |||
| 123 | @Override | ||
| 124 | public void step(int numDone, String message) { | ||
| 125 | long now = System.currentTimeMillis(); | ||
| 126 | boolean isLastUpdate = numDone == this.totalWork; | ||
| 127 | boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; | ||
| 128 | |||
| 129 | if (shouldReport) { | ||
| 130 | int percent = numDone * 100 / this.totalWork; | ||
| 131 | System.out.println(String.format("\tProgress: %3d%%", percent)); | ||
| 132 | this.lastReportTime = now; | ||
| 133 | } | ||
| 134 | if (isLastUpdate) { | ||
| 135 | double elapsedSeconds = (now - this.startTime) / 1000.0; | ||
| 136 | System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); | ||
| 137 | } | ||
| 138 | } | ||
| 139 | } | ||
| 140 | } | ||
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 00000000..75d3791d --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java | |||
| @@ -0,0 +1,47 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 4 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | ||
| 5 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | ||
| 6 | |||
| 7 | import java.io.File; | ||
| 8 | import java.nio.file.Path; | ||
| 9 | import java.util.Locale; | ||
| 10 | |||
| 11 | public class ConvertMappingsCommand extends Command { | ||
| 12 | |||
| 13 | public ConvertMappingsCommand() { | ||
| 14 | super("convertmappings"); | ||
| 15 | } | ||
| 16 | |||
| 17 | @Override | ||
| 18 | public String getUsage() { | ||
| 19 | return "<enigma mappings> <converted mappings> <ENIGMA_FILE|ENIGMA_DIRECTORY|SRG_FILE>"; | ||
| 20 | } | ||
| 21 | |||
| 22 | @Override | ||
| 23 | public boolean isValidArgument(int length) { | ||
| 24 | return length == 3; | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public void run(String... args) throws Exception { | ||
| 29 | Path fileMappings = getReadablePath(getArg(args, 0, "enigma mappings", true)); | ||
| 30 | File result = getWritableFile(getArg(args, 1, "converted mappings", true)); | ||
| 31 | String name = getArg(args, 2, "format desc", true); | ||
| 32 | MappingFormat saveFormat; | ||
| 33 | try { | ||
| 34 | saveFormat = MappingFormat.valueOf(name.toUpperCase(Locale.ROOT)); | ||
| 35 | } catch (IllegalArgumentException e) { | ||
| 36 | throw new IllegalArgumentException(name + "is not a valid mapping format!"); | ||
| 37 | } | ||
| 38 | |||
| 39 | System.out.println("Reading mappings..."); | ||
| 40 | |||
| 41 | MappingFormat readFormat = chooseEnigmaFormat(fileMappings); | ||
| 42 | EntryTree<EntryMapping> mappings = readFormat.read(fileMappings, new ConsoleProgressListener()); | ||
| 43 | System.out.println("Saving new mappings..."); | ||
| 44 | |||
| 45 | saveFormat.write(mappings, result.toPath(), new ConsoleProgressListener()); | ||
| 46 | } | ||
| 47 | } | ||
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 00000000..a58d9085 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/DecompileCommand.java | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.Deobfuscator; | ||
| 4 | |||
| 5 | import java.io.File; | ||
| 6 | import java.nio.file.Path; | ||
| 7 | import java.util.jar.JarFile; | ||
| 8 | |||
| 9 | public class DecompileCommand extends Command { | ||
| 10 | |||
| 11 | public DecompileCommand() { | ||
| 12 | super("decompile"); | ||
| 13 | } | ||
| 14 | |||
| 15 | @Override | ||
| 16 | public String getUsage() { | ||
| 17 | return "<in jar> <out folder> [<mappings file>]"; | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | public boolean isValidArgument(int length) { | ||
| 22 | return length == 2 || length == 3; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public void run(String... args) throws Exception { | ||
| 27 | File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); | ||
| 28 | File fileJarOut = getWritableFolder(getArg(args, 1, "out folder", true)); | ||
| 29 | Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); | ||
| 30 | Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); | ||
| 31 | deobfuscator.writeSources(fileJarOut.toPath(), new Command.ConsoleProgressListener()); | ||
| 32 | } | ||
| 33 | } | ||
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 00000000..5d499385 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import cuchaz.enigma.Deobfuscator; | ||
| 4 | |||
| 5 | import java.io.File; | ||
| 6 | import java.nio.file.Path; | ||
| 7 | import java.util.jar.JarFile; | ||
| 8 | |||
| 9 | public class DeobfuscateCommand extends Command { | ||
| 10 | |||
| 11 | public DeobfuscateCommand() { | ||
| 12 | super("deobfuscate"); | ||
| 13 | } | ||
| 14 | |||
| 15 | @Override | ||
| 16 | public String getUsage() { | ||
| 17 | return "<in jar> <out jar> [<mappings file>]"; | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | public boolean isValidArgument(int length) { | ||
| 22 | return length == 2 || length == 3; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public void run(String... args) throws Exception { | ||
| 27 | File fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)); | ||
| 28 | File fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)); | ||
| 29 | Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false)); | ||
| 30 | Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); | ||
| 31 | deobfuscator.writeTransformedJar(fileJarOut, new Command.ConsoleProgressListener()); | ||
| 32 | } | ||
| 33 | } | ||