summaryrefslogtreecommitdiff
path: root/enigma-cli
diff options
context:
space:
mode:
authorGravatar Runemoro2020-06-03 13:39:42 -0400
committerGravatar GitHub2020-06-03 18:39:42 +0100
commit0f47403d0220757fed189b76e2071e25b1025cb8 (patch)
tree879bf72c4476f0a5e0d82da99d7ff2b2276bcaca /enigma-cli
parentFix search dialog hanging for a short time sometimes (#250) (diff)
downloadenigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.gz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.tar.xz
enigma-fork-0f47403d0220757fed189b76e2071e25b1025cb8.zip
Split GUI code to separate module (#242)
* Split into modules * Post merge compile fixes Co-authored-by: modmuss50 <modmuss50@gmail.com>
Diffstat (limited to 'enigma-cli')
-rw-r--r--enigma-cli/build.gradle5
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java77
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/Command.java154
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java42
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java39
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/DecompileCommand.java54
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java37
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java41
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/Main.java105
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java71
-rw-r--r--enigma-cli/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java87
-rw-r--r--enigma-cli/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java21
-rw-r--r--enigma-cli/src/test/resources/packageAccess/correctMappings/base/Base.mapping1
-rw-r--r--enigma-cli/src/test/resources/packageAccess/correctMappings/base/One.mapping1
-rw-r--r--enigma-cli/src/test/resources/packageAccess/correctMappings/two/Two.mapping1
-rw-r--r--enigma-cli/src/test/resources/packageAccess/wrongMappings/base/Base.mapping1
-rw-r--r--enigma-cli/src/test/resources/packageAccess/wrongMappings/one/One.mapping1
-rw-r--r--enigma-cli/src/test/resources/packageAccess/wrongMappings/two/Two.mapping1
18 files changed, 739 insertions, 0 deletions
diff --git a/enigma-cli/build.gradle b/enigma-cli/build.gradle
new file mode 100644
index 0000000..3ee1239
--- /dev/null
+++ b/enigma-cli/build.gradle
@@ -0,0 +1,5 @@
1dependencies {
2 implementation project(':enigma')
3}
4
5jar.manifest.attributes 'Main-Class': 'cuchaz.enigma.command.Main'
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java
new file mode 100644
index 0000000..e4deef8
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java
@@ -0,0 +1,77 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.Enigma;
4import cuchaz.enigma.EnigmaProject;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.analysis.index.JarIndex;
7import cuchaz.enigma.translation.mapping.EntryMapping;
8import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
9import cuchaz.enigma.translation.mapping.serde.MappingFormat;
10import cuchaz.enigma.translation.mapping.tree.EntryTree;
11import cuchaz.enigma.translation.representation.entry.ClassEntry;
12
13import java.nio.file.Path;
14import java.util.Set;
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 Path fileJarIn = getReadableFile(getArg(args, 0, "in jar", true)).toPath();
36 Path fileMappings = getReadablePath(getArg(args, 1, "mappings file", true));
37
38 Enigma enigma = Enigma.create();
39
40 System.out.println("Reading JAR...");
41
42 EnigmaProject project = enigma.openJar(fileJarIn, ProgressListener.none());
43
44 System.out.println("Reading mappings...");
45
46 MappingFormat format = chooseEnigmaFormat(fileMappings);
47 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
48
49 EntryTree<EntryMapping> mappings = format.read(fileMappings, ProgressListener.none(), saveParameters);
50 project.setMappings(mappings);
51
52 JarIndex idx = project.getJarIndex();
53
54 boolean error = false;
55
56 for (Set<ClassEntry> partition : idx.getPackageVisibilityIndex().getPartitions()) {
57 long packages = partition.stream()
58 .map(project.getMapper()::deobfuscate)
59 .map(ClassEntry::getPackageName)
60 .distinct()
61 .count();
62 if (packages > 1) {
63 error = true;
64 System.err.println("ERROR: Must be in one package:\n" + partition.stream()
65 .map(project.getMapper()::deobfuscate)
66 .map(ClassEntry::toString)
67 .sorted()
68 .collect(Collectors.joining("\n"))
69 );
70 }
71 }
72
73 if (error) {
74 throw new IllegalStateException("Errors in package visibility detected, see SysErr above");
75 }
76 }
77}
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java b/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java
new file mode 100644
index 0000000..0640e3e
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java
@@ -0,0 +1,154 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.Enigma;
4import cuchaz.enigma.EnigmaProject;
5import cuchaz.enigma.ProgressListener;
6import cuchaz.enigma.translation.mapping.EntryMapping;
7import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.serde.MappingFormat;
9import cuchaz.enigma.translation.mapping.tree.EntryTree;
10
11import java.io.File;
12import java.nio.file.Files;
13import java.nio.file.Path;
14import java.nio.file.Paths;
15
16import com.google.common.io.MoreFiles;
17
18public abstract class Command {
19 public final String name;
20
21 protected Command(String name) {
22 this.name = name;
23 }
24
25 public abstract String getUsage();
26
27 public abstract boolean isValidArgument(int length);
28
29 public abstract void run(String... args) throws Exception;
30
31 protected static EnigmaProject openProject(Path fileJarIn, Path fileMappings) throws Exception {
32 ProgressListener progress = new ConsoleProgressListener();
33
34 Enigma enigma = Enigma.create();
35
36 System.out.println("Reading jar...");
37 EnigmaProject project = enigma.openJar(fileJarIn, progress);
38
39 if (fileMappings != null) {
40 System.out.println("Reading mappings...");
41
42 MappingSaveParameters saveParameters = enigma.getProfile().getMappingSaveParameters();
43 EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, progress, saveParameters);
44
45 project.setMappings(mappings);
46 }
47
48 return project;
49 }
50
51 protected static MappingFormat chooseEnigmaFormat(Path path) {
52 if (Files.isDirectory(path)) {
53 return MappingFormat.ENIGMA_DIRECTORY;
54 } else if ("zip".equalsIgnoreCase(MoreFiles.getFileExtension(path))) {
55 return MappingFormat.ENIGMA_ZIP;
56 } else {
57 return MappingFormat.ENIGMA_FILE;
58 }
59 }
60
61 protected static File getWritableFile(String path) {
62 if (path == null) {
63 return null;
64 }
65 File file = new File(path).getAbsoluteFile();
66 File dir = file.getParentFile();
67 if (dir == null) {
68 throw new IllegalArgumentException("Cannot write file: " + path);
69 }
70 // quick fix to avoid stupid stuff in Gradle code
71 if (!dir.isDirectory()) {
72 dir.mkdirs();
73 }
74 return file;
75 }
76
77 protected static File getWritableFolder(String path) {
78 if (path == null) {
79 return null;
80 }
81 File dir = new File(path).getAbsoluteFile();
82 if (!dir.exists()) {
83 throw new IllegalArgumentException("Cannot write to folder: " + dir);
84 }
85 return dir;
86 }
87
88 protected static File getReadableFile(String path) {
89 if (path == null) {
90 return null;
91 }
92 File file = new File(path).getAbsoluteFile();
93 if (!file.exists()) {
94 throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath());
95 }
96 return file;
97 }
98
99 protected static Path getReadablePath(String path) {
100 if (path == null) {
101 return null;
102 }
103 Path file = Paths.get(path).toAbsolutePath();
104 if (!Files.exists(file)) {
105 throw new IllegalArgumentException("Cannot find file: " + file.toString());
106 }
107 return file;
108 }
109
110 protected static String getArg(String[] args, int i, String name, boolean required) {
111 if (i >= args.length) {
112 if (required) {
113 throw new IllegalArgumentException(name + " is required");
114 } else {
115 return null;
116 }
117 }
118 return args[i];
119 }
120
121 public static class ConsoleProgressListener implements ProgressListener {
122
123 private static final int ReportTime = 5000; // 5s
124
125 private int totalWork;
126 private long startTime;
127 private long lastReportTime;
128
129 @Override
130 public void init(int totalWork, String title) {
131 this.totalWork = totalWork;
132 this.startTime = System.currentTimeMillis();
133 this.lastReportTime = this.startTime;
134 System.out.println(title);
135 }
136
137 @Override
138 public void step(int numDone, String message) {
139 long now = System.currentTimeMillis();
140 boolean isLastUpdate = numDone == this.totalWork;
141 boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime;
142
143 if (shouldReport) {
144 int percent = numDone * 100 / this.totalWork;
145 System.out.println(String.format("\tProgress: %3d%%", percent));
146 this.lastReportTime = now;
147 }
148 if (isLastUpdate) {
149 double elapsedSeconds = (now - this.startTime) / 1000.0;
150 System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds));
151 }
152 }
153 }
154}
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java
new file mode 100644
index 0000000..e10fd47
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/ComposeMappingsCommand.java
@@ -0,0 +1,42 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.translation.mapping.MappingOperations;
4import cuchaz.enigma.translation.mapping.serde.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat;
7import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.utils.Utils;
10
11import java.io.IOException;
12import java.nio.file.Path;
13import java.nio.file.Paths;
14
15public class ComposeMappingsCommand extends Command {
16 public ComposeMappingsCommand() {
17 super("compose-mappings");
18 }
19
20 @Override
21 public String getUsage() {
22 return "<left-format> <left> <right-format> <right> <result-format> <result> <keep-mode>";
23 }
24
25 @Override
26 public boolean isValidArgument(int length) {
27 return length == 7;
28 }
29
30 @Override
31 public void run(String... args) throws IOException, MappingParseException {
32 MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
33
34 EntryTree<EntryMapping> left = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters);
35 EntryTree<EntryMapping> right = MappingCommandsUtil.read(args[2], Paths.get(args[3]), saveParameters);
36 EntryTree<EntryMapping> result = MappingOperations.compose(left, right, args[6].equals("left") || args[6].equals("both"), args[6].equals("right") || args[6].equals("both"));
37
38 Path output = Paths.get(args[5]);
39 Utils.delete(output);
40 MappingCommandsUtil.write(result, args[4], output, saveParameters);
41 }
42}
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java
new file mode 100644
index 0000000..144d89c
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/ConvertMappingsCommand.java
@@ -0,0 +1,39 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.translation.mapping.serde.MappingParseException;
4import cuchaz.enigma.translation.mapping.EntryMapping;
5import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat;
6import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
7import cuchaz.enigma.translation.mapping.tree.EntryTree;
8import cuchaz.enigma.utils.Utils;
9
10import java.io.IOException;
11import java.nio.file.Path;
12import java.nio.file.Paths;
13
14public class ConvertMappingsCommand extends Command {
15 public ConvertMappingsCommand() {
16 super("convert-mappings");
17 }
18
19 @Override
20 public String getUsage() {
21 return "<source-format> <source> <result-format> <result>";
22 }
23
24 @Override
25 public boolean isValidArgument(int length) {
26 return length == 4;
27 }
28
29 @Override
30 public void run(String... args) throws IOException, MappingParseException {
31 MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
32
33 EntryTree<EntryMapping> mappings = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters);
34
35 Path output = Paths.get(args[3]);
36 Utils.delete(output);
37 MappingCommandsUtil.write(mappings, args[2], output, saveParameters);
38 }
39}
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/DecompileCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/DecompileCommand.java
new file mode 100644
index 0000000..3d15dac
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/DecompileCommand.java
@@ -0,0 +1,54 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.ProgressListener;
5import cuchaz.enigma.source.DecompilerService;
6import cuchaz.enigma.source.Decompilers;
7
8import java.lang.reflect.Field;
9import java.nio.file.Path;
10import java.util.Locale;
11
12public class DecompileCommand extends Command {
13
14 public DecompileCommand() {
15 super("decompile");
16 }
17
18 @Override
19 public String getUsage() {
20 return "<decompiler> <in jar> <out folder> [<mappings file>]";
21 }
22
23 @Override
24 public boolean isValidArgument(int length) {
25 return length == 2 || length == 3;
26 }
27
28 @Override
29 public void run(String... args) throws Exception {
30 String decompilerName = getArg(args, 1, "decompiler", true);
31 Path fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)).toPath();
32 Path fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)).toPath();
33 Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false));
34
35 DecompilerService decompilerService;
36
37 try {
38 Field decompilerField = Decompilers.class.getField(decompilerName.toUpperCase(Locale.ROOT));
39 decompilerService = (DecompilerService) decompilerField.get(null);
40 } catch (NoSuchFieldException e) {
41 System.err.println("Decompiler not found.");
42 return;
43 }
44
45 EnigmaProject project = openProject(fileJarIn, fileMappings);
46
47 ProgressListener progress = new ConsoleProgressListener();
48
49 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
50 EnigmaProject.SourceExport source = jar.decompile(progress, decompilerService);
51
52 source.write(fileJarOut, progress);
53 }
54}
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java
new file mode 100644
index 0000000..b0d2a7d
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/DeobfuscateCommand.java
@@ -0,0 +1,37 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.EnigmaProject;
4import cuchaz.enigma.ProgressListener;
5
6import java.nio.file.Path;
7
8public class DeobfuscateCommand extends Command {
9
10 public DeobfuscateCommand() {
11 super("deobfuscate");
12 }
13
14 @Override
15 public String getUsage() {
16 return "<in jar> <out jar> [<mappings file>]";
17 }
18
19 @Override
20 public boolean isValidArgument(int length) {
21 return length == 2 || length == 3;
22 }
23
24 @Override
25 public void run(String... args) throws Exception {
26 Path fileJarIn = getReadablePath(getArg(args, 0, "in jar", true));
27 Path fileJarOut = getWritableFile(getArg(args, 1, "out jar", true)).toPath();
28 Path fileMappings = getReadablePath(getArg(args, 2, "mappings file", false));
29
30 EnigmaProject project = openProject(fileJarIn, fileMappings);
31
32 ProgressListener progress = new ConsoleProgressListener();
33
34 EnigmaProject.JarExport jar = project.exportRemappedJar(progress);
35 jar.write(fileJarOut, progress);
36 }
37}
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java
new file mode 100644
index 0000000..0780a96
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/InvertMappingsCommand.java
@@ -0,0 +1,41 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.translation.mapping.MappingOperations;
4import cuchaz.enigma.translation.mapping.serde.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat;
7import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
8import cuchaz.enigma.translation.mapping.tree.EntryTree;
9import cuchaz.enigma.utils.Utils;
10
11import java.io.IOException;
12import java.nio.file.Path;
13import java.nio.file.Paths;
14
15public class InvertMappingsCommand extends Command {
16 public InvertMappingsCommand() {
17 super("invert-mappings");
18 }
19
20 @Override
21 public String getUsage() {
22 return "<source-format> <source> <result-format> <result>";
23 }
24
25 @Override
26 public boolean isValidArgument(int length) {
27 return length == 4;
28 }
29
30 @Override
31 public void run(String... args) throws IOException, MappingParseException {
32 MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
33
34 EntryTree<EntryMapping> source = MappingCommandsUtil.read(args[0], Paths.get(args[1]), saveParameters);
35 EntryTree<EntryMapping> result = MappingOperations.invert(source);
36
37 Path output = Paths.get(args[3]);
38 Utils.delete(output);
39 MappingCommandsUtil.write(result, args[2], output, saveParameters);
40 }
41}
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java b/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java
new file mode 100644
index 0000000..0a4c1b9
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/Main.java
@@ -0,0 +1,105 @@
1/*******************************************************************************
2 * Copyright (c) 2015 Jeff Martin.
3 * All rights reserved. This program and the accompanying materials
4 * are made available under the terms of the GNU Lesser General Public
5 * License v3.0 which accompanies this distribution, and is available at
6 * http://www.gnu.org/licenses/lgpl.html
7 * <p>
8 * Contributors:
9 * Jeff Martin - initial API and implementation
10 ******************************************************************************/
11
12package cuchaz.enigma.command;
13
14import cuchaz.enigma.Enigma;
15
16import java.util.LinkedHashMap;
17import java.util.Locale;
18import java.util.Map;
19
20public class Main {
21
22 private static final Map<String, Command> COMMANDS = new LinkedHashMap<>();
23
24 public static void main(String... args) throws Exception {
25 try {
26 // process the command
27 if (args.length < 1)
28 throw new IllegalArgumentException("Requires a command");
29 String command = args[0].toLowerCase(Locale.ROOT);
30
31 Command cmd = COMMANDS.get(command);
32 if (cmd == null)
33 throw new IllegalArgumentException("Command not recognized: " + command);
34
35 if (!cmd.isValidArgument(args.length - 1)) {
36 throw new CommandHelpException(cmd);
37 }
38
39 String[] cmdArgs = new String[args.length - 1];
40 System.arraycopy(args, 1, cmdArgs, 0, args.length - 1);
41
42 try {
43 cmd.run(cmdArgs);
44 } catch (Exception ex) {
45 throw new CommandHelpException(cmd, ex);
46 }
47 } catch (CommandHelpException ex) {
48 System.err.println(ex.getMessage());
49 System.out.println(String.format("%s - %s", Enigma.NAME, Enigma.VERSION));
50 System.out.println("Command " + ex.command.name + " has encountered an error! Usage:");
51 printHelp(ex.command);
52 System.exit(1);
53 } catch (IllegalArgumentException ex) {
54 System.err.println(ex.getMessage());
55 printHelp();
56 System.exit(1);
57 }
58 }
59
60 private static void printHelp() {
61 System.out.println(String.format("%s - %s", Enigma.NAME, Enigma.VERSION));
62 System.out.println("Usage:");
63 System.out.println("\tjava -cp enigma.jar cuchaz.enigma.command.CommandMain <command>");
64 System.out.println("\twhere <command> is one of:");
65
66 for (Command command : COMMANDS.values()) {
67 printHelp(command);
68 }
69 }
70
71 private static void printHelp(Command command) {
72 System.out.println("\t\t" + command.name + " " + command.getUsage());
73 }
74
75 private static void register(Command command) {
76 Command old = COMMANDS.put(command.name, command);
77 if (old != null) {
78 System.err.println("Command " + old + " with name " + command.name + " has been substituted by " + command);
79 }
80 }
81
82 static {
83 register(new DeobfuscateCommand());
84 register(new DecompileCommand());
85 register(new ConvertMappingsCommand());
86 register(new ComposeMappingsCommand());
87 register(new InvertMappingsCommand());
88 register(new CheckMappingsCommand());
89 register(new MapSpecializedMethodsCommand());
90 }
91
92 private static final class CommandHelpException extends IllegalArgumentException {
93
94 final Command command;
95
96 CommandHelpException(Command command) {
97 this.command = command;
98 }
99
100 CommandHelpException(Command command, Throwable cause) {
101 super(cause);
102 this.command = command;
103 }
104 }
105}
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java
new file mode 100644
index 0000000..292de19
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java
@@ -0,0 +1,71 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.analysis.ClassCache;
5import cuchaz.enigma.analysis.index.BridgeMethodIndex;
6import cuchaz.enigma.analysis.index.JarIndex;
7import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat;
8import cuchaz.enigma.translation.mapping.serde.MappingParseException;
9import cuchaz.enigma.translation.MappingTranslator;
10import cuchaz.enigma.translation.Translator;
11import cuchaz.enigma.translation.mapping.*;
12import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
13import cuchaz.enigma.translation.mapping.tree.EntryTree;
14import cuchaz.enigma.translation.mapping.tree.EntryTreeNode;
15import cuchaz.enigma.translation.mapping.tree.HashEntryTree;
16import cuchaz.enigma.translation.representation.entry.MethodEntry;
17import cuchaz.enigma.utils.Utils;
18
19import java.io.IOException;
20import java.nio.file.Path;
21import java.nio.file.Paths;
22import java.util.Map;
23
24public class MapSpecializedMethodsCommand extends Command {
25 public MapSpecializedMethodsCommand() {
26 super("map-specialized-methods");
27 }
28
29 @Override
30 public String getUsage() {
31 return "<jar> <source-format> <source> <result-format> <result>";
32 }
33
34 @Override
35 public boolean isValidArgument(int length) {
36 return length == 5;
37 }
38
39 @Override
40 public void run(String... args) throws IOException, MappingParseException {
41 run(Paths.get(args[0]), args[1], Paths.get(args[2]), args[3], Paths.get(args[4]));
42 }
43
44 public static void run(Path jar, String sourceFormat, Path sourcePath, String resultFormat, Path output) throws IOException, MappingParseException {
45 MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF);
46 EntryTree<EntryMapping> source = MappingCommandsUtil.read(sourceFormat, sourcePath, saveParameters);
47 EntryTree<EntryMapping> result = new HashEntryTree<>();
48 ClassCache classCache = ClassCache.of(jar);
49 JarIndex jarIndex = classCache.index(ProgressListener.none());
50 BridgeMethodIndex bridgeMethodIndex = jarIndex.getBridgeMethodIndex();
51 Translator translator = new MappingTranslator(source, jarIndex.getEntryResolver());
52
53 // Copy all non-specialized methods
54 for (EntryTreeNode<EntryMapping> node : source) {
55 if (!(node.getEntry() instanceof MethodEntry) || !bridgeMethodIndex.isSpecializedMethod((MethodEntry) node.getEntry())) {
56 result.insert(node.getEntry(), node.getValue());
57 }
58 }
59
60 // Add correct mappings for specialized methods
61 for (Map.Entry<MethodEntry, MethodEntry> entry : bridgeMethodIndex.getBridgeToSpecialized().entrySet()) {
62 MethodEntry bridge = entry.getKey();
63 MethodEntry specialized = entry.getValue();
64 String name = translator.translate(bridge).getName();
65 result.insert(specialized, new EntryMapping(name));
66 }
67
68 Utils.delete(output);
69 MappingCommandsUtil.write(result, resultFormat, output, saveParameters);
70 }
71}
diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java b/enigma-cli/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java
new file mode 100644
index 0000000..d365129
--- /dev/null
+++ b/enigma-cli/src/main/java/cuchaz/enigma/command/MappingCommandsUtil.java
@@ -0,0 +1,87 @@
1package cuchaz.enigma.command;
2
3import cuchaz.enigma.ProgressListener;
4import cuchaz.enigma.translation.mapping.serde.MappingParseException;
5import cuchaz.enigma.translation.mapping.EntryMapping;
6import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters;
7import cuchaz.enigma.translation.mapping.serde.*;
8import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader;
9import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsWriter;
10import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsReader;
11import cuchaz.enigma.translation.mapping.serde.tiny.TinyMappingsWriter;
12import cuchaz.enigma.translation.mapping.serde.tinyv2.TinyV2Writer;
13import cuchaz.enigma.translation.mapping.tree.EntryTree;
14
15import java.io.IOException;
16import java.nio.file.Files;
17import java.nio.file.Path;
18
19public final class MappingCommandsUtil {
20 private MappingCommandsUtil() {}
21
22 public static EntryTree<EntryMapping> read(String type, Path path, MappingSaveParameters saveParameters) throws MappingParseException, IOException {
23 if (type.equals("enigma")) {
24 return (Files.isDirectory(path) ? EnigmaMappingsReader.DIRECTORY : EnigmaMappingsReader.ZIP).read(path, ProgressListener.none(), saveParameters);
25 }
26
27 if (type.equals("tiny")) {
28 return TinyMappingsReader.INSTANCE.read(path, ProgressListener.none(), saveParameters);
29 }
30
31 MappingFormat format = null;
32 try {
33 format = MappingFormat.valueOf(type.toUpperCase());
34 } catch (IllegalArgumentException ignored) {
35 if (type.equals("tinyv2")) {
36 format = MappingFormat.TINY_V2;
37 }
38 }
39
40 if (format != null) {
41 return format.getReader().read(path, ProgressListener.none(), saveParameters);
42 }
43
44 throw new IllegalArgumentException("no reader for " + type);
45 }
46
47 public static void write(EntryTree<EntryMapping> mappings, String type, Path path, MappingSaveParameters saveParameters) {
48 if (type.equals("enigma")) {
49 EnigmaMappingsWriter.DIRECTORY.write(mappings, path, ProgressListener.none(), saveParameters);
50 return;
51 }
52
53 if (type.startsWith("tinyv2:") || type.startsWith("tiny_v2:")) {
54 String[] split = type.split(":");
55
56 if (split.length != 3) {
57 throw new IllegalArgumentException("specify column names as 'tinyv2:from_namespace:to_namespace'");
58 }
59
60 new TinyV2Writer(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters);
61 return;
62 }
63
64 if (type.startsWith("tiny:")) {
65 String[] split = type.split(":");
66
67 if (split.length != 3) {
68 throw new IllegalArgumentException("specify column names as 'tiny:from_column:to_column'");
69 }
70
71 new TinyMappingsWriter(split[1], split[2]).write(mappings, path, ProgressListener.none(), saveParameters);
72 return;
73 }
74
75 MappingFormat format = null;
76 try {
77 format = MappingFormat.valueOf(type.toUpperCase());
78 } catch (IllegalArgumentException ignored) {}
79
80 if (format != null) {
81 format.getWriter().write(mappings, path, ProgressListener.none(), saveParameters);
82 return;
83 }
84
85 throw new IllegalArgumentException("no writer for " + type);
86 }
87}
diff --git a/enigma-cli/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java b/enigma-cli/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java
new file mode 100644
index 0000000..a29bba4
--- /dev/null
+++ b/enigma-cli/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java
@@ -0,0 +1,21 @@
1package cuchaz.enigma.command;
2
3import org.junit.Test;
4
5import java.io.File;
6
7public class CheckMappingsCommandTest {
8 private static final String PACKAGE_ACCESS = "../enigma/build/test-obf/packageAccess.jar";
9
10 @Test(expected = IllegalStateException.class)
11 public void testWrong() throws Exception {
12 new CheckMappingsCommand().run(new File(PACKAGE_ACCESS).getAbsolutePath(), new File("src/test/resources" +
13 "/packageAccess/wrongMappings").getAbsolutePath());
14 }
15
16 @Test
17 public void testRight() throws Exception {
18 new CheckMappingsCommand().run(new File(PACKAGE_ACCESS).getAbsolutePath(), new File("src/test/resources" +
19 "/packageAccess/correctMappings").getAbsolutePath());
20 }
21}
diff --git a/enigma-cli/src/test/resources/packageAccess/correctMappings/base/Base.mapping b/enigma-cli/src/test/resources/packageAccess/correctMappings/base/Base.mapping
new file mode 100644
index 0000000..0a86def
--- /dev/null
+++ b/enigma-cli/src/test/resources/packageAccess/correctMappings/base/Base.mapping
@@ -0,0 +1 @@
CLASS a base/Base
diff --git a/enigma-cli/src/test/resources/packageAccess/correctMappings/base/One.mapping b/enigma-cli/src/test/resources/packageAccess/correctMappings/base/One.mapping
new file mode 100644
index 0000000..dd4c208
--- /dev/null
+++ b/enigma-cli/src/test/resources/packageAccess/correctMappings/base/One.mapping
@@ -0,0 +1 @@
CLASS b base/One
diff --git a/enigma-cli/src/test/resources/packageAccess/correctMappings/two/Two.mapping b/enigma-cli/src/test/resources/packageAccess/correctMappings/two/Two.mapping
new file mode 100644
index 0000000..a179349
--- /dev/null
+++ b/enigma-cli/src/test/resources/packageAccess/correctMappings/two/Two.mapping
@@ -0,0 +1 @@
CLASS c two/Two
diff --git a/enigma-cli/src/test/resources/packageAccess/wrongMappings/base/Base.mapping b/enigma-cli/src/test/resources/packageAccess/wrongMappings/base/Base.mapping
new file mode 100644
index 0000000..0a86def
--- /dev/null
+++ b/enigma-cli/src/test/resources/packageAccess/wrongMappings/base/Base.mapping
@@ -0,0 +1 @@
CLASS a base/Base
diff --git a/enigma-cli/src/test/resources/packageAccess/wrongMappings/one/One.mapping b/enigma-cli/src/test/resources/packageAccess/wrongMappings/one/One.mapping
new file mode 100644
index 0000000..15b42cf
--- /dev/null
+++ b/enigma-cli/src/test/resources/packageAccess/wrongMappings/one/One.mapping
@@ -0,0 +1 @@
CLASS b one/One
diff --git a/enigma-cli/src/test/resources/packageAccess/wrongMappings/two/Two.mapping b/enigma-cli/src/test/resources/packageAccess/wrongMappings/two/Two.mapping
new file mode 100644
index 0000000..a179349
--- /dev/null
+++ b/enigma-cli/src/test/resources/packageAccess/wrongMappings/two/Two.mapping
@@ -0,0 +1 @@
CLASS c two/Two