diff options
| author | 2019-06-10 18:23:59 +0200 | |
|---|---|---|
| committer | 2019-06-10 18:23:59 +0200 | |
| commit | 54bbafce6a72abcd740a27917b7f6f7b5947167b (patch) | |
| tree | aad1060c4c1db00d62850a00366c9b12a704774d /src | |
| parent | Separate JarProcessor and EntryNameProposer (diff) | |
| parent | Method type reference corrections (#142) (diff) | |
| download | enigma-fork-54bbafce6a72abcd740a27917b7f6f7b5947167b.tar.gz enigma-fork-54bbafce6a72abcd740a27917b7f6f7b5947167b.tar.xz enigma-fork-54bbafce6a72abcd740a27917b7f6f7b5947167b.zip | |
Merge remote-tracking branch 'origin/master' into proposal-tweak
Diffstat (limited to 'src')
26 files changed, 843 insertions, 191 deletions
diff --git a/src/main/java/cuchaz/enigma/CommandMain.java b/src/main/java/cuchaz/enigma/CommandMain.java index c9f8382..5b25087 100644 --- a/src/main/java/cuchaz/enigma/CommandMain.java +++ b/src/main/java/cuchaz/enigma/CommandMain.java | |||
| @@ -11,35 +11,49 @@ | |||
| 11 | 11 | ||
| 12 | package cuchaz.enigma; | 12 | package cuchaz.enigma; |
| 13 | 13 | ||
| 14 | import cuchaz.enigma.translation.mapping.EntryMapping; | 14 | import cuchaz.enigma.command.*; |
| 15 | import cuchaz.enigma.translation.mapping.serde.MappingFormat; | 15 | |
| 16 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | 16 | import java.util.LinkedHashMap; |
| 17 | |||
| 18 | import java.io.File; | ||
| 19 | import java.nio.file.Files; | ||
| 20 | import java.nio.file.Path; | ||
| 21 | import java.nio.file.Paths; | ||
| 22 | import java.util.Locale; | 17 | import java.util.Locale; |
| 23 | import java.util.jar.JarFile; | 18 | import java.util.Map; |
| 24 | 19 | ||
| 25 | public class CommandMain { | 20 | public class CommandMain { |
| 26 | 21 | ||
| 27 | 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 { | ||
| 28 | try { | 25 | try { |
| 29 | // process the command | 26 | // process the command |
| 30 | String command = getArg(args, 0, "command", true); | 27 | if (args.length < 1) |
| 31 | if (command.equalsIgnoreCase("deobfuscate")) { | 28 | throw new IllegalArgumentException("Requires a command"); |
| 32 | deobfuscate(args); | 29 | String command = args[0].toLowerCase(Locale.ROOT); |
| 33 | } else if (command.equalsIgnoreCase("decompile")) { | 30 | |
| 34 | decompile(args); | 31 | Command cmd = COMMANDS.get(command); |
| 35 | } else if (command.equalsIgnoreCase("convertmappings")) { | 32 | if (cmd == null) |
| 36 | convertMappings(args); | ||
| 37 | } else { | ||
| 38 | throw new IllegalArgumentException("Command not recognized: " + command); | 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); | ||
| 39 | } | 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); | ||
| 40 | } catch (IllegalArgumentException ex) { | 53 | } catch (IllegalArgumentException ex) { |
| 41 | System.out.println(ex.getMessage()); | 54 | System.err.println(ex.getMessage()); |
| 42 | printHelp(); | 55 | printHelp(); |
| 56 | System.exit(1); | ||
| 43 | } | 57 | } |
| 44 | } | 58 | } |
| 45 | 59 | ||
| @@ -48,157 +62,41 @@ public class CommandMain { | |||
| 48 | System.out.println("Usage:"); | 62 | System.out.println("Usage:"); |
| 49 | System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>"); | 63 | System.out.println("\tjava -cp enigma.jar cuchaz.enigma.CommandMain <command>"); |
| 50 | System.out.println("\twhere <command> is one of:"); | 64 | System.out.println("\twhere <command> is one of:"); |
| 51 | System.out.println("\t\tdeobfuscate <in jar> <out jar> [<mappings file>]"); | ||
| 52 | System.out.println("\t\tdecompile <in jar> <out folder> [<mappings file>]"); | ||
| 53 | System.out.println("\t\tconvertmappings <enigma mappings> <converted mappings> <ENIGMA_FILE|ENIGMA_DIRECTORY|SRG_FILE>"); | ||
| 54 | } | ||
| 55 | |||
| 56 | private static void decompile(String[] args) throws Exception { | ||
| 57 | File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); | ||
| 58 | File fileJarOut = getWritableFolder(getArg(args, 2, "out folder", true)); | ||
| 59 | Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); | ||
| 60 | Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); | ||
| 61 | deobfuscator.writeSources(fileJarOut.toPath(), new ConsoleProgressListener()); | ||
| 62 | } | ||
| 63 | |||
| 64 | private static void deobfuscate(String[] args) throws Exception { | ||
| 65 | File fileJarIn = getReadableFile(getArg(args, 1, "in jar", true)); | ||
| 66 | File fileJarOut = getWritableFile(getArg(args, 2, "out jar", true)); | ||
| 67 | Path fileMappings = getReadablePath(getArg(args, 3, "mappings file", false)); | ||
| 68 | Deobfuscator deobfuscator = getDeobfuscator(fileMappings, new JarFile(fileJarIn)); | ||
| 69 | deobfuscator.writeTransformedJar(fileJarOut, new ConsoleProgressListener()); | ||
| 70 | } | ||
| 71 | |||
| 72 | private static Deobfuscator getDeobfuscator(Path fileMappings, JarFile jar) throws Exception { | ||
| 73 | System.out.println("Reading jar..."); | ||
| 74 | Deobfuscator deobfuscator = new Deobfuscator(jar); | ||
| 75 | if (fileMappings != null) { | ||
| 76 | System.out.println("Reading mappings..."); | ||
| 77 | EntryTree<EntryMapping> mappings = chooseEnigmaFormat(fileMappings).read(fileMappings, new ConsoleProgressListener()); | ||
| 78 | deobfuscator.setMappings(mappings); | ||
| 79 | } | ||
| 80 | return deobfuscator; | ||
| 81 | } | ||
| 82 | |||
| 83 | private static void convertMappings(String[] args) throws Exception { | ||
| 84 | Path fileMappings = getReadablePath(getArg(args, 1, "enigma mapping", true)); | ||
| 85 | File result = getWritableFile(getArg(args, 2, "enigma mapping", true)); | ||
| 86 | String name = getArg(args, 3, "format desc", true); | ||
| 87 | MappingFormat saveFormat; | ||
| 88 | try { | ||
| 89 | saveFormat = MappingFormat.valueOf(name.toUpperCase(Locale.ROOT)); | ||
| 90 | } catch (IllegalArgumentException e) { | ||
| 91 | throw new IllegalArgumentException(name + "is not a valid mapping format!"); | ||
| 92 | } | ||
| 93 | |||
| 94 | System.out.println("Reading mappings..."); | ||
| 95 | |||
| 96 | MappingFormat readFormat = chooseEnigmaFormat(fileMappings); | ||
| 97 | EntryTree<EntryMapping> mappings = readFormat.read(fileMappings, new ConsoleProgressListener()); | ||
| 98 | System.out.println("Saving new mappings..."); | ||
| 99 | 65 | ||
| 100 | saveFormat.write(mappings, result.toPath(), new ConsoleProgressListener()); | 66 | for (Command command : COMMANDS.values()) { |
| 101 | } | 67 | printHelp(command); |
| 102 | |||
| 103 | private static MappingFormat chooseEnigmaFormat(Path path) { | ||
| 104 | if (Files.isDirectory(path)) { | ||
| 105 | return MappingFormat.ENIGMA_DIRECTORY; | ||
| 106 | } else { | ||
| 107 | return MappingFormat.ENIGMA_FILE; | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | private static String getArg(String[] args, int i, String name, boolean required) { | ||
| 112 | if (i >= args.length) { | ||
| 113 | if (required) { | ||
| 114 | throw new IllegalArgumentException(name + " is required"); | ||
| 115 | } else { | ||
| 116 | return null; | ||
| 117 | } | ||
| 118 | } | ||
| 119 | return args[i]; | ||
| 120 | } | ||
| 121 | |||
| 122 | private static File getWritableFile(String path) { | ||
| 123 | if (path == null) { | ||
| 124 | return null; | ||
| 125 | } | ||
| 126 | File file = new File(path).getAbsoluteFile(); | ||
| 127 | File dir = file.getParentFile(); | ||
| 128 | if (dir == null) { | ||
| 129 | throw new IllegalArgumentException("Cannot write file: " + path); | ||
| 130 | } | 68 | } |
| 131 | // quick fix to avoid stupid stuff in Gradle code | ||
| 132 | if (!dir.isDirectory()) { | ||
| 133 | dir.mkdirs(); | ||
| 134 | } | ||
| 135 | return file; | ||
| 136 | } | 69 | } |
| 137 | 70 | ||
| 138 | private static File getWritableFolder(String path) { | 71 | private static void printHelp(Command command) { |
| 139 | if (path == null) { | 72 | System.out.println("\t\t" + command.name + " " + command.getUsage()); |
| 140 | return null; | ||
| 141 | } | ||
| 142 | File dir = new File(path).getAbsoluteFile(); | ||
| 143 | if (!dir.exists()) { | ||
| 144 | throw new IllegalArgumentException("Cannot write to folder: " + dir); | ||
| 145 | } | ||
| 146 | return dir; | ||
| 147 | } | 73 | } |
| 148 | 74 | ||
| 149 | private static File getReadableFile(String path) { | 75 | private static void register(Command command) { |
| 150 | if (path == null) { | 76 | Command old = COMMANDS.put(command.name, command); |
| 151 | return null; | 77 | if (old != null) { |
| 78 | System.err.println("Command " + old + " with name " + command.name + " has been substituted by " + command); | ||
| 152 | } | 79 | } |
| 153 | File file = new File(path).getAbsoluteFile(); | ||
| 154 | if (!file.exists()) { | ||
| 155 | throw new IllegalArgumentException("Cannot find file: " + file.getAbsolutePath()); | ||
| 156 | } | ||
| 157 | return file; | ||
| 158 | } | 80 | } |
| 159 | 81 | ||
| 160 | private static Path getReadablePath(String path) { | 82 | static { |
| 161 | if (path == null) { | 83 | register(new DeobfuscateCommand()); |
| 162 | return null; | 84 | register(new DecompileCommand()); |
| 163 | } | 85 | register(new ConvertMappingsCommand()); |
| 164 | Path file = Paths.get(path).toAbsolutePath(); | 86 | register(new CheckMappingsCommand()); |
| 165 | if (!Files.exists(file)) { | ||
| 166 | throw new IllegalArgumentException("Cannot find file: " + file.toString()); | ||
| 167 | } | ||
| 168 | return file; | ||
| 169 | } | 87 | } |
| 170 | 88 | ||
| 171 | public static class ConsoleProgressListener implements ProgressListener { | 89 | private static final class CommandHelpException extends IllegalArgumentException { |
| 172 | |||
| 173 | private static final int ReportTime = 5000; // 5s | ||
| 174 | 90 | ||
| 175 | private int totalWork; | 91 | final Command command; |
| 176 | private long startTime; | ||
| 177 | private long lastReportTime; | ||
| 178 | 92 | ||
| 179 | @Override | 93 | CommandHelpException(Command command) { |
| 180 | public void init(int totalWork, String title) { | 94 | this.command = command; |
| 181 | this.totalWork = totalWork; | ||
| 182 | this.startTime = System.currentTimeMillis(); | ||
| 183 | this.lastReportTime = this.startTime; | ||
| 184 | System.out.println(title); | ||
| 185 | } | 95 | } |
| 186 | 96 | ||
| 187 | @Override | 97 | CommandHelpException(Command command, Throwable cause) { |
| 188 | public void step(int numDone, String message) { | 98 | super(cause); |
| 189 | long now = System.currentTimeMillis(); | 99 | this.command = command; |
| 190 | boolean isLastUpdate = numDone == this.totalWork; | ||
| 191 | boolean shouldReport = isLastUpdate || now - this.lastReportTime > ReportTime; | ||
| 192 | |||
| 193 | if (shouldReport) { | ||
| 194 | int percent = numDone * 100 / this.totalWork; | ||
| 195 | System.out.println(String.format("\tProgress: %3d%%", percent)); | ||
| 196 | this.lastReportTime = now; | ||
| 197 | } | ||
| 198 | if (isLastUpdate) { | ||
| 199 | double elapsedSeconds = (now - this.startTime) / 1000.0; | ||
| 200 | System.out.println(String.format("Finished in %.1f seconds", elapsedSeconds)); | ||
| 201 | } | ||
| 202 | } | 100 | } |
| 203 | } | 101 | } |
| 204 | } | 102 | } |
diff --git a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java index 773eaf1..31c6f54 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/EntryIndex.java | |||
| @@ -65,6 +65,11 @@ public class EntryIndex implements JarIndexer { | |||
| 65 | } | 65 | } |
| 66 | 66 | ||
| 67 | @Nullable | 67 | @Nullable |
| 68 | public AccessFlags getClassAccess(ClassEntry entry) { | ||
| 69 | return classes.get(entry); | ||
| 70 | } | ||
| 71 | |||
| 72 | @Nullable | ||
| 68 | public AccessFlags getEntryAccess(Entry<?> entry) { | 73 | public AccessFlags getEntryAccess(Entry<?> entry) { |
| 69 | if (entry instanceof MethodEntry) { | 74 | if (entry instanceof MethodEntry) { |
| 70 | return getMethodAccess((MethodEntry) entry); | 75 | return getMethodAccess((MethodEntry) entry); |
diff --git a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java index ba5d3b6..b730a8a 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java +++ b/src/main/java/cuchaz/enigma/analysis/index/IndexReferenceVisitor.java | |||
| @@ -1,16 +1,11 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | 1 | package cuchaz.enigma.analysis.index; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.translation.representation.AccessFlags; | 3 | import cuchaz.enigma.translation.representation.AccessFlags; |
| 4 | import cuchaz.enigma.translation.representation.Lambda; | ||
| 4 | import cuchaz.enigma.translation.representation.MethodDescriptor; | 5 | import cuchaz.enigma.translation.representation.MethodDescriptor; |
| 5 | import cuchaz.enigma.translation.representation.Signature; | 6 | import cuchaz.enigma.translation.representation.Signature; |
| 6 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 7 | import cuchaz.enigma.translation.representation.entry.*; |
| 7 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | 8 | import org.objectweb.asm.*; |
| 8 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 10 | import org.objectweb.asm.ClassVisitor; | ||
| 11 | import org.objectweb.asm.Handle; | ||
| 12 | import org.objectweb.asm.MethodVisitor; | ||
| 13 | import org.objectweb.asm.Opcodes; | ||
| 14 | 9 | ||
| 15 | public class IndexReferenceVisitor extends ClassVisitor { | 10 | public class IndexReferenceVisitor extends ClassVisitor { |
| 16 | private final JarIndexer indexer; | 11 | private final JarIndexer indexer; |
| @@ -54,29 +49,37 @@ public class IndexReferenceVisitor extends ClassVisitor { | |||
| 54 | this.indexer.indexMethodReference(callerEntry, methodEntry); | 49 | this.indexer.indexMethodReference(callerEntry, methodEntry); |
| 55 | } | 50 | } |
| 56 | 51 | ||
| 52 | private static ParentedEntry<?> getHandleEntry(Handle handle) { | ||
| 53 | switch (handle.getTag()) { | ||
| 54 | case Opcodes.H_GETFIELD: | ||
| 55 | case Opcodes.H_GETSTATIC: | ||
| 56 | case Opcodes.H_PUTFIELD: | ||
| 57 | case Opcodes.H_PUTSTATIC: | ||
| 58 | return FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); | ||
| 59 | case Opcodes.H_INVOKEINTERFACE: | ||
| 60 | case Opcodes.H_INVOKESPECIAL: | ||
| 61 | case Opcodes.H_INVOKESTATIC: | ||
| 62 | case Opcodes.H_INVOKEVIRTUAL: | ||
| 63 | case Opcodes.H_NEWINVOKESPECIAL: | ||
| 64 | return MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); | ||
| 65 | } | ||
| 66 | throw new RuntimeException("Invalid handle tag " + handle.getTag()); | ||
| 67 | } | ||
| 68 | |||
| 57 | @Override | 69 | @Override |
| 58 | public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { | 70 | public void visitInvokeDynamicInsn(String name, String desc, Handle bsm, Object... bsmArgs) { |
| 59 | for (Object bsmArg : bsmArgs) { | 71 | if ("java/lang/invoke/LambdaMetafactory".equals(bsm.getOwner()) && "metafactory".equals(bsm.getName())) { |
| 60 | if (bsmArg instanceof Handle) { | 72 | Type samMethodType = (Type) bsmArgs[0]; |
| 61 | Handle handle = (Handle) bsmArg; | 73 | Handle implMethod = (Handle) bsmArgs[1]; |
| 62 | switch (handle.getTag()) { | 74 | Type instantiatedMethodType = (Type) bsmArgs[2]; |
| 63 | case Opcodes.H_GETFIELD: | 75 | |
| 64 | case Opcodes.H_GETSTATIC: | 76 | this.indexer.indexLambda(callerEntry, new Lambda( |
| 65 | case Opcodes.H_PUTFIELD: | 77 | name, |
| 66 | case Opcodes.H_PUTSTATIC: | 78 | new MethodDescriptor(desc), |
| 67 | FieldEntry fieldEntry = FieldEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); | 79 | new MethodDescriptor(samMethodType.getDescriptor()), |
| 68 | this.indexer.indexFieldReference(callerEntry, fieldEntry); | 80 | getHandleEntry(implMethod), |
| 69 | break; | 81 | new MethodDescriptor(instantiatedMethodType.getDescriptor()) |
| 70 | case Opcodes.H_INVOKEINTERFACE: | 82 | )); |
| 71 | case Opcodes.H_INVOKESPECIAL: | ||
| 72 | case Opcodes.H_INVOKESTATIC: | ||
| 73 | case Opcodes.H_INVOKEVIRTUAL: | ||
| 74 | case Opcodes.H_NEWINVOKESPECIAL: | ||
| 75 | MethodEntry methodEntry = MethodEntry.parse(handle.getOwner(), handle.getName(), handle.getDesc()); | ||
| 76 | this.indexer.indexMethodReference(callerEntry, methodEntry); | ||
| 77 | break; | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | 83 | } |
| 81 | } | 84 | } |
| 82 | } | 85 | } |
diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java index a429ff6..fd4e618 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java | |||
| @@ -16,6 +16,7 @@ import com.google.common.collect.Multimap; | |||
| 16 | import cuchaz.enigma.analysis.ParsedJar; | 16 | import cuchaz.enigma.analysis.ParsedJar; |
| 17 | import cuchaz.enigma.translation.mapping.EntryResolver; | 17 | import cuchaz.enigma.translation.mapping.EntryResolver; |
| 18 | import cuchaz.enigma.translation.mapping.IndexEntryResolver; | 18 | import cuchaz.enigma.translation.mapping.IndexEntryResolver; |
| 19 | import cuchaz.enigma.translation.representation.Lambda; | ||
| 19 | import cuchaz.enigma.translation.representation.entry.*; | 20 | import cuchaz.enigma.translation.representation.entry.*; |
| 20 | import org.objectweb.asm.ClassReader; | 21 | import org.objectweb.asm.ClassReader; |
| 21 | import org.objectweb.asm.Opcodes; | 22 | import org.objectweb.asm.Opcodes; |
| @@ -29,18 +30,20 @@ public class JarIndex implements JarIndexer { | |||
| 29 | private final InheritanceIndex inheritanceIndex; | 30 | private final InheritanceIndex inheritanceIndex; |
| 30 | private final ReferenceIndex referenceIndex; | 31 | private final ReferenceIndex referenceIndex; |
| 31 | private final BridgeMethodIndex bridgeMethodIndex; | 32 | private final BridgeMethodIndex bridgeMethodIndex; |
| 33 | private final PackageVisibilityIndex packageVisibilityIndex; | ||
| 32 | private final EntryResolver entryResolver; | 34 | private final EntryResolver entryResolver; |
| 33 | 35 | ||
| 34 | private final Collection<JarIndexer> indexers; | 36 | private final Collection<JarIndexer> indexers; |
| 35 | 37 | ||
| 36 | private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create(); | 38 | private final Multimap<String, MethodDefEntry> methodImplementations = HashMultimap.create(); |
| 37 | 39 | ||
| 38 | public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex) { | 40 | public JarIndex(EntryIndex entryIndex, InheritanceIndex inheritanceIndex, ReferenceIndex referenceIndex, BridgeMethodIndex bridgeMethodIndex, PackageVisibilityIndex packageVisibilityIndex) { |
| 39 | this.entryIndex = entryIndex; | 41 | this.entryIndex = entryIndex; |
| 40 | this.inheritanceIndex = inheritanceIndex; | 42 | this.inheritanceIndex = inheritanceIndex; |
| 41 | this.referenceIndex = referenceIndex; | 43 | this.referenceIndex = referenceIndex; |
| 42 | this.bridgeMethodIndex = bridgeMethodIndex; | 44 | this.bridgeMethodIndex = bridgeMethodIndex; |
| 43 | this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex); | 45 | this.packageVisibilityIndex = packageVisibilityIndex; |
| 46 | this.indexers = Arrays.asList(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); | ||
| 44 | this.entryResolver = new IndexEntryResolver(this); | 47 | this.entryResolver = new IndexEntryResolver(this); |
| 45 | } | 48 | } |
| 46 | 49 | ||
| @@ -49,7 +52,8 @@ public class JarIndex implements JarIndexer { | |||
| 49 | InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); | 52 | InheritanceIndex inheritanceIndex = new InheritanceIndex(entryIndex); |
| 50 | ReferenceIndex referenceIndex = new ReferenceIndex(); | 53 | ReferenceIndex referenceIndex = new ReferenceIndex(); |
| 51 | BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex); | 54 | BridgeMethodIndex bridgeMethodIndex = new BridgeMethodIndex(entryIndex, inheritanceIndex, referenceIndex); |
| 52 | return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex); | 55 | PackageVisibilityIndex packageVisibilityIndex = new PackageVisibilityIndex(); |
| 56 | return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); | ||
| 53 | } | 57 | } |
| 54 | 58 | ||
| 55 | public void indexJar(ParsedJar jar, Consumer<String> progress) { | 59 | public void indexJar(ParsedJar jar, Consumer<String> progress) { |
| @@ -126,6 +130,15 @@ public class JarIndex implements JarIndexer { | |||
| 126 | indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry)); | 130 | indexers.forEach(indexer -> indexer.indexFieldReference(callerEntry, referencedEntry)); |
| 127 | } | 131 | } |
| 128 | 132 | ||
| 133 | @Override | ||
| 134 | public void indexLambda(MethodDefEntry callerEntry, Lambda lambda) { | ||
| 135 | if (callerEntry.getParent().isJre()) { | ||
| 136 | return; | ||
| 137 | } | ||
| 138 | |||
| 139 | indexers.forEach(indexer -> indexer.indexLambda(callerEntry, lambda)); | ||
| 140 | } | ||
| 141 | |||
| 129 | public EntryIndex getEntryIndex() { | 142 | public EntryIndex getEntryIndex() { |
| 130 | return entryIndex; | 143 | return entryIndex; |
| 131 | } | 144 | } |
| @@ -142,6 +155,10 @@ public class JarIndex implements JarIndexer { | |||
| 142 | return bridgeMethodIndex; | 155 | return bridgeMethodIndex; |
| 143 | } | 156 | } |
| 144 | 157 | ||
| 158 | public PackageVisibilityIndex getPackageVisibilityIndex() { | ||
| 159 | return packageVisibilityIndex; | ||
| 160 | } | ||
| 161 | |||
| 145 | public EntryResolver getEntryResolver() { | 162 | public EntryResolver getEntryResolver() { |
| 146 | return entryResolver; | 163 | return entryResolver; |
| 147 | } | 164 | } |
diff --git a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java index 457c223..4f885b3 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java +++ b/src/main/java/cuchaz/enigma/analysis/index/JarIndexer.java | |||
| @@ -1,5 +1,6 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | 1 | package cuchaz.enigma.analysis.index; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.translation.representation.Lambda; | ||
| 3 | import cuchaz.enigma.translation.representation.entry.*; | 4 | import cuchaz.enigma.translation.representation.entry.*; |
| 4 | 5 | ||
| 5 | public interface JarIndexer { | 6 | public interface JarIndexer { |
| @@ -18,6 +19,9 @@ public interface JarIndexer { | |||
| 18 | default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) { | 19 | default void indexFieldReference(MethodDefEntry callerEntry, FieldEntry referencedEntry) { |
| 19 | } | 20 | } |
| 20 | 21 | ||
| 22 | default void indexLambda(MethodDefEntry callerEntry, Lambda lambda) { | ||
| 23 | } | ||
| 24 | |||
| 21 | default void processIndex(JarIndex index) { | 25 | default void processIndex(JarIndex index) { |
| 22 | } | 26 | } |
| 23 | } | 27 | } |
diff --git a/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java new file mode 100644 index 0000000..da28ac4 --- /dev/null +++ b/src/main/java/cuchaz/enigma/analysis/index/PackageVisibilityIndex.java | |||
| @@ -0,0 +1,127 @@ | |||
| 1 | package cuchaz.enigma.analysis.index; | ||
| 2 | |||
| 3 | import com.google.common.collect.HashMultimap; | ||
| 4 | import com.google.common.collect.Lists; | ||
| 5 | import com.google.common.collect.Maps; | ||
| 6 | import com.google.common.collect.Sets; | ||
| 7 | import cuchaz.enigma.analysis.EntryReference; | ||
| 8 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 10 | |||
| 11 | import java.util.*; | ||
| 12 | |||
| 13 | public class PackageVisibilityIndex implements JarIndexer { | ||
| 14 | private static boolean requiresSamePackage(AccessFlags entryAcc, EntryReference ref, InheritanceIndex inheritanceIndex) { | ||
| 15 | if (entryAcc.isPublic()) return false; | ||
| 16 | if (entryAcc.isProtected()) { | ||
| 17 | Set<ClassEntry> callerAncestors = inheritanceIndex.getAncestors(ref.context.getContainingClass()); | ||
| 18 | return !callerAncestors.contains(ref.entry.getContainingClass()); | ||
| 19 | } | ||
| 20 | return !entryAcc.isPrivate(); // if isPrivate is false, it must be package-private | ||
| 21 | } | ||
| 22 | |||
| 23 | private final HashMultimap<ClassEntry, ClassEntry> connections = HashMultimap.create(); | ||
| 24 | private final List<Set<ClassEntry>> partitions = Lists.newArrayList(); | ||
| 25 | private final Map<ClassEntry, Set<ClassEntry>> classPartitions = Maps.newHashMap(); | ||
| 26 | |||
| 27 | private void addConnection(ClassEntry classA, ClassEntry classB) { | ||
| 28 | connections.put(classA, classB); | ||
| 29 | connections.put(classB, classA); | ||
| 30 | } | ||
| 31 | |||
| 32 | private void buildPartition(Set<ClassEntry> unassignedClasses, Set<ClassEntry> partition, ClassEntry member) { | ||
| 33 | for (ClassEntry connected : connections.get(member)) { | ||
| 34 | if (unassignedClasses.remove(connected)) { | ||
| 35 | partition.add(connected); | ||
| 36 | buildPartition(unassignedClasses, partition, connected); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
| 40 | |||
| 41 | private void addConnections(EntryIndex entryIndex, ReferenceIndex referenceIndex, InheritanceIndex inheritanceIndex) { | ||
| 42 | for (FieldEntry entry : entryIndex.getFields()) { | ||
| 43 | AccessFlags entryAcc = entryIndex.getFieldAccess(entry); | ||
| 44 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { | ||
| 45 | for (EntryReference<FieldEntry, MethodDefEntry> ref : referenceIndex.getReferencesToField(entry)) { | ||
| 46 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { | ||
| 47 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | ||
| 48 | } | ||
| 49 | } | ||
| 50 | } | ||
| 51 | } | ||
| 52 | |||
| 53 | for (MethodEntry entry : entryIndex.getMethods()) { | ||
| 54 | AccessFlags entryAcc = entryIndex.getMethodAccess(entry); | ||
| 55 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { | ||
| 56 | for (EntryReference<MethodEntry, MethodDefEntry> ref : referenceIndex.getReferencesToMethod(entry)) { | ||
| 57 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { | ||
| 58 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | ||
| 59 | } | ||
| 60 | } | ||
| 61 | } | ||
| 62 | } | ||
| 63 | |||
| 64 | for (ClassEntry entry : entryIndex.getClasses()) { | ||
| 65 | AccessFlags entryAcc = entryIndex.getClassAccess(entry); | ||
| 66 | if (!entryAcc.isPublic() && !entryAcc.isPrivate()) { | ||
| 67 | for (EntryReference<ClassEntry, FieldDefEntry> ref : referenceIndex.getFieldTypeReferencesToClass(entry)) { | ||
| 68 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { | ||
| 69 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | ||
| 70 | } | ||
| 71 | } | ||
| 72 | |||
| 73 | for (EntryReference<ClassEntry, MethodDefEntry> ref : referenceIndex.getMethodTypeReferencesToClass(entry)) { | ||
| 74 | if (requiresSamePackage(entryAcc, ref, inheritanceIndex)) { | ||
| 75 | addConnection(ref.entry.getContainingClass(), ref.context.getContainingClass()); | ||
| 76 | } | ||
| 77 | } | ||
| 78 | } | ||
| 79 | |||
| 80 | for (ClassEntry parent : inheritanceIndex.getParents(entry)) { | ||
| 81 | AccessFlags parentAcc = entryIndex.getClassAccess(parent); | ||
| 82 | if (parentAcc != null && !parentAcc.isPublic() && !parentAcc.isPrivate()) { | ||
| 83 | addConnection(entry, parent); | ||
| 84 | } | ||
| 85 | } | ||
| 86 | |||
| 87 | ClassEntry outerClass = entry.getOuterClass(); | ||
| 88 | if (outerClass != null) { | ||
| 89 | addConnection(entry, outerClass); | ||
| 90 | } | ||
| 91 | } | ||
| 92 | } | ||
| 93 | |||
| 94 | private void addPartitions(EntryIndex entryIndex) { | ||
| 95 | Set<ClassEntry> unassignedClasses = Sets.newHashSet(entryIndex.getClasses()); | ||
| 96 | while (!unassignedClasses.isEmpty()) { | ||
| 97 | Iterator<ClassEntry> iterator = unassignedClasses.iterator(); | ||
| 98 | ClassEntry initialEntry = iterator.next(); | ||
| 99 | iterator.remove(); | ||
| 100 | |||
| 101 | HashSet<ClassEntry> partition = Sets.newHashSet(); | ||
| 102 | partition.add(initialEntry); | ||
| 103 | buildPartition(unassignedClasses, partition, initialEntry); | ||
| 104 | partitions.add(partition); | ||
| 105 | for (ClassEntry entry : partition) { | ||
| 106 | classPartitions.put(entry, partition); | ||
| 107 | } | ||
| 108 | } | ||
| 109 | } | ||
| 110 | |||
| 111 | public Collection<Set<ClassEntry>> getPartitions() { | ||
| 112 | return partitions; | ||
| 113 | } | ||
| 114 | |||
| 115 | public Set<ClassEntry> getPartition(ClassEntry classEntry) { | ||
| 116 | return classPartitions.get(classEntry); | ||
| 117 | } | ||
| 118 | |||
| 119 | @Override | ||
| 120 | public void processIndex(JarIndex index) { | ||
| 121 | EntryIndex entryIndex = index.getEntryIndex(); | ||
| 122 | ReferenceIndex referenceIndex = index.getReferenceIndex(); | ||
| 123 | InheritanceIndex inheritanceIndex = index.getInheritanceIndex(); | ||
| 124 | addConnections(entryIndex, referenceIndex, inheritanceIndex); | ||
| 125 | addPartitions(entryIndex); | ||
| 126 | } | ||
| 127 | } | ||
diff --git a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java index 2b63c5d..f54c90d 100644 --- a/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java +++ b/src/main/java/cuchaz/enigma/analysis/index/ReferenceIndex.java | |||
| @@ -4,6 +4,9 @@ import com.google.common.collect.HashMultimap; | |||
| 4 | import com.google.common.collect.Multimap; | 4 | import com.google.common.collect.Multimap; |
| 5 | import cuchaz.enigma.analysis.EntryReference; | 5 | import cuchaz.enigma.analysis.EntryReference; |
| 6 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | 6 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; |
| 7 | import cuchaz.enigma.translation.representation.Lambda; | ||
| 8 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 9 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.*; | 10 | import cuchaz.enigma.translation.representation.entry.*; |
| 8 | 11 | ||
| 9 | import java.util.Collection; | 12 | import java.util.Collection; |
| @@ -15,6 +18,43 @@ public class ReferenceIndex implements JarIndexer { | |||
| 15 | private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> referencesToMethods = HashMultimap.create(); | 18 | private Multimap<MethodEntry, EntryReference<MethodEntry, MethodDefEntry>> referencesToMethods = HashMultimap.create(); |
| 16 | private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> referencesToClasses = HashMultimap.create(); | 19 | private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> referencesToClasses = HashMultimap.create(); |
| 17 | private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> referencesToFields = HashMultimap.create(); | 20 | private Multimap<FieldEntry, EntryReference<FieldEntry, MethodDefEntry>> referencesToFields = HashMultimap.create(); |
| 21 | private Multimap<ClassEntry, EntryReference<ClassEntry, FieldDefEntry>> fieldTypeReferences = HashMultimap.create(); | ||
| 22 | private Multimap<ClassEntry, EntryReference<ClassEntry, MethodDefEntry>> methodTypeReferences = HashMultimap.create(); | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public void indexMethod(MethodDefEntry methodEntry) { | ||
| 26 | indexMethodDescriptor(methodEntry, methodEntry.getDesc()); | ||
| 27 | } | ||
| 28 | |||
| 29 | private void indexMethodDescriptor(MethodDefEntry entry, MethodDescriptor descriptor) { | ||
| 30 | for (TypeDescriptor typeDescriptor : descriptor.getArgumentDescs()) { | ||
| 31 | indexMethodTypeDescriptor(entry, typeDescriptor); | ||
| 32 | } | ||
| 33 | indexMethodTypeDescriptor(entry, descriptor.getReturnDesc()); | ||
| 34 | } | ||
| 35 | |||
| 36 | private void indexMethodTypeDescriptor(MethodDefEntry method, TypeDescriptor typeDescriptor) { | ||
| 37 | if (typeDescriptor.isType()) { | ||
| 38 | ClassEntry referencedClass = typeDescriptor.getTypeEntry(); | ||
| 39 | methodTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), method)); | ||
| 40 | } else if (typeDescriptor.isArray()) { | ||
| 41 | indexMethodTypeDescriptor(method, typeDescriptor.getArrayType()); | ||
| 42 | } | ||
| 43 | } | ||
| 44 | |||
| 45 | @Override | ||
| 46 | public void indexField(FieldDefEntry fieldEntry) { | ||
| 47 | indexFieldTypeDescriptor(fieldEntry, fieldEntry.getDesc()); | ||
| 48 | } | ||
| 49 | |||
| 50 | private void indexFieldTypeDescriptor(FieldDefEntry field, TypeDescriptor typeDescriptor) { | ||
| 51 | if (typeDescriptor.isType()) { | ||
| 52 | ClassEntry referencedClass = typeDescriptor.getTypeEntry(); | ||
| 53 | fieldTypeReferences.put(referencedClass, new EntryReference<>(referencedClass, referencedClass.getName(), field)); | ||
| 54 | } else if (typeDescriptor.isArray()) { | ||
| 55 | indexFieldTypeDescriptor(field, typeDescriptor.getArrayType()); | ||
| 56 | } | ||
| 57 | } | ||
| 18 | 58 | ||
| 19 | @Override | 59 | @Override |
| 20 | public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) { | 60 | public void indexMethodReference(MethodDefEntry callerEntry, MethodEntry referencedEntry) { |
| @@ -33,15 +73,30 @@ public class ReferenceIndex implements JarIndexer { | |||
| 33 | } | 73 | } |
| 34 | 74 | ||
| 35 | @Override | 75 | @Override |
| 76 | public void indexLambda(MethodDefEntry callerEntry, Lambda lambda) { | ||
| 77 | if (lambda.getImplMethod() instanceof MethodEntry) { | ||
| 78 | indexMethodReference(callerEntry, (MethodEntry) lambda.getImplMethod()); | ||
| 79 | } else { | ||
| 80 | indexFieldReference(callerEntry, (FieldEntry) lambda.getImplMethod()); | ||
| 81 | } | ||
| 82 | |||
| 83 | indexMethodDescriptor(callerEntry, lambda.getInvokedType()); | ||
| 84 | indexMethodDescriptor(callerEntry, lambda.getSamMethodType()); | ||
| 85 | indexMethodDescriptor(callerEntry, lambda.getInstantiatedMethodType()); | ||
| 86 | } | ||
| 87 | |||
| 88 | @Override | ||
| 36 | public void processIndex(JarIndex index) { | 89 | public void processIndex(JarIndex index) { |
| 37 | methodReferences = remapReferences(index, methodReferences); | 90 | methodReferences = remapReferences(index, methodReferences); |
| 38 | referencesToMethods = remapReferencesTo(index, referencesToMethods); | 91 | referencesToMethods = remapReferencesTo(index, referencesToMethods); |
| 39 | referencesToClasses = remapReferencesTo(index, referencesToClasses); | 92 | referencesToClasses = remapReferencesTo(index, referencesToClasses); |
| 40 | referencesToFields = remapReferencesTo(index, referencesToFields); | 93 | referencesToFields = remapReferencesTo(index, referencesToFields); |
| 94 | fieldTypeReferences = remapReferencesTo(index, fieldTypeReferences); | ||
| 95 | methodTypeReferences = remapReferencesTo(index, methodTypeReferences); | ||
| 41 | } | 96 | } |
| 42 | 97 | ||
| 43 | private <K extends Entry<?>, V extends Entry<?>> Multimap<K, V> remapReferences(JarIndex index, Multimap<K, V> multimap) { | 98 | private <K extends Entry<?>, V extends Entry<?>> Multimap<K, V> remapReferences(JarIndex index, Multimap<K, V> multimap) { |
| 44 | Multimap<K, V> resolved = HashMultimap.create(); | 99 | Multimap<K, V> resolved = HashMultimap.create(multimap.keySet().size(), multimap.size() / multimap.keySet().size()); |
| 45 | for (Map.Entry<K, V> entry : multimap.entries()) { | 100 | for (Map.Entry<K, V> entry : multimap.entries()) { |
| 46 | resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); | 101 | resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); |
| 47 | } | 102 | } |
| @@ -49,7 +104,8 @@ public class ReferenceIndex implements JarIndexer { | |||
| 49 | } | 104 | } |
| 50 | 105 | ||
| 51 | private <E extends Entry<?>, C extends Entry<?>> Multimap<E, EntryReference<E, C>> remapReferencesTo(JarIndex index, Multimap<E, EntryReference<E, C>> multimap) { | 106 | private <E extends Entry<?>, C extends Entry<?>> Multimap<E, EntryReference<E, C>> remapReferencesTo(JarIndex index, Multimap<E, EntryReference<E, C>> multimap) { |
| 52 | Multimap<E, EntryReference<E, C>> resolved = HashMultimap.create(); | 107 | final int keySetSize = multimap.keySet().size(); |
| 108 | Multimap<E, EntryReference<E, C>> resolved = HashMultimap.create(keySetSize, keySetSize == 0 ? 0 : multimap.size() / keySetSize); | ||
| 53 | for (Map.Entry<E, EntryReference<E, C>> entry : multimap.entries()) { | 109 | for (Map.Entry<E, EntryReference<E, C>> entry : multimap.entries()) { |
| 54 | resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); | 110 | resolved.put(remap(index, entry.getKey()), remap(index, entry.getValue())); |
| 55 | } | 111 | } |
| @@ -79,4 +135,12 @@ public class ReferenceIndex implements JarIndexer { | |||
| 79 | public Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferencesToMethod(MethodEntry entry) { | 135 | public Collection<EntryReference<MethodEntry, MethodDefEntry>> getReferencesToMethod(MethodEntry entry) { |
| 80 | return referencesToMethods.get(entry); | 136 | return referencesToMethods.get(entry); |
| 81 | } | 137 | } |
| 138 | |||
| 139 | public Collection<EntryReference<ClassEntry, FieldDefEntry>> getFieldTypeReferencesToClass(ClassEntry entry) { | ||
| 140 | return fieldTypeReferences.get(entry); | ||
| 141 | } | ||
| 142 | |||
| 143 | public Collection<EntryReference<ClassEntry, MethodDefEntry>> getMethodTypeReferencesToClass(ClassEntry entry) { | ||
| 144 | return methodTypeReferences.get(entry); | ||
| 145 | } | ||
| 82 | } | 146 | } |
diff --git a/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java new file mode 100644 index 0000000..7ec7679 --- /dev/null +++ b/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java | |||
| @@ -0,0 +1,62 @@ | |||
| 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 0000000..b107fb6 --- /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 0000000..75d3791 --- /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 0000000..a58d908 --- /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 0000000..5d49938 --- /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 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/Lambda.java b/src/main/java/cuchaz/enigma/translation/representation/Lambda.java new file mode 100644 index 0000000..63eb563 --- /dev/null +++ b/src/main/java/cuchaz/enigma/translation/representation/Lambda.java | |||
| @@ -0,0 +1,105 @@ | |||
| 1 | package cuchaz.enigma.translation.representation; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.Translatable; | ||
| 4 | import cuchaz.enigma.translation.Translator; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryMap; | ||
| 6 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 7 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 8 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 9 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 10 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.ParentedEntry; | ||
| 12 | |||
| 13 | import java.util.Objects; | ||
| 14 | |||
| 15 | public class Lambda implements Translatable { | ||
| 16 | private final String invokedName; | ||
| 17 | private final MethodDescriptor invokedType; | ||
| 18 | private final MethodDescriptor samMethodType; | ||
| 19 | private final ParentedEntry<?> implMethod; | ||
| 20 | private final MethodDescriptor instantiatedMethodType; | ||
| 21 | |||
| 22 | public Lambda(String invokedName, MethodDescriptor invokedType, MethodDescriptor samMethodType, ParentedEntry<?> implMethod, MethodDescriptor instantiatedMethodType) { | ||
| 23 | this.invokedName = invokedName; | ||
| 24 | this.invokedType = invokedType; | ||
| 25 | this.samMethodType = samMethodType; | ||
| 26 | this.implMethod = implMethod; | ||
| 27 | this.instantiatedMethodType = instantiatedMethodType; | ||
| 28 | } | ||
| 29 | |||
| 30 | @Override | ||
| 31 | public Lambda translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | ||
| 32 | MethodEntry samMethod = new MethodEntry(getInterface(), invokedName, samMethodType); | ||
| 33 | EntryMapping samMethodMapping = resolveMapping(resolver, mappings, samMethod); | ||
| 34 | |||
| 35 | return new Lambda( | ||
| 36 | samMethodMapping != null ? samMethodMapping.getTargetName() : invokedName, | ||
| 37 | invokedType.translate(translator, resolver, mappings), | ||
| 38 | samMethodType.translate(translator, resolver, mappings), | ||
| 39 | implMethod.translate(translator, resolver, mappings), | ||
| 40 | instantiatedMethodType.translate(translator, resolver, mappings) | ||
| 41 | ); | ||
| 42 | } | ||
| 43 | |||
| 44 | private EntryMapping resolveMapping(EntryResolver resolver, EntryMap<EntryMapping> mappings, MethodEntry methodEntry) { | ||
| 45 | for (MethodEntry entry : resolver.resolveEntry(methodEntry, ResolutionStrategy.RESOLVE_ROOT)) { | ||
| 46 | EntryMapping mapping = mappings.get(entry); | ||
| 47 | if (mapping != null) { | ||
| 48 | return mapping; | ||
| 49 | } | ||
| 50 | } | ||
| 51 | return null; | ||
| 52 | } | ||
| 53 | |||
| 54 | public ClassEntry getInterface() { | ||
| 55 | return invokedType.getReturnDesc().getTypeEntry(); | ||
| 56 | } | ||
| 57 | |||
| 58 | public String getInvokedName() { | ||
| 59 | return invokedName; | ||
| 60 | } | ||
| 61 | |||
| 62 | public MethodDescriptor getInvokedType() { | ||
| 63 | return invokedType; | ||
| 64 | } | ||
| 65 | |||
| 66 | public MethodDescriptor getSamMethodType() { | ||
| 67 | return samMethodType; | ||
| 68 | } | ||
| 69 | |||
| 70 | public ParentedEntry<?> getImplMethod() { | ||
| 71 | return implMethod; | ||
| 72 | } | ||
| 73 | |||
| 74 | public MethodDescriptor getInstantiatedMethodType() { | ||
| 75 | return instantiatedMethodType; | ||
| 76 | } | ||
| 77 | |||
| 78 | @Override | ||
| 79 | public boolean equals(Object o) { | ||
| 80 | if (this == o) return true; | ||
| 81 | if (o == null || getClass() != o.getClass()) return false; | ||
| 82 | Lambda lambda = (Lambda) o; | ||
| 83 | return Objects.equals(invokedName, lambda.invokedName) && | ||
| 84 | Objects.equals(invokedType, lambda.invokedType) && | ||
| 85 | Objects.equals(samMethodType, lambda.samMethodType) && | ||
| 86 | Objects.equals(implMethod, lambda.implMethod) && | ||
| 87 | Objects.equals(instantiatedMethodType, lambda.instantiatedMethodType); | ||
| 88 | } | ||
| 89 | |||
| 90 | @Override | ||
| 91 | public int hashCode() { | ||
| 92 | return Objects.hash(invokedName, invokedType, samMethodType, implMethod, instantiatedMethodType); | ||
| 93 | } | ||
| 94 | |||
| 95 | @Override | ||
| 96 | public String toString() { | ||
| 97 | return "Lambda{" + | ||
| 98 | "invokedName='" + invokedName + '\'' + | ||
| 99 | ", invokedType=" + invokedType + | ||
| 100 | ", samMethodType=" + samMethodType + | ||
| 101 | ", implMethod=" + implMethod + | ||
| 102 | ", instantiatedMethodType=" + instantiatedMethodType + | ||
| 103 | '}'; | ||
| 104 | } | ||
| 105 | } | ||
diff --git a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java index c59751f..37a7014 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java +++ b/src/main/java/cuchaz/enigma/translation/representation/MethodDescriptor.java | |||
| @@ -118,7 +118,7 @@ public class MethodDescriptor implements Translatable { | |||
| 118 | } | 118 | } |
| 119 | 119 | ||
| 120 | @Override | 120 | @Override |
| 121 | public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | 121 | public MethodDescriptor translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { |
| 122 | List<TypeDescriptor> translatedArguments = new ArrayList<>(argumentDescs.size()); | 122 | List<TypeDescriptor> translatedArguments = new ArrayList<>(argumentDescs.size()); |
| 123 | for (TypeDescriptor argument : argumentDescs) { | 123 | for (TypeDescriptor argument : argumentDescs) { |
| 124 | translatedArguments.add(translator.translate(argument)); | 124 | translatedArguments.add(translator.translate(argument)); |
diff --git a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java index 834da8d..b753d3a 100644 --- a/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java +++ b/src/main/java/cuchaz/enigma/translation/representation/entry/ParentedEntry.java | |||
| @@ -52,7 +52,7 @@ public abstract class ParentedEntry<P extends Entry<?>> implements Entry<P> { | |||
| 52 | } | 52 | } |
| 53 | 53 | ||
| 54 | @Override | 54 | @Override |
| 55 | public Translatable translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { | 55 | public ParentedEntry<P> translate(Translator translator, EntryResolver resolver, EntryMap<EntryMapping> mappings) { |
| 56 | P parent = getParent(); | 56 | P parent = getParent(); |
| 57 | EntryMapping mapping = resolveMapping(resolver, mappings); | 57 | EntryMapping mapping = resolveMapping(resolver, mappings); |
| 58 | if (parent == null) { | 58 | if (parent == null) { |
diff --git a/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java b/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java new file mode 100644 index 0000000..ae5d6d2 --- /dev/null +++ b/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java | |||
| @@ -0,0 +1,55 @@ | |||
| 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 | * | ||
| 8 | * Contributors: | ||
| 9 | * Jeff Martin - initial API and implementation | ||
| 10 | ******************************************************************************/ | ||
| 11 | |||
| 12 | package cuchaz.enigma; | ||
| 13 | |||
| 14 | import cuchaz.enigma.analysis.ParsedJar; | ||
| 15 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 16 | import cuchaz.enigma.analysis.index.PackageVisibilityIndex; | ||
| 17 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 18 | import org.junit.Test; | ||
| 19 | |||
| 20 | import java.util.jar.JarFile; | ||
| 21 | |||
| 22 | import static cuchaz.enigma.TestEntryFactory.newClass; | ||
| 23 | import static org.hamcrest.MatcherAssert.assertThat; | ||
| 24 | import static org.hamcrest.Matchers.contains; | ||
| 25 | import static org.hamcrest.Matchers.containsInAnyOrder; | ||
| 26 | |||
| 27 | public class PackageVisibilityIndexTest { | ||
| 28 | |||
| 29 | private static final ClassEntry KEEP = newClass("cuchaz/enigma/inputs/Keep"); | ||
| 30 | private static final ClassEntry BASE = newClass("a"); | ||
| 31 | private static final ClassEntry SAME_PACKAGE_CHILD = newClass("b"); | ||
| 32 | private static final ClassEntry SAME_PACKAGE_CHILD_INNER = newClass("b$a"); | ||
| 33 | private static final ClassEntry OTHER_PACKAGE_CHILD = newClass("c"); | ||
| 34 | private static final ClassEntry OTHER_PACKAGE_CHILD_INNER = newClass("c$a"); | ||
| 35 | private final JarIndex jarIndex; | ||
| 36 | |||
| 37 | public PackageVisibilityIndexTest() throws Exception { | ||
| 38 | jarIndex = JarIndex.empty(); | ||
| 39 | ParsedJar jar = new ParsedJar(new JarFile("build/test-obf/packageAccess.jar")); | ||
| 40 | jarIndex.indexJar(jar, s -> { | ||
| 41 | }); | ||
| 42 | } | ||
| 43 | |||
| 44 | @Test | ||
| 45 | public void test() { | ||
| 46 | PackageVisibilityIndex visibilityIndex = jarIndex.getPackageVisibilityIndex(); | ||
| 47 | assertThat(visibilityIndex.getPartition(BASE), containsInAnyOrder(BASE, SAME_PACKAGE_CHILD, SAME_PACKAGE_CHILD_INNER)); | ||
| 48 | System.out.println(visibilityIndex.getPartitions()); | ||
| 49 | assertThat(visibilityIndex.getPartitions(), containsInAnyOrder( | ||
| 50 | containsInAnyOrder(BASE, SAME_PACKAGE_CHILD, SAME_PACKAGE_CHILD_INNER), | ||
| 51 | containsInAnyOrder(OTHER_PACKAGE_CHILD, OTHER_PACKAGE_CHILD_INNER), | ||
| 52 | contains(KEEP) | ||
| 53 | )); | ||
| 54 | } | ||
| 55 | } | ||
diff --git a/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java b/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java new file mode 100644 index 0000000..f3b9f85 --- /dev/null +++ b/src/test/java/cuchaz/enigma/command/CheckMappingsCommandTest.java | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | package cuchaz.enigma.command; | ||
| 2 | |||
| 3 | import org.junit.Test; | ||
| 4 | |||
| 5 | import java.io.File; | ||
| 6 | |||
| 7 | public class CheckMappingsCommandTest { | ||
| 8 | |||
| 9 | @Test(expected = IllegalStateException.class) | ||
| 10 | public void testWrong() throws Exception { | ||
| 11 | new CheckMappingsCommand().run(new File("build/test-obf/packageAccess.jar").getAbsolutePath(), new File("src/test/resources" + | ||
| 12 | "/packageAccess/wrongMappings").getAbsolutePath()); | ||
| 13 | } | ||
| 14 | |||
| 15 | @Test | ||
| 16 | public void testRight() throws Exception { | ||
| 17 | new CheckMappingsCommand().run(new File("build/test-obf/packageAccess.jar").getAbsolutePath(), new File("src/test/resources" + | ||
| 18 | "/packageAccess/correctMappings").getAbsolutePath()); | ||
| 19 | } | ||
| 20 | } | ||
diff --git a/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java b/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java new file mode 100644 index 0000000..6f5fe30 --- /dev/null +++ b/src/test/java/cuchaz/enigma/inputs/packageAccess/Base.java | |||
| @@ -0,0 +1,7 @@ | |||
| 1 | package cuchaz.enigma.inputs.packageAccess; | ||
| 2 | |||
| 3 | public class Base { | ||
| 4 | protected int make() { | ||
| 5 | return 42; | ||
| 6 | } | ||
| 7 | } | ||
diff --git a/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java b/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java new file mode 100644 index 0000000..cf0f657 --- /dev/null +++ b/src/test/java/cuchaz/enigma/inputs/packageAccess/SamePackageChild.java | |||
| @@ -0,0 +1,12 @@ | |||
| 1 | package cuchaz.enigma.inputs.packageAccess; | ||
| 2 | |||
| 3 | public class SamePackageChild extends Base { | ||
| 4 | |||
| 5 | class Inner { | ||
| 6 | final int value; | ||
| 7 | |||
| 8 | Inner() { | ||
| 9 | value = SamePackageChild.this.make(); // no synthetic method | ||
| 10 | } | ||
| 11 | } | ||
| 12 | } | ||
diff --git a/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java b/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java new file mode 100644 index 0000000..19fb19c --- /dev/null +++ b/src/test/java/cuchaz/enigma/inputs/packageAccess/sub/OtherPackageChild.java | |||
| @@ -0,0 +1,14 @@ | |||
| 1 | package cuchaz.enigma.inputs.packageAccess.sub; | ||
| 2 | |||
| 3 | import cuchaz.enigma.inputs.packageAccess.Base; | ||
| 4 | |||
| 5 | public class OtherPackageChild extends Base { | ||
| 6 | |||
| 7 | class Inner { | ||
| 8 | final int value; | ||
| 9 | |||
| 10 | Inner() { | ||
| 11 | value = OtherPackageChild.this.make(); // synthetic method call | ||
| 12 | } | ||
| 13 | } | ||
| 14 | } | ||
diff --git a/src/test/resources/packageAccess/correctMappings/base/Base.mapping b/src/test/resources/packageAccess/correctMappings/base/Base.mapping new file mode 100644 index 0000000..0a86def --- /dev/null +++ b/src/test/resources/packageAccess/correctMappings/base/Base.mapping | |||
| @@ -0,0 +1 @@ | |||
| CLASS a base/Base | |||
diff --git a/src/test/resources/packageAccess/correctMappings/base/One.mapping b/src/test/resources/packageAccess/correctMappings/base/One.mapping new file mode 100644 index 0000000..dd4c208 --- /dev/null +++ b/src/test/resources/packageAccess/correctMappings/base/One.mapping | |||
| @@ -0,0 +1 @@ | |||
| CLASS b base/One | |||
diff --git a/src/test/resources/packageAccess/correctMappings/two/Two.mapping b/src/test/resources/packageAccess/correctMappings/two/Two.mapping new file mode 100644 index 0000000..a179349 --- /dev/null +++ b/src/test/resources/packageAccess/correctMappings/two/Two.mapping | |||
| @@ -0,0 +1 @@ | |||
| CLASS c two/Two | |||
diff --git a/src/test/resources/packageAccess/wrongMappings/base/Base.mapping b/src/test/resources/packageAccess/wrongMappings/base/Base.mapping new file mode 100644 index 0000000..0a86def --- /dev/null +++ b/src/test/resources/packageAccess/wrongMappings/base/Base.mapping | |||
| @@ -0,0 +1 @@ | |||
| CLASS a base/Base | |||
diff --git a/src/test/resources/packageAccess/wrongMappings/one/One.mapping b/src/test/resources/packageAccess/wrongMappings/one/One.mapping new file mode 100644 index 0000000..15b42cf --- /dev/null +++ b/src/test/resources/packageAccess/wrongMappings/one/One.mapping | |||
| @@ -0,0 +1 @@ | |||
| CLASS b one/One | |||
diff --git a/src/test/resources/packageAccess/wrongMappings/two/Two.mapping b/src/test/resources/packageAccess/wrongMappings/two/Two.mapping new file mode 100644 index 0000000..a179349 --- /dev/null +++ b/src/test/resources/packageAccess/wrongMappings/two/Two.mapping | |||
| @@ -0,0 +1 @@ | |||
| CLASS c two/Two | |||