diff options
Diffstat (limited to 'src/main/java/cuchaz/enigma/source')
29 files changed, 2626 insertions, 0 deletions
diff --git a/src/main/java/cuchaz/enigma/source/Decompiler.java b/src/main/java/cuchaz/enigma/source/Decompiler.java new file mode 100644 index 0000000..c9666d5 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/Decompiler.java | |||
| @@ -0,0 +1,5 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | public interface Decompiler { | ||
| 4 | Source getSource(String className); | ||
| 5 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/DecompilerService.java b/src/main/java/cuchaz/enigma/source/DecompilerService.java new file mode 100644 index 0000000..377ccbc --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/DecompilerService.java | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | import cuchaz.enigma.ClassProvider; | ||
| 4 | import cuchaz.enigma.api.service.EnigmaService; | ||
| 5 | import cuchaz.enigma.api.service.EnigmaServiceType; | ||
| 6 | |||
| 7 | public interface DecompilerService extends EnigmaService { | ||
| 8 | EnigmaServiceType<DecompilerService> TYPE = EnigmaServiceType.create("decompiler"); | ||
| 9 | |||
| 10 | Decompiler create(ClassProvider classProvider, SourceSettings settings); | ||
| 11 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/Decompilers.java b/src/main/java/cuchaz/enigma/source/Decompilers.java new file mode 100644 index 0000000..7d154a6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/Decompilers.java | |||
| @@ -0,0 +1,9 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | import cuchaz.enigma.source.cfr.CfrDecompiler; | ||
| 4 | import cuchaz.enigma.source.procyon.ProcyonDecompiler; | ||
| 5 | |||
| 6 | public class Decompilers { | ||
| 7 | public static final DecompilerService PROCYON = ProcyonDecompiler::new; | ||
| 8 | public static final DecompilerService CFR = CfrDecompiler::new; | ||
| 9 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/Source.java b/src/main/java/cuchaz/enigma/source/Source.java new file mode 100644 index 0000000..43c4de0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/Source.java | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 4 | |||
| 5 | public interface Source { | ||
| 6 | String asString(); | ||
| 7 | |||
| 8 | Source addJavadocs(EntryRemapper remapper); | ||
| 9 | |||
| 10 | SourceIndex index(); | ||
| 11 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/SourceIndex.java b/src/main/java/cuchaz/enigma/source/SourceIndex.java new file mode 100644 index 0000000..6a335ec --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/SourceIndex.java | |||
| @@ -0,0 +1,174 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 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.Multimap; | ||
| 7 | import cuchaz.enigma.analysis.EntryReference; | ||
| 8 | import cuchaz.enigma.analysis.Token; | ||
| 9 | import cuchaz.enigma.gui.SourceRemapper; | ||
| 10 | import cuchaz.enigma.translation.mapping.EntryResolver; | ||
| 11 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.Entry; | ||
| 13 | |||
| 14 | import java.util.Collection; | ||
| 15 | import java.util.List; | ||
| 16 | import java.util.Map; | ||
| 17 | import java.util.TreeMap; | ||
| 18 | import java.util.stream.Collectors; | ||
| 19 | |||
| 20 | public class SourceIndex { | ||
| 21 | private String source; | ||
| 22 | private List<Integer> lineOffsets; | ||
| 23 | private final TreeMap<Token, EntryReference<Entry<?>, Entry<?>>> tokenToReference; | ||
| 24 | private final Multimap<EntryReference<Entry<?>, Entry<?>>, Token> referenceToTokens; | ||
| 25 | private final Map<Entry<?>, Token> declarationToToken; | ||
| 26 | |||
| 27 | public SourceIndex() { | ||
| 28 | tokenToReference = new TreeMap<>(); | ||
| 29 | referenceToTokens = HashMultimap.create(); | ||
| 30 | declarationToToken = Maps.newHashMap(); | ||
| 31 | } | ||
| 32 | |||
| 33 | public SourceIndex(String source) { | ||
| 34 | this(); | ||
| 35 | setSource(source); | ||
| 36 | } | ||
| 37 | |||
| 38 | public void setSource(String source) { | ||
| 39 | this.source = source; | ||
| 40 | lineOffsets = Lists.newArrayList(); | ||
| 41 | lineOffsets.add(0); | ||
| 42 | |||
| 43 | for (int i = 0; i < this.source.length(); i++) { | ||
| 44 | if (this.source.charAt(i) == '\n') { | ||
| 45 | lineOffsets.add(i + 1); | ||
| 46 | } | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | public String getSource() { | ||
| 51 | return source; | ||
| 52 | } | ||
| 53 | |||
| 54 | public int getLineNumber(int position) { | ||
| 55 | int line = 0; | ||
| 56 | |||
| 57 | for (int offset : lineOffsets) { | ||
| 58 | if (offset > position) { | ||
| 59 | break; | ||
| 60 | } | ||
| 61 | |||
| 62 | line++; | ||
| 63 | } | ||
| 64 | |||
| 65 | return line; | ||
| 66 | } | ||
| 67 | |||
| 68 | public int getColumnNumber(int position) { | ||
| 69 | return position - lineOffsets.get(getLineNumber(position) - 1) + 1; | ||
| 70 | } | ||
| 71 | |||
| 72 | public int getPosition(int line, int column) { | ||
| 73 | return lineOffsets.get(line - 1) + column - 1; | ||
| 74 | } | ||
| 75 | |||
| 76 | public Iterable<Entry<?>> declarations() { | ||
| 77 | return declarationToToken.keySet(); | ||
| 78 | } | ||
| 79 | |||
| 80 | public Iterable<Token> declarationTokens() { | ||
| 81 | return declarationToToken.values(); | ||
| 82 | } | ||
| 83 | |||
| 84 | public Token getDeclarationToken(Entry<?> entry) { | ||
| 85 | return declarationToToken.get(entry); | ||
| 86 | } | ||
| 87 | |||
| 88 | public void addDeclaration(Token token, Entry<?> deobfEntry) { | ||
| 89 | if (token != null) { | ||
| 90 | EntryReference<Entry<?>, Entry<?>> reference = new EntryReference<>(deobfEntry, token.text); | ||
| 91 | tokenToReference.put(token, reference); | ||
| 92 | referenceToTokens.put(reference, token); | ||
| 93 | declarationToToken.put(deobfEntry, token); | ||
| 94 | } | ||
| 95 | } | ||
| 96 | |||
| 97 | public Iterable<EntryReference<Entry<?>, Entry<?>>> references() { | ||
| 98 | return referenceToTokens.keySet(); | ||
| 99 | } | ||
| 100 | |||
| 101 | public EntryReference<Entry<?>, Entry<?>> getReference(Token token) { | ||
| 102 | if (token == null) { | ||
| 103 | return null; | ||
| 104 | } | ||
| 105 | |||
| 106 | return tokenToReference.get(token); | ||
| 107 | } | ||
| 108 | |||
| 109 | public Iterable<Token> referenceTokens() { | ||
| 110 | return tokenToReference.keySet(); | ||
| 111 | } | ||
| 112 | |||
| 113 | public Token getReferenceToken(int pos) { | ||
| 114 | Token token = tokenToReference.floorKey(new Token(pos, pos, null)); | ||
| 115 | |||
| 116 | if (token != null && token.contains(pos)) { | ||
| 117 | return token; | ||
| 118 | } | ||
| 119 | |||
| 120 | return null; | ||
| 121 | } | ||
| 122 | |||
| 123 | public Collection<Token> getReferenceTokens(EntryReference<Entry<?>, Entry<?>> deobfReference) { | ||
| 124 | return referenceToTokens.get(deobfReference); | ||
| 125 | } | ||
| 126 | |||
| 127 | public void addReference(Token token, Entry<?> deobfEntry, Entry<?> deobfContext) { | ||
| 128 | if (token != null) { | ||
| 129 | EntryReference<Entry<?>, Entry<?>> deobfReference = new EntryReference<>(deobfEntry, token.text, deobfContext); | ||
| 130 | tokenToReference.put(token, deobfReference); | ||
| 131 | referenceToTokens.put(deobfReference, token); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | |||
| 135 | public void resolveReferences(EntryResolver resolver) { | ||
| 136 | // resolve all the classes in the source references | ||
| 137 | for (Token token : Lists.newArrayList(referenceToTokens.values())) { | ||
| 138 | EntryReference<Entry<?>, Entry<?>> reference = tokenToReference.get(token); | ||
| 139 | EntryReference<Entry<?>, Entry<?>> resolvedReference = resolver.resolveFirstReference(reference, ResolutionStrategy.RESOLVE_CLOSEST); | ||
| 140 | |||
| 141 | // replace the reference | ||
| 142 | tokenToReference.replace(token, resolvedReference); | ||
| 143 | |||
| 144 | Collection<Token> tokens = referenceToTokens.removeAll(reference); | ||
| 145 | referenceToTokens.putAll(resolvedReference, tokens); | ||
| 146 | } | ||
| 147 | } | ||
| 148 | |||
| 149 | public SourceIndex remapTo(SourceRemapper.Result result) { | ||
| 150 | SourceIndex remapped = new SourceIndex(result.getSource()); | ||
| 151 | |||
| 152 | for (Map.Entry<Entry<?>, Token> entry : declarationToToken.entrySet()) { | ||
| 153 | remapped.declarationToToken.put(entry.getKey(), result.getRemappedToken(entry.getValue())); | ||
| 154 | } | ||
| 155 | |||
| 156 | for (Map.Entry<EntryReference<Entry<?>, Entry<?>>, Collection<Token>> entry : referenceToTokens.asMap().entrySet()) { | ||
| 157 | EntryReference<Entry<?>, Entry<?>> reference = entry.getKey(); | ||
| 158 | Collection<Token> oldTokens = entry.getValue(); | ||
| 159 | |||
| 160 | Collection<Token> newTokens = oldTokens | ||
| 161 | .stream() | ||
| 162 | .map(result::getRemappedToken) | ||
| 163 | .collect(Collectors.toList()); | ||
| 164 | |||
| 165 | remapped.referenceToTokens.putAll(reference, newTokens); | ||
| 166 | } | ||
| 167 | |||
| 168 | for (Map.Entry<Token, EntryReference<Entry<?>, Entry<?>>> entry : tokenToReference.entrySet()) { | ||
| 169 | remapped.tokenToReference.put(result.getRemappedToken(entry.getKey()), entry.getValue()); | ||
| 170 | } | ||
| 171 | |||
| 172 | return remapped; | ||
| 173 | } | ||
| 174 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/SourceSettings.java b/src/main/java/cuchaz/enigma/source/SourceSettings.java new file mode 100644 index 0000000..f6c68e9 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/SourceSettings.java | |||
| @@ -0,0 +1,11 @@ | |||
| 1 | package cuchaz.enigma.source; | ||
| 2 | |||
| 3 | public class SourceSettings { | ||
| 4 | public final boolean removeImports; | ||
| 5 | public final boolean removeVariableFinal; | ||
| 6 | |||
| 7 | public SourceSettings(boolean removeImports, boolean removeVariableFinal) { | ||
| 8 | this.removeImports = removeImports; | ||
| 9 | this.removeVariableFinal = removeVariableFinal; | ||
| 10 | } | ||
| 11 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java b/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java new file mode 100644 index 0000000..9e37f16 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java | |||
| @@ -0,0 +1,108 @@ | |||
| 1 | package cuchaz.enigma.source.cfr; | ||
| 2 | |||
| 3 | import com.google.common.io.ByteStreams; | ||
| 4 | import cuchaz.enigma.ClassProvider; | ||
| 5 | import cuchaz.enigma.source.Decompiler; | ||
| 6 | import cuchaz.enigma.source.Source; | ||
| 7 | import cuchaz.enigma.source.SourceSettings; | ||
| 8 | import org.benf.cfr.reader.apiunreleased.ClassFileSource2; | ||
| 9 | import org.benf.cfr.reader.apiunreleased.JarContent; | ||
| 10 | import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; | ||
| 11 | import org.benf.cfr.reader.entities.ClassFile; | ||
| 12 | import org.benf.cfr.reader.mapping.MappingFactory; | ||
| 13 | import org.benf.cfr.reader.mapping.ObfuscationMapping; | ||
| 14 | import org.benf.cfr.reader.relationship.MemberNameResolver; | ||
| 15 | import org.benf.cfr.reader.state.DCCommonState; | ||
| 16 | import org.benf.cfr.reader.state.TypeUsageCollectingDumper; | ||
| 17 | import org.benf.cfr.reader.util.AnalysisType; | ||
| 18 | import org.benf.cfr.reader.util.CannotLoadClassException; | ||
| 19 | import org.benf.cfr.reader.util.collections.ListFactory; | ||
| 20 | import org.benf.cfr.reader.util.getopt.Options; | ||
| 21 | import org.benf.cfr.reader.util.getopt.OptionsImpl; | ||
| 22 | import org.objectweb.asm.ClassWriter; | ||
| 23 | import org.objectweb.asm.tree.ClassNode; | ||
| 24 | |||
| 25 | import java.io.IOException; | ||
| 26 | import java.io.InputStream; | ||
| 27 | import java.util.Collection; | ||
| 28 | import java.util.HashMap; | ||
| 29 | import java.util.Map; | ||
| 30 | |||
| 31 | |||
| 32 | public class CfrDecompiler implements Decompiler { | ||
| 33 | private final DCCommonState state; | ||
| 34 | |||
| 35 | public CfrDecompiler(ClassProvider classProvider, SourceSettings sourceSettings) { | ||
| 36 | Map<String, String> options = new HashMap<>(); | ||
| 37 | |||
| 38 | state = new DCCommonState(OptionsImpl.getFactory().create(options), new ClassFileSource2() { | ||
| 39 | @Override | ||
| 40 | public JarContent addJarContent(String s, AnalysisType analysisType) { | ||
| 41 | return null; | ||
| 42 | } | ||
| 43 | |||
| 44 | @Override | ||
| 45 | public void informAnalysisRelativePathDetail(String usePath, String classFilePath) { | ||
| 46 | |||
| 47 | } | ||
| 48 | |||
| 49 | @Override | ||
| 50 | public Collection<String> addJar(String jarPath) { | ||
| 51 | return null; | ||
| 52 | } | ||
| 53 | |||
| 54 | @Override | ||
| 55 | public String getPossiblyRenamedPath(String path) { | ||
| 56 | return path; | ||
| 57 | } | ||
| 58 | |||
| 59 | @Override | ||
| 60 | public Pair<byte[], String> getClassFileContent(String path) { | ||
| 61 | ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.'))); | ||
| 62 | |||
| 63 | if (node == null) { | ||
| 64 | try (InputStream classResource = CfrDecompiler.class.getClassLoader().getResourceAsStream(path)) { | ||
| 65 | if (classResource != null) { | ||
| 66 | return new Pair<>(ByteStreams.toByteArray(classResource), path); | ||
| 67 | } | ||
| 68 | } catch (IOException ignored) {} | ||
| 69 | |||
| 70 | return null; | ||
| 71 | } | ||
| 72 | |||
| 73 | ClassWriter cw = new ClassWriter(0); | ||
| 74 | node.accept(cw); | ||
| 75 | return new Pair<>(cw.toByteArray(), path); | ||
| 76 | } | ||
| 77 | }); | ||
| 78 | } | ||
| 79 | |||
| 80 | @Override | ||
| 81 | public Source getSource(String className) { | ||
| 82 | DCCommonState state = this.state; | ||
| 83 | Options options = state.getOptions(); | ||
| 84 | |||
| 85 | ObfuscationMapping mapping = MappingFactory.get(options, state); | ||
| 86 | state = new DCCommonState(state, mapping); | ||
| 87 | ClassFile tree = state.getClassFileMaybePath(className); | ||
| 88 | |||
| 89 | state.configureWith(tree); | ||
| 90 | |||
| 91 | // To make sure we're analysing the cached version | ||
| 92 | try { | ||
| 93 | tree = state.getClassFile(tree.getClassType()); | ||
| 94 | } catch (CannotLoadClassException ignored) {} | ||
| 95 | |||
| 96 | if (options.getOption(OptionsImpl.DECOMPILE_INNER_CLASSES)) { | ||
| 97 | tree.loadInnerClasses(state); | ||
| 98 | } | ||
| 99 | |||
| 100 | if (options.getOption(OptionsImpl.RENAME_DUP_MEMBERS)) { | ||
| 101 | MemberNameResolver.resolveNames(state, ListFactory.newList(state.getClassCache().getLoadedTypes())); | ||
| 102 | } | ||
| 103 | |||
| 104 | TypeUsageCollectingDumper typeUsageCollector = new TypeUsageCollectingDumper(options, tree); | ||
| 105 | tree.analyseTop(state, typeUsageCollector); | ||
| 106 | return new CfrSource(tree, state, typeUsageCollector.getRealTypeUsageInformation()); | ||
| 107 | } | ||
| 108 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java b/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java new file mode 100644 index 0000000..d4f2da6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/cfr/CfrSource.java | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | package cuchaz.enigma.source.cfr; | ||
| 2 | |||
| 3 | import cuchaz.enigma.source.Source; | ||
| 4 | import cuchaz.enigma.source.SourceIndex; | ||
| 5 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 6 | import org.benf.cfr.reader.entities.ClassFile; | ||
| 7 | import org.benf.cfr.reader.state.DCCommonState; | ||
| 8 | import org.benf.cfr.reader.state.TypeUsageInformation; | ||
| 9 | |||
| 10 | public class CfrSource implements Source { | ||
| 11 | private final ClassFile tree; | ||
| 12 | private final SourceIndex index; | ||
| 13 | private final String string; | ||
| 14 | |||
| 15 | public CfrSource(ClassFile tree, DCCommonState state, TypeUsageInformation typeUsages) { | ||
| 16 | this.tree = tree; | ||
| 17 | |||
| 18 | EnigmaDumper dumper = new EnigmaDumper(typeUsages); | ||
| 19 | tree.dump(state.getObfuscationMapping().wrap(dumper)); | ||
| 20 | index = dumper.getIndex(); | ||
| 21 | string = dumper.getString(); | ||
| 22 | } | ||
| 23 | |||
| 24 | @Override | ||
| 25 | public String asString() { | ||
| 26 | return string; | ||
| 27 | } | ||
| 28 | |||
| 29 | @Override | ||
| 30 | public Source addJavadocs(EntryRemapper remapper) { | ||
| 31 | return this; // TODO | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public SourceIndex index() { | ||
| 36 | return index; | ||
| 37 | } | ||
| 38 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java b/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java new file mode 100644 index 0000000..b9cdbea --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/cfr/EnigmaDumper.java | |||
| @@ -0,0 +1,427 @@ | |||
| 1 | package cuchaz.enigma.source.cfr; | ||
| 2 | |||
| 3 | import cuchaz.enigma.analysis.Token; | ||
| 4 | import cuchaz.enigma.source.SourceIndex; | ||
| 5 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 6 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 7 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 8 | import org.benf.cfr.reader.bytecode.analysis.types.*; | ||
| 9 | import org.benf.cfr.reader.bytecode.analysis.variables.NamedVariable; | ||
| 10 | import org.benf.cfr.reader.entities.Field; | ||
| 11 | import org.benf.cfr.reader.entities.Method; | ||
| 12 | import org.benf.cfr.reader.mapping.NullMapping; | ||
| 13 | import org.benf.cfr.reader.mapping.ObfuscationMapping; | ||
| 14 | import org.benf.cfr.reader.state.TypeUsageInformation; | ||
| 15 | import org.benf.cfr.reader.util.collections.SetFactory; | ||
| 16 | import org.benf.cfr.reader.util.output.DelegatingDumper; | ||
| 17 | import org.benf.cfr.reader.util.output.Dumpable; | ||
| 18 | import org.benf.cfr.reader.util.output.Dumper; | ||
| 19 | import org.benf.cfr.reader.util.output.TypeContext; | ||
| 20 | |||
| 21 | import java.util.Set; | ||
| 22 | import java.util.stream.Collectors; | ||
| 23 | |||
| 24 | public class EnigmaDumper implements Dumper { | ||
| 25 | private int outputCount = 0; | ||
| 26 | private int indent; | ||
| 27 | private boolean atStart = true; | ||
| 28 | private boolean pendingCR = false; | ||
| 29 | private final StringBuilder sb = new StringBuilder(); | ||
| 30 | private final TypeUsageInformation typeUsageInformation; | ||
| 31 | private final Set<JavaTypeInstance> emitted = SetFactory.newSet(); | ||
| 32 | private final SourceIndex index = new SourceIndex(); | ||
| 33 | private int position; | ||
| 34 | |||
| 35 | |||
| 36 | public EnigmaDumper(TypeUsageInformation typeUsageInformation) { | ||
| 37 | this.typeUsageInformation = typeUsageInformation; | ||
| 38 | } | ||
| 39 | |||
| 40 | private void append(String s) { | ||
| 41 | sb.append(s); | ||
| 42 | position += s.length(); | ||
| 43 | } | ||
| 44 | |||
| 45 | private String getDesc(JavaTypeInstance type) { | ||
| 46 | type = type.getDeGenerifiedType(); | ||
| 47 | |||
| 48 | if (type instanceof JavaRefTypeInstance) { | ||
| 49 | return "L" + type.getRawName().replace('.', '/') + ";"; | ||
| 50 | } | ||
| 51 | |||
| 52 | if (type instanceof JavaArrayTypeInstance) { | ||
| 53 | return "[" + getDesc(((JavaArrayTypeInstance) type).removeAnArrayIndirection()); | ||
| 54 | } | ||
| 55 | |||
| 56 | if (type instanceof RawJavaType) { | ||
| 57 | switch ((RawJavaType) type) { | ||
| 58 | case BOOLEAN: | ||
| 59 | return "Z"; | ||
| 60 | case BYTE: | ||
| 61 | return "B"; | ||
| 62 | case CHAR: | ||
| 63 | return "C"; | ||
| 64 | case SHORT: | ||
| 65 | return "S"; | ||
| 66 | case INT: | ||
| 67 | return "I"; | ||
| 68 | case LONG: | ||
| 69 | return "J"; | ||
| 70 | case FLOAT: | ||
| 71 | return "F"; | ||
| 72 | case DOUBLE: | ||
| 73 | return "D"; | ||
| 74 | case VOID: | ||
| 75 | return "V"; | ||
| 76 | default: | ||
| 77 | throw new AssertionError(); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | |||
| 81 | throw new AssertionError(); | ||
| 82 | } | ||
| 83 | |||
| 84 | private MethodEntry getMethodEntry(MethodPrototype method) { | ||
| 85 | if (method.getClassType() == null) { | ||
| 86 | return null; | ||
| 87 | } | ||
| 88 | |||
| 89 | MethodDescriptor desc = new MethodDescriptor( | ||
| 90 | method.getArgs().stream().map(type -> new TypeDescriptor(getDesc(type))).collect(Collectors.toList()), | ||
| 91 | new TypeDescriptor(method.getName().equals("<init>") || method.getName().equals("<clinit>") ? "V" : getDesc(method.getReturnType())) | ||
| 92 | ); | ||
| 93 | |||
| 94 | return new MethodEntry(getClassEntry(method.getClassType()), method.getName(), desc); | ||
| 95 | } | ||
| 96 | |||
| 97 | private LocalVariableEntry getParameterEntry(MethodPrototype method, int parameterIndex, String name) { | ||
| 98 | int variableIndex = method.isInstanceMethod() ? 1 : 0; | ||
| 99 | for (int i = 0; i < parameterIndex; i++) { | ||
| 100 | variableIndex += method.getArgs().get(parameterIndex).getStackType().getComputationCategory(); | ||
| 101 | } | ||
| 102 | |||
| 103 | return new LocalVariableEntry(getMethodEntry(method), variableIndex, name, true, null); | ||
| 104 | } | ||
| 105 | |||
| 106 | private FieldEntry getFieldEntry(JavaTypeInstance owner, String name, JavaTypeInstance type) { | ||
| 107 | return new FieldEntry(getClassEntry(owner), name, new TypeDescriptor(getDesc(type))); | ||
| 108 | } | ||
| 109 | |||
| 110 | private ClassEntry getClassEntry(JavaTypeInstance type) { | ||
| 111 | return new ClassEntry(type.getRawName().replace('.', '/')); | ||
| 112 | } | ||
| 113 | |||
| 114 | @Override | ||
| 115 | public Dumper beginBlockComment(boolean inline) { | ||
| 116 | print("/*").newln(); | ||
| 117 | return this; | ||
| 118 | } | ||
| 119 | |||
| 120 | @Override | ||
| 121 | public Dumper endBlockComment() { | ||
| 122 | print(" */").newln(); | ||
| 123 | return this; | ||
| 124 | } | ||
| 125 | |||
| 126 | @Override | ||
| 127 | public Dumper label(String s, boolean inline) { | ||
| 128 | processPendingCR(); | ||
| 129 | append(s); | ||
| 130 | append(":"); | ||
| 131 | return this; | ||
| 132 | } | ||
| 133 | |||
| 134 | @Override | ||
| 135 | public Dumper comment(String s) { | ||
| 136 | append("// "); | ||
| 137 | append(s); | ||
| 138 | append("\n"); | ||
| 139 | return this; | ||
| 140 | } | ||
| 141 | |||
| 142 | @Override | ||
| 143 | public void enqueuePendingCarriageReturn() { | ||
| 144 | pendingCR = true; | ||
| 145 | } | ||
| 146 | |||
| 147 | @Override | ||
| 148 | public Dumper removePendingCarriageReturn() { | ||
| 149 | pendingCR = false; | ||
| 150 | return this; | ||
| 151 | } | ||
| 152 | |||
| 153 | private void processPendingCR() { | ||
| 154 | if (pendingCR) { | ||
| 155 | append("\n"); | ||
| 156 | atStart = true; | ||
| 157 | pendingCR = false; | ||
| 158 | } | ||
| 159 | } | ||
| 160 | |||
| 161 | @Override | ||
| 162 | public Dumper identifier(String s, Object ref, boolean defines) { | ||
| 163 | return print(s); | ||
| 164 | } | ||
| 165 | |||
| 166 | @Override | ||
| 167 | public Dumper methodName(String name, MethodPrototype method, boolean special, boolean defines) { | ||
| 168 | doIndent(); | ||
| 169 | Token token = new Token(position, position + name.length(), name); | ||
| 170 | Entry<?> entry = getMethodEntry(method); | ||
| 171 | |||
| 172 | if (entry != null) { | ||
| 173 | if (defines) { | ||
| 174 | index.addDeclaration(token, entry); | ||
| 175 | } else { | ||
| 176 | index.addReference(token, entry, null); | ||
| 177 | } | ||
| 178 | } | ||
| 179 | |||
| 180 | return identifier(name, null, defines); | ||
| 181 | } | ||
| 182 | |||
| 183 | @Override | ||
| 184 | public Dumper parameterName(String name, MethodPrototype method, int index, boolean defines) { | ||
| 185 | doIndent(); | ||
| 186 | Token token = new Token(position, position + name.length(), name); | ||
| 187 | Entry<?> entry = getParameterEntry(method, index, name); | ||
| 188 | |||
| 189 | if (entry != null) { | ||
| 190 | if (defines) { | ||
| 191 | this.index.addDeclaration(token, entry); | ||
| 192 | } else { | ||
| 193 | this.index.addReference(token, entry, null); | ||
| 194 | } | ||
| 195 | } | ||
| 196 | |||
| 197 | return identifier(name, null, defines); | ||
| 198 | } | ||
| 199 | |||
| 200 | @Override | ||
| 201 | public Dumper variableName(String name, NamedVariable variable, boolean defines) { | ||
| 202 | return identifier(name, null, defines); | ||
| 203 | } | ||
| 204 | |||
| 205 | @Override | ||
| 206 | public Dumper packageName(JavaRefTypeInstance t) { | ||
| 207 | String s = t.getPackageName(); | ||
| 208 | |||
| 209 | if (!s.isEmpty()) { | ||
| 210 | keyword("package ").print(s).endCodeln().newln(); | ||
| 211 | } | ||
| 212 | |||
| 213 | return this; | ||
| 214 | } | ||
| 215 | |||
| 216 | @Override | ||
| 217 | public Dumper fieldName(String name, Field field, JavaTypeInstance owner, boolean hiddenDeclaration, boolean defines) { | ||
| 218 | doIndent(); | ||
| 219 | Token token = new Token(position, position + name.length(), name); | ||
| 220 | Entry<?> entry = field == null ? null : getFieldEntry(owner, name, field.getJavaTypeInstance()); | ||
| 221 | |||
| 222 | if (entry != null) { | ||
| 223 | if (defines) { | ||
| 224 | index.addDeclaration(token, entry); | ||
| 225 | } else { | ||
| 226 | index.addReference(token, entry, null); | ||
| 227 | } | ||
| 228 | } | ||
| 229 | |||
| 230 | identifier(name, null, defines); | ||
| 231 | return this; | ||
| 232 | } | ||
| 233 | |||
| 234 | @Override | ||
| 235 | public Dumper print(String s) { | ||
| 236 | processPendingCR(); | ||
| 237 | doIndent(); | ||
| 238 | append(s); | ||
| 239 | atStart = s.endsWith("\n"); | ||
| 240 | outputCount++; | ||
| 241 | return this; | ||
| 242 | } | ||
| 243 | |||
| 244 | @Override | ||
| 245 | public Dumper print(char c) { | ||
| 246 | return print(String.valueOf(c)); | ||
| 247 | } | ||
| 248 | |||
| 249 | @Override | ||
| 250 | public Dumper newln() { | ||
| 251 | append("\n"); | ||
| 252 | atStart = true; | ||
| 253 | outputCount++; | ||
| 254 | return this; | ||
| 255 | } | ||
| 256 | |||
| 257 | @Override | ||
| 258 | public Dumper endCodeln() { | ||
| 259 | append(";\n"); | ||
| 260 | atStart = true; | ||
| 261 | outputCount++; | ||
| 262 | return this; | ||
| 263 | } | ||
| 264 | |||
| 265 | @Override | ||
| 266 | public Dumper keyword(String s) { | ||
| 267 | print(s); | ||
| 268 | return this; | ||
| 269 | } | ||
| 270 | |||
| 271 | @Override | ||
| 272 | public Dumper operator(String s) { | ||
| 273 | print(s); | ||
| 274 | return this; | ||
| 275 | } | ||
| 276 | |||
| 277 | @Override | ||
| 278 | public Dumper separator(String s) { | ||
| 279 | print(s); | ||
| 280 | return this; | ||
| 281 | } | ||
| 282 | |||
| 283 | @Override | ||
| 284 | public Dumper literal(String s, Object o) { | ||
| 285 | print(s); | ||
| 286 | return this; | ||
| 287 | } | ||
| 288 | |||
| 289 | private void doIndent() { | ||
| 290 | if (!atStart) return; | ||
| 291 | String indents = " "; | ||
| 292 | |||
| 293 | for (int x = 0; x < indent; ++x) { | ||
| 294 | append(indents); | ||
| 295 | } | ||
| 296 | |||
| 297 | atStart = false; | ||
| 298 | } | ||
| 299 | |||
| 300 | @Override | ||
| 301 | public void indent(int diff) { | ||
| 302 | indent += diff; | ||
| 303 | } | ||
| 304 | |||
| 305 | @Override | ||
| 306 | public Dumper dump(Dumpable d) { | ||
| 307 | if (d == null) { | ||
| 308 | keyword("null"); | ||
| 309 | return this; | ||
| 310 | } | ||
| 311 | |||
| 312 | d.dump(this); | ||
| 313 | return this; | ||
| 314 | } | ||
| 315 | |||
| 316 | @Override | ||
| 317 | public TypeUsageInformation getTypeUsageInformation() { | ||
| 318 | return typeUsageInformation; | ||
| 319 | } | ||
| 320 | |||
| 321 | @Override | ||
| 322 | public ObfuscationMapping getObfuscationMapping() { | ||
| 323 | return NullMapping.INSTANCE; | ||
| 324 | } | ||
| 325 | |||
| 326 | @Override | ||
| 327 | public String toString() { | ||
| 328 | return sb.toString(); | ||
| 329 | } | ||
| 330 | |||
| 331 | @Override | ||
| 332 | public void addSummaryError(Method method, String s) {} | ||
| 333 | |||
| 334 | @Override | ||
| 335 | public void close() { | ||
| 336 | } | ||
| 337 | |||
| 338 | @Override | ||
| 339 | public boolean canEmitClass(JavaTypeInstance type) { | ||
| 340 | return emitted.add(type); | ||
| 341 | } | ||
| 342 | |||
| 343 | @Override | ||
| 344 | public int getOutputCount() { | ||
| 345 | return outputCount; | ||
| 346 | } | ||
| 347 | |||
| 348 | @Override | ||
| 349 | public Dumper dump(JavaTypeInstance type) { | ||
| 350 | return dump(type, TypeContext.None, false); | ||
| 351 | } | ||
| 352 | |||
| 353 | @Override | ||
| 354 | public Dumper dump(JavaTypeInstance type, boolean defines) { | ||
| 355 | return dump(type, TypeContext.None, false); | ||
| 356 | } | ||
| 357 | |||
| 358 | @Override | ||
| 359 | public Dumper dump(JavaTypeInstance type, TypeContext context) { | ||
| 360 | return dump(type, context, false); | ||
| 361 | } | ||
| 362 | |||
| 363 | private Dumper dump(JavaTypeInstance type, TypeContext context, boolean defines) { | ||
| 364 | doIndent(); | ||
| 365 | if (type instanceof JavaRefTypeInstance) { | ||
| 366 | int start = position; | ||
| 367 | type.dumpInto(this, typeUsageInformation, TypeContext.None); | ||
| 368 | int end = position; | ||
| 369 | Token token = new Token(start, end, sb.toString().substring(start, end)); | ||
| 370 | |||
| 371 | if (defines) { | ||
| 372 | index.addDeclaration(token, getClassEntry(type)); | ||
| 373 | } else { | ||
| 374 | index.addReference(token, getClassEntry(type), null); | ||
| 375 | } | ||
| 376 | |||
| 377 | return this; | ||
| 378 | } | ||
| 379 | |||
| 380 | type.dumpInto(this, typeUsageInformation, context); | ||
| 381 | return this; | ||
| 382 | } | ||
| 383 | |||
| 384 | @Override | ||
| 385 | public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { | ||
| 386 | return new WithTypeUsageInformationDumper(this, innerclassTypeUsageInformation); | ||
| 387 | } | ||
| 388 | |||
| 389 | public SourceIndex getIndex() { | ||
| 390 | index.setSource(getString()); | ||
| 391 | return index; | ||
| 392 | } | ||
| 393 | |||
| 394 | public String getString() { | ||
| 395 | return sb.toString(); | ||
| 396 | } | ||
| 397 | |||
| 398 | public static class WithTypeUsageInformationDumper extends DelegatingDumper { | ||
| 399 | private final TypeUsageInformation typeUsageInformation; | ||
| 400 | |||
| 401 | WithTypeUsageInformationDumper(Dumper delegate, TypeUsageInformation typeUsageInformation) { | ||
| 402 | super(delegate); | ||
| 403 | this.typeUsageInformation = typeUsageInformation; | ||
| 404 | } | ||
| 405 | |||
| 406 | @Override | ||
| 407 | public TypeUsageInformation getTypeUsageInformation() { | ||
| 408 | return typeUsageInformation; | ||
| 409 | } | ||
| 410 | |||
| 411 | @Override | ||
| 412 | public Dumper dump(JavaTypeInstance javaTypeInstance) { | ||
| 413 | return dump(javaTypeInstance, TypeContext.None); | ||
| 414 | } | ||
| 415 | |||
| 416 | @Override | ||
| 417 | public Dumper dump(JavaTypeInstance javaTypeInstance, TypeContext typeContext) { | ||
| 418 | javaTypeInstance.dumpInto(this, typeUsageInformation, typeContext); | ||
| 419 | return this; | ||
| 420 | } | ||
| 421 | |||
| 422 | @Override | ||
| 423 | public Dumper withTypeUsageInformation(TypeUsageInformation innerclassTypeUsageInformation) { | ||
| 424 | return new WithTypeUsageInformationDumper(delegate, innerclassTypeUsageInformation); | ||
| 425 | } | ||
| 426 | } | ||
| 427 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java b/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java new file mode 100644 index 0000000..2fae61a --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/EntryParser.java | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | package cuchaz.enigma.source.procyon; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 4 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 5 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 6 | import com.strobel.assembler.metadata.TypeReference; | ||
| 7 | import cuchaz.enigma.translation.representation.AccessFlags; | ||
| 8 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 9 | import cuchaz.enigma.translation.representation.Signature; | ||
| 10 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 11 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 13 | import cuchaz.enigma.translation.representation.entry.FieldDefEntry; | ||
| 14 | import cuchaz.enigma.translation.representation.entry.MethodDefEntry; | ||
| 15 | |||
| 16 | public class EntryParser { | ||
| 17 | public static FieldDefEntry parse(FieldDefinition definition) { | ||
| 18 | ClassEntry owner = parse(definition.getDeclaringType()); | ||
| 19 | TypeDescriptor descriptor = new TypeDescriptor(definition.getErasedSignature()); | ||
| 20 | Signature signature = Signature.createTypedSignature(definition.getSignature()); | ||
| 21 | AccessFlags access = new AccessFlags(definition.getModifiers()); | ||
| 22 | return new FieldDefEntry(owner, definition.getName(), descriptor, signature, access, null); | ||
| 23 | } | ||
| 24 | |||
| 25 | public static ClassDefEntry parse(TypeDefinition def) { | ||
| 26 | String name = def.getInternalName(); | ||
| 27 | Signature signature = Signature.createSignature(def.getSignature()); | ||
| 28 | AccessFlags access = new AccessFlags(def.getModifiers()); | ||
| 29 | ClassEntry superClass = def.getBaseType() != null ? parse(def.getBaseType()) : null; | ||
| 30 | ClassEntry[] interfaces = def.getExplicitInterfaces().stream().map(EntryParser::parse).toArray(ClassEntry[]::new); | ||
| 31 | return new ClassDefEntry(name, signature, access, superClass, interfaces); | ||
| 32 | } | ||
| 33 | |||
| 34 | public static ClassEntry parse(TypeReference typeReference) { | ||
| 35 | return new ClassEntry(typeReference.getInternalName()); | ||
| 36 | } | ||
| 37 | |||
| 38 | public static MethodDefEntry parse(MethodDefinition definition) { | ||
| 39 | ClassEntry classEntry = parse(definition.getDeclaringType()); | ||
| 40 | MethodDescriptor descriptor = new MethodDescriptor(definition.getErasedSignature()); | ||
| 41 | Signature signature = Signature.createSignature(definition.getSignature()); | ||
| 42 | AccessFlags access = new AccessFlags(definition.getModifiers()); | ||
| 43 | return new MethodDefEntry(classEntry, definition.getName(), descriptor, signature, access, null); | ||
| 44 | } | ||
| 45 | |||
| 46 | public static TypeDescriptor parseTypeDescriptor(TypeReference type) { | ||
| 47 | return new TypeDescriptor(type.getErasedSignature()); | ||
| 48 | } | ||
| 49 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java new file mode 100644 index 0000000..37bc0c8 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java | |||
| @@ -0,0 +1,81 @@ | |||
| 1 | package cuchaz.enigma.source.procyon; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 4 | import com.strobel.assembler.metadata.MetadataSystem; | ||
| 5 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 6 | import com.strobel.assembler.metadata.TypeReference; | ||
| 7 | import com.strobel.decompiler.DecompilerContext; | ||
| 8 | import com.strobel.decompiler.DecompilerSettings; | ||
| 9 | import com.strobel.decompiler.languages.java.BraceStyle; | ||
| 10 | import com.strobel.decompiler.languages.java.JavaFormattingOptions; | ||
| 11 | import com.strobel.decompiler.languages.java.ast.AstBuilder; | ||
| 12 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 13 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; | ||
| 14 | import cuchaz.enigma.ClassProvider; | ||
| 15 | import cuchaz.enigma.api.EnigmaPluginContext; | ||
| 16 | import cuchaz.enigma.source.Source; | ||
| 17 | import cuchaz.enigma.source.Decompiler; | ||
| 18 | import cuchaz.enigma.source.SourceSettings; | ||
| 19 | import cuchaz.enigma.source.procyon.transformers.*; | ||
| 20 | import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader; | ||
| 21 | import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem; | ||
| 22 | import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader; | ||
| 23 | import cuchaz.enigma.utils.Utils; | ||
| 24 | |||
| 25 | public class ProcyonDecompiler implements Decompiler { | ||
| 26 | private final SourceSettings settings; | ||
| 27 | private final DecompilerSettings decompilerSettings; | ||
| 28 | private final MetadataSystem metadataSystem; | ||
| 29 | |||
| 30 | public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) { | ||
| 31 | ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider)); | ||
| 32 | |||
| 33 | metadataSystem = new NoRetryMetadataSystem(typeLoader); | ||
| 34 | metadataSystem.setEagerMethodLoadingEnabled(true); | ||
| 35 | |||
| 36 | decompilerSettings = DecompilerSettings.javaDefaults(); | ||
| 37 | decompilerSettings.setMergeVariables(Utils.getSystemPropertyAsBoolean("enigma.mergeVariables", true)); | ||
| 38 | decompilerSettings.setForceExplicitImports(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitImports", true)); | ||
| 39 | decompilerSettings.setForceExplicitTypeArguments(Utils.getSystemPropertyAsBoolean("enigma.forceExplicitTypeArguments", true)); | ||
| 40 | decompilerSettings.setShowDebugLineNumbers(Utils.getSystemPropertyAsBoolean("enigma.showDebugLineNumbers", false)); | ||
| 41 | decompilerSettings.setShowSyntheticMembers(Utils.getSystemPropertyAsBoolean("enigma.showSyntheticMembers", false)); | ||
| 42 | decompilerSettings.setTypeLoader(typeLoader); | ||
| 43 | |||
| 44 | JavaFormattingOptions formattingOptions = decompilerSettings.getJavaFormattingOptions(); | ||
| 45 | formattingOptions.ClassBraceStyle = BraceStyle.EndOfLine; | ||
| 46 | formattingOptions.InterfaceBraceStyle = BraceStyle.EndOfLine; | ||
| 47 | formattingOptions.EnumBraceStyle = BraceStyle.EndOfLine; | ||
| 48 | |||
| 49 | this.settings = settings; | ||
| 50 | } | ||
| 51 | |||
| 52 | @Override | ||
| 53 | public Source getSource(String className) { | ||
| 54 | TypeReference type = metadataSystem.lookupType(className); | ||
| 55 | if (type == null) { | ||
| 56 | throw new Error(String.format("Unable to find desc: %s", className)); | ||
| 57 | } | ||
| 58 | |||
| 59 | TypeDefinition resolvedType = type.resolve(); | ||
| 60 | |||
| 61 | DecompilerContext context = new DecompilerContext(); | ||
| 62 | context.setCurrentType(resolvedType); | ||
| 63 | context.setSettings(decompilerSettings); | ||
| 64 | |||
| 65 | AstBuilder builder = new AstBuilder(context); | ||
| 66 | builder.addType(resolvedType); | ||
| 67 | builder.runTransformations(null); | ||
| 68 | CompilationUnit source = builder.getCompilationUnit(); | ||
| 69 | |||
| 70 | new ObfuscatedEnumSwitchRewriterTransform(context).run(source); | ||
| 71 | new VarargsFixer(context).run(source); | ||
| 72 | new RemoveObjectCasts(context).run(source); | ||
| 73 | new Java8Generics().run(source); | ||
| 74 | new InvalidIdentifierFix().run(source); | ||
| 75 | if (settings.removeImports) DropImportAstTransform.INSTANCE.run(source); | ||
| 76 | if (settings.removeVariableFinal) DropVarModifiersAstTransform.INSTANCE.run(source); | ||
| 77 | source.acceptVisitor(new InsertParenthesesVisitor(), null); | ||
| 78 | |||
| 79 | return new ProcyonSource(source, decompilerSettings); | ||
| 80 | } | ||
| 81 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java b/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java new file mode 100644 index 0000000..53c8c70 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/ProcyonSource.java | |||
| @@ -0,0 +1,49 @@ | |||
| 1 | package cuchaz.enigma.source.procyon; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.DecompilerSettings; | ||
| 4 | import com.strobel.decompiler.PlainTextOutput; | ||
| 5 | import com.strobel.decompiler.languages.java.JavaOutputVisitor; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | ||
| 7 | import cuchaz.enigma.source.Source; | ||
| 8 | import cuchaz.enigma.source.SourceIndex; | ||
| 9 | import cuchaz.enigma.source.procyon.index.SourceIndexVisitor; | ||
| 10 | import cuchaz.enigma.source.procyon.transformers.AddJavadocsAstTransform; | ||
| 11 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 12 | |||
| 13 | import java.io.StringWriter; | ||
| 14 | |||
| 15 | public class ProcyonSource implements Source { | ||
| 16 | private final DecompilerSettings settings; | ||
| 17 | private final CompilationUnit tree; | ||
| 18 | private String string; | ||
| 19 | |||
| 20 | public ProcyonSource(CompilationUnit tree, DecompilerSettings settings) { | ||
| 21 | this.settings = settings; | ||
| 22 | this.tree = tree; | ||
| 23 | } | ||
| 24 | |||
| 25 | @Override | ||
| 26 | public SourceIndex index() { | ||
| 27 | SourceIndex index = new SourceIndex(asString()); | ||
| 28 | tree.acceptVisitor(new SourceIndexVisitor(), index); | ||
| 29 | return index; | ||
| 30 | } | ||
| 31 | |||
| 32 | @Override | ||
| 33 | public String asString() { | ||
| 34 | if (string == null) { | ||
| 35 | StringWriter writer = new StringWriter(); | ||
| 36 | tree.acceptVisitor(new JavaOutputVisitor(new PlainTextOutput(writer), settings), null); | ||
| 37 | string = writer.toString(); | ||
| 38 | } | ||
| 39 | |||
| 40 | return string; | ||
| 41 | } | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public Source addJavadocs(EntryRemapper remapper) { | ||
| 45 | CompilationUnit remappedTree = (CompilationUnit) tree.clone(); | ||
| 46 | new AddJavadocsAstTransform(remapper).run(remappedTree); | ||
| 47 | return new ProcyonSource(remappedTree, settings); | ||
| 48 | } | ||
| 49 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java new file mode 100644 index 0000000..f6eeb15 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexClassVisitor.java | |||
| @@ -0,0 +1,95 @@ | |||
| 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 | |||
| 12 | package cuchaz.enigma.source.procyon.index; | ||
| 13 | |||
| 14 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 15 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 16 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 17 | import com.strobel.assembler.metadata.TypeReference; | ||
| 18 | import com.strobel.decompiler.languages.TextLocation; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.*; | ||
| 20 | import cuchaz.enigma.source.SourceIndex; | ||
| 21 | import cuchaz.enigma.source.procyon.EntryParser; | ||
| 22 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 23 | |||
| 24 | public class SourceIndexClassVisitor extends SourceIndexVisitor { | ||
| 25 | private ClassDefEntry classEntry; | ||
| 26 | |||
| 27 | public SourceIndexClassVisitor(ClassDefEntry classEntry) { | ||
| 28 | this.classEntry = classEntry; | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { | ||
| 33 | // is this this class, or a subtype? | ||
| 34 | TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); | ||
| 35 | ClassDefEntry classEntry = EntryParser.parse(def); | ||
| 36 | if (!classEntry.equals(this.classEntry)) { | ||
| 37 | // it's a subtype, recurse | ||
| 38 | index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); | ||
| 39 | return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); | ||
| 40 | } | ||
| 41 | |||
| 42 | return visitChildren(node, index); | ||
| 43 | } | ||
| 44 | |||
| 45 | @Override | ||
| 46 | public Void visitSimpleType(SimpleType node, SourceIndex index) { | ||
| 47 | TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); | ||
| 48 | if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { | ||
| 49 | ClassEntry classEntry = new ClassEntry(ref.getInternalName()); | ||
| 50 | index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.classEntry); | ||
| 51 | } | ||
| 52 | |||
| 53 | return visitChildren(node, index); | ||
| 54 | } | ||
| 55 | |||
| 56 | @Override | ||
| 57 | public Void visitMethodDeclaration(MethodDeclaration node, SourceIndex index) { | ||
| 58 | MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); | ||
| 59 | MethodDefEntry methodEntry = EntryParser.parse(def); | ||
| 60 | AstNode tokenNode = node.getNameToken(); | ||
| 61 | if (methodEntry.isConstructor() && methodEntry.getName().equals("<clinit>")) { | ||
| 62 | // for static initializers, check elsewhere for the token node | ||
| 63 | tokenNode = node.getModifiers().firstOrNullObject(); | ||
| 64 | } | ||
| 65 | index.addDeclaration(TokenFactory.createToken(index, tokenNode), methodEntry); | ||
| 66 | return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); | ||
| 67 | } | ||
| 68 | |||
| 69 | @Override | ||
| 70 | public Void visitConstructorDeclaration(ConstructorDeclaration node, SourceIndex index) { | ||
| 71 | MethodDefinition def = node.getUserData(Keys.METHOD_DEFINITION); | ||
| 72 | MethodDefEntry methodEntry = EntryParser.parse(def); | ||
| 73 | index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), methodEntry); | ||
| 74 | return node.acceptVisitor(new SourceIndexMethodVisitor(methodEntry), index); | ||
| 75 | } | ||
| 76 | |||
| 77 | @Override | ||
| 78 | public Void visitFieldDeclaration(FieldDeclaration node, SourceIndex index) { | ||
| 79 | FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); | ||
| 80 | FieldDefEntry fieldEntry = EntryParser.parse(def); | ||
| 81 | assert (node.getVariables().size() == 1); | ||
| 82 | VariableInitializer variable = node.getVariables().firstOrNullObject(); | ||
| 83 | index.addDeclaration(TokenFactory.createToken(index, variable.getNameToken()), fieldEntry); | ||
| 84 | return visitChildren(node, index); | ||
| 85 | } | ||
| 86 | |||
| 87 | @Override | ||
| 88 | public Void visitEnumValueDeclaration(EnumValueDeclaration node, SourceIndex index) { | ||
| 89 | // treat enum declarations as field declarations | ||
| 90 | FieldDefinition def = node.getUserData(Keys.FIELD_DEFINITION); | ||
| 91 | FieldDefEntry fieldEntry = EntryParser.parse(def); | ||
| 92 | index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), fieldEntry); | ||
| 93 | return visitChildren(node, index); | ||
| 94 | } | ||
| 95 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java new file mode 100644 index 0000000..0e8bc51 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexMethodVisitor.java | |||
| @@ -0,0 +1,218 @@ | |||
| 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 | |||
| 12 | package cuchaz.enigma.source.procyon.index; | ||
| 13 | |||
| 14 | import com.google.common.collect.HashMultimap; | ||
| 15 | import com.google.common.collect.Multimap; | ||
| 16 | import com.strobel.assembler.metadata.*; | ||
| 17 | import com.strobel.decompiler.ast.Variable; | ||
| 18 | import com.strobel.decompiler.languages.TextLocation; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.*; | ||
| 20 | import cuchaz.enigma.source.SourceIndex; | ||
| 21 | import cuchaz.enigma.source.procyon.EntryParser; | ||
| 22 | import cuchaz.enigma.translation.representation.MethodDescriptor; | ||
| 23 | import cuchaz.enigma.translation.representation.TypeDescriptor; | ||
| 24 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 25 | |||
| 26 | import java.lang.Error; | ||
| 27 | import java.util.HashMap; | ||
| 28 | import java.util.Map; | ||
| 29 | |||
| 30 | public class SourceIndexMethodVisitor extends SourceIndexVisitor { | ||
| 31 | private final MethodDefEntry methodEntry; | ||
| 32 | |||
| 33 | private Multimap<String, Identifier> unmatchedIdentifier = HashMultimap.create(); | ||
| 34 | private Map<String, Entry<?>> identifierEntryCache = new HashMap<>(); | ||
| 35 | |||
| 36 | public SourceIndexMethodVisitor(MethodDefEntry methodEntry) { | ||
| 37 | this.methodEntry = methodEntry; | ||
| 38 | } | ||
| 39 | |||
| 40 | @Override | ||
| 41 | public Void visitInvocationExpression(InvocationExpression node, SourceIndex index) { | ||
| 42 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 43 | |||
| 44 | // get the behavior entry | ||
| 45 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 46 | MethodEntry methodEntry = null; | ||
| 47 | if (ref instanceof MethodReference) { | ||
| 48 | methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); | ||
| 49 | } | ||
| 50 | if (methodEntry != null) { | ||
| 51 | // get the node for the token | ||
| 52 | AstNode tokenNode = null; | ||
| 53 | if (node.getTarget() instanceof MemberReferenceExpression) { | ||
| 54 | tokenNode = ((MemberReferenceExpression) node.getTarget()).getMemberNameToken(); | ||
| 55 | } else if (node.getTarget() instanceof SuperReferenceExpression) { | ||
| 56 | tokenNode = node.getTarget(); | ||
| 57 | } else if (node.getTarget() instanceof ThisReferenceExpression) { | ||
| 58 | tokenNode = node.getTarget(); | ||
| 59 | } | ||
| 60 | if (tokenNode != null) { | ||
| 61 | index.addReference(TokenFactory.createToken(index, tokenNode), methodEntry, this.methodEntry); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | |||
| 65 | // Check for identifier | ||
| 66 | node.getArguments().stream().filter(expression -> expression instanceof IdentifierExpression) | ||
| 67 | .forEach(expression -> this.checkIdentifier((IdentifierExpression) expression, index)); | ||
| 68 | return visitChildren(node, index); | ||
| 69 | } | ||
| 70 | |||
| 71 | @Override | ||
| 72 | public Void visitMemberReferenceExpression(MemberReferenceExpression node, SourceIndex index) { | ||
| 73 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 74 | if (ref instanceof FieldReference) { | ||
| 75 | // make sure this is actually a field | ||
| 76 | String erasedSignature = ref.getErasedSignature(); | ||
| 77 | if (erasedSignature.indexOf('(') >= 0) { | ||
| 78 | throw new Error("Expected a field here! got " + ref); | ||
| 79 | } | ||
| 80 | |||
| 81 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 82 | FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(erasedSignature)); | ||
| 83 | index.addReference(TokenFactory.createToken(index, node.getMemberNameToken()), fieldEntry, this.methodEntry); | ||
| 84 | } | ||
| 85 | |||
| 86 | return visitChildren(node, index); | ||
| 87 | } | ||
| 88 | |||
| 89 | @Override | ||
| 90 | public Void visitSimpleType(SimpleType node, SourceIndex index) { | ||
| 91 | TypeReference ref = node.getUserData(Keys.TYPE_REFERENCE); | ||
| 92 | if (node.getIdentifierToken().getStartLocation() != TextLocation.EMPTY) { | ||
| 93 | ClassEntry classEntry = new ClassEntry(ref.getInternalName()); | ||
| 94 | index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), classEntry, this.methodEntry); | ||
| 95 | } | ||
| 96 | |||
| 97 | return visitChildren(node, index); | ||
| 98 | } | ||
| 99 | |||
| 100 | @Override | ||
| 101 | public Void visitParameterDeclaration(ParameterDeclaration node, SourceIndex index) { | ||
| 102 | ParameterDefinition def = node.getUserData(Keys.PARAMETER_DEFINITION); | ||
| 103 | int parameterIndex = def.getSlot(); | ||
| 104 | |||
| 105 | if (parameterIndex >= 0) { | ||
| 106 | MethodDefEntry ownerMethod = methodEntry; | ||
| 107 | if (def.getMethod() instanceof MethodDefinition) { | ||
| 108 | ownerMethod = EntryParser.parse((MethodDefinition) def.getMethod()); | ||
| 109 | } | ||
| 110 | |||
| 111 | TypeDescriptor parameterType = EntryParser.parseTypeDescriptor(def.getParameterType()); | ||
| 112 | LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, parameterIndex, node.getName(), true, parameterType, null); | ||
| 113 | Identifier identifier = node.getNameToken(); | ||
| 114 | // cache the argument entry and the identifier | ||
| 115 | identifierEntryCache.put(identifier.getName(), localVariableEntry); | ||
| 116 | index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); | ||
| 117 | } | ||
| 118 | |||
| 119 | return visitChildren(node, index); | ||
| 120 | } | ||
| 121 | |||
| 122 | @Override | ||
| 123 | public Void visitIdentifierExpression(IdentifierExpression node, SourceIndex index) { | ||
| 124 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 125 | if (ref != null) { | ||
| 126 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 127 | FieldEntry fieldEntry = new FieldEntry(classEntry, ref.getName(), new TypeDescriptor(ref.getErasedSignature())); | ||
| 128 | index.addReference(TokenFactory.createToken(index, node.getIdentifierToken()), fieldEntry, this.methodEntry); | ||
| 129 | } else | ||
| 130 | this.checkIdentifier(node, index); | ||
| 131 | return visitChildren(node, index); | ||
| 132 | } | ||
| 133 | |||
| 134 | private void checkIdentifier(IdentifierExpression node, SourceIndex index) { | ||
| 135 | if (identifierEntryCache.containsKey(node.getIdentifier())) // If it's in the argument cache, create a token! | ||
| 136 | index.addDeclaration(TokenFactory.createToken(index, node.getIdentifierToken()), identifierEntryCache.get(node.getIdentifier())); | ||
| 137 | else | ||
| 138 | unmatchedIdentifier.put(node.getIdentifier(), node.getIdentifierToken()); // Not matched actually, put it! | ||
| 139 | } | ||
| 140 | |||
| 141 | private void addDeclarationToUnmatched(String key, SourceIndex index) { | ||
| 142 | Entry<?> entry = identifierEntryCache.get(key); | ||
| 143 | |||
| 144 | // This cannot happened in theory | ||
| 145 | if (entry == null) | ||
| 146 | return; | ||
| 147 | for (Identifier identifier : unmatchedIdentifier.get(key)) | ||
| 148 | index.addDeclaration(TokenFactory.createToken(index, identifier), entry); | ||
| 149 | unmatchedIdentifier.removeAll(key); | ||
| 150 | } | ||
| 151 | |||
| 152 | @Override | ||
| 153 | public Void visitObjectCreationExpression(ObjectCreationExpression node, SourceIndex index) { | ||
| 154 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 155 | if (ref != null && node.getType() instanceof SimpleType) { | ||
| 156 | SimpleType simpleTypeNode = (SimpleType) node.getType(); | ||
| 157 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 158 | MethodEntry constructorEntry = new MethodEntry(classEntry, "<init>", new MethodDescriptor(ref.getErasedSignature())); | ||
| 159 | index.addReference(TokenFactory.createToken(index, simpleTypeNode.getIdentifierToken()), constructorEntry, this.methodEntry); | ||
| 160 | } | ||
| 161 | |||
| 162 | return visitChildren(node, index); | ||
| 163 | } | ||
| 164 | |||
| 165 | @Override | ||
| 166 | public Void visitVariableDeclaration(VariableDeclarationStatement node, SourceIndex index) { | ||
| 167 | AstNodeCollection<VariableInitializer> variables = node.getVariables(); | ||
| 168 | |||
| 169 | // Single assignation | ||
| 170 | if (variables.size() == 1) { | ||
| 171 | VariableInitializer initializer = variables.firstOrNullObject(); | ||
| 172 | if (initializer != null && node.getType() instanceof SimpleType) { | ||
| 173 | Identifier identifier = initializer.getNameToken(); | ||
| 174 | Variable variable = initializer.getUserData(Keys.VARIABLE); | ||
| 175 | if (variable != null) { | ||
| 176 | VariableDefinition originalVariable = variable.getOriginalVariable(); | ||
| 177 | if (originalVariable != null) { | ||
| 178 | int variableIndex = originalVariable.getSlot(); | ||
| 179 | if (variableIndex >= 0) { | ||
| 180 | MethodDefEntry ownerMethod = EntryParser.parse(originalVariable.getDeclaringMethod()); | ||
| 181 | TypeDescriptor variableType = EntryParser.parseTypeDescriptor(originalVariable.getVariableType()); | ||
| 182 | LocalVariableDefEntry localVariableEntry = new LocalVariableDefEntry(ownerMethod, variableIndex, initializer.getName(), false, variableType, null); | ||
| 183 | identifierEntryCache.put(identifier.getName(), localVariableEntry); | ||
| 184 | addDeclarationToUnmatched(identifier.getName(), index); | ||
| 185 | index.addDeclaration(TokenFactory.createToken(index, identifier), localVariableEntry); | ||
| 186 | } | ||
| 187 | } | ||
| 188 | } | ||
| 189 | } | ||
| 190 | } | ||
| 191 | return visitChildren(node, index); | ||
| 192 | } | ||
| 193 | |||
| 194 | @Override | ||
| 195 | public Void visitMethodGroupExpression(MethodGroupExpression node, SourceIndex index) { | ||
| 196 | MemberReference ref = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 197 | |||
| 198 | if (ref instanceof MethodReference) { | ||
| 199 | // get the behavior entry | ||
| 200 | ClassEntry classEntry = new ClassEntry(ref.getDeclaringType().getInternalName()); | ||
| 201 | MethodEntry methodEntry = new MethodEntry(classEntry, ref.getName(), new MethodDescriptor(ref.getErasedSignature())); | ||
| 202 | |||
| 203 | // get the node for the token | ||
| 204 | AstNode methodNameToken = node.getMethodNameToken(); | ||
| 205 | AstNode targetToken = node.getTarget(); | ||
| 206 | |||
| 207 | if (methodNameToken != null) { | ||
| 208 | index.addReference(TokenFactory.createToken(index, methodNameToken), methodEntry, this.methodEntry); | ||
| 209 | } | ||
| 210 | |||
| 211 | if (targetToken != null && !(targetToken instanceof ThisReferenceExpression)) { | ||
| 212 | index.addReference(TokenFactory.createToken(index, targetToken), methodEntry.getParent(), this.methodEntry); | ||
| 213 | } | ||
| 214 | } | ||
| 215 | |||
| 216 | return visitChildren(node, index); | ||
| 217 | } | ||
| 218 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java new file mode 100644 index 0000000..dad505f --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/SourceIndexVisitor.java | |||
| @@ -0,0 +1,40 @@ | |||
| 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 | |||
| 12 | package cuchaz.enigma.source.procyon.index; | ||
| 13 | |||
| 14 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 15 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 16 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 17 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 19 | import cuchaz.enigma.source.SourceIndex; | ||
| 20 | import cuchaz.enigma.source.procyon.EntryParser; | ||
| 21 | import cuchaz.enigma.translation.representation.entry.ClassDefEntry; | ||
| 22 | |||
| 23 | public class SourceIndexVisitor extends DepthFirstAstVisitor<SourceIndex, Void> { | ||
| 24 | @Override | ||
| 25 | public Void visitTypeDeclaration(TypeDeclaration node, SourceIndex index) { | ||
| 26 | TypeDefinition def = node.getUserData(Keys.TYPE_DEFINITION); | ||
| 27 | ClassDefEntry classEntry = EntryParser.parse(def); | ||
| 28 | index.addDeclaration(TokenFactory.createToken(index, node.getNameToken()), classEntry); | ||
| 29 | |||
| 30 | return node.acceptVisitor(new SourceIndexClassVisitor(classEntry), index); | ||
| 31 | } | ||
| 32 | |||
| 33 | @Override | ||
| 34 | protected Void visitChildren(AstNode node, SourceIndex index) { | ||
| 35 | for (final AstNode child : node.getChildren()) { | ||
| 36 | child.acceptVisitor(this, index); | ||
| 37 | } | ||
| 38 | return null; | ||
| 39 | } | ||
| 40 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java b/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java new file mode 100644 index 0000000..db90ffa --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/index/TokenFactory.java | |||
| @@ -0,0 +1,41 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.index; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.languages.Region; | ||
| 4 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 5 | import com.strobel.decompiler.languages.java.ast.ConstructorDeclaration; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 7 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 8 | import cuchaz.enigma.analysis.Token; | ||
| 9 | import cuchaz.enigma.source.SourceIndex; | ||
| 10 | |||
| 11 | import java.util.regex.Pattern; | ||
| 12 | |||
| 13 | public class TokenFactory { | ||
| 14 | private static final Pattern ANONYMOUS_INNER = Pattern.compile("\\$\\d+$"); | ||
| 15 | |||
| 16 | public static Token createToken(SourceIndex index, AstNode node) { | ||
| 17 | String name = node instanceof Identifier ? ((Identifier) node).getName() : ""; | ||
| 18 | Region region = node.getRegion(); | ||
| 19 | |||
| 20 | int start = index.getPosition(region.getBeginLine(), region.getBeginColumn()); | ||
| 21 | int end = index.getPosition(region.getEndLine(), region.getEndColumn()); | ||
| 22 | String text = index.getSource().substring(start, end); | ||
| 23 | Token token = new Token(start, end, text); | ||
| 24 | |||
| 25 | boolean isAnonymousInner = | ||
| 26 | node instanceof Identifier && | ||
| 27 | name.indexOf('$') >= 0 && node.getParent() instanceof ConstructorDeclaration && | ||
| 28 | name.lastIndexOf('$') >= 0 && | ||
| 29 | !ANONYMOUS_INNER.matcher(name).matches(); | ||
| 30 | |||
| 31 | if (isAnonymousInner) { | ||
| 32 | TypeDeclaration type = node.getParent().getParent() instanceof TypeDeclaration ? (TypeDeclaration) node.getParent().getParent() : null; | ||
| 33 | if (type != null) { | ||
| 34 | name = type.getName(); | ||
| 35 | token.end = token.start + name.length(); | ||
| 36 | } | ||
| 37 | } | ||
| 38 | |||
| 39 | return token; | ||
| 40 | } | ||
| 41 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java new file mode 100644 index 0000000..70fc8c6 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/AddJavadocsAstTransform.java | |||
| @@ -0,0 +1,134 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.google.common.base.Function; | ||
| 4 | import com.google.common.base.Strings; | ||
| 5 | import com.strobel.assembler.metadata.ParameterDefinition; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.*; | ||
| 7 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 8 | import cuchaz.enigma.source.procyon.EntryParser; | ||
| 9 | import cuchaz.enigma.translation.mapping.EntryMapping; | ||
| 10 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 11 | import cuchaz.enigma.translation.mapping.ResolutionStrategy; | ||
| 12 | import cuchaz.enigma.translation.representation.entry.*; | ||
| 13 | |||
| 14 | import java.util.ArrayList; | ||
| 15 | import java.util.Collections; | ||
| 16 | import java.util.List; | ||
| 17 | import java.util.Objects; | ||
| 18 | import java.util.stream.Stream; | ||
| 19 | |||
| 20 | public final class AddJavadocsAstTransform implements IAstTransform { | ||
| 21 | |||
| 22 | private final EntryRemapper remapper; | ||
| 23 | |||
| 24 | public AddJavadocsAstTransform(EntryRemapper remapper) { | ||
| 25 | this.remapper = remapper; | ||
| 26 | } | ||
| 27 | |||
| 28 | @Override | ||
| 29 | public void run(AstNode compilationUnit) { | ||
| 30 | compilationUnit.acceptVisitor(new Visitor(remapper), null); | ||
| 31 | } | ||
| 32 | |||
| 33 | static class Visitor extends DepthFirstAstVisitor<Void, Void> { | ||
| 34 | |||
| 35 | private final EntryRemapper remapper; | ||
| 36 | |||
| 37 | Visitor(EntryRemapper remapper) { | ||
| 38 | this.remapper = remapper; | ||
| 39 | } | ||
| 40 | |||
| 41 | private <T extends AstNode> void addDoc(T node, Function<T, Entry<?>> retriever) { | ||
| 42 | final Comment[] comments = getComments(node, retriever); | ||
| 43 | if (comments != null) { | ||
| 44 | node.insertChildrenBefore(node.getFirstChild(), Roles.COMMENT, comments); | ||
| 45 | } | ||
| 46 | } | ||
| 47 | |||
| 48 | private <T extends AstNode> Comment[] getComments(T node, Function<T, Entry<?>> retriever) { | ||
| 49 | final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); | ||
| 50 | final String docs = mapping == null ? null : Strings.emptyToNull(mapping.getJavadoc()); | ||
| 51 | return docs == null ? null : Stream.of(docs.split("\\R")).map(st -> new Comment(st, | ||
| 52 | CommentType.Documentation)).toArray(Comment[]::new); | ||
| 53 | } | ||
| 54 | |||
| 55 | private Comment[] getParameterComments(ParameterDeclaration node, Function<ParameterDeclaration, Entry<?>> retriever) { | ||
| 56 | final EntryMapping mapping = remapper.getDeobfMapping(retriever.apply(node)); | ||
| 57 | final Comment[] ret = getComments(node, retriever); | ||
| 58 | if (ret != null) { | ||
| 59 | final String paramPrefix = "@param " + mapping.getTargetName() + " "; | ||
| 60 | final String indent = Strings.repeat(" ", paramPrefix.length()); | ||
| 61 | ret[0].setContent(paramPrefix + ret[0].getContent()); | ||
| 62 | for (int i = 1; i < ret.length; i++) { | ||
| 63 | ret[i].setContent(indent + ret[i].getContent()); | ||
| 64 | } | ||
| 65 | } | ||
| 66 | return ret; | ||
| 67 | } | ||
| 68 | |||
| 69 | private void visitMethod(AstNode node) { | ||
| 70 | final MethodDefEntry methodDefEntry = EntryParser.parse(node.getUserData(Keys.METHOD_DEFINITION)); | ||
| 71 | final Comment[] baseComments = getComments(node, $ -> methodDefEntry); | ||
| 72 | List<Comment> comments = new ArrayList<>(); | ||
| 73 | if (baseComments != null) | ||
| 74 | Collections.addAll(comments, baseComments); | ||
| 75 | |||
| 76 | for (ParameterDeclaration dec : node.getChildrenByRole(Roles.PARAMETER)) { | ||
| 77 | ParameterDefinition def = dec.getUserData(Keys.PARAMETER_DEFINITION); | ||
| 78 | final Comment[] paramComments = getParameterComments(dec, $ -> new LocalVariableDefEntry(methodDefEntry, def.getSlot(), def.getName(), | ||
| 79 | true, | ||
| 80 | EntryParser.parseTypeDescriptor(def.getParameterType()), null)); | ||
| 81 | if (paramComments != null) | ||
| 82 | Collections.addAll(comments, paramComments); | ||
| 83 | } | ||
| 84 | |||
| 85 | if (!comments.isEmpty()) { | ||
| 86 | if (remapper.getObfResolver().resolveEntry(methodDefEntry, ResolutionStrategy.RESOLVE_ROOT).stream().noneMatch(e -> Objects.equals(e, methodDefEntry))) { | ||
| 87 | comments.add(0, new Comment("{@inheritDoc}", CommentType.Documentation)); | ||
| 88 | } | ||
| 89 | final AstNode oldFirst = node.getFirstChild(); | ||
| 90 | for (Comment comment : comments) { | ||
| 91 | node.insertChildBefore(oldFirst, comment, Roles.COMMENT); | ||
| 92 | } | ||
| 93 | } | ||
| 94 | } | ||
| 95 | |||
| 96 | @Override | ||
| 97 | protected Void visitChildren(AstNode node, Void data) { | ||
| 98 | for (final AstNode child : node.getChildren()) { | ||
| 99 | child.acceptVisitor(this, data); | ||
| 100 | } | ||
| 101 | return null; | ||
| 102 | } | ||
| 103 | |||
| 104 | @Override | ||
| 105 | public Void visitMethodDeclaration(MethodDeclaration node, Void data) { | ||
| 106 | visitMethod(node); | ||
| 107 | return super.visitMethodDeclaration(node, data); | ||
| 108 | } | ||
| 109 | |||
| 110 | @Override | ||
| 111 | public Void visitConstructorDeclaration(ConstructorDeclaration node, Void data) { | ||
| 112 | visitMethod(node); | ||
| 113 | return super.visitConstructorDeclaration(node, data); | ||
| 114 | } | ||
| 115 | |||
| 116 | @Override | ||
| 117 | public Void visitFieldDeclaration(FieldDeclaration node, Void data) { | ||
| 118 | addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); | ||
| 119 | return super.visitFieldDeclaration(node, data); | ||
| 120 | } | ||
| 121 | |||
| 122 | @Override | ||
| 123 | public Void visitTypeDeclaration(TypeDeclaration node, Void data) { | ||
| 124 | addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.TYPE_DEFINITION))); | ||
| 125 | return super.visitTypeDeclaration(node, data); | ||
| 126 | } | ||
| 127 | |||
| 128 | @Override | ||
| 129 | public Void visitEnumValueDeclaration(EnumValueDeclaration node, Void data) { | ||
| 130 | addDoc(node, dec -> EntryParser.parse(dec.getUserData(Keys.FIELD_DEFINITION))); | ||
| 131 | return super.visitEnumValueDeclaration(node, data); | ||
| 132 | } | ||
| 133 | } | ||
| 134 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java new file mode 100644 index 0000000..39e599d --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropImportAstTransform.java | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 4 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 5 | import com.strobel.decompiler.languages.java.ast.ImportDeclaration; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.PackageDeclaration; | ||
| 7 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 8 | |||
| 9 | public final class DropImportAstTransform implements IAstTransform { | ||
| 10 | public static final DropImportAstTransform INSTANCE = new DropImportAstTransform(); | ||
| 11 | |||
| 12 | private DropImportAstTransform() { | ||
| 13 | } | ||
| 14 | |||
| 15 | @Override | ||
| 16 | public void run(AstNode compilationUnit) { | ||
| 17 | compilationUnit.acceptVisitor(new Visitor(), null); | ||
| 18 | } | ||
| 19 | |||
| 20 | static class Visitor extends DepthFirstAstVisitor<Void, Void> { | ||
| 21 | @Override | ||
| 22 | public Void visitPackageDeclaration(PackageDeclaration node, Void data) { | ||
| 23 | node.remove(); | ||
| 24 | return null; | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | public Void visitImportDeclaration(ImportDeclaration node, Void data) { | ||
| 29 | node.remove(); | ||
| 30 | return null; | ||
| 31 | } | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java new file mode 100644 index 0000000..b8c087b --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/DropVarModifiersAstTransform.java | |||
| @@ -0,0 +1,37 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.languages.java.ast.*; | ||
| 4 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 5 | |||
| 6 | import javax.lang.model.element.Modifier; | ||
| 7 | |||
| 8 | public final class DropVarModifiersAstTransform implements IAstTransform { | ||
| 9 | public static final DropVarModifiersAstTransform INSTANCE = new DropVarModifiersAstTransform(); | ||
| 10 | |||
| 11 | private DropVarModifiersAstTransform() { | ||
| 12 | } | ||
| 13 | |||
| 14 | @Override | ||
| 15 | public void run(AstNode compilationUnit) { | ||
| 16 | compilationUnit.acceptVisitor(new Visitor(), null); | ||
| 17 | } | ||
| 18 | |||
| 19 | static class Visitor extends DepthFirstAstVisitor<Void, Void> { | ||
| 20 | @Override | ||
| 21 | public Void visitParameterDeclaration(ParameterDeclaration node, Void data) { | ||
| 22 | for (JavaModifierToken modifierToken : node.getChildrenByRole(EntityDeclaration.MODIFIER_ROLE)) { | ||
| 23 | if (modifierToken.getModifier() == Modifier.FINAL) { | ||
| 24 | modifierToken.remove(); | ||
| 25 | } | ||
| 26 | } | ||
| 27 | |||
| 28 | return null; | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public Void visitVariableDeclaration(VariableDeclarationStatement node, Void data) { | ||
| 33 | node.removeModifier(Modifier.FINAL); | ||
| 34 | return null; | ||
| 35 | } | ||
| 36 | } | ||
| 37 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java new file mode 100644 index 0000000..34d95fa --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/InvalidIdentifierFix.java | |||
| @@ -0,0 +1,29 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 4 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 5 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 7 | |||
| 8 | /** | ||
| 9 | * Created by Thiakil on 13/07/2018. | ||
| 10 | */ | ||
| 11 | public class InvalidIdentifierFix implements IAstTransform { | ||
| 12 | @Override | ||
| 13 | public void run(AstNode compilationUnit) { | ||
| 14 | compilationUnit.acceptVisitor(new Visitor(), null); | ||
| 15 | } | ||
| 16 | |||
| 17 | class Visitor extends DepthFirstAstVisitor<Void,Void>{ | ||
| 18 | @Override | ||
| 19 | public Void visitIdentifier(Identifier node, Void data) { | ||
| 20 | super.visitIdentifier(node, data); | ||
| 21 | if (node.getName().equals("do") || node.getName().equals("if")){ | ||
| 22 | Identifier newIdentifier = Identifier.create(node.getName() + "_", node.getStartLocation()); | ||
| 23 | newIdentifier.copyUserDataFrom(node); | ||
| 24 | node.replaceWith(newIdentifier); | ||
| 25 | } | ||
| 26 | return null; | ||
| 27 | } | ||
| 28 | } | ||
| 29 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java new file mode 100644 index 0000000..8accfc7 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/Java8Generics.java | |||
| @@ -0,0 +1,107 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.BuiltinTypes; | ||
| 4 | import com.strobel.assembler.metadata.CommonTypeReferences; | ||
| 5 | import com.strobel.assembler.metadata.Flags; | ||
| 6 | import com.strobel.assembler.metadata.IGenericInstance; | ||
| 7 | import com.strobel.assembler.metadata.IMemberDefinition; | ||
| 8 | import com.strobel.assembler.metadata.JvmType; | ||
| 9 | import com.strobel.assembler.metadata.MemberReference; | ||
| 10 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 11 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 12 | import com.strobel.assembler.metadata.TypeReference; | ||
| 13 | import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; | ||
| 14 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 15 | import com.strobel.decompiler.languages.java.ast.AstNodeCollection; | ||
| 16 | import com.strobel.decompiler.languages.java.ast.AstType; | ||
| 17 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.ComposedType; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.Expression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.Identifier; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.Roles; | ||
| 27 | import com.strobel.decompiler.languages.java.ast.SimpleType; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.WildcardType; | ||
| 29 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 30 | |||
| 31 | /** | ||
| 32 | * Created by Thiakil on 12/07/2018. | ||
| 33 | */ | ||
| 34 | public class Java8Generics implements IAstTransform { | ||
| 35 | |||
| 36 | @Override | ||
| 37 | public void run(AstNode compilationUnit) { | ||
| 38 | compilationUnit.acceptVisitor(new Visitor(), null); | ||
| 39 | } | ||
| 40 | |||
| 41 | static class Visitor extends DepthFirstAstVisitor<Void,Void>{ | ||
| 42 | |||
| 43 | @Override | ||
| 44 | public Void visitInvocationExpression(InvocationExpression node, Void data) { | ||
| 45 | super.visitInvocationExpression(node, data); | ||
| 46 | if (node.getTarget() instanceof MemberReferenceExpression){ | ||
| 47 | MemberReferenceExpression referenceExpression = (MemberReferenceExpression) node.getTarget(); | ||
| 48 | if (referenceExpression.getTypeArguments().stream().map(t->{ | ||
| 49 | TypeReference tr = t.toTypeReference(); | ||
| 50 | if (tr.getDeclaringType() != null){//ensure that inner types are resolved so we can get the TypeDefinition below | ||
| 51 | TypeReference resolved = tr.resolve(); | ||
| 52 | if (resolved != null) | ||
| 53 | return resolved; | ||
| 54 | } | ||
| 55 | return tr; | ||
| 56 | }).anyMatch(t -> t.isWildcardType() || (t instanceof TypeDefinition && ((TypeDefinition) t).isAnonymous()))) { | ||
| 57 | //these are invalid for invocations, let the compiler work it out | ||
| 58 | referenceExpression.getTypeArguments().clear(); | ||
| 59 | } else if (referenceExpression.getTypeArguments().stream().allMatch(t->t.toTypeReference().equals(CommonTypeReferences.Object))){ | ||
| 60 | //all are <Object>, thereby redundant and/or bad | ||
| 61 | referenceExpression.getTypeArguments().clear(); | ||
| 62 | } | ||
| 63 | } | ||
| 64 | return null; | ||
| 65 | } | ||
| 66 | |||
| 67 | @Override | ||
| 68 | public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { | ||
| 69 | super.visitObjectCreationExpression(node, data); | ||
| 70 | AstType type = node.getType(); | ||
| 71 | if (type instanceof SimpleType && !((SimpleType) type).getTypeArguments().isEmpty()){ | ||
| 72 | SimpleType simpleType = (SimpleType) type; | ||
| 73 | AstNodeCollection<AstType> typeArguments = simpleType.getTypeArguments(); | ||
| 74 | if (typeArguments.size() == 1 && typeArguments.firstOrNullObject().toTypeReference().equals(CommonTypeReferences.Object)){ | ||
| 75 | //all are <Object>, thereby redundant and/or bad | ||
| 76 | typeArguments.firstOrNullObject().getChildByRole(Roles.IDENTIFIER).replaceWith(Identifier.create("")); | ||
| 77 | } | ||
| 78 | } | ||
| 79 | return null; | ||
| 80 | } | ||
| 81 | |||
| 82 | @Override | ||
| 83 | public Void visitCastExpression(CastExpression node, Void data) { | ||
| 84 | boolean doReplace = false; | ||
| 85 | TypeReference typeReference = node.getType().toTypeReference(); | ||
| 86 | if (typeReference.isArray() && typeReference.getElementType().isGenericType()){ | ||
| 87 | doReplace = true; | ||
| 88 | } else if (typeReference.isGenericType()) { | ||
| 89 | Expression target = node.getExpression(); | ||
| 90 | if (typeReference instanceof IGenericInstance && ((IGenericInstance)typeReference).getTypeArguments().stream().anyMatch(t->t.isWildcardType())){ | ||
| 91 | doReplace = true; | ||
| 92 | } else if (target instanceof InvocationExpression) { | ||
| 93 | InvocationExpression invocationExpression = (InvocationExpression)target; | ||
| 94 | if (invocationExpression.getTarget() instanceof MemberReferenceExpression && !((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().isEmpty()) { | ||
| 95 | ((MemberReferenceExpression) invocationExpression.getTarget()).getTypeArguments().clear(); | ||
| 96 | doReplace = true; | ||
| 97 | } | ||
| 98 | } | ||
| 99 | } | ||
| 100 | super.visitCastExpression(node, data); | ||
| 101 | if (doReplace){ | ||
| 102 | node.replaceWith(node.getExpression()); | ||
| 103 | } | ||
| 104 | return null; | ||
| 105 | } | ||
| 106 | } | ||
| 107 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java new file mode 100644 index 0000000..32bb72f --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/ObfuscatedEnumSwitchRewriterTransform.java | |||
| @@ -0,0 +1,414 @@ | |||
| 1 | /* | ||
| 2 | * Originally: | ||
| 3 | * EnumSwitchRewriterTransform.java | ||
| 4 | * | ||
| 5 | * Copyright (c) 2013 Mike Strobel | ||
| 6 | * | ||
| 7 | * This source code is based on Mono.Cecil from Jb Evain, Copyright (c) Jb Evain; | ||
| 8 | * and ILSpy/ICSharpCode from SharpDevelop, Copyright (c) AlphaSierraPapa. | ||
| 9 | * | ||
| 10 | * This source code is subject to terms and conditions of the Apache License, Version 2.0. | ||
| 11 | * A copy of the license can be found in the License.html file at the root of this distribution. | ||
| 12 | * By using this source code in any fashion, you are agreeing to be bound by the terms of the | ||
| 13 | * Apache License, Version 2.0. | ||
| 14 | * | ||
| 15 | * You must not remove this notice, or any other, from this software. | ||
| 16 | */ | ||
| 17 | |||
| 18 | package cuchaz.enigma.source.procyon.transformers; | ||
| 19 | |||
| 20 | import com.strobel.assembler.metadata.BuiltinTypes; | ||
| 21 | import com.strobel.assembler.metadata.FieldDefinition; | ||
| 22 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 23 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 24 | import com.strobel.assembler.metadata.TypeReference; | ||
| 25 | import com.strobel.core.SafeCloseable; | ||
| 26 | import com.strobel.core.VerifyArgument; | ||
| 27 | import com.strobel.decompiler.DecompilerContext; | ||
| 28 | import com.strobel.decompiler.languages.java.ast.AssignmentExpression; | ||
| 29 | import com.strobel.decompiler.languages.java.ast.AstBuilder; | ||
| 30 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 31 | import com.strobel.decompiler.languages.java.ast.CaseLabel; | ||
| 32 | import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; | ||
| 33 | import com.strobel.decompiler.languages.java.ast.Expression; | ||
| 34 | import com.strobel.decompiler.languages.java.ast.IdentifierExpression; | ||
| 35 | import com.strobel.decompiler.languages.java.ast.IndexerExpression; | ||
| 36 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 37 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 38 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 39 | import com.strobel.decompiler.languages.java.ast.PrimitiveExpression; | ||
| 40 | import com.strobel.decompiler.languages.java.ast.SwitchSection; | ||
| 41 | import com.strobel.decompiler.languages.java.ast.SwitchStatement; | ||
| 42 | import com.strobel.decompiler.languages.java.ast.TypeDeclaration; | ||
| 43 | import com.strobel.decompiler.languages.java.ast.TypeReferenceExpression; | ||
| 44 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 45 | |||
| 46 | import java.util.ArrayList; | ||
| 47 | import java.util.IdentityHashMap; | ||
| 48 | import java.util.LinkedHashMap; | ||
| 49 | import java.util.List; | ||
| 50 | import java.util.Map; | ||
| 51 | |||
| 52 | /** | ||
| 53 | * Copy of {@link com.strobel.decompiler.languages.java.ast.transforms.EnumSwitchRewriterTransform} modified to: | ||
| 54 | * - Not rely on a field containing "$SwitchMap$" (Proguard strips it) | ||
| 55 | * - Ignore classes *with* SwitchMap$ names (so the original can handle it) | ||
| 56 | * - Ignores inner synthetics that are not package private | ||
| 57 | */ | ||
| 58 | @SuppressWarnings("Duplicates") | ||
| 59 | public class ObfuscatedEnumSwitchRewriterTransform implements IAstTransform { | ||
| 60 | private final DecompilerContext _context; | ||
| 61 | |||
| 62 | public ObfuscatedEnumSwitchRewriterTransform(final DecompilerContext context) { | ||
| 63 | _context = VerifyArgument.notNull(context, "context"); | ||
| 64 | } | ||
| 65 | |||
| 66 | @Override | ||
| 67 | public void run(final AstNode compilationUnit) { | ||
| 68 | compilationUnit.acceptVisitor(new Visitor(_context), null); | ||
| 69 | } | ||
| 70 | |||
| 71 | private final static class Visitor extends ContextTrackingVisitor<Void> { | ||
| 72 | private final static class SwitchMapInfo { | ||
| 73 | final String enclosingType; | ||
| 74 | final Map<String, List<SwitchStatement>> switches = new LinkedHashMap<>(); | ||
| 75 | final Map<String, Map<Integer, Expression>> mappings = new LinkedHashMap<>(); | ||
| 76 | |||
| 77 | TypeDeclaration enclosingTypeDeclaration; | ||
| 78 | |||
| 79 | SwitchMapInfo(final String enclosingType) { | ||
| 80 | this.enclosingType = enclosingType; | ||
| 81 | } | ||
| 82 | } | ||
| 83 | |||
| 84 | private final Map<String, SwitchMapInfo> _switchMaps = new LinkedHashMap<>(); | ||
| 85 | private boolean _isSwitchMapWrapper; | ||
| 86 | |||
| 87 | protected Visitor(final DecompilerContext context) { | ||
| 88 | super(context); | ||
| 89 | } | ||
| 90 | |||
| 91 | @Override | ||
| 92 | public Void visitTypeDeclaration(final TypeDeclaration typeDeclaration, final Void p) { | ||
| 93 | final boolean oldIsSwitchMapWrapper = _isSwitchMapWrapper; | ||
| 94 | final TypeDefinition typeDefinition = typeDeclaration.getUserData(Keys.TYPE_DEFINITION); | ||
| 95 | final boolean isSwitchMapWrapper = isSwitchMapWrapper(typeDefinition); | ||
| 96 | |||
| 97 | if (isSwitchMapWrapper) { | ||
| 98 | final String internalName = typeDefinition.getInternalName(); | ||
| 99 | |||
| 100 | SwitchMapInfo info = _switchMaps.get(internalName); | ||
| 101 | |||
| 102 | if (info == null) { | ||
| 103 | _switchMaps.put(internalName, info = new SwitchMapInfo(internalName)); | ||
| 104 | } | ||
| 105 | |||
| 106 | info.enclosingTypeDeclaration = typeDeclaration; | ||
| 107 | } | ||
| 108 | |||
| 109 | _isSwitchMapWrapper = isSwitchMapWrapper; | ||
| 110 | |||
| 111 | try { | ||
| 112 | super.visitTypeDeclaration(typeDeclaration, p); | ||
| 113 | } | ||
| 114 | finally { | ||
| 115 | _isSwitchMapWrapper = oldIsSwitchMapWrapper; | ||
| 116 | } | ||
| 117 | |||
| 118 | rewrite(); | ||
| 119 | |||
| 120 | return null; | ||
| 121 | } | ||
| 122 | |||
| 123 | @Override | ||
| 124 | public Void visitSwitchStatement(final SwitchStatement node, final Void data) { | ||
| 125 | final Expression test = node.getExpression(); | ||
| 126 | |||
| 127 | if (test instanceof IndexerExpression) { | ||
| 128 | final IndexerExpression indexer = (IndexerExpression) test; | ||
| 129 | final Expression array = indexer.getTarget(); | ||
| 130 | final Expression argument = indexer.getArgument(); | ||
| 131 | |||
| 132 | if (!(array instanceof MemberReferenceExpression)) { | ||
| 133 | return super.visitSwitchStatement(node, data); | ||
| 134 | } | ||
| 135 | |||
| 136 | final MemberReferenceExpression arrayAccess = (MemberReferenceExpression) array; | ||
| 137 | final Expression arrayOwner = arrayAccess.getTarget(); | ||
| 138 | final String mapName = arrayAccess.getMemberName(); | ||
| 139 | |||
| 140 | if (mapName == null || mapName.startsWith("$SwitchMap$") || !(arrayOwner instanceof TypeReferenceExpression)) { | ||
| 141 | return super.visitSwitchStatement(node, data); | ||
| 142 | } | ||
| 143 | |||
| 144 | final TypeReferenceExpression enclosingTypeExpression = (TypeReferenceExpression) arrayOwner; | ||
| 145 | final TypeReference enclosingType = enclosingTypeExpression.getType().getUserData(Keys.TYPE_REFERENCE); | ||
| 146 | |||
| 147 | if (!isSwitchMapWrapper(enclosingType) || !(argument instanceof InvocationExpression)) { | ||
| 148 | return super.visitSwitchStatement(node, data); | ||
| 149 | } | ||
| 150 | |||
| 151 | final InvocationExpression invocation = (InvocationExpression) argument; | ||
| 152 | final Expression invocationTarget = invocation.getTarget(); | ||
| 153 | |||
| 154 | if (!(invocationTarget instanceof MemberReferenceExpression)) { | ||
| 155 | return super.visitSwitchStatement(node, data); | ||
| 156 | } | ||
| 157 | |||
| 158 | final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; | ||
| 159 | |||
| 160 | if (!"ordinal".equals(memberReference.getMemberName())) { | ||
| 161 | return super.visitSwitchStatement(node, data); | ||
| 162 | } | ||
| 163 | |||
| 164 | final String enclosingTypeName = enclosingType.getInternalName(); | ||
| 165 | |||
| 166 | SwitchMapInfo info = _switchMaps.get(enclosingTypeName); | ||
| 167 | |||
| 168 | if (info == null) { | ||
| 169 | _switchMaps.put(enclosingTypeName, info = new SwitchMapInfo(enclosingTypeName)); | ||
| 170 | |||
| 171 | final TypeDefinition resolvedType = enclosingType.resolve(); | ||
| 172 | |||
| 173 | if (resolvedType != null) { | ||
| 174 | AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); | ||
| 175 | |||
| 176 | if (astBuilder == null) { | ||
| 177 | astBuilder = new AstBuilder(context); | ||
| 178 | } | ||
| 179 | |||
| 180 | try (final SafeCloseable importSuppression = astBuilder.suppressImports()) { | ||
| 181 | final TypeDeclaration declaration = astBuilder.createType(resolvedType); | ||
| 182 | |||
| 183 | declaration.acceptVisitor(this, data); | ||
| 184 | } | ||
| 185 | } | ||
| 186 | } | ||
| 187 | |||
| 188 | List<SwitchStatement> switches = info.switches.get(mapName); | ||
| 189 | |||
| 190 | if (switches == null) { | ||
| 191 | info.switches.put(mapName, switches = new ArrayList<>()); | ||
| 192 | } | ||
| 193 | |||
| 194 | switches.add(node); | ||
| 195 | } | ||
| 196 | |||
| 197 | return super.visitSwitchStatement(node, data); | ||
| 198 | } | ||
| 199 | |||
| 200 | @Override | ||
| 201 | public Void visitAssignmentExpression(final AssignmentExpression node, final Void data) { | ||
| 202 | final TypeDefinition currentType = context.getCurrentType(); | ||
| 203 | final MethodDefinition currentMethod = context.getCurrentMethod(); | ||
| 204 | |||
| 205 | if (_isSwitchMapWrapper && | ||
| 206 | currentType != null && | ||
| 207 | currentMethod != null && | ||
| 208 | currentMethod.isTypeInitializer()) { | ||
| 209 | |||
| 210 | final Expression left = node.getLeft(); | ||
| 211 | final Expression right = node.getRight(); | ||
| 212 | |||
| 213 | if (left instanceof IndexerExpression && | ||
| 214 | right instanceof PrimitiveExpression) { | ||
| 215 | |||
| 216 | String mapName = null; | ||
| 217 | |||
| 218 | final Expression array = ((IndexerExpression) left).getTarget(); | ||
| 219 | final Expression argument = ((IndexerExpression) left).getArgument(); | ||
| 220 | |||
| 221 | if (array instanceof MemberReferenceExpression) { | ||
| 222 | mapName = ((MemberReferenceExpression) array).getMemberName(); | ||
| 223 | } | ||
| 224 | else if (array instanceof IdentifierExpression) { | ||
| 225 | mapName = ((IdentifierExpression) array).getIdentifier(); | ||
| 226 | } | ||
| 227 | |||
| 228 | if (mapName == null || mapName.startsWith("$SwitchMap$")) { | ||
| 229 | return super.visitAssignmentExpression(node, data); | ||
| 230 | } | ||
| 231 | |||
| 232 | if (!(argument instanceof InvocationExpression)) { | ||
| 233 | return super.visitAssignmentExpression(node, data); | ||
| 234 | } | ||
| 235 | |||
| 236 | final InvocationExpression invocation = (InvocationExpression) argument; | ||
| 237 | final Expression invocationTarget = invocation.getTarget(); | ||
| 238 | |||
| 239 | if (!(invocationTarget instanceof MemberReferenceExpression)) { | ||
| 240 | return super.visitAssignmentExpression(node, data); | ||
| 241 | } | ||
| 242 | |||
| 243 | final MemberReferenceExpression memberReference = (MemberReferenceExpression) invocationTarget; | ||
| 244 | final Expression memberTarget = memberReference.getTarget(); | ||
| 245 | |||
| 246 | if (!(memberTarget instanceof MemberReferenceExpression) || !"ordinal".equals(memberReference.getMemberName())) { | ||
| 247 | return super.visitAssignmentExpression(node, data); | ||
| 248 | } | ||
| 249 | |||
| 250 | final MemberReferenceExpression outerMemberReference = (MemberReferenceExpression) memberTarget; | ||
| 251 | final Expression outerMemberTarget = outerMemberReference.getTarget(); | ||
| 252 | |||
| 253 | if (!(outerMemberTarget instanceof TypeReferenceExpression)) { | ||
| 254 | return super.visitAssignmentExpression(node, data); | ||
| 255 | } | ||
| 256 | |||
| 257 | final String enclosingType = currentType.getInternalName(); | ||
| 258 | |||
| 259 | SwitchMapInfo info = _switchMaps.get(enclosingType); | ||
| 260 | |||
| 261 | if (info == null) { | ||
| 262 | _switchMaps.put(enclosingType, info = new SwitchMapInfo(enclosingType)); | ||
| 263 | |||
| 264 | AstBuilder astBuilder = context.getUserData(Keys.AST_BUILDER); | ||
| 265 | |||
| 266 | if (astBuilder == null) { | ||
| 267 | astBuilder = new AstBuilder(context); | ||
| 268 | } | ||
| 269 | |||
| 270 | info.enclosingTypeDeclaration = astBuilder.createType(currentType); | ||
| 271 | } | ||
| 272 | |||
| 273 | final PrimitiveExpression value = (PrimitiveExpression) right; | ||
| 274 | |||
| 275 | assert value.getValue() instanceof Integer; | ||
| 276 | |||
| 277 | Map<Integer, Expression> mapping = info.mappings.get(mapName); | ||
| 278 | |||
| 279 | if (mapping == null) { | ||
| 280 | info.mappings.put(mapName, mapping = new LinkedHashMap<>()); | ||
| 281 | } | ||
| 282 | |||
| 283 | final IdentifierExpression enumValue = new IdentifierExpression( Expression.MYSTERY_OFFSET, outerMemberReference.getMemberName()); | ||
| 284 | |||
| 285 | enumValue.putUserData(Keys.MEMBER_REFERENCE, outerMemberReference.getUserData(Keys.MEMBER_REFERENCE)); | ||
| 286 | |||
| 287 | mapping.put(((Number) value.getValue()).intValue(), enumValue); | ||
| 288 | } | ||
| 289 | } | ||
| 290 | |||
| 291 | return super.visitAssignmentExpression(node, data); | ||
| 292 | } | ||
| 293 | |||
| 294 | private void rewrite() { | ||
| 295 | if (_switchMaps.isEmpty()) { | ||
| 296 | return; | ||
| 297 | } | ||
| 298 | |||
| 299 | for (final SwitchMapInfo info : _switchMaps.values()) { | ||
| 300 | rewrite(info); | ||
| 301 | } | ||
| 302 | |||
| 303 | // | ||
| 304 | // Remove switch map type wrappers that are no longer referenced. | ||
| 305 | // | ||
| 306 | |||
| 307 | outer: | ||
| 308 | for (final SwitchMapInfo info : _switchMaps.values()) { | ||
| 309 | for (final String mapName : info.switches.keySet()) { | ||
| 310 | final List<SwitchStatement> switches = info.switches.get(mapName); | ||
| 311 | |||
| 312 | if (switches != null && !switches.isEmpty()) { | ||
| 313 | continue outer; | ||
| 314 | } | ||
| 315 | } | ||
| 316 | |||
| 317 | final TypeDeclaration enclosingTypeDeclaration = info.enclosingTypeDeclaration; | ||
| 318 | |||
| 319 | if (enclosingTypeDeclaration != null) { | ||
| 320 | enclosingTypeDeclaration.remove(); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | |||
| 325 | private void rewrite(final SwitchMapInfo info) { | ||
| 326 | if (info.switches.isEmpty()) { | ||
| 327 | return; | ||
| 328 | } | ||
| 329 | |||
| 330 | for (final String mapName : info.switches.keySet()) { | ||
| 331 | final List<SwitchStatement> switches = info.switches.get(mapName); | ||
| 332 | final Map<Integer, Expression> mappings = info.mappings.get(mapName); | ||
| 333 | |||
| 334 | if (switches != null && mappings != null) { | ||
| 335 | for (int i = 0; i < switches.size(); i++) { | ||
| 336 | if (rewriteSwitch(switches.get(i), mappings)) { | ||
| 337 | switches.remove(i--); | ||
| 338 | } | ||
| 339 | } | ||
| 340 | } | ||
| 341 | } | ||
| 342 | } | ||
| 343 | |||
| 344 | private boolean rewriteSwitch(final SwitchStatement s, final Map<Integer, Expression> mappings) { | ||
| 345 | final Map<Expression, Expression> replacements = new IdentityHashMap<>(); | ||
| 346 | |||
| 347 | for (final SwitchSection section : s.getSwitchSections()) { | ||
| 348 | for (final CaseLabel caseLabel : section.getCaseLabels()) { | ||
| 349 | final Expression expression = caseLabel.getExpression(); | ||
| 350 | |||
| 351 | if (expression.isNull()) { | ||
| 352 | continue; | ||
| 353 | } | ||
| 354 | |||
| 355 | if (expression instanceof PrimitiveExpression) { | ||
| 356 | final Object value = ((PrimitiveExpression) expression).getValue(); | ||
| 357 | |||
| 358 | if (value instanceof Integer) { | ||
| 359 | final Expression replacement = mappings.get(value); | ||
| 360 | |||
| 361 | if (replacement != null) { | ||
| 362 | replacements.put(expression, replacement); | ||
| 363 | continue; | ||
| 364 | } | ||
| 365 | } | ||
| 366 | } | ||
| 367 | |||
| 368 | // | ||
| 369 | // If we can't rewrite all cases, we abort. | ||
| 370 | // | ||
| 371 | |||
| 372 | return false; | ||
| 373 | } | ||
| 374 | } | ||
| 375 | |||
| 376 | final IndexerExpression indexer = (IndexerExpression) s.getExpression(); | ||
| 377 | final InvocationExpression argument = (InvocationExpression) indexer.getArgument(); | ||
| 378 | final MemberReferenceExpression memberReference = (MemberReferenceExpression) argument.getTarget(); | ||
| 379 | final Expression newTest = memberReference.getTarget(); | ||
| 380 | |||
| 381 | newTest.remove(); | ||
| 382 | indexer.replaceWith(newTest); | ||
| 383 | |||
| 384 | for (final Map.Entry<Expression, Expression> entry : replacements.entrySet()) { | ||
| 385 | entry.getKey().replaceWith(entry.getValue().clone()); | ||
| 386 | } | ||
| 387 | |||
| 388 | return true; | ||
| 389 | } | ||
| 390 | |||
| 391 | private static boolean isSwitchMapWrapper(final TypeReference type) { | ||
| 392 | if (type == null) { | ||
| 393 | return false; | ||
| 394 | } | ||
| 395 | |||
| 396 | final TypeDefinition definition = type instanceof TypeDefinition ? (TypeDefinition) type | ||
| 397 | : type.resolve(); | ||
| 398 | |||
| 399 | if (definition == null || !definition.isSynthetic() || !definition.isInnerClass() || !definition.isPackagePrivate()) { | ||
| 400 | return false; | ||
| 401 | } | ||
| 402 | |||
| 403 | for (final FieldDefinition field : definition.getDeclaredFields()) { | ||
| 404 | if (!field.getName().startsWith("$SwitchMap$") && | ||
| 405 | BuiltinTypes.Integer.makeArrayType().equals(field.getFieldType())) { | ||
| 406 | |||
| 407 | return true; | ||
| 408 | } | ||
| 409 | } | ||
| 410 | |||
| 411 | return false; | ||
| 412 | } | ||
| 413 | } | ||
| 414 | } \ No newline at end of file | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java new file mode 100644 index 0000000..cf0376f --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/RemoveObjectCasts.java | |||
| @@ -0,0 +1,39 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.BuiltinTypes; | ||
| 4 | import com.strobel.decompiler.DecompilerContext; | ||
| 5 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 6 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 7 | import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; | ||
| 8 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * Created by Thiakil on 11/07/2018. | ||
| 12 | */ | ||
| 13 | public class RemoveObjectCasts implements IAstTransform { | ||
| 14 | private final DecompilerContext _context; | ||
| 15 | |||
| 16 | public RemoveObjectCasts(DecompilerContext context) { | ||
| 17 | _context = context; | ||
| 18 | } | ||
| 19 | |||
| 20 | @Override | ||
| 21 | public void run(AstNode compilationUnit) { | ||
| 22 | compilationUnit.acceptVisitor(new Visitor(_context), null); | ||
| 23 | } | ||
| 24 | |||
| 25 | private final static class Visitor extends ContextTrackingVisitor<Void>{ | ||
| 26 | |||
| 27 | protected Visitor(DecompilerContext context) { | ||
| 28 | super(context); | ||
| 29 | } | ||
| 30 | |||
| 31 | @Override | ||
| 32 | public Void visitCastExpression(CastExpression node, Void data) { | ||
| 33 | if (node.getType().toTypeReference().equals(BuiltinTypes.Object)){ | ||
| 34 | node.replaceWith(node.getExpression()); | ||
| 35 | } | ||
| 36 | return super.visitCastExpression(node, data); | ||
| 37 | } | ||
| 38 | } | ||
| 39 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java b/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java new file mode 100644 index 0000000..d3ddaab --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/transformers/VarargsFixer.java | |||
| @@ -0,0 +1,197 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.transformers; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.MemberReference; | ||
| 4 | import com.strobel.assembler.metadata.MetadataFilters; | ||
| 5 | import com.strobel.assembler.metadata.MetadataHelper; | ||
| 6 | import com.strobel.assembler.metadata.MethodBinder; | ||
| 7 | import com.strobel.assembler.metadata.MethodDefinition; | ||
| 8 | import com.strobel.assembler.metadata.MethodReference; | ||
| 9 | import com.strobel.assembler.metadata.TypeReference; | ||
| 10 | import com.strobel.core.StringUtilities; | ||
| 11 | import com.strobel.core.VerifyArgument; | ||
| 12 | import com.strobel.decompiler.DecompilerContext; | ||
| 13 | import com.strobel.decompiler.languages.java.ast.ArrayCreationExpression; | ||
| 14 | import com.strobel.decompiler.languages.java.ast.ArrayInitializerExpression; | ||
| 15 | import com.strobel.decompiler.languages.java.ast.AstNode; | ||
| 16 | import com.strobel.decompiler.languages.java.ast.AstNodeCollection; | ||
| 17 | import com.strobel.decompiler.languages.java.ast.CastExpression; | ||
| 18 | import com.strobel.decompiler.languages.java.ast.ContextTrackingVisitor; | ||
| 19 | import com.strobel.decompiler.languages.java.ast.DepthFirstAstVisitor; | ||
| 20 | import com.strobel.decompiler.languages.java.ast.Expression; | ||
| 21 | import com.strobel.decompiler.languages.java.ast.InvocationExpression; | ||
| 22 | import com.strobel.decompiler.languages.java.ast.JavaResolver; | ||
| 23 | import com.strobel.decompiler.languages.java.ast.Keys; | ||
| 24 | import com.strobel.decompiler.languages.java.ast.MemberReferenceExpression; | ||
| 25 | import com.strobel.decompiler.languages.java.ast.ObjectCreationExpression; | ||
| 26 | import com.strobel.decompiler.languages.java.ast.transforms.IAstTransform; | ||
| 27 | import com.strobel.decompiler.semantics.ResolveResult; | ||
| 28 | |||
| 29 | import java.util.ArrayList; | ||
| 30 | import java.util.List; | ||
| 31 | |||
| 32 | /** | ||
| 33 | * Created by Thiakil on 12/07/2018. | ||
| 34 | */ | ||
| 35 | public class VarargsFixer implements IAstTransform { | ||
| 36 | private final DecompilerContext _context; | ||
| 37 | |||
| 38 | public VarargsFixer(final DecompilerContext context) { | ||
| 39 | _context = VerifyArgument.notNull(context, "context"); | ||
| 40 | } | ||
| 41 | |||
| 42 | @Override | ||
| 43 | public void run(AstNode compilationUnit) { | ||
| 44 | compilationUnit.acceptVisitor(new Visitor(_context), null); | ||
| 45 | } | ||
| 46 | |||
| 47 | class Visitor extends ContextTrackingVisitor<Void> { | ||
| 48 | private final JavaResolver _resolver; | ||
| 49 | protected Visitor(DecompilerContext context) { | ||
| 50 | super(context); | ||
| 51 | _resolver = new JavaResolver(context); | ||
| 52 | } | ||
| 53 | |||
| 54 | //remove `new Object[0]` on varagrs as the normal tranformer doesnt do them | ||
| 55 | @Override | ||
| 56 | public Void visitInvocationExpression(InvocationExpression node, Void data) { | ||
| 57 | super.visitInvocationExpression(node, data); | ||
| 58 | MemberReference definition = node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 59 | if (definition instanceof MethodDefinition && ((MethodDefinition) definition).isVarArgs()){ | ||
| 60 | AstNodeCollection<Expression> arguments = node.getArguments(); | ||
| 61 | Expression lastParam = arguments.lastOrNullObject(); | ||
| 62 | if (!lastParam.isNull() && lastParam instanceof ArrayCreationExpression){ | ||
| 63 | ArrayCreationExpression varargArray = (ArrayCreationExpression)lastParam; | ||
| 64 | if (varargArray.getInitializer().isNull() || varargArray.getInitializer().getElements().isEmpty()){ | ||
| 65 | lastParam.remove(); | ||
| 66 | } else { | ||
| 67 | for (Expression e : varargArray.getInitializer().getElements()){ | ||
| 68 | arguments.insertBefore(varargArray, e.clone()); | ||
| 69 | } | ||
| 70 | varargArray.remove(); | ||
| 71 | } | ||
| 72 | } | ||
| 73 | } | ||
| 74 | return null; | ||
| 75 | } | ||
| 76 | |||
| 77 | //applies the vararg transform to object creation | ||
| 78 | @Override | ||
| 79 | public Void visitObjectCreationExpression(ObjectCreationExpression node, Void data) { | ||
| 80 | super.visitObjectCreationExpression(node, data); | ||
| 81 | final AstNodeCollection<Expression> arguments = node.getArguments(); | ||
| 82 | final Expression lastArgument = arguments.lastOrNullObject(); | ||
| 83 | |||
| 84 | Expression arrayArg = lastArgument; | ||
| 85 | |||
| 86 | if (arrayArg instanceof CastExpression) | ||
| 87 | arrayArg = ((CastExpression) arrayArg).getExpression(); | ||
| 88 | |||
| 89 | if (arrayArg == null || | ||
| 90 | arrayArg.isNull() || | ||
| 91 | !(arrayArg instanceof ArrayCreationExpression && | ||
| 92 | node.getTarget() instanceof MemberReferenceExpression)) { | ||
| 93 | |||
| 94 | return null; | ||
| 95 | } | ||
| 96 | |||
| 97 | final ArrayCreationExpression newArray = (ArrayCreationExpression) arrayArg; | ||
| 98 | final MemberReferenceExpression target = (MemberReferenceExpression) node.getTarget(); | ||
| 99 | |||
| 100 | if (!newArray.getAdditionalArraySpecifiers().hasSingleElement()) { | ||
| 101 | return null; | ||
| 102 | } | ||
| 103 | |||
| 104 | final MethodReference method = (MethodReference) node.getUserData(Keys.MEMBER_REFERENCE); | ||
| 105 | |||
| 106 | if (method == null) { | ||
| 107 | return null; | ||
| 108 | } | ||
| 109 | |||
| 110 | final MethodDefinition resolved = method.resolve(); | ||
| 111 | |||
| 112 | if (resolved == null || !resolved.isVarArgs()) { | ||
| 113 | return null; | ||
| 114 | } | ||
| 115 | |||
| 116 | final List<MethodReference> candidates; | ||
| 117 | final Expression invocationTarget = target.getTarget(); | ||
| 118 | |||
| 119 | if (invocationTarget == null || invocationTarget.isNull()) { | ||
| 120 | candidates = MetadataHelper.findMethods( | ||
| 121 | context.getCurrentType(), | ||
| 122 | MetadataFilters.matchName(resolved.getName()) | ||
| 123 | ); | ||
| 124 | } | ||
| 125 | else { | ||
| 126 | final ResolveResult targetResult = _resolver.apply(invocationTarget); | ||
| 127 | |||
| 128 | if (targetResult == null || targetResult.getType() == null) { | ||
| 129 | return null; | ||
| 130 | } | ||
| 131 | |||
| 132 | candidates = MetadataHelper.findMethods( | ||
| 133 | targetResult.getType(), | ||
| 134 | MetadataFilters.matchName(resolved.getName()) | ||
| 135 | ); | ||
| 136 | } | ||
| 137 | |||
| 138 | final List<TypeReference> argTypes = new ArrayList<>(); | ||
| 139 | |||
| 140 | for (final Expression argument : arguments) { | ||
| 141 | final ResolveResult argResult = _resolver.apply(argument); | ||
| 142 | |||
| 143 | if (argResult == null || argResult.getType() == null) { | ||
| 144 | return null; | ||
| 145 | } | ||
| 146 | |||
| 147 | argTypes.add(argResult.getType()); | ||
| 148 | } | ||
| 149 | |||
| 150 | final MethodBinder.BindResult c1 = MethodBinder.selectMethod(candidates, argTypes); | ||
| 151 | |||
| 152 | if (c1.isFailure() || c1.isAmbiguous()) { | ||
| 153 | return null; | ||
| 154 | } | ||
| 155 | |||
| 156 | argTypes.remove(argTypes.size() - 1); | ||
| 157 | |||
| 158 | final ArrayInitializerExpression initializer = newArray.getInitializer(); | ||
| 159 | final boolean hasElements = !initializer.isNull() && !initializer.getElements().isEmpty(); | ||
| 160 | |||
| 161 | if (hasElements) { | ||
| 162 | for (final Expression argument : initializer.getElements()) { | ||
| 163 | final ResolveResult argResult = _resolver.apply(argument); | ||
| 164 | |||
| 165 | if (argResult == null || argResult.getType() == null) { | ||
| 166 | return null; | ||
| 167 | } | ||
| 168 | |||
| 169 | argTypes.add(argResult.getType()); | ||
| 170 | } | ||
| 171 | } | ||
| 172 | |||
| 173 | final MethodBinder.BindResult c2 = MethodBinder.selectMethod(candidates, argTypes); | ||
| 174 | |||
| 175 | if (c2.isFailure() || | ||
| 176 | c2.isAmbiguous() || | ||
| 177 | !StringUtilities.equals(c2.getMethod().getErasedSignature(), c1.getMethod().getErasedSignature())) { | ||
| 178 | |||
| 179 | return null; | ||
| 180 | } | ||
| 181 | |||
| 182 | lastArgument.remove(); | ||
| 183 | |||
| 184 | if (!hasElements) { | ||
| 185 | lastArgument.remove(); | ||
| 186 | return null; | ||
| 187 | } | ||
| 188 | |||
| 189 | for (final Expression newArg : initializer.getElements()) { | ||
| 190 | newArg.remove(); | ||
| 191 | arguments.add(newArg); | ||
| 192 | } | ||
| 193 | |||
| 194 | return null; | ||
| 195 | } | ||
| 196 | } | ||
| 197 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java new file mode 100644 index 0000000..e702956 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java | |||
| @@ -0,0 +1,33 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.Buffer; | ||
| 4 | import com.strobel.assembler.metadata.ClasspathTypeLoader; | ||
| 5 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Caching version of {@link ClasspathTypeLoader} | ||
| 9 | */ | ||
| 10 | public class CachingClasspathTypeLoader extends CachingTypeLoader { | ||
| 11 | private static ITypeLoader extraClassPathLoader = null; | ||
| 12 | |||
| 13 | public static void setExtraClassPathLoader(ITypeLoader loader){ | ||
| 14 | extraClassPathLoader = loader; | ||
| 15 | } | ||
| 16 | |||
| 17 | private final ITypeLoader classpathLoader = new ClasspathTypeLoader(); | ||
| 18 | |||
| 19 | @Override | ||
| 20 | protected byte[] doLoad(String className) { | ||
| 21 | Buffer parentBuf = new Buffer(); | ||
| 22 | if (classpathLoader.tryLoadType(className, parentBuf)) { | ||
| 23 | return parentBuf.array(); | ||
| 24 | } | ||
| 25 | if (extraClassPathLoader != null){ | ||
| 26 | parentBuf.reset(); | ||
| 27 | if (extraClassPathLoader.tryLoadType(className, parentBuf)){ | ||
| 28 | return parentBuf.array(); | ||
| 29 | } | ||
| 30 | } | ||
| 31 | return EMPTY_ARRAY;//need to return *something* as null means no store | ||
| 32 | } | ||
| 33 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java new file mode 100644 index 0000000..5be5ddd --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 2 | |||
| 3 | import com.google.common.collect.Maps; | ||
| 4 | import com.strobel.assembler.metadata.Buffer; | ||
| 5 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 6 | |||
| 7 | import java.util.Map; | ||
| 8 | |||
| 9 | /** | ||
| 10 | * Common cache functions | ||
| 11 | */ | ||
| 12 | public abstract class CachingTypeLoader implements ITypeLoader { | ||
| 13 | protected static final byte[] EMPTY_ARRAY = {}; | ||
| 14 | |||
| 15 | private final Map<String, byte[]> cache = Maps.newHashMap(); | ||
| 16 | |||
| 17 | protected abstract byte[] doLoad(String className); | ||
| 18 | |||
| 19 | @Override | ||
| 20 | public boolean tryLoadType(String className, Buffer out) { | ||
| 21 | |||
| 22 | // check the cache | ||
| 23 | byte[] data = this.cache.computeIfAbsent(className, this::doLoad); | ||
| 24 | |||
| 25 | if (data == EMPTY_ARRAY) { | ||
| 26 | return false; | ||
| 27 | } | ||
| 28 | |||
| 29 | out.reset(data.length); | ||
| 30 | System.arraycopy(data, 0, out.array(), out.position(), data.length); | ||
| 31 | out.position(0); | ||
| 32 | return true; | ||
| 33 | } | ||
| 34 | |||
| 35 | public void clearCache() { | ||
| 36 | this.cache.clear(); | ||
| 37 | } | ||
| 38 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java new file mode 100644 index 0000000..e703d3b --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java | |||
| @@ -0,0 +1,140 @@ | |||
| 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 | |||
| 12 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 13 | |||
| 14 | import com.google.common.collect.Lists; | ||
| 15 | import com.strobel.assembler.metadata.Buffer; | ||
| 16 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 17 | import cuchaz.enigma.ClassProvider; | ||
| 18 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | ||
| 19 | import org.objectweb.asm.ClassVisitor; | ||
| 20 | import org.objectweb.asm.ClassWriter; | ||
| 21 | import org.objectweb.asm.Opcodes; | ||
| 22 | import org.objectweb.asm.tree.AbstractInsnNode; | ||
| 23 | import org.objectweb.asm.tree.ClassNode; | ||
| 24 | import org.objectweb.asm.tree.MethodInsnNode; | ||
| 25 | import org.objectweb.asm.tree.MethodNode; | ||
| 26 | |||
| 27 | import java.util.Collection; | ||
| 28 | import java.util.LinkedList; | ||
| 29 | import java.util.List; | ||
| 30 | import java.util.function.Function; | ||
| 31 | |||
| 32 | public class CompiledSourceTypeLoader extends CachingTypeLoader { | ||
| 33 | //Store one instance as the classpath shouldn't change during load | ||
| 34 | private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader(); | ||
| 35 | |||
| 36 | private final ClassProvider compiledSource; | ||
| 37 | private final LinkedList<Function<ClassVisitor, ClassVisitor>> visitors = new LinkedList<>(); | ||
| 38 | |||
| 39 | public CompiledSourceTypeLoader(ClassProvider compiledSource) { | ||
| 40 | this.compiledSource = compiledSource; | ||
| 41 | } | ||
| 42 | |||
| 43 | public void addVisitor(Function<ClassVisitor, ClassVisitor> visitor) { | ||
| 44 | this.visitors.addFirst(visitor); | ||
| 45 | } | ||
| 46 | |||
| 47 | @Override | ||
| 48 | protected byte[] doLoad(String className) { | ||
| 49 | byte[] data = loadType(className); | ||
| 50 | if (data == null) { | ||
| 51 | return loadClasspath(className); | ||
| 52 | } | ||
| 53 | |||
| 54 | return data; | ||
| 55 | } | ||
| 56 | |||
| 57 | private byte[] loadClasspath(String name) { | ||
| 58 | Buffer parentBuf = new Buffer(); | ||
| 59 | if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) { | ||
| 60 | return parentBuf.array(); | ||
| 61 | } | ||
| 62 | return EMPTY_ARRAY; | ||
| 63 | } | ||
| 64 | |||
| 65 | private byte[] loadType(String className) { | ||
| 66 | ClassEntry entry = new ClassEntry(className); | ||
| 67 | |||
| 68 | // find the class in the jar | ||
| 69 | ClassNode node = findClassNode(entry); | ||
| 70 | if (node == null) { | ||
| 71 | // couldn't find it | ||
| 72 | return null; | ||
| 73 | } | ||
| 74 | |||
| 75 | removeRedundantClassCalls(node); | ||
| 76 | |||
| 77 | ClassWriter writer = new ClassWriter(0); | ||
| 78 | |||
| 79 | ClassVisitor visitor = writer; | ||
| 80 | for (Function<ClassVisitor, ClassVisitor> visitorFunction : this.visitors) { | ||
| 81 | visitor = visitorFunction.apply(visitor); | ||
| 82 | } | ||
| 83 | |||
| 84 | node.accept(visitor); | ||
| 85 | |||
| 86 | // we have a transformed class! | ||
| 87 | return writer.toByteArray(); | ||
| 88 | } | ||
| 89 | |||
| 90 | private void removeRedundantClassCalls(ClassNode node) { | ||
| 91 | // remove <obj>.getClass() calls that are seemingly injected | ||
| 92 | // DUP | ||
| 93 | // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; | ||
| 94 | // POP | ||
| 95 | for (MethodNode methodNode : node.methods) { | ||
| 96 | AbstractInsnNode insnNode = methodNode.instructions.getFirst(); | ||
| 97 | while (insnNode != null) { | ||
| 98 | if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { | ||
| 99 | MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; | ||
| 100 | if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { | ||
| 101 | AbstractInsnNode previous = methodInsnNode.getPrevious(); | ||
| 102 | AbstractInsnNode next = methodInsnNode.getNext(); | ||
| 103 | if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { | ||
| 104 | insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction | ||
| 105 | methodNode.instructions.remove(previous); | ||
| 106 | methodNode.instructions.remove(methodInsnNode); | ||
| 107 | methodNode.instructions.remove(next); | ||
| 108 | } | ||
| 109 | } | ||
| 110 | } | ||
| 111 | insnNode = insnNode.getNext(); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | private ClassNode findClassNode(ClassEntry entry) { | ||
| 117 | // try to find the class in the jar | ||
| 118 | for (String className : getClassNamesToTry(entry)) { | ||
| 119 | ClassNode node = compiledSource.getClassNode(className); | ||
| 120 | if (node != null) { | ||
| 121 | return node; | ||
| 122 | } | ||
| 123 | } | ||
| 124 | |||
| 125 | // didn't find it ;_; | ||
| 126 | return null; | ||
| 127 | } | ||
| 128 | |||
| 129 | private Collection<String> getClassNamesToTry(ClassEntry entry) { | ||
| 130 | List<String> classNamesToTry = Lists.newArrayList(); | ||
| 131 | classNamesToTry.add(entry.getFullName()); | ||
| 132 | |||
| 133 | ClassEntry outerClass = entry.getOuterClass(); | ||
| 134 | if (outerClass != null) { | ||
| 135 | classNamesToTry.addAll(getClassNamesToTry(outerClass)); | ||
| 136 | } | ||
| 137 | |||
| 138 | return classNamesToTry; | ||
| 139 | } | ||
| 140 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java new file mode 100644 index 0000000..c4732b0 --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java | |||
| @@ -0,0 +1,38 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 4 | import com.strobel.assembler.metadata.MetadataSystem; | ||
| 5 | import com.strobel.assembler.metadata.TypeDefinition; | ||
| 6 | import com.strobel.assembler.metadata.TypeReference; | ||
| 7 | |||
| 8 | import java.util.Collections; | ||
| 9 | import java.util.Set; | ||
| 10 | import java.util.concurrent.ConcurrentHashMap; | ||
| 11 | |||
| 12 | public final class NoRetryMetadataSystem extends MetadataSystem { | ||
| 13 | private final Set<String> failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>()); | ||
| 14 | |||
| 15 | public NoRetryMetadataSystem(final ITypeLoader typeLoader) { | ||
| 16 | super(typeLoader); | ||
| 17 | } | ||
| 18 | |||
| 19 | @Override | ||
| 20 | protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { | ||
| 21 | if (failedTypes.contains(descriptor)) { | ||
| 22 | return null; | ||
| 23 | } | ||
| 24 | |||
| 25 | final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); | ||
| 26 | |||
| 27 | if (result == null) { | ||
| 28 | failedTypes.add(descriptor); | ||
| 29 | } | ||
| 30 | |||
| 31 | return result; | ||
| 32 | } | ||
| 33 | |||
| 34 | @Override | ||
| 35 | public synchronized TypeDefinition resolve(final TypeReference type) { | ||
| 36 | return super.resolve(type); | ||
| 37 | } | ||
| 38 | } | ||
diff --git a/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java b/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java new file mode 100644 index 0000000..86c6ecc --- /dev/null +++ b/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | package cuchaz.enigma.source.procyon.typeloader; | ||
| 2 | |||
| 3 | import com.strobel.assembler.metadata.Buffer; | ||
| 4 | import com.strobel.assembler.metadata.ITypeLoader; | ||
| 5 | |||
| 6 | /** | ||
| 7 | * Typeloader with synchronized tryLoadType method | ||
| 8 | */ | ||
| 9 | public class SynchronizedTypeLoader implements ITypeLoader { | ||
| 10 | private final ITypeLoader delegate; | ||
| 11 | |||
| 12 | public SynchronizedTypeLoader(ITypeLoader delegate) { | ||
| 13 | this.delegate = delegate; | ||
| 14 | } | ||
| 15 | |||
| 16 | @Override | ||
| 17 | public synchronized boolean tryLoadType(String internalName, Buffer buffer) { | ||
| 18 | return delegate.tryLoadType(internalName, buffer); | ||
| 19 | } | ||
| 20 | } | ||