summaryrefslogtreecommitdiff
path: root/src/main/java
diff options
context:
space:
mode:
authorGravatar liach2019-05-15 22:03:13 -0700
committerGravatar Gegy2019-05-16 07:03:13 +0200
commitcb8823eb0b446d5c1b9b580e5578866e691771d8 (patch)
tree1e8c1a5b981f3ad42c393f5d7cb75754f25f51ba /src/main/java
parentcheckmappings command (#137) (diff)
downloadenigma-cb8823eb0b446d5c1b9b580e5578866e691771d8.tar.gz
enigma-cb8823eb0b446d5c1b9b580e5578866e691771d8.tar.xz
enigma-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')
-rw-r--r--src/main/java/cuchaz/enigma/CommandMain.java248
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java10
-rw-r--r--src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java3
-rw-r--r--src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java62
-rw-r--r--src/main/java/cuchaz/enigma/command/Command.java140
-rw-r--r--src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java47
-rw-r--r--src/main/java/cuchaz/enigma/command/DecompileCommand.java33
-rw-r--r--src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java33
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
12package cuchaz.enigma; 12package cuchaz.enigma;
13 13
14import cuchaz.enigma.analysis.index.JarIndex; 14import cuchaz.enigma.command.*;
15import cuchaz.enigma.translation.mapping.EntryMapping; 15
16import cuchaz.enigma.translation.mapping.serde.MappingFormat; 16import java.util.LinkedHashMap;
17import cuchaz.enigma.translation.mapping.tree.EntryTree;
18import cuchaz.enigma.translation.representation.entry.ClassEntry;
19
20import java.io.File;
21import java.nio.file.Files;
22import java.nio.file.Path;
23import java.nio.file.Paths;
24import java.util.Locale; 17import java.util.Locale;
25import java.util.Set; 18import java.util.Map;
26import java.util.jar.JarFile;
27import java.util.stream.Collectors;
28 19
29public class CommandMain { 20public 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.*;
11import java.util.*; 11import java.util.*;
12 12
13public class PackageVisibilityIndex implements JarIndexer { 13public 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 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.Deobfuscator;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.analysis.index.JarIndex;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.serde.MappingFormat;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.translation.representation.entry.ClassEntry;
10
11import java.io.File;
12import java.nio.file.Path;
13import java.util.Set;
14import java.util.jar.JarFile;
15import java.util.stream.Collectors;
16
17public 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 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.Deobfuscator;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.serde.MappingFormat;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8
9import java.io.File;
10import java.nio.file.Files;
11import java.nio.file.Path;
12import java.nio.file.Paths;
13import java.util.jar.JarFile;
14
15public 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 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.translation.mapping.EntryMapping;
4import cuchaz.enigma.translation.mapping.serde.MappingFormat;
5import cuchaz.enigma.translation.mapping.tree.EntryTree;
6
7import java.io.File;
8import java.nio.file.Path;
9import java.util.Locale;
10
11public 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 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.Deobfuscator;
4
5import java.io.File;
6import java.nio.file.Path;
7import java.util.jar.JarFile;
8
9public 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 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.Deobfuscator;
4
5import java.io.File;
6import java.nio.file.Path;
7import java.util.jar.JarFile;
8
9public 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}