From f0885819aeeb2edbfcfc0b23566cccb571166a02 Mon Sep 17 00:00:00 2001 From: Runemoro Date: Mon, 6 Jul 2020 06:34:10 -0400 Subject: Make class loading more flexible (#277) --- .../enigma/command/CheckMappingsCommand.java | 3 +- .../main/java/cuchaz/enigma/command/Command.java | 3 +- .../command/MapSpecializedMethodsCommand.java | 16 ++- .../enigma/network/DedicatedEnigmaServer.java | 3 +- .../main/java/cuchaz/enigma/gui/GuiController.java | 3 +- .../src/main/java/cuchaz/enigma/ClassProvider.java | 10 -- enigma/src/main/java/cuchaz/enigma/Enigma.java | 19 ++- .../src/main/java/cuchaz/enigma/EnigmaProject.java | 40 +++--- .../java/cuchaz/enigma/analysis/BuiltinPlugin.java | 3 +- .../java/cuchaz/enigma/analysis/ClassCache.java | 126 ------------------- .../cuchaz/enigma/analysis/index/JarIndex.java | 24 +++- .../enigma/api/service/JarIndexerService.java | 15 ++- .../enigma/classhandle/ClassHandleProvider.java | 17 +-- .../enigma/classprovider/CachingClassProvider.java | 36 ++++++ .../cuchaz/enigma/classprovider/ClassProvider.java | 17 +++ .../classprovider/ClasspathClassProvider.java | 27 ++++ .../classprovider/CombiningClassProvider.java | 31 +++++ .../enigma/classprovider/JarClassProvider.java | 64 ++++++++++ .../classprovider/ObfuscationFixClassProvider.java | 85 +++++++++++++ .../cuchaz/enigma/source/DecompilerService.java | 2 +- .../cuchaz/enigma/source/cfr/CfrDecompiler.java | 20 +-- .../enigma/source/procyon/ProcyonDecompiler.java | 23 +++- .../typeloader/CachingClasspathTypeLoader.java | 33 ----- .../procyon/typeloader/CachingTypeLoader.java | 38 ------ .../typeloader/CompiledSourceTypeLoader.java | 140 --------------------- .../procyon/typeloader/NoRetryMetadataSystem.java | 38 ------ .../procyon/typeloader/SynchronizedTypeLoader.java | 20 --- .../src/main/java/cuchaz/enigma/utils/AsmUtil.java | 20 +++ .../cuchaz/enigma/PackageVisibilityIndexTest.java | 10 +- .../src/test/java/cuchaz/enigma/TestDeobfed.java | 27 ++-- .../test/java/cuchaz/enigma/TestDeobfuscator.java | 5 +- .../test/java/cuchaz/enigma/TestInnerClasses.java | 13 +- .../enigma/TestJarIndexConstructorReferences.java | 10 +- .../cuchaz/enigma/TestJarIndexInheritanceTree.java | 13 +- .../java/cuchaz/enigma/TestJarIndexLoneClass.java | 9 +- .../src/test/java/cuchaz/enigma/TokenChecker.java | 9 +- .../mapping/TestTinyV2InnerClasses.java | 3 +- 37 files changed, 440 insertions(+), 535 deletions(-) delete mode 100644 enigma/src/main/java/cuchaz/enigma/ClassProvider.java delete mode 100644 enigma/src/main/java/cuchaz/enigma/analysis/ClassCache.java create mode 100644 enigma/src/main/java/cuchaz/enigma/classprovider/CachingClassProvider.java create mode 100644 enigma/src/main/java/cuchaz/enigma/classprovider/ClassProvider.java create mode 100644 enigma/src/main/java/cuchaz/enigma/classprovider/ClasspathClassProvider.java create mode 100644 enigma/src/main/java/cuchaz/enigma/classprovider/CombiningClassProvider.java create mode 100644 enigma/src/main/java/cuchaz/enigma/classprovider/JarClassProvider.java create mode 100644 enigma/src/main/java/cuchaz/enigma/classprovider/ObfuscationFixClassProvider.java delete mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingClasspathTypeLoader.java delete mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CachingTypeLoader.java delete mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/CompiledSourceTypeLoader.java delete mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/NoRetryMetadataSystem.java delete mode 100644 enigma/src/main/java/cuchaz/enigma/source/procyon/typeloader/SynchronizedTypeLoader.java create mode 100644 enigma/src/main/java/cuchaz/enigma/utils/AsmUtil.java diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java index e4deef8..75ef225 100644 --- a/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/CheckMappingsCommand.java @@ -4,6 +4,7 @@ import cuchaz.enigma.Enigma; import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.ProgressListener; import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.classprovider.ClasspathClassProvider; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; import cuchaz.enigma.translation.mapping.serde.MappingFormat; @@ -39,7 +40,7 @@ public class CheckMappingsCommand extends Command { System.out.println("Reading JAR..."); - EnigmaProject project = enigma.openJar(fileJarIn, ProgressListener.none()); + EnigmaProject project = enigma.openJar(fileJarIn, new ClasspathClassProvider(), ProgressListener.none()); System.out.println("Reading mappings..."); diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java b/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java index 0640e3e..0d71f02 100644 --- a/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/Command.java @@ -3,6 +3,7 @@ package cuchaz.enigma.command; import cuchaz.enigma.Enigma; import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.classprovider.ClasspathClassProvider; import cuchaz.enigma.translation.mapping.EntryMapping; import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; import cuchaz.enigma.translation.mapping.serde.MappingFormat; @@ -34,7 +35,7 @@ public abstract class Command { Enigma enigma = Enigma.create(); System.out.println("Reading jar..."); - EnigmaProject project = enigma.openJar(fileJarIn, progress); + EnigmaProject project = enigma.openJar(fileJarIn, new ClasspathClassProvider(), progress); if (fileMappings != null) { System.out.println("Reading mappings..."); diff --git a/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java b/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java index 292de19..94f7010 100644 --- a/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java +++ b/enigma-cli/src/main/java/cuchaz/enigma/command/MapSpecializedMethodsCommand.java @@ -1,14 +1,15 @@ package cuchaz.enigma.command; import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.index.BridgeMethodIndex; import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; -import cuchaz.enigma.translation.mapping.serde.MappingParseException; +import cuchaz.enigma.classprovider.CachingClassProvider; +import cuchaz.enigma.classprovider.JarClassProvider; import cuchaz.enigma.translation.MappingTranslator; import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.*; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.serde.MappingFileNameFormat; +import cuchaz.enigma.translation.mapping.serde.MappingParseException; import cuchaz.enigma.translation.mapping.serde.MappingSaveParameters; import cuchaz.enigma.translation.mapping.tree.EntryTree; import cuchaz.enigma.translation.mapping.tree.EntryTreeNode; @@ -45,8 +46,11 @@ public class MapSpecializedMethodsCommand extends Command { MappingSaveParameters saveParameters = new MappingSaveParameters(MappingFileNameFormat.BY_DEOBF); EntryTree source = MappingCommandsUtil.read(sourceFormat, sourcePath, saveParameters); EntryTree result = new HashEntryTree<>(); - ClassCache classCache = ClassCache.of(jar); - JarIndex jarIndex = classCache.index(ProgressListener.none()); + + JarClassProvider jcp = new JarClassProvider(jar); + JarIndex jarIndex = JarIndex.empty(); + jarIndex.indexJar(jcp.getClassNames(), new CachingClassProvider(jcp), ProgressListener.none()); + BridgeMethodIndex bridgeMethodIndex = jarIndex.getBridgeMethodIndex(); Translator translator = new MappingTranslator(source, jarIndex.getEntryResolver()); diff --git a/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java index 924302f..41f0834 100644 --- a/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java +++ b/enigma-server/src/main/java/cuchaz/enigma/network/DedicatedEnigmaServer.java @@ -2,6 +2,7 @@ package cuchaz.enigma.network; import com.google.common.io.MoreFiles; import cuchaz.enigma.*; +import cuchaz.enigma.classprovider.ClasspathClassProvider; import cuchaz.enigma.translation.mapping.serde.MappingParseException; import cuchaz.enigma.translation.mapping.EntryRemapper; import cuchaz.enigma.translation.mapping.serde.MappingFormat; @@ -108,7 +109,7 @@ public class DedicatedEnigmaServer extends EnigmaServer { EnigmaProfile profile = EnigmaProfile.read(profileFile); Enigma enigma = Enigma.builder().setProfile(profile).build(); System.out.println("Indexing Jar..."); - EnigmaProject project = enigma.openJar(jar, ProgressListener.none()); + EnigmaProject project = enigma.openJar(jar, new ClasspathClassProvider(), ProgressListener.none()); MappingFormat mappingFormat = MappingFormat.ENIGMA_DIRECTORY; EntryRemapper mappings; diff --git a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java index 66a21b0..2dc1d77 100644 --- a/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java +++ b/enigma-swing/src/main/java/cuchaz/enigma/gui/GuiController.java @@ -34,6 +34,7 @@ import cuchaz.enigma.EnigmaProfile; import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.analysis.*; import cuchaz.enigma.api.service.ObfuscationTestService; +import cuchaz.enigma.classprovider.ClasspathClassProvider; import cuchaz.enigma.classhandle.ClassHandle; import cuchaz.enigma.classhandle.ClassHandleProvider; import cuchaz.enigma.gui.config.Config; @@ -95,7 +96,7 @@ public class GuiController implements ClientPacketHandler { this.gui.onStartOpenJar(); return ProgressDialog.runOffThread(gui.getFrame(), progress -> { - project = enigma.openJar(jarPath, progress); + project = enigma.openJar(jarPath, new ClasspathClassProvider(), progress); indexTreeBuilder = new IndexTreeBuilder(project.getJarIndex()); chp = new ClassHandleProvider(project, Config.getInstance().decompiler.service); gui.onFinishOpenJar(jarPath.getFileName().toString()); 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 @@ -package cuchaz.enigma; - -import org.objectweb.asm.tree.ClassNode; - -import javax.annotation.Nullable; - -public interface ClassProvider { - @Nullable - ClassNode getClassNode(String name); -} 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; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableListMultimap; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.api.EnigmaPlugin; import cuchaz.enigma.api.EnigmaPluginContext; @@ -21,6 +20,10 @@ import cuchaz.enigma.api.service.EnigmaService; import cuchaz.enigma.api.service.EnigmaServiceFactory; import cuchaz.enigma.api.service.EnigmaServiceType; import cuchaz.enigma.api.service.JarIndexerService; +import cuchaz.enigma.classprovider.CachingClassProvider; +import cuchaz.enigma.classprovider.ClassProvider; +import cuchaz.enigma.classprovider.CombiningClassProvider; +import cuchaz.enigma.classprovider.JarClassProvider; import cuchaz.enigma.utils.Utils; import org.objectweb.asm.Opcodes; @@ -28,6 +31,7 @@ import java.io.IOException; import java.nio.file.Path; import java.util.List; import java.util.ServiceLoader; +import java.util.Set; public class Enigma { public static final String NAME = "Enigma"; @@ -51,13 +55,16 @@ public class Enigma { return new Builder(); } - public EnigmaProject openJar(Path path, ProgressListener progress) throws IOException { - ClassCache classCache = ClassCache.of(path); - JarIndex jarIndex = classCache.index(progress); + public EnigmaProject openJar(Path path, ClassProvider libraryClassProvider, ProgressListener progress) throws IOException { + JarClassProvider jarClassProvider = new JarClassProvider(path); + ClassProvider classProvider = new CachingClassProvider(new CombiningClassProvider(jarClassProvider, libraryClassProvider)); + Set scope = jarClassProvider.getClassNames(); - services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(classCache, jarIndex)); + JarIndex index = JarIndex.empty(); + index.indexJar(scope, classProvider, progress); + services.get(JarIndexerService.TYPE).forEach(indexer -> indexer.acceptJar(scope, classProvider, index)); - return new EnigmaProject(this, classCache, jarIndex, Utils.zipSha1(path)); + return new EnigmaProject(this, classProvider, index, Utils.zipSha1(path)); } 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; import com.google.common.base.Functions; import com.google.common.base.Preconditions; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.api.service.NameProposalService; import cuchaz.enigma.bytecode.translators.SourceFixVisitor; import cuchaz.enigma.bytecode.translators.TranslationClassVisitor; -import cuchaz.enigma.source.*; +import cuchaz.enigma.classprovider.ClassProvider; +import cuchaz.enigma.source.Decompiler; +import cuchaz.enigma.source.DecompilerService; +import cuchaz.enigma.source.SourceSettings; import cuchaz.enigma.translation.ProposingTranslator; import cuchaz.enigma.translation.Translator; -import cuchaz.enigma.translation.mapping.*; +import cuchaz.enigma.translation.mapping.EntryMapping; +import cuchaz.enigma.translation.mapping.EntryRemapper; +import cuchaz.enigma.translation.mapping.MappingsChecker; import cuchaz.enigma.translation.mapping.tree.DeltaTrackingTree; import cuchaz.enigma.translation.mapping.tree.EntryTree; import cuchaz.enigma.translation.representation.entry.ClassEntry; @@ -19,11 +23,11 @@ import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.translation.representation.entry.LocalVariableEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; import cuchaz.enigma.utils.I18n; - import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; -import java.io.*; +import java.io.BufferedWriter; +import java.io.IOException; import java.nio.file.Files; import java.nio.file.Path; import java.util.Collection; @@ -37,16 +41,16 @@ import java.util.stream.Collectors; public class EnigmaProject { private final Enigma enigma; - private final ClassCache classCache; + private final ClassProvider classProvider; private final JarIndex jarIndex; private final byte[] jarChecksum; private EntryRemapper mapper; - public EnigmaProject(Enigma enigma, ClassCache classCache, JarIndex jarIndex, byte[] jarChecksum) { + public EnigmaProject(Enigma enigma, ClassProvider classProvider, JarIndex jarIndex, byte[] jarChecksum) { Preconditions.checkArgument(jarChecksum.length == 20); this.enigma = enigma; - this.classCache = classCache; + this.classProvider = classProvider; this.jarIndex = jarIndex; this.jarChecksum = jarChecksum; @@ -65,8 +69,8 @@ public class EnigmaProject { return enigma; } - public ClassCache getClassCache() { - return classCache; + public ClassProvider getClassProvider() { + return classProvider; } public JarIndex getJarIndex() { @@ -103,20 +107,6 @@ public class EnigmaProject { return droppedMappings.keySet(); } - public Decompiler createDecompiler(DecompilerService decompilerService) { - return decompilerService.create(name -> { - ClassNode node = this.getClassCache().getClassNode(name); - - if (node == null) { - return null; - } - - ClassNode fixedNode = new ClassNode(); - node.accept(new SourceFixVisitor(Enigma.ASM_VERSION, fixedNode, getJarIndex())); - return fixedNode; - }, new SourceSettings(true, true)); - } - public boolean isRenamable(Entry obfEntry) { if (obfEntry instanceof MethodEntry) { // HACKHACK: Object methods are not obfuscated identifiers @@ -171,7 +161,7 @@ public class EnigmaProject { ClassEntry translatedEntry = deobfuscator.translate(entry); progress.step(count.getAndIncrement(), translatedEntry.toString()); - ClassNode node = classCache.getClassNode(entry.getFullName()); + ClassNode node = classProvider.get(entry.getFullName()); if (node != null) { ClassNode translatedNode = new ClassNode(); 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; import cuchaz.enigma.translation.representation.entry.Entry; import cuchaz.enigma.translation.representation.entry.FieldEntry; import cuchaz.enigma.utils.Pair; -import org.objectweb.asm.ClassReader; import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.FieldVisitor; import org.objectweb.asm.MethodVisitor; @@ -51,7 +50,7 @@ public final class BuiltinPlugin implements EnigmaPlugin { final Map, String> names = new HashMap<>(); final EnumFieldNameFindingVisitor visitor = new EnumFieldNameFindingVisitor(names); - ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> (classCache, jarIndex) -> classCache.visit(() -> visitor, ClassReader.SKIP_FRAMES)); + ctx.registerService("enigma:enum_initializer_indexer", JarIndexerService.TYPE, ctx1 -> JarIndexerService.fromVisitor(visitor)); ctx.registerService("enigma:enum_name_proposer", NameProposalService.TYPE, ctx1 -> (obfEntry, remapper) -> Optional.ofNullable(names.get(obfEntry))); } 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 @@ -package cuchaz.enigma.analysis; - -import com.google.common.cache.Cache; -import com.google.common.cache.CacheBuilder; -import com.google.common.collect.ImmutableSet; -import cuchaz.enigma.ClassProvider; -import cuchaz.enigma.Enigma; -import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.analysis.index.JarIndex; -import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor; -import org.objectweb.asm.ClassReader; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.tree.ClassNode; - -import javax.annotation.Nullable; -import java.io.IOException; -import java.nio.file.FileSystem; -import java.nio.file.FileSystems; -import java.nio.file.Files; -import java.nio.file.Path; -import java.util.concurrent.ExecutionException; -import java.util.concurrent.TimeUnit; -import java.util.function.Supplier; - -public final class ClassCache implements AutoCloseable, ClassProvider { - private final FileSystem fileSystem; - private final ImmutableSet classNames; - - private final Cache nodeCache = CacheBuilder.newBuilder() - .maximumSize(128) - .expireAfterAccess(1, TimeUnit.MINUTES) - .build(); - - private ClassCache(FileSystem fileSystem, ImmutableSet classNames) { - this.fileSystem = fileSystem; - this.classNames = classNames; - } - - public static ClassCache of(Path jarPath) throws IOException { - FileSystem fileSystem = FileSystems.newFileSystem(jarPath, (ClassLoader) null); - ImmutableSet classNames = collectClassNames(fileSystem); - - return new ClassCache(fileSystem, classNames); - } - - private static ImmutableSet collectClassNames(FileSystem fileSystem) throws IOException { - ImmutableSet.Builder classNames = ImmutableSet.builder(); - for (Path root : fileSystem.getRootDirectories()) { - Files.walk(root).map(Path::toString) - .forEach(path -> { - if (path.endsWith(".class")) { - String name = path.substring(1, path.length() - ".class".length()); - classNames.add(name); - } - }); - } - - return classNames.build(); - } - - @Nullable - @Override - public ClassNode getClassNode(String name) { - if (!classNames.contains(name)) { - return null; - } - - try { - return nodeCache.get(name, () -> parseNode(name)); - } catch (ExecutionException e) { - throw new RuntimeException(e); - } - } - - private ClassNode parseNode(String name) throws IOException { - ClassReader reader = getReader(name); - - ClassNode node = new ClassNode(); - - LocalVariableFixVisitor visitor = new LocalVariableFixVisitor(Enigma.ASM_VERSION, node); - reader.accept(visitor, 0); - - return node; - } - - private ClassReader getReader(String name) throws IOException { - Path path = fileSystem.getPath(name + ".class"); - byte[] bytes = Files.readAllBytes(path); - return new ClassReader(bytes); - } - - public int getClassCount() { - return classNames.size(); - } - - public void visit(Supplier visitorSupplier, int readFlags) { - for (String className : classNames) { - ClassVisitor visitor = visitorSupplier.get(); - - ClassNode cached = nodeCache.getIfPresent(className); - if (cached != null) { - cached.accept(visitor); - continue; - } - - try { - ClassReader reader = getReader(className); - reader.accept(visitor, readFlags); - } catch (IOException e) { - System.out.println("Failed to visit class " + className); - e.printStackTrace(); - } - } - } - - @Override - public void close() throws IOException { - this.fileSystem.close(); - } - - public JarIndex index(ProgressListener progress) { - JarIndex index = JarIndex.empty(); - index.indexJar(this, progress); - return index; - } -} 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; import com.google.common.collect.Multimap; import cuchaz.enigma.Enigma; import cuchaz.enigma.ProgressListener; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.ReferenceTargetType; +import cuchaz.enigma.classprovider.ClassProvider; import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.mapping.IndexEntryResolver; import cuchaz.enigma.translation.representation.Lambda; import cuchaz.enigma.translation.representation.entry.*; import cuchaz.enigma.utils.I18n; -import org.objectweb.asm.ClassReader; - import java.util.Arrays; import java.util.Collection; +import java.util.HashSet; +import java.util.Set; public class JarIndex implements JarIndexer { + private final Set indexedClasses = new HashSet<>(); private final EntryIndex entryIndex; private final InheritanceIndex inheritanceIndex; private final ReferenceIndex referenceIndex; @@ -59,14 +60,21 @@ public class JarIndex implements JarIndexer { return new JarIndex(entryIndex, inheritanceIndex, referenceIndex, bridgeMethodIndex, packageVisibilityIndex); } - public void indexJar(ClassCache classCache, ProgressListener progress) { + public void indexJar(Set classNames, ClassProvider classProvider, ProgressListener progress) { + indexedClasses.addAll(classNames); progress.init(4, I18n.translate("progress.jar.indexing")); progress.step(1, I18n.translate("progress.jar.indexing.entries")); - classCache.visit(() -> new IndexClassVisitor(this, Enigma.ASM_VERSION), ClassReader.SKIP_CODE); + + for (String className : classNames) { + classProvider.get(className).accept(new IndexClassVisitor(this, Enigma.ASM_VERSION)); + } progress.step(2, I18n.translate("progress.jar.indexing.references")); - classCache.visit(() -> new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Enigma.ASM_VERSION), 0); + + for (String className : classNames) { + classProvider.get(className).accept(new IndexReferenceVisitor(this, entryIndex, inheritanceIndex, Enigma.ASM_VERSION)); + } progress.step(3, I18n.translate("progress.jar.indexing.methods")); bridgeMethodIndex.findBridgeMethods(); @@ -167,4 +175,8 @@ public class JarIndex implements JarIndexer { public EntryResolver getEntryResolver() { return entryResolver; } + + public boolean isIndexed(String internalName) { + return indexedClasses.contains(internalName); + } } 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 @@ package cuchaz.enigma.api.service; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.classprovider.ClassProvider; +import org.objectweb.asm.ClassVisitor; + +import java.util.Set; public interface JarIndexerService extends EnigmaService { EnigmaServiceType TYPE = EnigmaServiceType.create("jar_indexer"); - void acceptJar(ClassCache classCache, JarIndex jarIndex); + void acceptJar(Set scope, ClassProvider classProvider, JarIndex jarIndex); + + static JarIndexerService fromVisitor(ClassVisitor visitor) { + return (scope, classProvider, jarIndex) -> { + for (String className : scope) { + classProvider.get(className).accept(visitor); + } + }; + } } 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; import javax.annotation.Nullable; -import org.objectweb.asm.tree.ClassNode; +import cuchaz.enigma.classprovider.CachingClassProvider; +import cuchaz.enigma.classprovider.ObfuscationFixClassProvider; -import cuchaz.enigma.Enigma; import cuchaz.enigma.EnigmaProject; -import cuchaz.enigma.bytecode.translators.SourceFixVisitor; import cuchaz.enigma.events.ClassHandleListener; import cuchaz.enigma.events.ClassHandleListener.InvalidationType; import cuchaz.enigma.source.*; @@ -89,17 +88,7 @@ public final class ClassHandleProvider { } private Decompiler createDecompiler() { - return ds.create(name -> { - ClassNode node = project.getClassCache().getClassNode(name); - - if (node == null) { - return null; - } - - ClassNode fixedNode = new ClassNode(); - node.accept(new SourceFixVisitor(Enigma.ASM_VERSION, fixedNode, project.getJarIndex())); - return fixedNode; - }, new SourceSettings(true, true)); + return ds.create(new CachingClassProvider(new ObfuscationFixClassProvider(project.getClassProvider(), project.getJarIndex())), new SourceSettings(true, true)); } /** 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 @@ +package cuchaz.enigma.classprovider; + +import com.google.common.cache.Cache; +import com.google.common.cache.CacheBuilder; +import org.objectweb.asm.tree.ClassNode; + +import javax.annotation.Nullable; +import java.util.Optional; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +/** + * Wraps a ClassProvider to provide caching and synchronization. + */ +public class CachingClassProvider implements ClassProvider { + private final ClassProvider classProvider; + private final Cache> cache = CacheBuilder.newBuilder() + .maximumSize(128) + .expireAfterAccess(1, TimeUnit.MINUTES) + .concurrencyLevel(1) + .build(); + + public CachingClassProvider(ClassProvider classProvider) { + this.classProvider = classProvider; + } + + @Override + @Nullable + public ClassNode get(String name) { + try { + return cache.get(name, () -> Optional.ofNullable(classProvider.get(name))).orElse(null); + } catch (ExecutionException e) { + throw new RuntimeException(e); + } + } +} 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 @@ +package cuchaz.enigma.classprovider; + +import org.objectweb.asm.tree.ClassNode; + +import javax.annotation.Nullable; + +public interface ClassProvider { + /** + * Gets the {@linkplain ClassNode} for a class. The class provider may return a cached result, + * so it's important to not mutate it. + * + * @param name the internal name of the class + * @return the {@linkplain ClassNode} for that class, or {@code null} if it was not found + */ + @Nullable + ClassNode get(String name); +} 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 @@ +package cuchaz.enigma.classprovider; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.tree.ClassNode; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.io.InputStream; + +/** + * Provides classes by loading them from the classpath. + */ +public class ClasspathClassProvider implements ClassProvider { + @Nullable @Override public ClassNode get(String name) { + try (InputStream in = ClasspathClassProvider.class.getResourceAsStream("/" + name + ".class")) { + if (in == null) { + return null; + } + + ClassNode node = new ClassNode(); + new ClassReader(in).accept(node, 0); + return node; + } catch (IOException e) { + return null; + } + } +} 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 @@ +package cuchaz.enigma.classprovider; + +import org.objectweb.asm.tree.ClassNode; + +import javax.annotation.Nullable; + +/** + * Combines a list of {@link ClassProvider}s into one, calling each one in a row + * until one can provide the class. + */ +public class CombiningClassProvider implements ClassProvider { + private final ClassProvider[] classProviders; + + public CombiningClassProvider(ClassProvider... classProviders) { + this.classProviders = classProviders; + } + + @Override + @Nullable + public ClassNode get(String name) { + for (ClassProvider cp : classProviders) { + ClassNode node = cp.get(name); + + if (node != null) { + return node; + } + } + + return null; + } +} 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 @@ +package cuchaz.enigma.classprovider; + +import com.google.common.collect.ImmutableSet; +import cuchaz.enigma.utils.AsmUtil; +import org.objectweb.asm.tree.ClassNode; + +import javax.annotation.Nullable; +import java.io.IOException; +import java.nio.file.FileSystem; +import java.nio.file.FileSystems; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Set; + +/** + * Provides classes by loading them from a JAR file. + */ +public class JarClassProvider implements AutoCloseable, ClassProvider { + private final FileSystem fileSystem; + private final Set classNames; + + public JarClassProvider(Path jarPath) throws IOException { + this.fileSystem = FileSystems.newFileSystem(jarPath, (ClassLoader) null); + this.classNames = collectClassNames(fileSystem); + } + + private static ImmutableSet collectClassNames(FileSystem fileSystem) throws IOException { + ImmutableSet.Builder classNames = ImmutableSet.builder(); + for (Path root : fileSystem.getRootDirectories()) { + Files.walk(root).map(Path::toString) + .forEach(path -> { + if (path.endsWith(".class")) { + String name = path.substring(1, path.length() - ".class".length()); + classNames.add(name); + } + }); + } + + return classNames.build(); + } + + public Set getClassNames() { + return classNames; + } + + @Nullable + @Override + public ClassNode get(String name) { + if (!classNames.contains(name)) { + return null; + } + + try { + return AsmUtil.bytesToNode(Files.readAllBytes(fileSystem.getPath(name + ".class"))); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @Override + public void close() throws Exception { + fileSystem.close(); + } +} 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 @@ +package cuchaz.enigma.classprovider; + +import cuchaz.enigma.Enigma; +import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.bytecode.translators.LocalVariableFixVisitor; +import cuchaz.enigma.bytecode.translators.SourceFixVisitor; +import cuchaz.enigma.classprovider.ClassProvider; +import org.objectweb.asm.ClassVisitor; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; + +import javax.annotation.Nullable; + +/** + * Wraps a ClassProvider to apply fixes to the following problems introduced by the obfuscator, + * so that the classes can be decompiled correctly: + *
    + *
  • Bridge methods missing the "bridge" or "synthetic" access modifier + *
  • .getClass() calls added by Proguard (Proguard adds these to preserve semantics when Proguard removes + * a field access which may have caused a {@code NullPointerException}). + *
  • LVT names that don't match parameter table name + *
  • LVT names that are invalid or duplicate + *
  • Enum constructor parameters that are incorrectly named or missing the "synthetic" access modifier + *
  • "this" parameter which is incorrectly named + *
+ *

+ * These fixes are only applied to classes that were indexed by the JarIndex provided, and not library classes. + */ +public class ObfuscationFixClassProvider implements ClassProvider { + private final ClassProvider classProvider; + private final JarIndex jarIndex; + + public ObfuscationFixClassProvider(ClassProvider classProvider, JarIndex jarIndex) { + this.classProvider = classProvider; + this.jarIndex = jarIndex; + } + + @Override + @Nullable + public ClassNode get(String name) { + ClassNode node = classProvider.get(name); + + if (!jarIndex.isIndexed(name)) { + return node; + } + + ClassNode fixedNode = new ClassNode(); + ClassVisitor visitor = fixedNode; + visitor = new LocalVariableFixVisitor(Enigma.ASM_VERSION, visitor); + visitor = new SourceFixVisitor(Enigma.ASM_VERSION, visitor, jarIndex); + node.accept(visitor); + removeRedundantClassCalls(fixedNode); + + return fixedNode; + } + + private void removeRedundantClassCalls(ClassNode node) { + // Removes .getClass() calls added by Proguard: + // DUP + // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; + // POP + for (MethodNode methodNode : node.methods) { + AbstractInsnNode insnNode = methodNode.instructions.getFirst(); + while (insnNode != null) { + if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { + AbstractInsnNode previous = methodInsnNode.getPrevious(); + AbstractInsnNode next = methodInsnNode.getNext(); + if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { + insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction + methodNode.instructions.remove(previous); + methodNode.instructions.remove(methodInsnNode); + methodNode.instructions.remove(next); + } + } + } + insnNode = insnNode.getNext(); + } + } + } +} 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 @@ package cuchaz.enigma.source; -import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.classprovider.ClassProvider; import cuchaz.enigma.api.service.EnigmaService; import cuchaz.enigma.api.service.EnigmaServiceType; 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 @@ package cuchaz.enigma.source.cfr; -import com.google.common.io.ByteStreams; -import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.classprovider.ClassProvider; import cuchaz.enigma.source.Decompiler; import cuchaz.enigma.source.Source; import cuchaz.enigma.source.SourceSettings; +import cuchaz.enigma.utils.AsmUtil; import org.benf.cfr.reader.apiunreleased.ClassFileSource2; import org.benf.cfr.reader.apiunreleased.JarContent; import org.benf.cfr.reader.bytecode.analysis.parse.utils.Pair; @@ -19,16 +19,12 @@ import org.benf.cfr.reader.util.CannotLoadClassException; import org.benf.cfr.reader.util.collections.ListFactory; import org.benf.cfr.reader.util.getopt.Options; import org.benf.cfr.reader.util.getopt.OptionsImpl; -import org.objectweb.asm.ClassWriter; import org.objectweb.asm.tree.ClassNode; -import java.io.IOException; -import java.io.InputStream; import java.util.Collection; import java.util.HashMap; import java.util.Map; - public class CfrDecompiler implements Decompiler { private final DCCommonState state; @@ -58,21 +54,13 @@ public class CfrDecompiler implements Decompiler { @Override public Pair getClassFileContent(String path) { - ClassNode node = classProvider.getClassNode(path.substring(0, path.lastIndexOf('.'))); + ClassNode node = classProvider.get(path.substring(0, path.lastIndexOf('.'))); if (node == null) { - try (InputStream classResource = CfrDecompiler.class.getClassLoader().getResourceAsStream(path)) { - if (classResource != null) { - return new Pair<>(ByteStreams.toByteArray(classResource), path); - } - } catch (IOException ignored) {} - return null; } - ClassWriter cw = new ClassWriter(0); - node.accept(cw); - return new Pair<>(cw.toByteArray(), path); + return new Pair<>(AsmUtil.nodeToBytes(node), path); } }); } 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; import com.strobel.decompiler.languages.java.ast.AstBuilder; import com.strobel.decompiler.languages.java.ast.CompilationUnit; import com.strobel.decompiler.languages.java.ast.InsertParenthesesVisitor; -import cuchaz.enigma.ClassProvider; +import cuchaz.enigma.classprovider.ClassProvider; import cuchaz.enigma.source.Source; import cuchaz.enigma.source.Decompiler; import cuchaz.enigma.source.SourceSettings; import cuchaz.enigma.source.procyon.transformers.*; -import cuchaz.enigma.source.procyon.typeloader.CompiledSourceTypeLoader; -import cuchaz.enigma.source.procyon.typeloader.NoRetryMetadataSystem; -import cuchaz.enigma.source.procyon.typeloader.SynchronizedTypeLoader; +import cuchaz.enigma.utils.AsmUtil; +import org.objectweb.asm.tree.ClassNode; public class ProcyonDecompiler implements Decompiler { private final SourceSettings settings; @@ -26,9 +25,21 @@ public class ProcyonDecompiler implements Decompiler { private final MetadataSystem metadataSystem; public ProcyonDecompiler(ClassProvider classProvider, SourceSettings settings) { - ITypeLoader typeLoader = new SynchronizedTypeLoader(new CompiledSourceTypeLoader(classProvider)); + ITypeLoader typeLoader = (name, buffer) -> { + ClassNode node = classProvider.get(name); - metadataSystem = new NoRetryMetadataSystem(typeLoader); + if (node == null) { + return false; + } + + byte[] data = AsmUtil.nodeToBytes(node); + buffer.reset(data.length); + System.arraycopy(data, 0, buffer.array(), buffer.position(), data.length); + buffer.position(0); + return true; + }; + + metadataSystem = new MetadataSystem(typeLoader); metadataSystem.setEagerMethodLoadingEnabled(true); 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 @@ -package cuchaz.enigma.source.procyon.typeloader; - -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ClasspathTypeLoader; -import com.strobel.assembler.metadata.ITypeLoader; - -/** - * Caching version of {@link ClasspathTypeLoader} - */ -public class CachingClasspathTypeLoader extends CachingTypeLoader { - private static ITypeLoader extraClassPathLoader = null; - - public static void setExtraClassPathLoader(ITypeLoader loader){ - extraClassPathLoader = loader; - } - - private final ITypeLoader classpathLoader = new ClasspathTypeLoader(); - - @Override - protected byte[] doLoad(String className) { - Buffer parentBuf = new Buffer(); - if (classpathLoader.tryLoadType(className, parentBuf)) { - return parentBuf.array(); - } - if (extraClassPathLoader != null){ - parentBuf.reset(); - if (extraClassPathLoader.tryLoadType(className, parentBuf)){ - return parentBuf.array(); - } - } - return EMPTY_ARRAY;//need to return *something* as null means no store - } -} 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 @@ -package cuchaz.enigma.source.procyon.typeloader; - -import com.google.common.collect.Maps; -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ITypeLoader; - -import java.util.Map; - -/** - * Common cache functions - */ -public abstract class CachingTypeLoader implements ITypeLoader { - protected static final byte[] EMPTY_ARRAY = {}; - - private final Map cache = Maps.newHashMap(); - - protected abstract byte[] doLoad(String className); - - @Override - public boolean tryLoadType(String className, Buffer out) { - - // check the cache - byte[] data = this.cache.computeIfAbsent(className, this::doLoad); - - if (data == EMPTY_ARRAY) { - return false; - } - - out.reset(data.length); - System.arraycopy(data, 0, out.array(), out.position(), data.length); - out.position(0); - return true; - } - - public void clearCache() { - this.cache.clear(); - } -} 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 @@ -/******************************************************************************* - * Copyright (c) 2015 Jeff Martin. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Lesser General Public - * License v3.0 which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/lgpl.html - *

- * Contributors: - * Jeff Martin - initial API and implementation - ******************************************************************************/ - -package cuchaz.enigma.source.procyon.typeloader; - -import com.google.common.collect.Lists; -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ITypeLoader; -import cuchaz.enigma.ClassProvider; -import cuchaz.enigma.translation.representation.entry.ClassEntry; -import org.objectweb.asm.ClassVisitor; -import org.objectweb.asm.ClassWriter; -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; - -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; -import java.util.function.Function; - -public class CompiledSourceTypeLoader extends CachingTypeLoader { - //Store one instance as the classpath shouldn't change during load - private static final ITypeLoader CLASSPATH_TYPE_LOADER = new CachingClasspathTypeLoader(); - - private final ClassProvider compiledSource; - private final LinkedList> visitors = new LinkedList<>(); - - public CompiledSourceTypeLoader(ClassProvider compiledSource) { - this.compiledSource = compiledSource; - } - - public void addVisitor(Function visitor) { - this.visitors.addFirst(visitor); - } - - @Override - protected byte[] doLoad(String className) { - byte[] data = loadType(className); - if (data == null) { - return loadClasspath(className); - } - - return data; - } - - private byte[] loadClasspath(String name) { - Buffer parentBuf = new Buffer(); - if (CLASSPATH_TYPE_LOADER.tryLoadType(name, parentBuf)) { - return parentBuf.array(); - } - return EMPTY_ARRAY; - } - - private byte[] loadType(String className) { - ClassEntry entry = new ClassEntry(className); - - // find the class in the jar - ClassNode node = findClassNode(entry); - if (node == null) { - // couldn't find it - return null; - } - - removeRedundantClassCalls(node); - - ClassWriter writer = new ClassWriter(0); - - ClassVisitor visitor = writer; - for (Function visitorFunction : this.visitors) { - visitor = visitorFunction.apply(visitor); - } - - node.accept(visitor); - - // we have a transformed class! - return writer.toByteArray(); - } - - private void removeRedundantClassCalls(ClassNode node) { - // remove .getClass() calls that are seemingly injected - // DUP - // INVOKEVIRTUAL java/lang/Object.getClass ()Ljava/lang/Class; - // POP - for (MethodNode methodNode : node.methods) { - AbstractInsnNode insnNode = methodNode.instructions.getFirst(); - while (insnNode != null) { - if (insnNode instanceof MethodInsnNode && insnNode.getOpcode() == Opcodes.INVOKEVIRTUAL) { - MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; - if (methodInsnNode.name.equals("getClass") && methodInsnNode.owner.equals("java/lang/Object") && methodInsnNode.desc.equals("()Ljava/lang/Class;")) { - AbstractInsnNode previous = methodInsnNode.getPrevious(); - AbstractInsnNode next = methodInsnNode.getNext(); - if (previous.getOpcode() == Opcodes.DUP && next.getOpcode() == Opcodes.POP) { - insnNode = previous.getPrevious();//reset the iterator so it gets the new next instruction - methodNode.instructions.remove(previous); - methodNode.instructions.remove(methodInsnNode); - methodNode.instructions.remove(next); - } - } - } - insnNode = insnNode.getNext(); - } - } - } - - private ClassNode findClassNode(ClassEntry entry) { - // try to find the class in the jar - for (String className : getClassNamesToTry(entry)) { - ClassNode node = compiledSource.getClassNode(className); - if (node != null) { - return node; - } - } - - // didn't find it ;_; - return null; - } - - private Collection getClassNamesToTry(ClassEntry entry) { - List classNamesToTry = Lists.newArrayList(); - classNamesToTry.add(entry.getFullName()); - - ClassEntry outerClass = entry.getOuterClass(); - if (outerClass != null) { - classNamesToTry.addAll(getClassNamesToTry(outerClass)); - } - - return classNamesToTry; - } -} 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 @@ -package cuchaz.enigma.source.procyon.typeloader; - -import com.strobel.assembler.metadata.ITypeLoader; -import com.strobel.assembler.metadata.MetadataSystem; -import com.strobel.assembler.metadata.TypeDefinition; -import com.strobel.assembler.metadata.TypeReference; - -import java.util.Collections; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; - -public final class NoRetryMetadataSystem extends MetadataSystem { - private final Set failedTypes = Collections.newSetFromMap(new ConcurrentHashMap<>()); - - public NoRetryMetadataSystem(final ITypeLoader typeLoader) { - super(typeLoader); - } - - @Override - protected synchronized TypeDefinition resolveType(final String descriptor, final boolean mightBePrimitive) { - if (failedTypes.contains(descriptor)) { - return null; - } - - final TypeDefinition result = super.resolveType(descriptor, mightBePrimitive); - - if (result == null) { - failedTypes.add(descriptor); - } - - return result; - } - - @Override - public synchronized TypeDefinition resolve(final TypeReference type) { - return super.resolve(type); - } -} 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 @@ -package cuchaz.enigma.source.procyon.typeloader; - -import com.strobel.assembler.metadata.Buffer; -import com.strobel.assembler.metadata.ITypeLoader; - -/** - * Typeloader with synchronized tryLoadType method - */ -public class SynchronizedTypeLoader implements ITypeLoader { - private final ITypeLoader delegate; - - public SynchronizedTypeLoader(ITypeLoader delegate) { - this.delegate = delegate; - } - - @Override - public synchronized boolean tryLoadType(String internalName, Buffer buffer) { - return delegate.tryLoadType(internalName, buffer); - } -} 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 @@ +package cuchaz.enigma.utils; + +import org.objectweb.asm.ClassReader; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.tree.ClassNode; + +public class AsmUtil { + public static byte[] nodeToBytes(ClassNode node) { + ClassWriter w = new ClassWriter(0); + node.accept(w); + return w.toByteArray(); + } + + public static ClassNode bytesToNode(byte[] bytes) { + ClassReader r = new ClassReader(bytes); + ClassNode node = new ClassNode(); + r.accept(node, 0); + return node; + } +} diff --git a/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java b/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java index 1dc9748..1251535 100644 --- a/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java +++ b/enigma/src/test/java/cuchaz/enigma/PackageVisibilityIndexTest.java @@ -11,12 +11,13 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.index.JarIndex; import cuchaz.enigma.analysis.index.PackageVisibilityIndex; +import cuchaz.enigma.classprovider.JarClassProvider; import cuchaz.enigma.translation.representation.entry.ClassEntry; import org.junit.Test; +import java.nio.file.Path; import java.nio.file.Paths; import static cuchaz.enigma.TestEntryFactory.newClass; @@ -25,7 +26,7 @@ import static org.hamcrest.Matchers.contains; import static org.hamcrest.Matchers.containsInAnyOrder; public class PackageVisibilityIndexTest { - + public static final Path JAR = Paths.get("build/test-obf/packageAccess.jar"); private static final ClassEntry KEEP = newClass("cuchaz/enigma/inputs/Keep"); private static final ClassEntry BASE = newClass("a"); private static final ClassEntry SAME_PACKAGE_CHILD = newClass("b"); @@ -35,8 +36,9 @@ public class PackageVisibilityIndexTest { private final JarIndex jarIndex; public PackageVisibilityIndexTest() throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/packageAccess.jar")); - jarIndex = classCache.index(ProgressListener.none()); + JarClassProvider jcp = new JarClassProvider(JAR); + jarIndex = JarIndex.empty(); + jarIndex.indexJar(jcp.getClassNames(), jcp, ProgressListener.none()); } @Test diff --git a/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java b/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java index 494d959..6daa293 100644 --- a/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java +++ b/enigma/src/test/java/cuchaz/enigma/TestDeobfed.java @@ -11,8 +11,7 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.ClassCache; -import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.classprovider.ClasspathClassProvider; import cuchaz.enigma.source.Decompiler; import cuchaz.enigma.source.Decompilers; import cuchaz.enigma.source.SourceSettings; @@ -28,27 +27,24 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.containsInAnyOrder; public class TestDeobfed { - private static Enigma enigma; - private static ClassCache classCache; - private static JarIndex index; + public static final Path OBF = Paths.get("build/test-obf/translation.jar"); + public static final Path DEOBF = Paths.get("build/test-deobf/translation.jar"); + private static EnigmaProject deobfProject; @BeforeClass public static void beforeClass() throws Exception { - enigma = Enigma.create(); + Enigma enigma = Enigma.create(); - Path obf = Paths.get("build/test-obf/translation.jar"); - Path deobf = Paths.get("build/test-deobf/translation.jar"); - Files.createDirectories(deobf.getParent()); - EnigmaProject project = enigma.openJar(obf, ProgressListener.none()); - project.exportRemappedJar(ProgressListener.none()).write(deobf, ProgressListener.none()); + Files.createDirectories(DEOBF.getParent()); + EnigmaProject obfProject = enigma.openJar(OBF, new ClasspathClassProvider(), ProgressListener.none()); + obfProject.exportRemappedJar(ProgressListener.none()).write(DEOBF, ProgressListener.none()); - classCache = ClassCache.of(deobf); - index = classCache.index(ProgressListener.none()); + deobfProject = enigma.openJar(DEOBF, new ClasspathClassProvider(), ProgressListener.none()); } @Test public void obfEntries() { - assertThat(index.getEntryIndex().getClasses(), containsInAnyOrder( + assertThat(deobfProject.getJarIndex().getEntryIndex().getClasses(), containsInAnyOrder( newClass("cuchaz/enigma/inputs/Keep"), newClass("a"), newClass("b"), @@ -77,8 +73,7 @@ public class TestDeobfed { @Test public void decompile() { - EnigmaProject project = new EnigmaProject(enigma, classCache, index, new byte[20]); - Decompiler decompiler = Decompilers.PROCYON.create(project.getClassCache(), new SourceSettings(false, false)); + Decompiler decompiler = Decompilers.PROCYON.create(deobfProject.getClassProvider(), new SourceSettings(false, false)); decompiler.getSource("a"); decompiler.getSource("b"); diff --git a/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java b/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java index 6619d26..ae42235 100644 --- a/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java +++ b/enigma/src/test/java/cuchaz/enigma/TestDeobfuscator.java @@ -11,6 +11,7 @@ package cuchaz.enigma; +import cuchaz.enigma.classprovider.ClasspathClassProvider; import cuchaz.enigma.source.Decompiler; import cuchaz.enigma.source.Decompilers; import cuchaz.enigma.source.SourceSettings; @@ -22,7 +23,7 @@ import java.nio.file.Paths; public class TestDeobfuscator { private EnigmaProject openProject() throws IOException { Enigma enigma = Enigma.create(); - return enigma.openJar(Paths.get("build/test-obf/loneClass.jar"), ProgressListener.none()); + return enigma.openJar(Paths.get("build/test-obf/loneClass.jar"), new ClasspathClassProvider(), ProgressListener.none()); } @Test @@ -34,7 +35,7 @@ public class TestDeobfuscator { @Test public void decompileClass() throws Exception { EnigmaProject project = openProject(); - Decompiler decompiler = Decompilers.PROCYON.create(project.getClassCache(), new SourceSettings(false, false)); + Decompiler decompiler = Decompilers.PROCYON.create(project.getClassProvider(), new SourceSettings(false, false)); decompiler.getSource("a").asString(); } diff --git a/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java b/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java index 85c72f8..6b60994 100644 --- a/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java +++ b/enigma/src/test/java/cuchaz/enigma/TestInnerClasses.java @@ -11,14 +11,16 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.classprovider.CachingClassProvider; +import cuchaz.enigma.classprovider.JarClassProvider; import cuchaz.enigma.source.Decompiler; import cuchaz.enigma.source.Decompilers; import cuchaz.enigma.source.SourceSettings; import cuchaz.enigma.translation.representation.entry.ClassEntry; import org.junit.Test; +import java.nio.file.Path; import java.nio.file.Paths; import static cuchaz.enigma.TestEntryFactory.newClass; @@ -35,13 +37,16 @@ public class TestInnerClasses { private static final ClassEntry ClassTreeLevel1 = newClass("f$a"); private static final ClassEntry ClassTreeLevel2 = newClass("f$a$a"); private static final ClassEntry ClassTreeLevel3 = newClass("f$a$a$a"); + public static final Path JAR = Paths.get("build/test-obf/innerClasses.jar"); private final JarIndex index; private final Decompiler decompiler; public TestInnerClasses() throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/innerClasses.jar")); - index = classCache.index(ProgressListener.none()); - decompiler = Decompilers.PROCYON.create(classCache, new SourceSettings(false, false)); + JarClassProvider jcp = new JarClassProvider(JAR); + CachingClassProvider classProvider = new CachingClassProvider(jcp); + index = JarIndex.empty(); + index.indexJar(jcp.getClassNames(), classProvider, ProgressListener.none()); + decompiler = Decompilers.PROCYON.create(classProvider, new SourceSettings(false, false)); } @Test diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java index 48975c8..0790193 100644 --- a/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java +++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexConstructorReferences.java @@ -11,14 +11,16 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.classprovider.CachingClassProvider; +import cuchaz.enigma.classprovider.JarClassProvider; import cuchaz.enigma.translation.representation.entry.ClassEntry; import cuchaz.enigma.translation.representation.entry.MethodDefEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; import org.junit.Test; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; @@ -28,6 +30,7 @@ import static org.hamcrest.Matchers.*; public class TestJarIndexConstructorReferences { + public static final Path JAR = Paths.get("build/test-obf/constructors.jar"); private JarIndex index; private ClassEntry baseClass = newClass("a"); @@ -37,8 +40,9 @@ public class TestJarIndexConstructorReferences { private ClassEntry callerClass = newClass("b"); public TestJarIndexConstructorReferences() throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/constructors.jar")); - index = classCache.index(ProgressListener.none()); + JarClassProvider jcp = new JarClassProvider(JAR); + index = JarIndex.empty(); + index.indexJar(jcp.getClassNames(), new CachingClassProvider(jcp), ProgressListener.none()); } @Test diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java index 76e379c..a9045f9 100644 --- a/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java +++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexInheritanceTree.java @@ -11,11 +11,12 @@ package cuchaz.enigma; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.EntryReference; import cuchaz.enigma.analysis.index.EntryIndex; import cuchaz.enigma.analysis.index.InheritanceIndex; import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.classprovider.CachingClassProvider; +import cuchaz.enigma.classprovider.JarClassProvider; import cuchaz.enigma.translation.mapping.EntryResolver; import cuchaz.enigma.translation.mapping.IndexEntryResolver; import cuchaz.enigma.translation.representation.AccessFlags; @@ -26,6 +27,7 @@ import cuchaz.enigma.translation.representation.entry.MethodEntry; import org.junit.Test; import org.objectweb.asm.Opcodes; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; @@ -35,6 +37,7 @@ import static org.hamcrest.Matchers.*; public class TestJarIndexInheritanceTree { + public static final Path JAR = Paths.get("build/test-obf/inheritanceTree.jar"); private JarIndex index; private ClassEntry baseClass = newClass("a"); @@ -44,10 +47,10 @@ public class TestJarIndexInheritanceTree { private FieldEntry nameField = newField(baseClass, "a", "Ljava/lang/String;"); private FieldEntry numThingsField = newField(subClassB, "a", "I"); - public TestJarIndexInheritanceTree() - throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/inheritanceTree.jar")); - index = classCache.index(ProgressListener.none()); + public TestJarIndexInheritanceTree() throws Exception { + JarClassProvider jcp = new JarClassProvider(JAR); + index = JarIndex.empty(); + index.indexJar(jcp.getClassNames(), new CachingClassProvider(jcp), ProgressListener.none()); } @Test diff --git a/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java b/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java index 103c366..6e3755c 100644 --- a/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java +++ b/enigma/src/test/java/cuchaz/enigma/TestJarIndexLoneClass.java @@ -15,6 +15,8 @@ import cuchaz.enigma.analysis.*; import cuchaz.enigma.analysis.index.EntryIndex; import cuchaz.enigma.analysis.index.InheritanceIndex; import cuchaz.enigma.analysis.index.JarIndex; +import cuchaz.enigma.classprovider.CachingClassProvider; +import cuchaz.enigma.classprovider.JarClassProvider; import cuchaz.enigma.translation.VoidTranslator; import cuchaz.enigma.translation.representation.AccessFlags; import cuchaz.enigma.translation.representation.entry.ClassEntry; @@ -23,6 +25,7 @@ import cuchaz.enigma.translation.representation.entry.MethodDefEntry; import cuchaz.enigma.translation.representation.entry.MethodEntry; import org.junit.Test; +import java.nio.file.Path; import java.nio.file.Paths; import java.util.Collection; import java.util.List; @@ -33,11 +36,13 @@ import static org.hamcrest.Matchers.*; public class TestJarIndexLoneClass { + public static final Path JAR = Paths.get("build/test-obf/loneClass.jar"); private JarIndex index; public TestJarIndexLoneClass() throws Exception { - ClassCache classCache = ClassCache.of(Paths.get("build/test-obf/loneClass.jar")); - index = classCache.index(ProgressListener.none()); + JarClassProvider jcp = new JarClassProvider(JAR); + index = JarIndex.empty(); + index.indexJar(jcp.getClassNames(), new CachingClassProvider(jcp), ProgressListener.none()); } @Test diff --git a/enigma/src/test/java/cuchaz/enigma/TokenChecker.java b/enigma/src/test/java/cuchaz/enigma/TokenChecker.java index 96fc6da..feef272 100644 --- a/enigma/src/test/java/cuchaz/enigma/TokenChecker.java +++ b/enigma/src/test/java/cuchaz/enigma/TokenChecker.java @@ -12,11 +12,10 @@ package cuchaz.enigma; import com.google.common.collect.Lists; -import cuchaz.enigma.analysis.ClassCache; import cuchaz.enigma.analysis.EntryReference; -import cuchaz.enigma.source.SourceIndex; +import cuchaz.enigma.classprovider.CachingClassProvider; +import cuchaz.enigma.classprovider.JarClassProvider; import cuchaz.enigma.source.*; -import cuchaz.enigma.source.Token; import cuchaz.enigma.translation.representation.entry.Entry; import java.io.IOException; @@ -28,8 +27,8 @@ public class TokenChecker { private final Decompiler decompiler; protected TokenChecker(Path path) throws IOException { - ClassCache classCache = ClassCache.of(path); - decompiler = Decompilers.PROCYON.create(classCache, new SourceSettings(false, false)); + CachingClassProvider classProvider = new CachingClassProvider(new JarClassProvider(path)); + decompiler = Decompilers.PROCYON.create(classProvider, new SourceSettings(false, false)); } protected String getDeclarationToken(Entry entry) { diff --git a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java index 65941e5..60c70b7 100644 --- a/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java +++ b/enigma/src/test/java/cuchaz/enigma/translation/mapping/TestTinyV2InnerClasses.java @@ -14,6 +14,7 @@ package cuchaz.enigma.translation.mapping; import cuchaz.enigma.Enigma; import cuchaz.enigma.EnigmaProject; import cuchaz.enigma.ProgressListener; +import cuchaz.enigma.classprovider.ClasspathClassProvider; import cuchaz.enigma.translation.mapping.serde.enigma.EnigmaMappingsReader; import java.nio.file.Path; @@ -30,7 +31,7 @@ public final class TestTinyV2InnerClasses { // @Test public void testMappings() throws Exception { - EnigmaProject project = Enigma.create().openJar(jar, ProgressListener.none()); + EnigmaProject project = Enigma.create().openJar(jar, new ClasspathClassProvider(), ProgressListener.none()); project.setMappings(EnigmaMappingsReader.DIRECTORY.read(mappings, ProgressListener.none(), project.getEnigma().getProfile().getMappingSaveParameters())); } -- cgit v1.2.3