diff options
| author | 2020-07-06 06:34:10 -0400 | |
|---|---|---|
| committer | 2020-07-06 12:34:10 +0200 | |
| commit | f0885819aeeb2edbfcfc0b23566cccb571166a02 (patch) | |
| tree | d650aa68641fdfd9cc5f5ed0093229d1840506d8 /enigma/src/main | |
| parent | Fix "Mark as Deobfuscated" menu entry not working... this time without breaki... (diff) | |
| download | enigma-fork-f0885819aeeb2edbfcfc0b23566cccb571166a02.tar.gz enigma-fork-f0885819aeeb2edbfcfc0b23566cccb571166a02.tar.xz enigma-fork-f0885819aeeb2edbfcfc0b23566cccb571166a02.zip | |
Make class loading more flexible (#277)
Diffstat (limited to 'enigma/src/main')
23 files changed, 365 insertions, 483 deletions
diff --git a/enigma/src/main/java/cuchaz/enigma/ClassProvider.java b/enigma/src/main/java/cuchaz/enigma/ClassProvider.java deleted file mode 100644 index 2b91379..0000000 --- a/enigma/src/main/java/cuchaz/enigma/ClassProvider.java +++ /dev/null | |||
| @@ -1,10 +0,0 @@ | |||
| 1 | package cuchaz.enigma; | ||
| 2 | |||
| 3 | import org.objectweb.asm.tree.ClassNode; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 6 | |||
| 7 | public interface ClassProvider { | ||
| 8 | @Nullable | ||
| 9 | ClassNode getClassNode(String name); | ||
| 10 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/Enigma.java b/enigma/src/main/java/cuchaz/enigma/Enigma.java index 73c9a09..2e9be34 100644 --- a/enigma/src/main/java/cuchaz/enigma/Enigma.java +++ b/enigma/src/main/java/cuchaz/enigma/Enigma.java | |||
| @@ -13,7 +13,6 @@ package cuchaz.enigma; | |||
| 13 | 13 | ||
| 14 | import com.google.common.base.Preconditions; | 14 | import com.google.common.base.Preconditions; |
| 15 | import com.google.common.collect.ImmutableListMultimap; | 15 | import com.google.common.collect.ImmutableListMultimap; |
| 16 | import cuchaz.enigma.analysis.ClassCache; | ||
| 17 | import cuchaz.enigma.analysis.index.JarIndex; | 16 | import cuchaz.enigma.analysis.index.JarIndex; |
| 18 | import cuchaz.enigma.api.EnigmaPlugin; | 17 | import cuchaz.enigma.api.EnigmaPlugin; |
| 19 | import cuchaz.enigma.api.EnigmaPluginContext; | 18 | import cuchaz.enigma.api.EnigmaPluginContext; |
| @@ -21,6 +20,10 @@ import cuchaz.enigma.api.service.EnigmaService; | |||
| 21 | import cuchaz.enigma.api.service.EnigmaServiceFactory; | 20 | import cuchaz.enigma.api.service.EnigmaServiceFactory; |
| 22 | import cuchaz.enigma.api.service.EnigmaServiceType; | 21 | import cuchaz.enigma.api.service.EnigmaServiceType; |
| 23 | import cuchaz.enigma.api.service.JarIndexerService; | 22 | import cuchaz.enigma.api.service.JarIndexerService; |
| 23 | import cuchaz.enigma.classprovider.CachingClassProvider; | ||
| 24 | import cuchaz.enigma.classprovider.ClassProvider; | ||
| 25 | import cuchaz.enigma.classprovider.CombiningClassProvider; | ||
| 26 | import cuchaz.enigma.classprovider.JarClassProvider; | ||
| 24 | import cuchaz.enigma.utils.Utils; | 27 | import cuchaz.enigma.utils.Utils; |
| 25 | import org.objectweb.asm.Opcodes; | 28 | import org.objectweb.asm.Opcodes; |
| 26 | 29 | ||
| @@ -28,6 +31,7 @@ import java.io.IOException; | |||
| 28 | import java.nio.file.Path; | 31 | import java.nio.file.Path; |
| 29 | import java.util.List; | 32 | import java.util.List; |
| 30 | import java.util.ServiceLoader; | 33 | import java.util.ServiceLoader; |
| 34 | import java.util.Set; | ||
| 31 | 35 | ||
| 32 | public class Enigma { | 36 | public class Enigma { |
| 33 | public static final String NAME = "Enigma"; | 37 | public static final String NAME = "Enigma"; |
| @@ -51,13 +55,16 @@ public class Enigma { | |||
| 51 | return new Builder(); | 55 | return new Builder(); |
| 52 | } | 56 | } |
| 53 | 57 | ||
| 54 | public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException { | 58 | public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException { |
| 55 | ClassCache classCache = ClassCache.of(path); | 59 | JarClassProvider jarClassProvider = new JarClassProvider(path); |
| 56 | JarIndex jarIndex = classCache.index(progress); | 60 | ClassProvider classProvider = new CachingClassProvider(new CombiningClassProvider(jarClassProvider, libraryClassProvider)); |
| 61 | Set<String> scope = jarClassProvider.getClassNames(); | ||
| 57 | 62 | ||
| 58 | services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(classCache, jarIndex)); | 63 | JarIndex index = JarIndex.empty(); |
| 64 | index.indexJar(scope, classProvider, progress); | ||
| 65 | services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(scope, classProvider, index)); | ||
| 59 | 66 | ||
| 60 | return new EnigmaProject(this, classCache, jarIndex, Utils.zipSha1(path)); | 67 | return new EnigmaProject(this, classProvider, index, Utils.zipSha1(path)); |
| 61 | } | 68 | } |
| 62 | 69 | ||
| 63 | public EnigmaProfile getProfile() { | 70 | public EnigmaProfile getProfile() { |
diff --git a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java index 43ea14e..3999572 100644 --- a/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java +++ b/enigma/src/main/java/cuchaz/enigma/EnigmaProject.java | |||
| @@ -2,16 +2,20 @@ package cuchaz.enigma; | |||
| 2 | 2 | ||
| 3 | import com.google.common.base.Functions; | 3 | import com.google.common.base.Functions; |
| 4 | import com.google.common.base.Preconditions; | 4 | import com.google.common.base.Preconditions; |
| 5 | import cuchaz.enigma.analysis.ClassCache; | ||
| 6 | import cuchaz.enigma.analysis.EntryReference; | 5 | import cuchaz.enigma.analysis.EntryReference; |
| 7 | import cuchaz.enigma.analysis.index.JarIndex; | 6 | import cuchaz.enigma.analysis.index.JarIndex; |
| 8 | import cuchaz.enigma.api.service.NameProposalService; | 7 | import cuchaz.enigma.api.service.NameProposalService; |
| 9 | import cuchaz.enigma.bytecode.translators.SourceFixVisitor; | 8 | import cuchaz.enigma.bytecode.translators.SourceFixVisitor; |
| 10 | import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; | 9 | import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; |
| 11 | import cuchaz.enigma.source.*; | 10 | import cuchaz.enigma.classprovider.ClassProvider; |
| 11 | import cuchaz.enigma.source.Decompiler; | ||
| 12 | import cuchaz.enigma.source.DecompilerService; | ||
| 13 | import cuchaz.enigma.source.SourceSettings; | ||
| 12 | import cuchaz.enigma.translation.ProposingTranslator; | 14 | import cuchaz.enigma.translation.ProposingTranslator; |
| 13 | import cuchaz.enigma.translation.Translator; | 15 | import cuchaz.enigma.translation.Translator; |
| 14 | import cuchaz.enigma.translation.mapping.*; | 16 | import cuchaz.enigma.translation.mapping.EntryMapping; |
| 17 | import cuchaz.enigma.translation.mapping.EntryRemapper; | ||
| 18 | import cuchaz.enigma.translation.mapping.MappingsChecker; | ||
| 15 | import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; | 19 | import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; |
| 16 | import cuchaz.enigma.translation.mapping.tree.EntryTree; | 20 | import cuchaz.enigma.translation.mapping.tree.EntryTree; |
| 17 | import cuchaz.enigma.translation.representation.entry.ClassEntry; | 21 | import cuchaz.enigma.translation.representation.entry.ClassEntry; |
| @@ -19,11 +23,11 @@ import cuchaz.enigma.translation.representation.entry.Entry; | |||
| 19 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; | 23 | import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; |
| 20 | import cuchaz.enigma.translation.representation.entry.MethodEntry; | 24 | import cuchaz.enigma.translation.representation.entry.MethodEntry; |
| 21 | import cuchaz.enigma.utils.I18n; | 25 | import cuchaz.enigma.utils.I18n; |
| 22 | |||
| 23 | import org.objectweb.asm.ClassWriter; | 26 | import org.objectweb.asm.ClassWriter; |
| 24 | import org.objectweb.asm.tree.ClassNode; | 27 | import org.objectweb.asm.tree.ClassNode; |
| 25 | 28 | ||
| 26 | import java.io.*; | 29 | import java.io.BufferedWriter; |
| 30 | import java.io.IOException; | ||
| 27 | import java.nio.file.Files; | 31 | import java.nio.file.Files; |
| 28 | import java.nio.file.Path; | 32 | import java.nio.file.Path; |
| 29 | import java.util.Collection; | 33 | import java.util.Collection; |
| @@ -37,16 +41,16 @@ import java.util.stream.Collectors; | |||
| 37 | public class EnigmaProject { | 41 | public class EnigmaProject { |
| 38 | private final Enigma enigma; | 42 | private final Enigma enigma; |
| 39 | 43 | ||
| 40 | private final ClassCache classCache; | 44 | private final ClassProvider classProvider; |
| 41 | private final JarIndex jarIndex; | 45 | private final JarIndex jarIndex; |
| 42 | private final byte[] jarChecksum; | 46 | private final byte[] jarChecksum; |
| 43 | 47 | ||
| 44 | private EntryRemapper mapper; | 48 | private EntryRemapper mapper; |
| 45 | 49 | ||
| 46 | public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex, byte[] jarChecksum) { | 50 | public EnigmaProject(Enigma enigma, ClassProvider classProvider, JarIndex jarIndex, byte[] jarChecksum) { |
| 47 | Preconditions.checkArgument(jarChecksum.length == 20); | 51 | Preconditions.checkArgument(jarChecksum.length == 20); |
| 48 | this.enigma = enigma; | 52 | this.enigma = enigma; |
| 49 | this.classCache = classCache; | 53 | this.classProvider = classProvider; |
| 50 | this.jarIndex = jarIndex; | 54 | this.jarIndex = jarIndex; |
| 51 | this.jarChecksum = jarChecksum; | 55 | this.jarChecksum = jarChecksum; |
| 52 | 56 | ||
| @@ -65,8 +69,8 @@ public class EnigmaProject { | |||
| 65 | return enigma; | 69 | return enigma; |
| 66 | } | 70 | } |
| 67 | 71 | ||
| 68 | public ClassCache getClassCache() { | 72 | public ClassProvider getClassProvider() { |
| 69 | return classCache; | 73 | return classProvider; |
| 70 | } | 74 | } |
| 71 | 75 | ||
| 72 | public JarIndex getJarIndex() { | 76 | public JarIndex getJarIndex() { |
| @@ -103,20 +107,6 @@ public class EnigmaProject { | |||
| 103 | return droppedMappings.keySet(); | 107 | return droppedMappings.keySet(); |
| 104 | } | 108 | } |
| 105 | 109 | ||
| 106 | public Decompiler createDecompiler(DecompilerService decompilerService) { | ||
| 107 | return decompilerService.create(name -> { | ||
| 108 | ClassNode node = this.getClassCache().getClassNode(name); | ||
| 109 | |||
| 110 | if (node == null) { | ||
| 111 | return null; | ||
| 112 | } | ||
| 113 | |||
| 114 | ClassNode fixedNode = new ClassNode(); | ||
| 115 | node.accept(new SourceFixVisitor(Enigma.ASM_VERSION, fixedNode, getJarIndex())); | ||
| 116 | return fixedNode; | ||
| 117 | }, new SourceSettings(true, true)); | ||
| 118 | } | ||
| 119 | |||
| 120 | public boolean isRenamable(Entry<?> obfEntry) { | 110 | public boolean isRenamable(Entry<?> obfEntry) { |
| 121 | if (obfEntry instanceof MethodEntry) { | 111 | if (obfEntry instanceof MethodEntry) { |
| 122 | // HACKHACK: Object methods are not obfuscated identifiers | 112 | // HACKHACK: Object methods are not obfuscated identifiers |
| @@ -171,7 +161,7 @@ public class EnigmaProject { | |||
| 171 | ClassEntry translatedEntry = deobfuscator.translate(entry); | 161 | ClassEntry translatedEntry = deobfuscator.translate(entry); |
| 172 | progress.step(count.getAndIncrement(), translatedEntry.toString()); | 162 | progress.step(count.getAndIncrement(), translatedEntry.toString()); |
| 173 | 163 | ||
| 174 | ClassNode node = classCache.getClassNode(entry.getFullName()); | 164 | ClassNode node = classProvider.get(entry.getFullName()); |
| 175 | if (node != null) { | 165 | if (node != null) { |
| 176 | ClassNode translatedNode = new ClassNode(); | 166 | ClassNode translatedNode = new ClassNode(); |
| 177 | node.accept(new TranslationClassVisitor(deobfuscator, Enigma.ASM_VERSION, new SourceFixVisitor(Enigma.ASM_VERSION, translatedNode, jarIndex))); | 167 | node.accept(new TranslationClassVisitor(deobfuscator, Enigma.ASM_VERSION, new SourceFixVisitor(Enigma.ASM_VERSION, translatedNode, jarIndex))); |
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java index 4685b39..989464d 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/BuiltinPlugin.java | |||
| @@ -12,7 +12,6 @@ import cuchaz.enigma.translation.representation.entry.ClassEntry; | |||
| 12 | import cuchaz.enigma.translation.representation.entry.Entry; | 12 | import cuchaz.enigma.translation.representation.entry.Entry; |
| 13 | import cuchaz.enigma.translation.representation.entry.FieldEntry; | 13 | import cuchaz.enigma.translation.representation.entry.FieldEntry; |
| 14 | import cuchaz.enigma.utils.Pair; | 14 | import cuchaz.enigma.utils.Pair; |
| 15 | import org.objectweb.asm.ClassReader; | ||
| 16 | import org.objectweb.asm.ClassVisitor; | 15 | import org.objectweb.asm.ClassVisitor; |
| 17 | import org.objectweb.asm.FieldVisitor; | 16 | import org.objectweb.asm.FieldVisitor; |
| 18 | import org.objectweb.asm.MethodVisitor; | 17 | import org.objectweb.asm.MethodVisitor; |
| @@ -51,7 +50,7 @@ public final class BuiltinPlugin implements EnigmaPlugin { | |||
| 51 | final Map<Entry<?>, String> names = new HashMap<>(); | 50 | final Map<Entry<?>, String> names = new HashMap<>(); |
| 52 | final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); | 51 | final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); |
| 53 | 52 | ||
| 54 | ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> (classCache, jarIndex) -> classCache.visit(() -> visitor, ClassReader.SKIP_FRAMES)); | 53 | ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> JarIndexerService.fromVisitor(visitor)); |
| 55 | ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); | 54 | ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); |
| 56 | } | 55 | } |
| 57 | 56 | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java b/enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java deleted file mode 100644 index a3d24e3..0000000 --- a/enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java +++ /dev/null | |||
| @@ -1,126 +0,0 @@ | |||
| 1 | package cuchaz.enigma.analysis; | ||
| 2 | |||
| 3 | import com.google.common.cache.Cache; | ||
| 4 | import com.google.common.cache.CacheBuilder; | ||
| 5 | import com.google.common.collect.ImmutableSet; | ||
| 6 | import cuchaz.enigma.ClassProvider; | ||
| 7 | import cuchaz.enigma.Enigma; | ||
| 8 | import cuchaz.enigma.ProgressListener; | ||
| 9 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 10 | import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor; | ||
| 11 | import org.objectweb.asm.ClassReader; | ||
| 12 | import org.objectweb.asm.ClassVisitor; | ||
| 13 | import org.objectweb.asm.tree.ClassNode; | ||
| 14 | |||
| 15 | import javax.annotation.Nullable; | ||
| 16 | import java.io.IOException; | ||
| 17 | import java.nio.file.FileSystem; | ||
| 18 | import java.nio.file.FileSystems; | ||
| 19 | import java.nio.file.Files; | ||
| 20 | import java.nio.file.Path; | ||
| 21 | import java.util.concurrent.ExecutionException; | ||
| 22 | import java.util.concurrent.TimeUnit; | ||
| 23 | import java.util.function.Supplier; | ||
| 24 | |||
| 25 | public final class ClassCache implements AutoCloseable, ClassProvider { | ||
| 26 | private final FileSystem fileSystem; | ||
| 27 | private final ImmutableSet<String> classNames; | ||
| 28 | |||
| 29 | private final Cache<String, ClassNode> nodeCache = CacheBuilder.newBuilder() | ||
| 30 | .maximumSize(128) | ||
| 31 | .expireAfterAccess(1, TimeUnit.MINUTES) | ||
| 32 | .build(); | ||
| 33 | |||
| 34 | private ClassCache(FileSystem fileSystem, ImmutableSet<String> classNames) { | ||
| 35 | this.fileSystem = fileSystem; | ||
| 36 | this.classNames = classNames; | ||
| 37 | } | ||
| 38 | |||
| 39 | public static ClassCache of(Path jarPath) throws IOException { | ||
| 40 | FileSystem fileSystem = FileSystems.newFileSystem(jarPath, (ClassLoader) null); | ||
| 41 | ImmutableSet<String> classNames = collectClassNames(fileSystem); | ||
| 42 | |||
| 43 | return new ClassCache(fileSystem, classNames); | ||
| 44 | } | ||
| 45 | |||
| 46 | private static ImmutableSet<String> collectClassNames(FileSystem fileSystem) throws IOException { | ||
| 47 | ImmutableSet.Builder<String> classNames = ImmutableSet.builder(); | ||
| 48 | for (Path root : fileSystem.getRootDirectories()) { | ||
| 49 | Files.walk(root).map(Path::toString) | ||
| 50 | .forEach(path -> { | ||
| 51 | if (path.endsWith(".class")) { | ||
| 52 | String name = path.substring(1, path.length() - ".class".length()); | ||
| 53 | classNames.add(name); | ||
| 54 | } | ||
| 55 | }); | ||
| 56 | } | ||
| 57 | |||
| 58 | return classNames.build(); | ||
| 59 | } | ||
| 60 | |||
| 61 | @Nullable | ||
| 62 | @Override | ||
| 63 | public ClassNode getClassNode(String name) { | ||
| 64 | if (!classNames.contains(name)) { | ||
| 65 | return null; | ||
| 66 | } | ||
| 67 | |||
| 68 | try { | ||
| 69 | return nodeCache.get(name, () -> parseNode(name)); | ||
| 70 | } catch (ExecutionException e) { | ||
| 71 | throw new RuntimeException(e); | ||
| 72 | } | ||
| 73 | } | ||
| 74 | |||
| 75 | private ClassNode parseNode(String name) throws IOException { | ||
| 76 | ClassReader reader = getReader(name); | ||
| 77 | |||
| 78 | ClassNode node = new ClassNode(); | ||
| 79 | |||
| 80 | LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Enigma.ASM_VERSION, node); | ||
| 81 | reader.accept(visitor, 0); | ||
| 82 | |||
| 83 | return node; | ||
| 84 | } | ||
| 85 | |||
| 86 | private ClassReader getReader(String name) throws IOException { | ||
| 87 | Path path = fileSystem.getPath(name + ".class"); | ||
| 88 | byte[] bytes = Files.readAllBytes(path); | ||
| 89 | return new ClassReader(bytes); | ||
| 90 | } | ||
| 91 | |||
| 92 | public int getClassCount() { | ||
| 93 | return classNames.size(); | ||
| 94 | } | ||
| 95 | |||
| 96 | public void visit(Supplier<ClassVisitor> visitorSupplier, int readFlags) { | ||
| 97 | for (String className : classNames) { | ||
| 98 | ClassVisitor visitor = visitorSupplier.get(); | ||
| 99 | |||
| 100 | ClassNode cached = nodeCache.getIfPresent(className); | ||
| 101 | if (cached != null) { | ||
| 102 | cached.accept(visitor); | ||
| 103 | continue; | ||
| 104 | } | ||
| 105 | |||
| 106 | try { | ||
| 107 | ClassReader reader = getReader(className); | ||
| 108 | reader.accept(visitor, readFlags); | ||
| 109 | } catch (IOException e) { | ||
| 110 | System.out.println("Failed to visit class " + className); | ||
| 111 | e.printStackTrace(); | ||
| 112 | } | ||
| 113 | } | ||
| 114 | } | ||
| 115 | |||
| 116 | @Override | ||
| 117 | public void close() throws IOException { | ||
| 118 | this.fileSystem.close(); | ||
| 119 | } | ||
| 120 | |||
| 121 | public JarIndex index(ProgressListener progress) { | ||
| 122 | JarIndex index = JarIndex.empty(); | ||
| 123 | index.indexJar(this, progress); | ||
| 124 | return index; | ||
| 125 | } | ||
| 126 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java index de3f5f5..b5ad91a 100644 --- a/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java +++ b/enigma/src/main/java/cuchaz/enigma/analysis/index/JarIndex.java | |||
| @@ -15,20 +15,21 @@ import com.google.common.collect.HashMultimap; | |||
| 15 | import com.google.common.collect.Multimap; | 15 | import com.google.common.collect.Multimap; |
| 16 | import cuchaz.enigma.Enigma; | 16 | import cuchaz.enigma.Enigma; |
| 17 | import cuchaz.enigma.ProgressListener; | 17 | import cuchaz.enigma.ProgressListener; |
| 18 | import cuchaz.enigma.analysis.ClassCache; | ||
| 19 | import cuchaz.enigma.analysis.ReferenceTargetType; | 18 | import cuchaz.enigma.analysis.ReferenceTargetType; |
| 19 | import cuchaz.enigma.classprovider.ClassProvider; | ||
| 20 | import cuchaz.enigma.translation.mapping.EntryResolver; | 20 | import cuchaz.enigma.translation.mapping.EntryResolver; |
| 21 | import cuchaz.enigma.translation.mapping.IndexEntryResolver; | 21 | import cuchaz.enigma.translation.mapping.IndexEntryResolver; |
| 22 | import cuchaz.enigma.translation.representation.Lambda; | 22 | import cuchaz.enigma.translation.representation.Lambda; |
| 23 | import cuchaz.enigma.translation.representation.entry.*; | 23 | import cuchaz.enigma.translation.representation.entry.*; |
| 24 | import cuchaz.enigma.utils.I18n; | 24 | import cuchaz.enigma.utils.I18n; |
| 25 | 25 | ||
| 26 | import org.objectweb.asm.ClassReader; | ||
| 27 | |||
| 28 | import java.util.Arrays; | 26 | import java.util.Arrays; |
| 29 | import java.util.Collection; | 27 | import java.util.Collection; |
| 28 | import java.util.HashSet; | ||
| 29 | import java.util.Set; | ||
| 30 | 30 | ||
| 31 | public class JarIndex implements JarIndexer { | 31 | public class JarIndex implements JarIndexer { |
| 32 | private final Set<String> indexedClasses = new HashSet<>(); | ||
| 32 | private final EntryIndex entryIndex; | 33 | private final EntryIndex entryIndex; |
| 33 | private final InheritanceIndex inheritanceIndex; | 34 | private final InheritanceIndex inheritanceIndex; |
| 34 | private final ReferenceIndex referenceIndex; | 35 | private final ReferenceIndex referenceIndex; |
| @@ -59,14 +60,21 @@ public class JarIndex implements JarIndexer { | |||
| 59 | return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); | 60 | return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); |
| 60 | } | 61 | } |
| 61 | 62 | ||
| 62 | public void indexJar(ClassCache classCache, ProgressListener progress) { | 63 | public void indexJar(Set<String> classNames, ClassProvider classProvider, ProgressListener progress) { |
| 64 | indexedClasses.addAll(classNames); | ||
| 63 | progress.init(4, I18n.translate("progress.jar.indexing")); | 65 | progress.init(4, I18n.translate("progress.jar.indexing")); |
| 64 | 66 | ||
| 65 | progress.step(1, I18n.translate("progress.jar.indexing.entries")); | 67 | progress.step(1, I18n.translate("progress.jar.indexing.entries")); |
| 66 | classCache.visit(() -> new IndexClassVisitor(this, Enigma.ASM_VERSION), ClassReader.SKIP_CODE); | 68 | |
| 69 | for (String className : classNames) { | ||
| 70 | classProvider.get(className).accept(new IndexClassVisitor(this, Enigma.ASM_VERSION)); | ||
| 71 | } | ||
| 67 | 72 | ||
| 68 | progress.step(2, I18n.translate("progress.jar.indexing.references")); | 73 | progress.step(2, I18n.translate("progress.jar.indexing.references")); |
| 69 | classCache.visit(() -> new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Enigma.ASM_VERSION), 0); | 74 | |
| 75 | for (String className : classNames) { | ||
| 76 | classProvider.get(className).accept(new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Enigma.ASM_VERSION)); | ||
| 77 | } | ||
| 70 | 78 | ||
| 71 | progress.step(3, I18n.translate("progress.jar.indexing.methods")); | 79 | progress.step(3, I18n.translate("progress.jar.indexing.methods")); |
| 72 | bridgeMethodIndex.findBridgeMethods(); | 80 | bridgeMethodIndex.findBridgeMethods(); |
| @@ -167,4 +175,8 @@ public class JarIndex implements JarIndexer { | |||
| 167 | public EntryResolver getEntryResolver() { | 175 | public EntryResolver getEntryResolver() { |
| 168 | return entryResolver; | 176 | return entryResolver; |
| 169 | } | 177 | } |
| 178 | |||
| 179 | public boolean isIndexed(String internalName) { | ||
| 180 | return indexedClasses.contains(internalName); | ||
| 181 | } | ||
| 170 | } | 182 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java b/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java index 0cda199..5417531 100644 --- a/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java +++ b/enigma/src/main/java/cuchaz/enigma/api/service/JarIndexerService.java | |||
| @@ -1,10 +1,21 @@ | |||
| 1 | package cuchaz.enigma.api.service; | 1 | package cuchaz.enigma.api.service; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.analysis.ClassCache; | ||
| 4 | import cuchaz.enigma.analysis.index.JarIndex; | 3 | import cuchaz.enigma.analysis.index.JarIndex; |
| 4 | import cuchaz.enigma.classprovider.ClassProvider; | ||
| 5 | import org.objectweb.asm.ClassVisitor; | ||
| 6 | |||
| 7 | import java.util.Set; | ||
| 5 | 8 | ||
| 6 | public interface JarIndexerService extends EnigmaService { | 9 | public interface JarIndexerService extends EnigmaService { |
| 7 | EnigmaServiceType<JarIndexerService> TYPE = EnigmaServiceType.create("jar_indexer"); | 10 | EnigmaServiceType<JarIndexerService> TYPE = EnigmaServiceType.create("jar_indexer"); |
| 8 | 11 | ||
| 9 | void acceptJar(ClassCache classCache, JarIndex jarIndex); | 12 | void acceptJar(Set<String> scope, ClassProvider classProvider, JarIndex jarIndex); |
| 13 | |||
| 14 | static JarIndexerService fromVisitor(ClassVisitor visitor) { | ||
| 15 | return (scope, classProvider, jarIndex) -> { | ||
| 16 | for (String className : scope) { | ||
| 17 | classProvider.get(className).accept(visitor); | ||
| 18 | } | ||
| 19 | }; | ||
| 20 | } | ||
| 10 | } | 21 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java index f9e4eff..bc38e61 100644 --- a/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java +++ b/enigma/src/main/java/cuchaz/enigma/classhandle/ClassHandleProvider.java | |||
| @@ -11,11 +11,10 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; | |||
| 11 | 11 | ||
| 12 | import javax.annotation.Nullable; | 12 | import javax.annotation.Nullable; |
| 13 | 13 | ||
| 14 | import org.objectweb.asm.tree.ClassNode; | 14 | import cuchaz.enigma.classprovider.CachingClassProvider; |
| 15 | import cuchaz.enigma.classprovider.ObfuscationFixClassProvider; | ||
| 15 | 16 | ||
| 16 | import cuchaz.enigma.Enigma; | ||
| 17 | import cuchaz.enigma.EnigmaProject; | 17 | import cuchaz.enigma.EnigmaProject; |
| 18 | import cuchaz.enigma.bytecode.translators.SourceFixVisitor; | ||
| 19 | import cuchaz.enigma.events.ClassHandleListener; | 18 | import cuchaz.enigma.events.ClassHandleListener; |
| 20 | import cuchaz.enigma.events.ClassHandleListener.InvalidationType; | 19 | import cuchaz.enigma.events.ClassHandleListener.InvalidationType; |
| 21 | import cuchaz.enigma.source.*; | 20 | import cuchaz.enigma.source.*; |
| @@ -89,17 +88,7 @@ public final class ClassHandleProvider { | |||
| 89 | } | 88 | } |
| 90 | 89 | ||
| 91 | private Decompiler createDecompiler() { | 90 | private Decompiler createDecompiler() { |
| 92 | return ds.create(name -> { | 91 | return ds.create(new CachingClassProvider(new ObfuscationFixClassProvider(project.getClassProvider(), project.getJarIndex())), new SourceSettings(true, true)); |
| 93 | ClassNode node = project.getClassCache().getClassNode(name); | ||
| 94 | |||
| 95 | if (node == null) { | ||
| 96 | return null; | ||
| 97 | } | ||
| 98 | |||
| 99 | ClassNode fixedNode = new ClassNode(); | ||
| 100 | node.accept(new SourceFixVisitor(Enigma.ASM_VERSION, fixedNode, project.getJarIndex())); | ||
| 101 | return fixedNode; | ||
| 102 | }, new SourceSettings(true, true)); | ||
| 103 | } | 92 | } |
| 104 | 93 | ||
| 105 | /** | 94 | /** |
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/CachingClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/CachingClassProvider.java new file mode 100644 index 0000000..47f5eb8 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classprovider/CachingClassProvider.java | |||
| @@ -0,0 +1,36 @@ | |||
| 1 | package cuchaz.enigma.classprovider; | ||
| 2 | |||
| 3 | import com.google.common.cache.Cache; | ||
| 4 | import com.google.common.cache.CacheBuilder; | ||
| 5 | import org.objectweb.asm.tree.ClassNode; | ||
| 6 | |||
| 7 | import javax.annotation.Nullable; | ||
| 8 | import java.util.Optional; | ||
| 9 | import java.util.concurrent.ExecutionException; | ||
| 10 | import java.util.concurrent.TimeUnit; | ||
| 11 | |||
| 12 | /** | ||
| 13 | * Wraps a ClassProvider to provide caching and synchronization. | ||
| 14 | */ | ||
| 15 | public class CachingClassProvider implements ClassProvider { | ||
| 16 | private final ClassProvider classProvider; | ||
| 17 | private final Cache<String, Optional<ClassNode>> cache = CacheBuilder.newBuilder() | ||
| 18 | .maximumSize(128) | ||
| 19 | .expireAfterAccess(1, TimeUnit.MINUTES) | ||
| 20 | .concurrencyLevel(1) | ||
| 21 | .build(); | ||
| 22 | |||
| 23 | public CachingClassProvider(ClassProvider classProvider) { | ||
| 24 | this.classProvider = classProvider; | ||
| 25 | } | ||
| 26 | |||
| 27 | @Override | ||
| 28 | @Nullable | ||
| 29 | public ClassNode get(String name) { | ||
| 30 | try { | ||
| 31 | return cache.get(name, () -> Optional.ofNullable(classProvider.get(name))).orElse(null); | ||
| 32 | } catch (ExecutionException e) { | ||
| 33 | throw new RuntimeException(e); | ||
| 34 | } | ||
| 35 | } | ||
| 36 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/ClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/ClassProvider.java new file mode 100644 index 0000000..6e4a665 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classprovider/ClassProvider.java | |||
| @@ -0,0 +1,17 @@ | |||
| 1 | package cuchaz.enigma.classprovider; | ||
| 2 | |||
| 3 | import org.objectweb.asm.tree.ClassNode; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 6 | |||
| 7 | public interface ClassProvider { | ||
| 8 | /** | ||
| 9 | * Gets the {@linkplain ClassNode} for a class. The class provider may return a cached result, | ||
| 10 | * so it's important to not mutate it. | ||
| 11 | * | ||
| 12 | * @param name the internal name of the class | ||
| 13 | * @return the {@linkplain ClassNode} for that class, or {@code null} if it was not found | ||
| 14 | */ | ||
| 15 | @Nullable | ||
| 16 | ClassNode get(String name); | ||
| 17 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/ClasspathClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/ClasspathClassProvider.java new file mode 100644 index 0000000..e9472fa --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classprovider/ClasspathClassProvider.java | |||
| @@ -0,0 +1,27 @@ | |||
| 1 | package cuchaz.enigma.classprovider; | ||
| 2 | |||
| 3 | import org.objectweb.asm.ClassReader; | ||
| 4 | import org.objectweb.asm.tree.ClassNode; | ||
| 5 | |||
| 6 | import javax.annotation.Nullable; | ||
| 7 | import java.io.IOException; | ||
| 8 | import java.io.InputStream; | ||
| 9 | |||
| 10 | /** | ||
| 11 | * Provides classes by loading them from the classpath. | ||
| 12 | */ | ||
| 13 | public class ClasspathClassProvider implements ClassProvider { | ||
| 14 | @Nullable @Override public ClassNode get(String name) { | ||
| 15 | try (InputStream in = ClasspathClassProvider.class.getResourceAsStream("/" + name + ".class")) { | ||
| 16 | if (in == null) { | ||
| 17 | return null; | ||
| 18 | } | ||
| 19 | |||
| 20 | ClassNode node = new ClassNode(); | ||
| 21 | new ClassReader(in).accept(node, 0); | ||
| 22 | return node; | ||
| 23 | } catch (IOException e) { | ||
| 24 | return null; | ||
| 25 | } | ||
| 26 | } | ||
| 27 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/CombiningClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/CombiningClassProvider.java new file mode 100644 index 0000000..865464c --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classprovider/CombiningClassProvider.java | |||
| @@ -0,0 +1,31 @@ | |||
| 1 | package cuchaz.enigma.classprovider; | ||
| 2 | |||
| 3 | import org.objectweb.asm.tree.ClassNode; | ||
| 4 | |||
| 5 | import javax.annotation.Nullable; | ||
| 6 | |||
| 7 | /** | ||
| 8 | * Combines a list of {@link ClassProvider}s into one, calling each one in a row | ||
| 9 | * until one can provide the class. | ||
| 10 | */ | ||
| 11 | public class CombiningClassProvider implements ClassProvider { | ||
| 12 | private final ClassProvider[] classProviders; | ||
| 13 | |||
| 14 | public CombiningClassProvider(ClassProvider... classProviders) { | ||
| 15 | this.classProviders = classProviders; | ||
| 16 | } | ||
| 17 | |||
| 18 | @Override | ||
| 19 | @Nullable | ||
| 20 | public ClassNode get(String name) { | ||
| 21 | for (ClassProvider cp : classProviders) { | ||
| 22 | ClassNode node = cp.get(name); | ||
| 23 | |||
| 24 | if (node != null) { | ||
| 25 | return node; | ||
| 26 | } | ||
| 27 | } | ||
| 28 | |||
| 29 | return null; | ||
| 30 | } | ||
| 31 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/JarClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/JarClassProvider.java new file mode 100644 index 0000000..c614b0a --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classprovider/JarClassProvider.java | |||
| @@ -0,0 +1,64 @@ | |||
| 1 | package cuchaz.enigma.classprovider; | ||
| 2 | |||
| 3 | import com.google.common.collect.ImmutableSet; | ||
| 4 | import cuchaz.enigma.utils.AsmUtil; | ||
| 5 | import org.objectweb.asm.tree.ClassNode; | ||
| 6 | |||
| 7 | import javax.annotation.Nullable; | ||
| 8 | import java.io.IOException; | ||
| 9 | import java.nio.file.FileSystem; | ||
| 10 | import java.nio.file.FileSystems; | ||
| 11 | import java.nio.file.Files; | ||
| 12 | import java.nio.file.Path; | ||
| 13 | import java.util.Set; | ||
| 14 | |||
| 15 | /** | ||
| 16 | * Provides classes by loading them from a JAR file. | ||
| 17 | */ | ||
| 18 | public class JarClassProvider implements AutoCloseable, ClassProvider { | ||
| 19 | private final FileSystem fileSystem; | ||
| 20 | private final Set<String> classNames; | ||
| 21 | |||
| 22 | public JarClassProvider(Path jarPath) throws IOException { | ||
| 23 | this.fileSystem = FileSystems.newFileSystem(jarPath, (ClassLoader) null); | ||
| 24 | this.classNames = collectClassNames(fileSystem); | ||
| 25 | } | ||
| 26 | |||
| 27 | private static ImmutableSet<String> collectClassNames(FileSystem fileSystem) throws IOException { | ||
| 28 | ImmutableSet.Builder<String> classNames = ImmutableSet.builder(); | ||
| 29 | for (Path root : fileSystem.getRootDirectories()) { | ||
| 30 | Files.walk(root).map(Path::toString) | ||
| 31 | .forEach(path -> { | ||
| 32 | if (path.endsWith(".class")) { | ||
| 33 | String name = path.substring(1, path.length() - ".class".length()); | ||
| 34 | classNames.add(name); | ||
| 35 | } | ||
| 36 | }); | ||
| 37 | } | ||
| 38 | |||
| 39 | return classNames.build(); | ||
| 40 | } | ||
| 41 | |||
| 42 | public Set<String> getClassNames() { | ||
| 43 | return classNames; | ||
| 44 | } | ||
| 45 | |||
| 46 | @Nullable | ||
| 47 | @Override | ||
| 48 | public ClassNode get(String name) { | ||
| 49 | if (!classNames.contains(name)) { | ||
| 50 | return null; | ||
| 51 | } | ||
| 52 | |||
| 53 | try { | ||
| 54 | return AsmUtil.bytesToNode(Files.readAllBytes(fileSystem.getPath(name + ".class"))); | ||
| 55 | } catch (IOException e) { | ||
| 56 | throw new RuntimeException(e); | ||
| 57 | } | ||
| 58 | } | ||
| 59 | |||
| 60 | @Override | ||
| 61 | public void close() throws Exception { | ||
| 62 | fileSystem.close(); | ||
| 63 | } | ||
| 64 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/classprovider/ObfuscationFixClassProvider.java b/enigma/src/main/java/cuchaz/enigma/classprovider/ObfuscationFixClassProvider.java new file mode 100644 index 0000000..db9e2d0 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/classprovider/ObfuscationFixClassProvider.java | |||
| @@ -0,0 +1,85 @@ | |||
| 1 | package cuchaz.enigma.classprovider; | ||
| 2 | |||
| 3 | import cuchaz.enigma.Enigma; | ||
| 4 | import cuchaz.enigma.analysis.index.JarIndex; | ||
| 5 | import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor; | ||
| 6 | import cuchaz.enigma.bytecode.translators.SourceFixVisitor; | ||
| 7 | import cuchaz.enigma.classprovider.ClassProvider; | ||
| 8 | import org.objectweb.asm.ClassVisitor; | ||
| 9 | import org.objectweb.asm.Opcodes; | ||
| 10 | import org.objectweb.asm.tree.AbstractInsnNode; | ||
| 11 | import org.objectweb.asm.tree.ClassNode; | ||
| 12 | import org.objectweb.asm.tree.MethodInsnNode; | ||
| 13 | import org.objectweb.asm.tree.MethodNode; | ||
| 14 | |||
| 15 | import javax.annotation.Nullable; | ||
| 16 | |||
| 17 | /** | ||
| 18 | * Wraps a ClassProvider to apply fixes to the following problems introduced by the obfuscator, | ||
| 19 | * so that the classes can be decompiled correctly: | ||
| 20 | * <ul> | ||
| 21 | * <li>Bridge methods missing the "bridge" or "synthetic" access modifier | ||
| 22 | * <li>.getClass() calls added by Proguard (Proguard adds these to preserve semantics when Proguard removes | ||
| 23 | * a field access which may have caused a {@code NullPointerException}). | ||
| 24 | * <li>LVT names that don't match parameter table name | ||
| 25 | * <li>LVT names that are invalid or duplicate | ||
| 26 | * <li>Enum constructor parameters that are incorrectly named or missing the "synthetic" access modifier | ||
| 27 | * <li>"this" parameter which is incorrectly named | ||
| 28 | * </ul> | ||
| 29 | * <p> | ||
| 30 | * These fixes are only applied to classes that were indexed by the JarIndex provided, and not library classes. | ||
| 31 | */ | ||
| 32 | public class ObfuscationFixClassProvider implements ClassProvider { | ||
| 33 | private final ClassProvider classProvider; | ||
| 34 | private final JarIndex jarIndex; | ||
| 35 | |||
| 36 | public ObfuscationFixClassProvider(ClassProvider classProvider, JarIndex jarIndex) { | ||
| 37 | this.classProvider = classProvider; | ||
| 38 | this.jarIndex = jarIndex; | ||
| 39 | } | ||
| 40 | |||
| 41 | @Override | ||
| 42 | @Nullable | ||
| 43 | public ClassNode get(String name) { | ||
| 44 | ClassNode node = classProvider.get(name); | ||
| 45 | |||
| 46 | if (!jarIndex.isIndexed(name)) { | ||
| 47 | return node; | ||
| 48 | } | ||
| 49 | |||
| 50 | ClassNode fixedNode = new ClassNode(); | ||
| 51 | ClassVisitor visitor = fixedNode; | ||
| 52 | visitor = new LocalVariableFixVisitor(Enigma.ASM_VERSION, visitor); | ||
| 53 | visitor = new SourceFixVisitor(Enigma.ASM_VERSION, visitor, jarIndex); | ||
| 54 | node.accept(visitor); | ||
| 55 | removeRedundantClassCalls(fixedNode); | ||
| 56 | |||
| 57 | return fixedNode; | ||
| 58 | } | ||
| 59 | |||
| 60 | private void removeRedundantClassCalls(ClassNode node) { | ||
| 61 | // Removes .getClass() calls added by Proguard: | ||
| 62 | // DUP | ||
| 63 | // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; | ||
| 64 | // POP | ||
| 65 | for (MethodNode methodNode : node.methods) { | ||
| 66 | AbstractInsnNode insnNode = methodNode.instructions.getFirst(); | ||
| 67 | while (insnNode != null) { | ||
| 68 | if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { | ||
| 69 | MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; | ||
| 70 | if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { | ||
| 71 | AbstractInsnNode previous = methodInsnNode.getPrevious(); | ||
| 72 | AbstractInsnNode next = methodInsnNode.getNext(); | ||
| 73 | if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { | ||
| 74 | insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction | ||
| 75 | methodNode.instructions.remove(previous); | ||
| 76 | methodNode.instructions.remove(methodInsnNode); | ||
| 77 | methodNode.instructions.remove(next); | ||
| 78 | } | ||
| 79 | } | ||
| 80 | } | ||
| 81 | insnNode = insnNode.getNext(); | ||
| 82 | } | ||
| 83 | } | ||
| 84 | } | ||
| 85 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java b/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java index 377ccbc..638498f 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java +++ b/enigma/src/main/java/cuchaz/enigma/source/DecompilerService.java | |||
| @@ -1,6 +1,6 @@ | |||
| 1 | package cuchaz.enigma.source; | 1 | package cuchaz.enigma.source; |
| 2 | 2 | ||
| 3 | import cuchaz.enigma.ClassProvider; | 3 | import cuchaz.enigma.classprovider.ClassProvider; |
| 4 | import cuchaz.enigma.api.service.EnigmaService; | 4 | import cuchaz.enigma.api.service.EnigmaService; |
| 5 | import cuchaz.enigma.api.service.EnigmaServiceType; | 5 | import cuchaz.enigma.api.service.EnigmaServiceType; |
| 6 | 6 | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java index 9e37f16..941a699 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java +++ b/enigma/src/main/java/cuchaz/enigma/source/cfr/CfrDecompiler.java | |||
| @@ -1,10 +1,10 @@ | |||
| 1 | package cuchaz.enigma.source.cfr; | 1 | package cuchaz.enigma.source.cfr; |
| 2 | 2 | ||
| 3 | import com.google.common.io.ByteStreams; | 3 | import cuchaz.enigma.classprovider.ClassProvider; |
| 4 | import cuchaz.enigma.ClassProvider; | ||
| 5 | import cuchaz.enigma.source.Decompiler; | 4 | import cuchaz.enigma.source.Decompiler; |
| 6 | import cuchaz.enigma.source.Source; | 5 | import cuchaz.enigma.source.Source; |
| 7 | import cuchaz.enigma.source.SourceSettings; | 6 | import cuchaz.enigma.source.SourceSettings; |
| 7 | import cuchaz.enigma.utils.AsmUtil; | ||
| 8 | import org.benf.cfr.reader.apiunreleased.ClassFileSource2; | 8 | import org.benf.cfr.reader.apiunreleased.ClassFileSource2; |
| 9 | import org.benf.cfr.reader.apiunreleased.JarContent; | 9 | import org.benf.cfr.reader.apiunreleased.JarContent; |
| 10 | import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; | 10 | import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; |
| @@ -19,16 +19,12 @@ import org.benf.cfr.reader.util.CannotLoadClassException; | |||
| 19 | import org.benf.cfr.reader.util.collections.ListFactory; | 19 | import org.benf.cfr.reader.util.collections.ListFactory; |
| 20 | import org.benf.cfr.reader.util.getopt.Options; | 20 | import org.benf.cfr.reader.util.getopt.Options; |
| 21 | import org.benf.cfr.reader.util.getopt.OptionsImpl; | 21 | import org.benf.cfr.reader.util.getopt.OptionsImpl; |
| 22 | import org.objectweb.asm.ClassWriter; | ||
| 23 | import org.objectweb.asm.tree.ClassNode; | 22 | import org.objectweb.asm.tree.ClassNode; |
| 24 | 23 | ||
| 25 | import java.io.IOException; | ||
| 26 | import java.io.InputStream; | ||
| 27 | import java.util.Collection; | 24 | import java.util.Collection; |
| 28 | import java.util.HashMap; | 25 | import java.util.HashMap; |
| 29 | import java.util.Map; | 26 | import java.util.Map; |
| 30 | 27 | ||
| 31 | |||
| 32 | public class CfrDecompiler implements Decompiler { | 28 | public class CfrDecompiler implements Decompiler { |
| 33 | private final DCCommonState state; | 29 | private final DCCommonState state; |
| 34 | 30 | ||
| @@ -58,21 +54,13 @@ public class CfrDecompiler implements Decompiler { | |||
| 58 | 54 | ||
| 59 | @Override | 55 | @Override |
| 60 | public Pair<byte[], String> getClassFileContent(String path) { | 56 | public Pair<byte[], String> getClassFileContent(String path) { |
| 61 | ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.'))); | 57 | ClassNode node = classProvider.get(path.substring(0, path.lastIndexOf('.'))); |
| 62 | 58 | ||
| 63 | if (node == null) { | 59 | 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; | 60 | return null; |
| 71 | } | 61 | } |
| 72 | 62 | ||
| 73 | ClassWriter cw = new ClassWriter(0); | 63 | return new Pair<>(AsmUtil.nodeToBytes(node), path); |
| 74 | node.accept(cw); | ||
| 75 | return new Pair<>(cw.toByteArray(), path); | ||
| 76 | } | 64 | } |
| 77 | }); | 65 | }); |
| 78 | } | 66 | } |
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java index 3cbd680..9dc1e0a 100644 --- a/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java +++ b/enigma/src/main/java/cuchaz/enigma/source/procyon/ProcyonDecompiler.java | |||
| @@ -11,14 +11,13 @@ import com.strobel.decompiler.languages.java.JavaFormattingOptions; | |||
| 11 | import com.strobel.decompiler.languages.java.ast.AstBuilder; | 11 | import com.strobel.decompiler.languages.java.ast.AstBuilder; |
| 12 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; | 12 | import com.strobel.decompiler.languages.java.ast.CompilationUnit; |
| 13 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; | 13 | import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; |
| 14 | import cuchaz.enigma.ClassProvider; | 14 | import cuchaz.enigma.classprovider.ClassProvider; |
| 15 | import cuchaz.enigma.source.Source; | 15 | import cuchaz.enigma.source.Source; |
| 16 | import cuchaz.enigma.source.Decompiler; | 16 | import cuchaz.enigma.source.Decompiler; |
| 17 | import cuchaz.enigma.source.SourceSettings; | 17 | import cuchaz.enigma.source.SourceSettings; |
| 18 | import cuchaz.enigma.source.procyon.transformers.*; | 18 | import cuchaz.enigma.source.procyon.transformers.*; |
| 19 | import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader; | 19 | import cuchaz.enigma.utils.AsmUtil; |
| 20 | import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem; | 20 | import org.objectweb.asm.tree.ClassNode; |
| 21 | import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader; | ||
| 22 | 21 | ||
| 23 | public class ProcyonDecompiler implements Decompiler { | 22 | public class ProcyonDecompiler implements Decompiler { |
| 24 | private final SourceSettings settings; | 23 | private final SourceSettings settings; |
| @@ -26,9 +25,21 @@ public class ProcyonDecompiler implements Decompiler { | |||
| 26 | private final MetadataSystem metadataSystem; | 25 | private final MetadataSystem metadataSystem; |
| 27 | 26 | ||
| 28 | public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) { | 27 | public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) { |
| 29 | ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider)); | 28 | ITypeLoader typeLoader = (name, buffer) -> { |
| 29 | ClassNode node = classProvider.get(name); | ||
| 30 | 30 | ||
| 31 | metadataSystem = new NoRetryMetadataSystem(typeLoader); | 31 | if (node == null) { |
| 32 | return false; | ||
| 33 | } | ||
| 34 | |||
| 35 | byte[] data = AsmUtil.nodeToBytes(node); | ||
| 36 | buffer.reset(data.length); | ||
| 37 | System.arraycopy(data, 0, buffer.array(), buffer.position(), data.length); | ||
| 38 | buffer.position(0); | ||
| 39 | return true; | ||
| 40 | }; | ||
| 41 | |||
| 42 | metadataSystem = new MetadataSystem(typeLoader); | ||
| 32 | metadataSystem.setEagerMethodLoadingEnabled(true); | 43 | metadataSystem.setEagerMethodLoadingEnabled(true); |
| 33 | 44 | ||
| 34 | decompilerSettings = DecompilerSettings.javaDefaults(); | 45 | decompilerSettings = DecompilerSettings.javaDefaults(); |
diff --git a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java deleted file mode 100644 index e702956..0000000 --- a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java +++ /dev/null | |||
| @@ -1,33 +0,0 @@ | |||
| 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/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java deleted file mode 100644 index 5be5ddd..0000000 --- a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java +++ /dev/null | |||
| @@ -1,38 +0,0 @@ | |||
| 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/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java deleted file mode 100644 index e703d3b..0000000 --- a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java +++ /dev/null | |||
| @@ -1,140 +0,0 @@ | |||
| 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/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java deleted file mode 100644 index c4732b0..0000000 --- a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java +++ /dev/null | |||
| @@ -1,38 +0,0 @@ | |||
| 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/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java b/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java deleted file mode 100644 index 86c6ecc..0000000 --- a/enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java +++ /dev/null | |||
| @@ -1,20 +0,0 @@ | |||
| 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 | } | ||
diff --git a/enigma/src/main/java/cuchaz/enigma/utils/AsmUtil.java b/enigma/src/main/java/cuchaz/enigma/utils/AsmUtil.java new file mode 100644 index 0000000..7d34b02 --- /dev/null +++ b/enigma/src/main/java/cuchaz/enigma/utils/AsmUtil.java | |||
| @@ -0,0 +1,20 @@ | |||
| 1 | package cuchaz.enigma.utils; | ||
| 2 | |||
| 3 | import org.objectweb.asm.ClassReader; | ||
| 4 | import org.objectweb.asm.ClassWriter; | ||
| 5 | import org.objectweb.asm.tree.ClassNode; | ||
| 6 | |||
| 7 | public class AsmUtil { | ||
| 8 | public static byte[] nodeToBytes(ClassNode node) { | ||
| 9 | ClassWriter w = new ClassWriter(0); | ||
| 10 | node.accept(w); | ||
| 11 | return w.toByteArray(); | ||
| 12 | } | ||
| 13 | |||
| 14 | public static ClassNode bytesToNode(byte[] bytes) { | ||
| 15 | ClassReader r = new ClassReader(bytes); | ||
| 16 | ClassNode node = new ClassNode(); | ||
| 17 | r.accept(node, 0); | ||
| 18 | return node; | ||
| 19 | } | ||
| 20 | } | ||